UNPKG

carbon-custom-elements

Version:

A Carbon Design System variant that's as easy to use as native HTML elements, with no framework tax, no framework silo.

1 lines 11 kB
{"version":3,"sources":["components/modal/modal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EAAwC,UAAU,EAAE,MAAM,aAAa,CAAC;AAc/E;;GAEG;AACH,oBAAY,UAAU;IACpB;;OAEG;IACH,WAAW,OAAO;IAElB;;OAEG;IACH,KAAK,OAAO;IAEZ;;OAEG;IACH,OAAO,KAAK;IAEZ;;OAEG;IACH,KAAK,OAAO;CACb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAED;;;;;;;GAOG;AACH,cACM,OAAQ,SAAQ,YAA6B;IACjD;;OAEG;IACH,OAAO,CAAC,SAAS,CAAwB;IAEzC;;OAEG;IAEH,OAAO,CAAC,kBAAkB,CAAqB;IAE/C;;OAEG;IAEH,OAAO,CAAC,gBAAgB,CAAqB;IAE7C;;;OAGG;IAGH,OAAO,CAAC,YAAY,CAIlB;IAEF;;;OAGG;IAGH,OAAO,CAAC,WAAW,CAmCjB;IAIF,OAAO,CAAC,cAAc,CAIpB;IAEF;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAM7B;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAiBjC;;OAEG;IAEH,cAAc,SAAM;IAEpB;;OAEG;IAEH,IAAI,UAAS;IAEb;;OAEG;IAEH,IAAI,aAAsB;IAE1B,MAAM;IAoBN,iBAAiB;IAOX,OAAO,CAAC,iBAAiB,KAAA;IA8B/B;;OAEG;IACH,MAAM,KAAK,mBAAmB,WAE7B;IAED;;OAEG;IACH,MAAM,KAAK,gBAAgB,WAE1B;IAED;;OAEG;IACH,MAAM,KAAK,oBAAoB,WAE9B;IAED;;;OAGG;IACH,MAAM,KAAK,gBAAgB,WAE1B;IAED;;OAEG;IACH,MAAM,KAAK,UAAU,WAEpB;IAED,MAAM,CAAC,MAAM,MAAU;CACxB;AAED,eAAe,OAAO,CAAC","file":"modal.d.ts","sourcesContent":["/**\n * @license\n *\n * Copyright IBM Corp. 2019, 2020\n *\n * This source code is licensed under the Apache-2.0 license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport settings from 'carbon-components/es/globals/js/settings';\nimport findLast from 'lodash-es/findLast';\nimport { classMap } from 'lit-html/directives/class-map';\nimport { html, property, query, customElement, LitElement } from 'lit-element';\nimport HostListener from '../../globals/decorators/host-listener';\nimport HostListenerMixin from '../../globals/mixins/host-listener';\nimport styles from './modal.scss';\nimport { selectorTabbable } from '../../globals/settings';\nimport { find } from '../../globals/internal/collection-helpers';\n\nconst { prefix } = settings;\n\n// eslint-disable-next-line no-bitwise\nconst PRECEDING = Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS;\n// eslint-disable-next-line no-bitwise\nconst FOLLOWING = Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_CONTAINED_BY;\n\n/**\n * Modal size.\n */\nexport enum MODAL_SIZE {\n /**\n * Extra small size.\n */\n EXTRA_SMALL = 'xs',\n\n /**\n * Small size.\n */\n SMALL = 'sm',\n\n /**\n * Regular size.\n */\n REGULAR = '',\n\n /**\n * Large size.\n */\n LARGE = 'lg',\n}\n\n/**\n * Modal.\n * @element bx-modal\n * @fires bx-modal-beingclosed\n * The custom event fired before this modal is being closed upon a user gesture.\n * Cancellation of this event stops the user-initiated action of closing this modal.\n * @fires bx-modal-closed - The custom event fired after this modal is closed upon a user gesture.\n */\n@customElement(`${prefix}-modal`)\nclass BXModal extends HostListenerMixin(LitElement) {\n /**\n * The element that had focus before this modal gets open.\n */\n private _launcher: Element | null = null;\n\n /**\n * Node to track focus going outside of modal content.\n */\n @query('#start-sentinel')\n private _startSentinelNode!: HTMLAnchorElement;\n\n /**\n * Node to track focus going outside of modal content.\n */\n @query('#end-sentinel')\n private _endSentinelNode!: HTMLAnchorElement;\n\n /**\n * Handles `click` event on this element.\n * @param event The event.\n */\n @HostListener('click')\n // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to\n private _handleClick = (event: MouseEvent) => {\n if (event.composedPath().indexOf(this.shadowRoot!) < 0) {\n this._handleUserInitiatedClose(event.target);\n }\n };\n\n /**\n * Handles `blur` event on this element.\n * @param event The event.\n */\n @HostListener('shadowRoot:focusout')\n // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to\n private _handleBlur = ({ target, relatedTarget }: FocusEvent) => {\n const oldContains = target !== this && this.contains(target as Node);\n const currentContains = relatedTarget !== this && this.contains(relatedTarget as Node);\n\n // Performs focus wrapping if _all_ of the following is met:\n // * This modal is open\n // * The viewport still has focus\n // * Modal body used to have focus but no longer has focus\n const { open, _startSentinelNode: startSentinelNode, _endSentinelNode: endSentinelNode } = this;\n const { selectorTabbable: selectorTabbableForModal } = this.constructor as typeof BXModal;\n if (open && relatedTarget && oldContains && !currentContains) {\n const comparisonResult = (target as Node).compareDocumentPosition(relatedTarget as Node);\n // eslint-disable-next-line no-bitwise\n if (relatedTarget === startSentinelNode || comparisonResult & PRECEDING) {\n const tabbable = findLast(this.querySelectorAll(selectorTabbableForModal), elem =>\n Boolean((elem as HTMLElement).offsetParent)\n );\n if (tabbable) {\n (tabbable as HTMLElement).focus();\n } else if (relatedTarget !== this) {\n this.focus();\n }\n }\n // eslint-disable-next-line no-bitwise\n else if (relatedTarget === endSentinelNode || comparisonResult & FOLLOWING) {\n const tabbable = find(this.querySelectorAll(selectorTabbableForModal), elem =>\n Boolean((elem as HTMLElement).offsetParent)\n );\n if (tabbable) {\n (tabbable as HTMLElement).focus();\n } else {\n this.focus();\n }\n }\n }\n };\n\n @HostListener('document:keydown')\n // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to\n private _handleKeydown = ({ key, target }: KeyboardEvent) => {\n if (key === 'Esc' || key === 'Escape') {\n this._handleUserInitiatedClose(target);\n }\n };\n\n /**\n * Handles `click` event on the modal container.\n * @param event The event.\n */\n private _handleClickContainer(event: MouseEvent) {\n if ((event.target as Element).matches((this.constructor as typeof BXModal).selectorCloseButton)) {\n this._handleUserInitiatedClose(event.target);\n }\n }\n\n /**\n * Handles user-initiated close request of this modal.\n * @param triggeredBy The element that triggered this close request.\n */\n private _handleUserInitiatedClose(triggeredBy: EventTarget | null) {\n if (this.open) {\n const init = {\n bubbles: true,\n cancelable: true,\n composed: true,\n detail: {\n triggeredBy,\n },\n };\n if (this.dispatchEvent(new CustomEvent((this.constructor as typeof BXModal).eventBeforeClose, init))) {\n this.open = false;\n this.dispatchEvent(new CustomEvent((this.constructor as typeof BXModal).eventClose, init));\n }\n }\n }\n\n /**\n * The additional CSS class names for the container <div> of the element.\n */\n @property({ attribute: 'container-class' })\n containerClass = '';\n\n /**\n * `true` if the modal should be open.\n */\n @property({ type: Boolean, reflect: true })\n open = false;\n\n /**\n * Modal size.\n */\n @property({ reflect: true })\n size = MODAL_SIZE.REGULAR;\n\n render() {\n const { size } = this;\n const containerClass = this.containerClass\n .split(' ')\n .filter(Boolean)\n .reduce((acc, item) => ({ ...acc, [item]: true }), {});\n const containerClasses = classMap({\n [`${prefix}--modal-container`]: true,\n [`${prefix}--modal-container--${size}`]: size,\n ...containerClass,\n });\n return html`\n <a id=\"start-sentinel\" class=\"${prefix}--visually-hidden\" href=\"javascript:void 0\" role=\"navigation\"></a>\n <div class=${containerClasses} role=\"dialog\" tabidnex=\"-1\" @click=${this._handleClickContainer}>\n <slot></slot>\n </div>\n <a id=\"end-sentinel\" class=\"${prefix}--visually-hidden\" href=\"javascript:void 0\" role=\"navigation\"></a>\n `;\n }\n\n connectedCallback() {\n if (!this.hasAttribute('tabindex')) {\n this.setAttribute('tabindex', '0');\n }\n super.connectedCallback();\n }\n\n async updated(changedProperties) {\n if (changedProperties.has('open')) {\n if (this.open) {\n this._launcher = this.ownerDocument!.activeElement;\n const primaryFocusNode = this.querySelector((this.constructor as typeof BXModal).selectorPrimaryFocus);\n if (primaryFocusNode) {\n // For cases where a `carbon-custom-elements` component (e.g. `<bx-btn>`) being `primaryFocusNode`,\n // where its first update/render cycle that makes it focusable happens after `<bx-modal>`'s first update/render cycle\n await 0;\n (primaryFocusNode as HTMLElement).focus();\n } else {\n const tabbable = find(this.querySelectorAll((this.constructor as typeof BXModal).selectorTabbable), elem =>\n Boolean((elem as HTMLElement).offsetParent)\n );\n if (tabbable) {\n // For cases where a `carbon-custom-elements` component (e.g. `<bx-btn>`) being `tabbable`,\n // where its first update/render cycle that makes it focusable happens after `<bx-modal>`'s first update/render cycle\n await 0;\n (tabbable as HTMLElement).focus();\n } else {\n this.focus();\n }\n }\n } else if (this._launcher && typeof (this._launcher as HTMLElement).focus === 'function') {\n (this._launcher as HTMLElement).focus();\n this._launcher = null;\n }\n }\n }\n\n /**\n * A selector selecting buttons that should close this modal.\n */\n static get selectorCloseButton() {\n return `[data-modal-close],${prefix}-modal-close-button`;\n }\n\n /**\n * A selector selecting tabbable nodes.\n */\n static get selectorTabbable() {\n return selectorTabbable;\n }\n\n /**\n * A selector selecting the nodes that should be focused when modal gets open.\n */\n static get selectorPrimaryFocus() {\n return `[data-modal-primary-focus],${prefix}-modal-footer ${prefix}-btn[kind=\"primary\"]`;\n }\n\n /**\n * The name of the custom event fired before this modal is being closed upon a user gesture.\n * Cancellation of this event stops the user-initiated action of closing this modal.\n */\n static get eventBeforeClose() {\n return `${prefix}-modal-beingclosed`;\n }\n\n /**\n * The name of the custom event fired after this modal is closed upon a user gesture.\n */\n static get eventClose() {\n return `${prefix}-modal-closed`;\n }\n\n static styles = styles; // `styles` here is a `CSSResult` generated by custom WebPack loader\n}\n\nexport default BXModal;\n"]}