/**
 * Panels are {@link Ext.Container containers} with an optional
 * {@link Ext.panel.Header header} that can be positioned using the
 * {@link #cfg-headerPosition headerPosition} config option.
 *
 * Panels add extra functionality by providing various options for configuring a header
 * that is docked inside the panel.  Setting any of the following panel config options
 * will automatically create a header:
 * - {@link #cfg-title title}
 * - {@link #cfg-iconCls iconCls}
 * - {@link #cfg-icon icon}
 * - {@link #cfg-tools tools}
 * - {@link #cfg-closable closable}
 *
 * It is also possible to configure the header directly using the {@link #header}
 * configuration. See {@link Ext.panel.Header} for more information.
 *
 * ### Simple Panel Example (with body text / html)
 *
 * Usually, Panels are used as constituents within an
 * {@link Ext.app.Application application}, in which case, they
 * would be used as child items of {@link Ext.Container Containers}, and would themselves
 * use {@link Ext.Component Ext.Components} as child {@link #cfg-items items}. However,
 * to illustrate simply rendering a Panel into the document, here's how to do it:
 *
 * ```javascript
 * @example({ framework: 'extjs' })
 *     Ext.create({
 *         xtype: 'panel',
 *         title: 'Panel Title',
 *         iconCls: 'x-fa fa-html5',
 *         height: 400,
 *         width: 400,
 *         bodyPadding: 12,
 *         html: 'Sample HTML text',
 *         renderTo: Ext.getBody()
 *     });
 * ```
 *
 * ### Panel Example (with child items)
 *
 * Panels are, by virtue of their inheritance from {@link Ext.Container}, capable of
 * being configured with a {@link Ext.Container#layout layout}, and containing child
 * {@link Ext.Component Components}.
 *
 * ```javascript
 * @example({ framework: 'extjs' })
 *     Ext.create({
 *         xtype: 'panel',
 *         bodyPadding: true, // don't want content to crunch against the borders
 *         width: 300,
 *         title: 'Filters',
 *         items: [{
 *             xtype: 'datefield',
 *             label: 'Start date'
 *         }, {
 *             xtype: 'datefield',
 *             label: 'End date'
 *         }],
 *         renderTo: Ext.getBody()
 *     });
 * ```
 *
 * Panel also provides built-in {@link #cfg-collapsible collapsible, expandable}, and
 * {@link #cfg-closable closable} behavior. Panels can be easily dropped into any
 * {@link Ext.Container Container} or layout, and the layout and rendering pipeline
 * is {@link Ext.Container#method-add completely managed by the framework}.
 *
 * ### Floating Panels
 *
 * Panels are also useful as Overlays - containers that float over your application.
 * If configured with `{@link #cfg-anchor anchor}` set to `true`, when you
 * {@link #method-showBy showBy} another component, there will be an anchor arrow
 * pointing to the reference component.
 *
 * ```javascript
 * @example({ framework: 'extjs' })
 *     var panel = Ext.create({
 *         xtype: 'panel',
 *         title: 'Floated',
 *         bodyPadding: true,
 *         html: 'context panel text',
 *         // the panel will be hidden until shown
 *         floated: true,
 *         // adds the close tool in the panel header
 *         closable: true,
 *         // hides, rather than destroys the closed panel
 *         closeAction: 'hide',
 *         anchor: true
 *     });
 *
 *     Ext.create({
 *         xtype: 'button',
 *         text: 'Show Popup',
 *         margin: 20,
 *         // shows the floated panel next to the button
 *         handler: function () {
 *             panel.showBy(this, 'tl-bl');
 *         },
 *         renderTo: Ext.getBody()
 *     });
 * ```
 * ```javascript
 * @example({framework: 'ext-react', packages:['ext-react']})
 * import React, { Component } from 'react';
 * import { ExtContainer, ExtPanel, ExtButton } from '@sencha/ext-react';
 * Ext.require('Ext.Toast');
 *
 * export default class MyExample extends Component {
 *      render() {
 *          function toolHandler(owner, tool) {
 *              Ext.toast(`You clicked ${tool.config.type}`);
 *         }
 *         return (
 *             <ExtContainer>
 *                 <ExtPanel
 *                     shadow
 *                     title="Panel"
 *                     height={300}
 *                     width={500}
 *                     tools={[
 *                         { type: 'minimize', handler: toolHandler },
 *                         { type: 'refresh', handler: toolHandler },
 *                         { type: 'save', handler: toolHandler },
 *                         { type: 'search', handler: toolHandler },
 *                         { type: 'close', handler: toolHandler }
 *                     ]}
 *                 >
 *                     <p>Panel Body</p>
 *                 </ExtPanel>
 *                 <ExtButton ui="action" handler={() => this.modal.cmp.show()}
 *                       margin="20 0 0 0" text="Show Modal"/>
 *                 <ExtPanel
 *                     ref={modal => this.modal = modal}
 *                     title="Floated Panel"
 *                     modal
 *                     floated
 *                     centered
 *                     hideOnMaskTap
 *                     width={Ext.filterPlatform('ie10') ? '100%' :
 *                            (Ext.os.deviceType == 'Phone') ? 260 : 400}
 *                     maxHeight={Ext.filterPlatform('ie10') ? '30%' :
 *                          (Ext.os.deviceType == 'Phone') ? 220 : 400}
 *                     showAnimation={{
 *                         type: 'popIn',
 *                         duration: 250,
 *                         easing: 'ease-out'
 *                     }}
 *                     hideAnimation={{
 *                         type: 'popOut',
 *                         duration: 250,
 *                         easing: 'ease-out'
 *                     }}
 *                 >
 *                     <p>
 *                         This is a modal, centered and floated panel.
 *                          hideOnMaskTap is true by default so we can tap anywhere
 *                          outside the overlay to hide it.
 *                      </p>
 *                 </ExtPanel>
 *             </ExtContainer>
 *         )
 *     }
 * }
 * ```
 * ```javascript
 * @example({framework: 'ext-angular', packages:['ext-angular']})
 * declare var Ext: any;
 * import {Component} from '@angular/core';
 * Ext.require('Ext.Toast');
 *
 * @Component({
 *   selector: 'app-component',
 *   template: `<ExtContainer>
 *     <ExtPanel
 *         [shadow]="true"
 *         title="Panel"
 *         [height]="300"
 *         [width]="500"
 *         (ready)="this.onMainPanelReady($event)"
 *         [html]= "'<p>Panel Body</p>'"
 *     >
 *     </ExtPanel>
 *     <ExtButton [ui]="'action'" (tap)="modalClick()"
 *              margin="20 0 0 0" text="Show Modal">
 *        </ExtButton>
 *     <ExtPanel
 *         title="Floated Panel"
 *         [modal]="true"
 *         [floated]="true"
 *         [centered]="true"
 *         hideOnMaskTap="true"
 *         [width]="400"
 *         maxHeight="400"
 *         (ready)="this.onModalPanelReady($event)"
 *         [showAnimation]="{
 *         type: 'popIn',
 *         duration: 250,
 *         easing: 'ease-out'
 *         }"
 *         [hideAnimation]="{
 *         type: 'popOut',
 *         duration: 250,
 *         easing: 'ease-out'
 *         }"
 *         [html]="'<p>This is a modal, centered and floated panel.
 *              hideOnMaskTap is true by default so we can tap anywhere
 *              outside the overlay to hide it.</p>'"
 *     >
 *     </ExtPanel>
 *   </ExtContainer>`,
 *   styles: [``]
 * })
 *
 *   export class AppComponent  {
 *   mainPanel:any;
 *   modalPanelCmp:any;
 *   toolHandler = (owner, tool) => {
 *      Ext.toast(`You clicked ${tool.config.type}`);
 *   }
 *
 *   onMainPanelReady = (event) => {
 *     this.mainPanel = event.detail.cmp;
 *     this.mainPanel.setTools([
 *       { type: "minimize", handler: this.toolHandler.bind(this) },
 *       { type: "refresh", handler: this.toolHandler.bind(this) },
 *       { type: "save", handler: this.toolHandler.bind(this) },
 *       { type: "search", handler: this.toolHandler.bind(this) },
 *       { type: "close", handler: this.toolHandler.bind(this) }
 *     ]);
 *   }
 *
 *   onModalPanelReady = (event) => {
 *     this.modalPanelCmp = event.detail.cmp;
 *   }
 *
 *   modalClick = (owner, tool) => {
 *     this.modalPanelCmp.show();
 *   }
 * }
 * ```
 * ```html
 * @example({framework: 'ext-web-components', packages:['ext-web-components'], tab: 1 })
 * <ext-container>
 *    <ext-panel
 *        shadow="true"
 *        title="Panel"
 *        height="300"
 *        width="500"
 *        onready="mainPanel.onMainPanelReady"
 *    >
 *        <p>Panel Body</p>
 *    </ext-panel>
 *    <ext-button ui="action" ontap="mainPanel.modalClick"
 *      margin="20 0 0 0" text="Show Modal"></ext-button>
 *    <ext-panel
 *        title="Floated Panel"
 *        modal="true"
 *        floated="true"
 *        centered="true"
 *        hideOnMaskTap="true"
 *        width="400"
 *        maxHeight="400"
 *        onready="mainPanel.onModalPanelReady"
 *        showAnimation='{
 *             "type": "popIn",
 *             "duration": 250,
 *             "easing": "ease-out"
 *        }'
 *        hideAnimation='{
 *             "type": "popOut",
 *             "duration": 250,
 *             "easing": "ease-out"
 *        }'
 *    >
 *        <p>This is a modal, centered and floated panel.
 *          hideOnMaskTap is true by default so
 *          we can tap anywhere outside the overlay to hide it.</p>
 *    </ext-panel>
 * </ext-container>
 * ```
 * ```javascript
 * @example({framework: 'ext-web-components', packages:['ext-web-components'], tab: 2 })
 * import '@sencha/ext-web-components/dist/ext-container.component';
 * import '@sencha/ext-web-components/dist/ext-button.component';
 * import '@sencha/ext-web-components/dist/ext-panel.component';
 *
 * Ext.require('Ext.Toast');
 *
 * export default class MainPanelComponent {
 *   toolHandler = (owner, tool) => {
 *      Ext.toast(`You clicked ${tool.config.type}`);
 *   }
 *
 *   onMainPanelReady = (event) => {
 *       this.mainPanel = event.detail.cmp;
 *       this.mainPanel.setTools([
 *          { type: "minimize", handler: this.toolHandler.bind(this) },
 *          { type: "refresh", handler: this.toolHandler.bind(this) },
 *          { type: "save", handler: this.toolHandler.bind(this) },
 *          { type: "search", handler: this.toolHandler.bind(this) },
 *          { type: "close", handler: this.toolHandler.bind(this) }
 *       ]);
 *   }
 *
 *   onModalPanelReady = (event) => {
 *      this.modalPanelCmp = event.detail.cmp;
 *   }
 *
 *   modalClick = (owner, tool) => {
 *      this.modalPanelCmp.show();
 *   }
 * }
 * window.mainPanel = new MainPanelComponent();
 * ```
 *
 * **Note:** By default, the `{@link #cfg-closable close}` header tool _destroys_ the
 * Panel resulting in removal of the Panel and the destruction of any descendant
 * Components. This makes the Panel object, and all its descendants **unusable**. To
 * enable the close tool to simply _hide_ a Panel for later re-use, configure the Panel
 * with `{@link #closeAction closeAction}: 'hide'`.
 */
