| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 |
- /**
- * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
- /**
- * @module ui/dropdown/dropdownview
- */
- import View from '../view';
- import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
- import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
- import '../../theme/components/dropdown/dropdown.css';
- import { getOptimalPosition } from '@ckeditor/ckeditor5-utils/src/dom/position';
- /**
- * The dropdown view class. It manages the dropdown button and dropdown panel.
- *
- * In most cases, the easiest way to create a dropdown is by using the {@link module:ui/dropdown/utils~createDropdown}
- * util:
- *
- * const dropdown = createDropdown( locale );
- *
- * // Configure dropdown's button properties:
- * dropdown.buttonView.set( {
- * label: 'A dropdown',
- * withText: true
- * } );
- *
- * dropdown.render();
- *
- * dropdown.panelView.element.textContent = 'Content of the panel';
- *
- * // Will render a dropdown with a panel containing a "Content of the panel" text.
- * document.body.appendChild( dropdown.element );
- *
- * If you want to add a richer content to the dropdown panel, you can use the {@link module:ui/dropdown/utils~addListToDropdown}
- * and {@link module:ui/dropdown/utils~addToolbarToDropdown} helpers. See more examples in
- * {@link module:ui/dropdown/utils~createDropdown} documentation.
- *
- * If you want to create a completely custom dropdown, then you can compose it manually:
- *
- * const button = new DropdownButtonView( locale );
- * const panel = new DropdownPanelView( locale );
- * const dropdown = new DropdownView( locale, button, panel );
- *
- * button.set( {
- * label: 'A dropdown',
- * withText: true
- * } );
- *
- * dropdown.render();
- *
- * panel.element.textContent = 'Content of the panel';
- *
- * // Will render a dropdown with a panel containing a "Content of the panel" text.
- * document.body.appendChild( dropdown.element );
- *
- * However, dropdown created this way will contain little behavior. You will need to implement handlers for actions
- * such as {@link module:ui/bindings/clickoutsidehandler~clickOutsideHandler clicking outside an open dropdown}
- * (which should close it) and support for arrow keys inside the panel. Therefore, unless you really know what
- * you do and you really need to do it, it is recommended to use the {@link module:ui/dropdown/utils~createDropdown} helper.
- *
- * @extends module:ui/view~View
- */
- export default class DropdownView extends View {
- /**
- * Creates an instance of the dropdown.
- *
- * Also see {@link #render}.
- *
- * @param {module:utils/locale~Locale} [locale] The localization services instance.
- * @param {module:ui/dropdown/button/dropdownbutton~DropdownButton} buttonView
- * @param {module:ui/dropdown/dropdownpanelview~DropdownPanelView} panelView
- */
- constructor( locale, buttonView, panelView ) {
- super( locale );
- const bind = this.bindTemplate;
- /**
- * Button of the dropdown view. Clicking the button opens the {@link #panelView}.
- *
- * @readonly
- * @member {module:ui/button/buttonview~ButtonView} #buttonView
- */
- this.buttonView = buttonView;
- /**
- * Panel of the dropdown. It opens when the {@link #buttonView} is
- * {@link module:ui/button/buttonview~ButtonView#event:execute executed} (i.e. clicked).
- *
- * Child views can be added to the panel's `children` collection:
- *
- * dropdown.panelView.children.add( childView );
- *
- * See {@link module:ui/dropdown/dropdownpanelview~DropdownPanelView#children} and
- * {@link module:ui/viewcollection~ViewCollection#add}.
- *
- * @readonly
- * @member {module:ui/dropdown/dropdownpanelview~DropdownPanelView} #panelView
- */
- this.panelView = panelView;
- /**
- * Controls whether the dropdown view is open, i.e. shows or hides the {@link #panelView panel}.
- *
- * @observable
- * @member {Boolean} #isOpen
- */
- this.set( 'isOpen', false );
- /**
- * Controls whether the dropdown is enabled, i.e. it can be clicked and execute an action.
- *
- * See {@link module:ui/button/buttonview~ButtonView#isEnabled}.
- *
- * @observable
- * @member {Boolean} #isEnabled
- */
- this.set( 'isEnabled', true );
- /**
- * (Optional) The additional CSS class set on the dropdown {@link #element}.
- *
- * @observable
- * @member {String} #class
- */
- this.set( 'class' );
- /**
- * The position of the panel, relative to the dropdown.
- *
- * **Note**: When `'auto'`, the panel will use one of the remaining positions to stay
- * in the viewport, visible to the user. The positions correspond directly to
- * {@link module:ui/dropdown/dropdownview~DropdownView.defaultPanelPositions default panel positions}.
- *
- * **Note**: This value has an impact on the
- * {@link module:ui/dropdown/dropdownpanelview~DropdownPanelView#position} property
- * each time the panel becomes {@link #isOpen open}.
- *
- * @observable
- * @default 'auto'
- * @member {'auto'|'se'|'sw'|'ne'|'nw'} #panelPosition
- */
- this.set( 'panelPosition', 'auto' );
- /**
- * Tracks information about DOM focus in the dropdown.
- *
- * @readonly
- * @member {module:utils/focustracker~FocusTracker}
- */
- this.focusTracker = new FocusTracker();
- /**
- * Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. It manages
- * keystrokes of the dropdown:
- *
- * * <kbd>▼</kbd> opens the dropdown,
- * * <kbd>◀</kbd> and <kbd>Esc</kbd> closes the dropdown.
- *
- * @readonly
- * @member {module:utils/keystrokehandler~KeystrokeHandler}
- */
- this.keystrokes = new KeystrokeHandler();
- this.setTemplate( {
- tag: 'div',
- attributes: {
- class: [
- 'ck',
- 'ck-dropdown',
- bind.to( 'class' ),
- bind.if( 'isEnabled', 'ck-disabled', value => !value )
- ]
- },
- children: [
- buttonView,
- panelView
- ]
- } );
- buttonView.extendTemplate( {
- attributes: {
- class: [
- 'ck-dropdown__button',
- ]
- }
- } );
- /**
- * A child {@link module:ui/list/listview~ListView list view} of the dropdown located
- * in its {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}.
- *
- * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addListToDropdown}.
- *
- * @readonly
- * @member {module:ui/list/listview~ListView} #listView
- */
- /**
- * A child toolbar of the dropdown located in the
- * {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}.
- *
- * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addToolbarToDropdown}.
- *
- * @readonly
- * @member {module:ui/toolbar/toolbarview~ToolbarView} #toolbarView
- */
- /**
- * Fired when the toolbar button or list item is executed.
- *
- * For {@link #listView} It fires when a child of some {@link module:ui/list/listitemview~ListItemView}
- * fired `execute`.
- *
- * For {@link #toolbarView} It fires when one of the buttons has been
- * {@link module:ui/button/buttonview~ButtonView#event:execute executed}.
- *
- * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addListToDropdown}
- * or {@link module:ui/dropdown/utils~addToolbarToDropdown}.
- *
- * @event execute
- */
- }
- /**
- * @inheritDoc
- */
- render() {
- super.render();
- // Toggle the dropdown when its button has been clicked.
- this.listenTo( this.buttonView, 'open', () => {
- this.isOpen = !this.isOpen;
- } );
- // Toggle the visibility of the panel when the dropdown becomes open.
- this.panelView.bind( 'isVisible' ).to( this, 'isOpen' );
- // Let the dropdown control the position of the panel. The position must
- // be updated every time the dropdown is open.
- this.on( 'change:isOpen', () => {
- if ( !this.isOpen ) {
- return;
- }
- // If "auto", find the best position of the panel to fit into the viewport.
- // Otherwise, simply assign the static position.
- if ( this.panelPosition === 'auto' ) {
- this.panelView.position = DropdownView._getOptimalPosition( {
- element: this.panelView.element,
- target: this.buttonView.element,
- fitInViewport: true,
- positions: this._panelPositions
- } ).name;
- } else {
- this.panelView.position = this.panelPosition;
- }
- } );
- // Listen for keystrokes coming from within #element.
- this.keystrokes.listenTo( this.element );
- // Register #element in the focus tracker.
- this.focusTracker.add( this.element );
- const closeDropdown = ( data, cancel ) => {
- if ( this.isOpen ) {
- this.buttonView.focus();
- this.isOpen = false;
- cancel();
- }
- };
- // Open the dropdown panel using the arrow down key, just like with return or space.
- this.keystrokes.set( 'arrowdown', ( data, cancel ) => {
- // Don't open if the dropdown is disabled or already open.
- if ( this.buttonView.isEnabled && !this.isOpen ) {
- this.isOpen = true;
- cancel();
- }
- } );
- // Block the right arrow key (until nested dropdowns are implemented).
- this.keystrokes.set( 'arrowright', ( data, cancel ) => {
- if ( this.isOpen ) {
- cancel();
- }
- } );
- // Close the dropdown using the arrow left/escape key.
- this.keystrokes.set( 'arrowleft', closeDropdown );
- this.keystrokes.set( 'esc', closeDropdown );
- }
- /**
- * Focuses the {@link #buttonView}.
- */
- focus() {
- this.buttonView.focus();
- }
- /**
- * Returns {@link #panelView panel} positions to be used by the
- * {@link module:utils/dom/position~getOptimalPosition `getOptimalPosition()`}
- * utility considering the direction of the language the UI of the editor is displayed in.
- *
- * @type {module:utils/dom/position~Options#positions}
- * @private
- */
- get _panelPositions() {
- const { southEast, southWest, northEast, northWest } = DropdownView.defaultPanelPositions;
- if ( this.locale.languageDirection === 'ltr' ) {
- return [ southEast, southWest, northEast, northWest ];
- } else {
- return [ southWest, southEast, northWest, northEast ];
- }
- }
- }
- /**
- * A set of positioning functions used by the dropdown view to determine
- * the optimal position (i.e. fitting into the browser viewport) of its
- * {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel} when
- * {@link module:ui/dropdown/dropdownview~DropdownView#panelPosition} is set to 'auto'`.
- *
- * The available positioning functions are as follow:
- *
- * **South**
- *
- * * `southEast`
- *
- * [ Button ]
- * +-----------------+
- * | Panel |
- * +-----------------+
- *
- * * `southWest`
- *
- * [ Button ]
- * +-----------------+
- * | Panel |
- * +-----------------+
- *
- * **North**
- *
- * * `northEast`
- *
- * +-----------------+
- * | Panel |
- * +-----------------+
- * [ Button ]
- *
- * * `northWest`
- *
- * +-----------------+
- * | Panel |
- * +-----------------+
- * [ Button ]
- *
- * Positioning functions are compatible with {@link module:utils/dom/position~Position}.
- *
- * The name that position function returns will be reflected in dropdown panel's class that
- * controls its placement. See {@link module:ui/dropdown/dropdownview~DropdownView#panelPosition}
- * to learn more.
- *
- * @member {Object} module:ui/dropdown/dropdownview~DropdownView.defaultPanelPositions
- */
- DropdownView.defaultPanelPositions = {
- southEast: buttonRect => {
- return {
- top: buttonRect.bottom,
- left: buttonRect.left,
- name: 'se'
- };
- },
- southWest: ( buttonRect, panelRect ) => {
- return {
- top: buttonRect.bottom,
- left: buttonRect.left - panelRect.width + buttonRect.width,
- name: 'sw'
- };
- },
- northEast: ( buttonRect, panelRect ) => {
- return {
- top: buttonRect.top - panelRect.height,
- left: buttonRect.left,
- name: 'ne'
- };
- },
- northWest: ( buttonRect, panelRect ) => {
- return {
- top: buttonRect.bottom - panelRect.height,
- left: buttonRect.left - panelRect.width + buttonRect.width,
- name: 'nw'
- };
- }
- };
- /**
- * A function used to calculate the optimal position for the dropdown panel.
- *
- * @protected
- * @member {Function} module:ui/dropdown/dropdownview~DropdownView._getOptimalPosition
- */
- DropdownView._getOptimalPosition = getOptimalPosition;
|