Ext.define('Ext.Panel', {
    extend: 'Ext.Container',
    xtype: 'panel',
    alternateClassName: 'Ext.panel.Panel',
    isPanel: true,
 
    mixins: [
        'Ext.panel.Buttons',
        'Ext.mixin.Toolable'
    ],
 
    requires: [
        'Ext.layout.Box',
        'Ext.Toolbar'
    ],
 
    /**
     * @property defaultBindProperty
     * @inheritdoc
     */
    defaultBindProperty: 'title',
 
    config: {
        /**
         * @cfg {'top'/'right'/'bottom'/'left'} headerPosition
         * The position of the header. Ignored if no {@link #cfg-header} is created.
         *
         * @since 6.5.0
         */
        headerPosition: 'top',
 
        /**
         * @cfg {Boolean/Object} header
         * Pass as `false` to prevent a header from being created.
         *
         * You may also assign a header with a config object (optionally containing an
         * `xtype`) to custom-configure your panel's header.
         *
         * See {@link Ext.panel.Header} for all the options that may be specified here.
         */
        header: null,
 
        /**
         * @cfg icon
         * @inheritdoc Ext.panel.Header#cfg-icon
         */
        icon: null,
 
        /**
         * @cfg iconCls
         * @inheritdoc Ext.panel.Header#cfg-iconCls
         */
        iconCls: null,
 
        /**
         * @cfg [iconAlign='left']
         * @inheritdoc Ext.panel.Header#cfg-iconAlign
         */
        iconAlign: null,
 
        /**
         * @cfg title
         * @inheritdoc Ext.panel.Header#cfg-title
         */
        title: null,
 
        /**
         * @cfg [titleAlign='left']
         * @inheritdoc Ext.panel.Header#cfg-titleAlign
         */
        titleAlign: null,
 
        /**
         * @cfg {Boolean} [anchor=false]
         * Configure `true` to show an anchor element pointing to the target component
         * when this Panel is floating and {@link #showBy shown by} another component.
         */
        anchor: null,
 
        /**
         * @cfg {String} anchorPosition
         * Set the anchor position.
         *
         * @private
         */
        anchorPosition: null,
 
        /**
         * @cfg {Boolean} closable
         * True to display the 'close' tool button and allow the user to close the panel
         * or false to hide the button and disallow closing the window.
         *
         * By default, when close is requested by clicking the close button in the
         * header, the {@link #method-close} method will be called. This will
         * _{@link Ext.Component#method-destroy destroy}_ the Panel and its content
         * meaning that it may not be reused.
         *
         * To make closing a Panel _hide_ the Panel so that it may be reused, set
         * {@link #closeAction} to 'hide'.
         */
        closable: null,
 
        // eslint-disable-next-line max-len
        // @cmd-auto-dependency {aliasPrefix: "widget.", typeProperty: "xtype", defaultType: "toolbar"}
        /**
         * @cfg {Object/Object[]} bbar
         * Convenience config. Short for 'Bottom Bar'.
         *
         * ```javascript
         * @example({ framework: 'extjs' })
         *     Ext.create({
         *         xtype: 'panel',
         *         fullscreen: true,
         *         html: 'hello world',
         *         padding: 20,
         *         bbar: [{
         *             xtype: 'button',
         *             text : 'Button 1'
         *         }]
         *     });
         * ```
         *
         * is equivalent to
         *
         * ```javascript
         * @example({ framework: 'extjs' })
         *     Ext.create({
         *         xtype: 'panel',
         *         fullscreen: true,
         *         html: 'hello world',
         *         padding: 20,
         *         items: [{
         *             xtype: 'toolbar',
         *             docked: 'bottom',
         *             items: [{
         *                 xtype: 'button',
         *                 text: 'Button 1'
         *             }]
         *         }]
         *     });
         * ```
         *
         * @since 6.5.0
         */
        bbar: null,
 
        // eslint-disable-next-line max-len
        // @cmd-auto-dependency {aliasPrefix: "widget.", typeProperty: "xtype", defaultType: "toolbar"}
        /**
         * @cfg {Object/Object[]} lbar
         * Convenience config. Short for 'Left Bar' (left-docked, vertical toolbar).
         *
         * ```javascript
         * @example({ framework: 'extjs' })
         *     Ext.create({
         *         xtype: 'panel',
         *         fullscreen: true,
         *         html: 'hello world',
         *         padding: 20,
         *         lbar: [{
         *             xtype: 'button',
         *             text : 'Button 1'
         *         }]
         *     });
         * ```
         *
         * is equivalent to
         *
         * ```javascript
         * @example({ framework: 'extjs' })
         *     Ext.create({
         *         xtype: 'panel',
         *         fullscreen: true,
         *         html: 'hello world',
         *         padding: 20,
         *         items: [{
         *             xtype: 'toolbar',
         *             docked: 'left',
         *             items: [{
         *                 xtype: 'button',
         *                 text: 'Button 1'
         *             }]
         *         }]
         *     });
         * ```
         *
         * @since 6.5.0
         */
        lbar: null,
 
        // eslint-disable-next-line max-len
        // @cmd-auto-dependency {aliasPrefix: "widget.", typeProperty: "xtype", defaultType: "toolbar"}
        /**
         * @cfg {Object/Object[]} rbar
         * Convenience config. Short for 'Right Bar' (right-docked, vertical toolbar).
         *
         * ```javascript
         * @example({ framework: 'extjs' })
         *     Ext.create({
         *         xtype: 'panel',
         *         fullscreen: true,
         *         html: 'hello world',
         *         padding: 20,
         *         rbar: [{
         *             xtype: 'button',
         *             text : 'Button 1'
         *         }]
         *     });
         * ```
         *
         * is equivalent to
         *
         * ```javascript
         * @example({ framework: 'extjs' })
         *     Ext.create({
         *         xtype: 'panel',
         *         fullscreen: true,
         *         html: 'hello world',
         *         padding: 20,
         *         items: [{
         *             xtype: 'toolbar',
         *             docked: 'right',
         *             items: [{
         *                 xtype: 'button',
         *                 text: 'Button 1'
         *             }]
         *         }]
         *     });
         * ```
         *
         * @since 6.5.0
         */
        rbar: null,
 
        // eslint-disable-next-line max-len
        // @cmd-auto-dependency {aliasPrefix: "widget.", typeProperty: "xtype", defaultType: "toolbar"}
        /**
         * @cfg {Object/Object[]} tbar
         * Convenience config. Short for 'Top Bar'.
         *
         * ```javascript
         * @example({ framework: 'extjs' })
         *     Ext.create({
         *         xtype: 'panel',
         *         fullscreen: true,
         *         html: 'hello world',
         *         padding: 20,
         *         tbar: [{
         *             xtype: 'button',
         *             text : 'Button 1'
         *         }]
         *     });
         * ```
         *
         * is equivalent to
         *
         * ```javascript
         * @example({ framework: 'extjs' })
         *     Ext.create({
         *         xtype: 'panel',
         *         fullscreen: true,
         *         html: 'hello world',
         *         padding: 20,
         *         items: [{
         *             xtype: 'toolbar',
         *             docked: 'top',
         *             items: [{
         *                 xtype: 'button',
         *                 text: 'Button 1'
         *             }]
         *         }]
         *     });
         * ```
         *
         * @since 6.5.0
         */
        tbar: null
    },
 
    cachedConfig: {
        /**
         * @cfg border
         * @inheritdoc
         */
        border: false,
 
        /**
         * @cfg {Boolean} bodyBorder
         * Controls the border style of the panel body using the following values:
         *
         * - `true` to enable the border around the panel body (as defined by the theme)
         * Note that even when enabled, the bodyBorder is only visible when there are
         * docked items around the edges of the panel.  Where the bodyBorder touches the
         * panel's outer border it is automatically collapsed into a single border.
         *
         * - `false` to disable the body border
         *
         * - `null` - use the value of {@link #cfg-border border} as the value for
         * `bodyBorder`
         */
        bodyBorder: null,
 
        /**
         * @cfg {Number/Boolean/String} bodyPadding
         * A shortcut for setting a padding style on the body element. The value can
         * either be a number to be applied to all sides, or a normal CSS string
         * describing padding.
         *
         *     bodyPadding: 5 // 5px padding on all sides
         *
         *     bodyPadding: '10 20' // 10px top and bottom padding - 20px side padding
         *
         * *See the {@link Ext.dom.Element#static-method-unitizeBox unitizeBox} method
         * for more information on what string values are valid*
         */
        bodyPadding: null,
 
        /**
         * @cfg {String/Object} bodyStyle
         * Custom CSS styles to be applied to the panel's body element, which can be
         * supplied as a valid CSS style string or an object containing style property
         * name/value pairs.
         *
         * For example, these two formats are interpreted to be equivalent:
         *
         *     bodyStyle: 'background:#ffc; padding:10px;'
         *
         *     bodyStyle: {
         *         background: '#ffc',
         *         padding: '10px'
         *     }
         *
         * @accessor set
         * @since 6.5.0
         */
        bodyStyle: null,
 
        /**
         * @cfg {Object/Ext.Button[]} buttons
         * The buttons for this panel to be displayed in the `buttonToolbar` as a keyed
         * object (or array) of button configuration objects.
         *
         *```javascript
         * @example({ framework: 'extjs' })
         *     Ext.create({
         *         xtype: 'panel',
         *         html: 'hello world',
         *         padding: 20,
         *         buttons: {
         *            ok: {text: 'OK', handler: 'onOK'}
         *         }
         *     });
         * ```
         *
         * For buttons that are defined in `standardButtons` (such as `'ok'`), there is a
         * more convenient short-hand for this config:
         *
         * ```javascript
         * @example({ framework: 'extjs' })
         *     Ext.create({
         *         fullscreen: true,
         *         xtype: 'panel',
         *         html: 'hello world',
         *         padding: 20,
         *         buttons: {
         *            ok: 'onOK',
         *            cancel: 'onCancel'
         *         }
         *     });
         * ```
         *
         * The {@link #minButtonWidth} is used as the default
         * {@link Ext.Button#minWidth minWidth} for the buttons in the buttons toolbar.
         * @since 6.5.0
         */
 
        /**
         * @cfg {Object/Ext.Toolbar} buttonToolbar
         * Configure the toolbar that holds the `buttons` inside.
         * @since 6.5.0
         */
        buttonToolbar: {
            xtype: 'toolbar',
            itemId: 'buttonToolbar',
            docked: 'bottom',
            defaultType: 'button',
            weighted: true,
            ui: 'footer',
            defaultButtonUI: 'action',
 
            layout: {
                type: 'box',
                vertical: false,
                pack: 'center'
            }
        },
 
        /**
         * @cfg {String} closeAction
         * The action to take when the close header tool is clicked:
         *
         * - **`'{@link #method-destroy}'`** :
         *
         *   {@link #method-remove remove} the window from the DOM and
         *   {@link Ext.Component#method-destroy destroy} it and all descendant Components.
         *   The window will **not** be available to be redisplayed via the
         *   {@link #method-show} method.
         *
         * - **`'{@link #method-hide}'`** :
         *
         *   {@link #method-hide} the window by setting visibility to hidden and applying
         *   negative offsets. The window will be available to be redisplayed via the
         *   {@link #method-show} method.
         *
         * **Note:** This behavior has changed! setting *does* affect the {@link #method-close}
         * method which will invoke the appropriate closeAction.
         */
        closeAction: 'destroy',
 
        /**
         * @cfg {String} closeToolText
         * Text to be announced by screen readers when the **close**
         * {@link Ext.Tool tool} is focused.  Will also be set as the close tool's
         * {@link Ext.Tool#cfg-tooltip tooltip} text.
         *
         * **Note:** Applicable when the panel is {@link #closable}: true
         * @locale
         */
        closeToolText: 'Close panel'
    },
 
    /**
     * @property classCls
     * @inheritdoc
     */
    classCls: Ext.baseCSSPrefix + 'panel',
 
    headerCls: null,
    titleCls: null,
    toolCls: Ext.baseCSSPrefix + 'paneltool',
    sideCls: {
        top: Ext.baseCSSPrefix + 'top',
        right: Ext.baseCSSPrefix + 'right',
        bottom: Ext.baseCSSPrefix + 'bottom',
        left: Ext.baseCSSPrefix + 'left'
    },
 
    /**
     * @cfg manageBorders
     * @inheritdoc
     */
    manageBorders: true,
 
    allowHeader: true,
 
    /**
     * @property template
     * @inheritdoc
     */
    template: [{
        reference: 'bodyWrapElement',
        cls: Ext.baseCSSPrefix + 'body-wrap-el',
        uiCls: 'body-wrap-el',
        children: [{
            reference: 'bodyElement',
            cls: Ext.baseCSSPrefix + 'body-el',
            uiCls: 'body-el'
        }]
    }],
 
    initialize: function() {
        var me = this,
            scrollable;
 
        me.callParent();
        // TODO: make autoAutoRefresh public and remove this code from here
        scrollable = me.getScrollable();
 
        if (scrollable && scrollable.isVirtualScroller) {
            scrollable.setAutoRefresh(true);
        }
    },
 
    /**
     * Adds a CSS class to the body element. If not rendered, the class will be added
     * when the panel is rendered.
     * @param {String} cls The class to add
     * @return {Ext.Panel} this
     */
    addBodyCls: function(cls) {
        this.bodyElement.addCls(cls);
 
        return this;
    },
 
    /**
     * Removes a CSS class from the body element
     * @param {String} cls The class to remove
     * @return {Ext.Panel} this
     */
    removeBodyCls: function(cls) {
        this.bodyElement.removeCls(cls);
 
        return this;
    },
 
    applyBodyPadding: function(bodyPadding) {
        if (bodyPadding === true) {
            bodyPadding = 5;
        }
 
        if (bodyPadding) {
            bodyPadding = Ext.dom.Element.unitizeBox(bodyPadding);
        }
 
        return bodyPadding;
    },
 
    applyBodyStyle: function(bodyStyle, oldBodyStyle) {
        // If we're doing something with data binding, say:
        // style: {
        //     backgroundColor: 'rgba({r}, {g}, {b}, 1)'
        // }
        // The inner values will change, but the object won't, so force
        // a copy to be created here if necessary
        if (oldBodyStyle && bodyStyle === oldBodyStyle && Ext.isObject(oldBodyStyle)) {
            bodyStyle = Ext.apply({}, bodyStyle);
        }
 
        this.bodyElement.applyStyles(bodyStyle);
 
        return null;
    },
 
    //<debug>
    getBodyStyle: function() {
        Ext.Error.raise(
            "'bodyStyle' is a write-only config.  To query element styles use the " +
            "Ext.dom.Element API.");
    },
    //</debug>
 
    /**
     * Add tools to this panel {@link Ext.panel.Header header}
     *
     *     panel.addTool({
     *         type: 'gear',
     *         handler: function () {
     *             // ....
     *         }
     *     });
     *
     *     panel.addTool([{
     *         type: 'gear',
     *         handler: 'viewControllerGearMethod'
     *     }, {
     *         type: 'save',
     *         handler: 'viewControllerSaveMethod'
     *     }]);
     *
     * By default the tools will be accessible via keyboard, with the exception of
     * automatically added collapse/expand and close tools.
     *
     * If you implement keyboard equivalents of your tools' actions elsewhere and do not
     * want the tools to participate in keyboard navigation, you can make them
     * presentational instead:
     *
     *     panel.addTool({
     *         type: 'mytool',
     *         focusable: false,
     *         ariaRole: 'presentation'
     *         // ...
     *     });
     *
     * @param {Object/Object[]/Ext.Tool/Ext.Tool[]} tool The tool or tools to add.
     */
    addTool: function(tool) {
        var header = this.ensureHeader(),  // creates if header !== false
            items;
 
        if (header) {
            items = this.createTools(Ext.Array.from(tool));
 
            if (items && items.length) {
                items = header.add(items);
            }
        }
 
        return items;
    },
 
    applyHeader: function(newHeader, oldHeader) {
        // This method should never call any getters here doing so will cause re-entry into
        // this method. Extra Headers will be created
        var me = this,
            header = oldHeader,
            isTrue;
 
        me.allowHeader = newHeader !== false;
 
        if (oldHeader && !newHeader) {
            header = Ext.destroy(header);
        }
 
        if (newHeader && me.allowHeader) {
            isTrue = newHeader === true;
 
            if (header) {
                if (!isTrue) {
                    header.setConfig(newHeader);
                }
            }
            else {
                if (isTrue) {
                    newHeader = {};
                }
 
                newHeader.$initParent = me;
                header = Ext.factory(me.createHeader(newHeader));
                me.header = header;
                delete header.$initParent;
                delete newHeader.$initParent;
 
                // Must not use the parent linkage. That implies that this is in the
                // items collection, and available to be removed using the remove method.
                header.ownerCmp = me;
 
                (me.maxHeightElement || me.el).insertFirst(header.el);
 
                header.doInheritUi();
            }
        }
 
        return header || null;
    },
 
    updateHeader: function(header) {
        if (header) {
            this.positionHeader(header);
        }
        else {
            this.syncBorders();
        }
    },
 
    applyTools: function(tools) {
        var header = this.ensureHeader(),  // creates if header !== false
            items;
 
        if (header) {
            // Remove all tools (since we are the impl of a setTools([...]) call)
            header.clearTools();
 
            items = this.createTools(tools);
 
            if (items && items.length) {
                header.add(items);
            }
        }
 
        // we don't return anything since the tools are "stored" on the Header
    },
 
    /**
     * Closes this panel as described by the `closeAction`.
     */
    close: function() {
        var me = this,
            action = me.getCloseAction(),
            destroy = action === 'destroy';
 
        if (me.fireEvent('beforeclose', me) !== false) {
            if (action && !destroy) {
                me[action]();
            }
 
            me.fireEvent('close', me);
 
            if (destroy) {
                me.destroy();
            }
        }
    },
 
    createHeader: function(config) {
        var me = this,
            ret = {
                xtype: 'panelheader',
                instanceCls: me.headerCls,
                docked: 'top'
            },
            icon, title;
 
        me._isCreatingHeader = true;
 
        if (config && config !== true) {
            Ext.merge(ret, config);
        }
 
        if (me.initialized) {
            // Only attempt to configure title if we are not currently initializing.
            // During initialization the updater for title will run if present and apply
            // it to the header so there is no work to be done here.
            title = me.getTitle();
 
            if (title != null) {
                if (typeof title === 'string') {
                    title = {
                        text: title
                    };
                }
 
                Ext.merge(ret, {
                    title: title
                });
            }
 
            icon = me.getIconCls();
 
            if (icon != null) {
                ret.iconCls = icon;
            }
            else {
                icon = me.getIcon();
 
                if (icon != null) {
                    ret.icon = icon;
                }
            }
        }
 
        me._isCreatingHeader = false;
 
        return ret;
    },
 
    applyAnchor: function(anchor, oldAnchor) {
        var me = this,
            el = me.el.dom,
            svgEl, pathEl;
 
        // true results in us owning an anchor element in the anchor property
        if (anchor) {
            // Already have one - undefined means no change`
            if (oldAnchor) {
                return;
            }
            else {
                anchor = me.el.insertFirst({
                    cls: Ext.baseCSSPrefix + 'anchor-el'
                });
                svgEl = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
                svgEl.setAttribute('class', Ext.baseCSSPrefix + 'pointer-el');
                pathEl = document.createElementNS("http://www.w3.org/2000/svg", 'path');
                svgEl.appendChild(pathEl);
                anchor.dom.appendChild(svgEl);
            }
 
            // Anchor is positioned outside the element bounds.
            // Must show overflow while anchor is enabled.
            el.style.overflow = 'visible';
        }
        // false destroys the anchor element and dereferences the pointerEl
        else if (oldAnchor) {
            me.anchorSize = oldAnchor.destroy();
            el.style.overflow = '';
        }
 
        return anchor;
    },
 
    initAnchor: function() {
        var me = this,
            anchor = me.getAnchor(),
            cls = me.sideCls.top,
            svgEl = anchor.dom.firstChild,
            pathEl = svgEl.firstChild,
            anchorSize;
 
        anchor.addCls(cls);
        anchor.show();
        anchorSize = anchor.measure();
        me.anchorSize = anchorSize = new Ext.util.Offset(anchorSize.width, anchorSize.height);
 
        // A small space between the anchor point and the target
        me.anchorMargin = parseFloat(anchor.getStyle('marginLeft')) || 0;
        anchor.dom.style.margin = '0';
 
        // Draw our arrow.
        svgEl.setAttribute('height', anchorSize.y);
        svgEl.setAttribute('width', anchorSize.x);
        pathEl.setAttribute(
            'd',
            'M0 ' + anchorSize.y + ' L' + anchorSize.x / 2 + ' 0.5 L' + anchorSize.x +
            ' ' + anchorSize.y);
        anchorSize.y -= parseFloat(Ext.fly(pathEl).getStyle('stroke-width'));
 
        anchor.removeCls(cls);
        anchor.hide();
    },
 
    updateAnchorPosition: function(anchorPosition, oldAnchorPosition) {
        var me = this,
            anchorEl = me.getAnchor(),
            sideCls = me.sideCls;
 
        // If we have no anchor, there's nothing to do.
        if (anchorEl) {
            if (oldAnchorPosition) {
                anchorEl.removeCls(sideCls[oldAnchorPosition.side]);
            }
 
            if (anchorPosition) {
                anchorEl.addCls(sideCls[anchorPosition.side]);
                anchorEl.translate(anchorPosition.x, anchorPosition.y);
                anchorEl.show();
            }
            else {
                anchorEl.hide();
            }
        }
    },
 
    updateBorder: function(border, oldBorder) {
        var me = this;
 
        me.callParent([border, oldBorder]);
 
        if (me.getBodyBorder() === null) {
            me.setBodyBorderEnabled(border !== false);
        }
 
        me.syncBorders();
    },
 
    updateBodyPadding: function(newBodyPadding) {
        this.bodyElement.setStyle('padding', newBodyPadding);
    },
 
    updateBodyBorder: function(bodyBorder) {
        var me = this;
 
        bodyBorder = (bodyBorder === null) ? me.getBorder() : bodyBorder;
 
        me.setBodyBorderEnabled(bodyBorder !== false);
 
        me.syncBorders();
    },
 
    updateClosable: function(closable) {
        var me = this,
            tools;
 
        if (closable) {
            tools = me.addTool({
                type: 'close',
                weight: 1000,
                scope: me,
                handler: 'onCloseTool',
                tooltip: me.getCloseToolText(),
                $internal: true
            });
 
            if (tools && tools.length) {
                me.closeTool = tools[0];
            }
        }
        else {
            Ext.destroy(me.closeTool);
        }
    },
 
    updateHeaderPosition: function(headerPosition, oldHeaderPosition) {
        this.moveHeaderPosition(headerPosition, oldHeaderPosition);
    },
 
    updateIcon: function(icon) {
        var header = this.ensureHeader();  // creates if header !== false
 
        if (header) {
            header.setIcon(icon);
        }
    },
 
    updateIconCls: function(iconCls) {
        var header = this.ensureHeader();  // creates if header !== false
 
        if (header) {
            header.setIconCls(iconCls);
        }
    },
 
    updateIconAlign: function(iconAlign) {
        var header = this.ensureHeader();  // creates if header !== false
 
        if (header) {
            header.setIconAlign(iconAlign);
        }
    },
 
    applyBbar: function(toolbar, previous) {
        return this.normalizeButtonBar(toolbar, previous, 'bottom');
    },
 
    applyLbar: function(toolbar, previous) {
        return this.normalizeButtonBar(toolbar, previous, 'left');
    },
 
    applyRbar: function(toolbar, previous) {
        return this.normalizeButtonBar(toolbar, previous, 'right');
    },
 
    applyTbar: function(toolbar, previous) {
        return this.normalizeButtonBar(toolbar, previous, 'top');
    },
 
    updateTitle: function(title) {
        var header = this.ensureHeader(),
            tab = this.tab;
 
        if (header) {
            header.setTitle(title);
        }
 
        if (tab && tab.isTab && !tab.destroying && !tab.destroyed) {
            tab.setText(title);
        }
    },
 
    updateTitleAlign: function(titleAlign) {
        var header = this.ensureHeader();  // creates if header !== false
 
        if (header) {
            header.setTitleAlign(titleAlign);
        }
    },
 
    updateUi: function(ui, oldUi) {
        this.callParent([ui, oldUi]);
 
        if (this.hasResizable) {
            this.onResizableUiChange(ui, oldUi);
        }
 
        // invalidate anchor size so it is measured again on next alignTo
        this.anchorSize = null;
    },
 
    alignTo: function(component, alignment, options) {
        var me = this,
            anchorElement = me.getAnchor(),
            config = me.initialConfig,
            positioned = me.isPositioned(),
            setX = positioned ? me.setLeft : me.setX,
            setY = positioned ? me.setTop : me.setY,
            x, y, target, anchorMargin, alignmentInfo, resultRegion, oldHeight, parent;
 
        // Initialize anchor size, content and margin if not done.
        if (anchorElement) {
            if (!me.anchorSize) {
                me.initAnchor();
            }
        }
 
        // Call through the Component class (which registers a viewportResizeListener), and
        // up to Widget which does pure alignment.
        // We only need extra if we're showing an anchor.
        else {
            return me.callParent([component, alignment, options]);
        }
 
        anchorMargin = me.anchorMargin;
 
        // Passed "component" may be a Region, Component, oer element
        target = component.isRegion
            ? component
            : (component.isWidget
                ? component.el
                : Ext.fly(component)).getRegion();
 
        target.adjust(-anchorMargin, anchorMargin, anchorMargin, -anchorMargin);
        alignmentInfo = me.getAlignmentInfo(target, alignment);
 
        if (alignmentInfo.isAligned) {
            return;
        }
 
        parent = me.getParent();
 
        if (!me.getFloated()) {
            if (!parent) {
                me.setFloated(true);
            }
            else {
                me.positioned = true;
            }
        }
 
        if ('unconstrainedWidth' in me) {
            me.setWidth(me.unconstrainedWidth);
        }
 
        if ('unconstrainedHeight' in me) {
            me.setHeight(me.unconstrainedHeight);
        }
 
        // Cache the alignment options for any realign call which might happen on
        // viewport resize or configuration change.
        // See Ext.Widget#realign
        me.alignToArgs = [component, alignment, options];
 
        resultRegion = me.getAlignRegion(target, alignment, Ext.apply({
            anchorSize: me.anchorSize,
            axisLock: me.getAxisLock()
        }, options));
 
        // If already aligned, will return undefined
        if (resultRegion) {
            setX.call(me, resultRegion.x);
            setY.call(me, resultRegion.y);
 
            if (resultRegion.constrainWidth) {
                me.unconstrainedWidth = config.width || me.self.prototype.width;
 
                // We must deal with height changing if we restrict width and we are aligning
                // above
                oldHeight = me.el.getHeight();
                me.setWidth(alignmentInfo.stats.width = resultRegion.getWidth());
 
                // We are being positioned above, bump upwards by how much the
                // element has expanded as a result of width restriction.
                if (resultRegion.align.position === 0) {
                    setY.call(me, resultRegion.y + (oldHeight - me.el.getHeight()));
                }
            }
 
            if (resultRegion.constrainHeight) {
                me.unconstrainedHeight = config.height || me.self.prototype.height;
                me.setHeight(alignmentInfo.stats.height = resultRegion.getHeight());
            }
 
            if (resultRegion.anchor) {
                x = 0;
                y = 0;
 
                // The result is to the left or right of the target
                if (resultRegion.anchor.align & 1) {
                    y = resultRegion.anchor.y - resultRegion.y;
                }
                else {
                    x = resultRegion.anchor.x - resultRegion.x;
                }
 
                me.setAnchorPosition({
                    side: resultRegion.anchor.position,
                    x: x,
                    y: y
                });
            }
            else {
                me.setAnchorPosition(null);
            }
 
            me.setCurrentAlignmentInfo(alignmentInfo);
        }
        else if (anchorElement) {
            // Already aligned
            anchorElement.show();
        }
 
        if (!me.viewportResizeListener) {
            me.viewportResizeListener = Ext.on({
                resize: 'onViewportResize',
                scope: me,
                destroyable: true
            });
        }
    },
 
    getRefItems: function(deep) {
        var items = this.callParent([deep]),
            header = this.getConfig('header', false, true);
 
        if (header) {
            // Header is logically and visually the first item, so
            // header, then header items are *prepended* to results.
            if (deep && header.getRefItems) {
                items.unshift.apply(items, header.getRefItems(deep));
            }
 
            items.unshift(header);
        }
 
        return items;
    },
 
    onCloseTool: function() {
        this.close();
    },
 
    onRender: function() {
        var me = this,
            header;
 
        me.callParent();
 
        header = me.getHeader();
 
        if (header) {
            header.setRendered(true);
        }
 
        if (me.hasCollapsible) {
            me.onCollapsibleRendered();
        }
    },
 
    doDestroy: function() {
        Ext.destroy(this.header, this.anchor);
        this.callParent();
    },
 
    privates: {
        headerPositionMap: {
            top: {
                cls: Ext.baseCSSPrefix + 'header-position-top',
                dom: 0,
                horz: true
            },
            right: {
                cls: Ext.baseCSSPrefix + 'header-position-right',
                dom: 1,
                vert: true
            },
            bottom: {
                cls: Ext.baseCSSPrefix + 'header-position-bottom',
                dom: 1,
                horz: true
            },
            left: {
                cls: Ext.baseCSSPrefix + 'header-position-left',
                dom: 0,
                vert: true
            }
        },
 
        adjustButtons: function(buttons, oldButtons) {
            return this.normalizeButtonBar(buttons, oldButtons, 'bottom',
                                           this.getButtonToolbar());
        },
 
        ensureHeader: function() {
            var me = this,
                header;
 
            if (!me._isCreatingHeader) {
                me.getItems();
 
                header = me.getHeader();
 
                if (!header && me.allowHeader) {
                    me.setHeader(true);
                    header = me.getHeader();
                }
            }
 
            return header;
        },
 
        moveHeaderPosition: function(headerPosition, oldHeaderPosition) {
            var me = this,
                el = me.element,
                map = me.headerPositionMap,
                oldItem = map[oldHeaderPosition],
                newItem = map[headerPosition],
                oldCls = oldItem ? oldItem.cls : '',
                newCls = newItem.cls,
                positionedHeader,
                header;
 
            if (oldCls !== newCls) {
                if (oldHeaderPosition) {
                    el.removeCls(oldCls);
                }
 
                el.addCls(newCls);
            }
 
            if (oldHeaderPosition || headerPosition !== 'top') {
                header = me.ensureHeader();
 
                if (header) {
                    if (!me.isConfiguring) {
                        me.positionHeader(header, headerPosition);
                        positionedHeader = true;
                    }
                }
            }
 
            if (!positionedHeader) {
                me.syncBorders();
            }
 
            return header;
        },
 
        positionHeader: function(header, position) {
            var me = this,
                pos = position || me.getHeaderPosition();
 
            header.setPosition(pos);
 
            me.syncBorders();
        },
 
        setBodyBorderEnabled: function(enabled) {
            this.bodyElement.setStyle('border-width', enabled ? '' : '0');
        },
 
        syncBorders: function() {
            if (!this.isConfiguring) {
                this.getLayout().handleDockedItemBorders(true);
            }
        }
    }
});