diff options
Diffstat (limited to 'chromium/third_party/catapult/third_party/polymer2/bower_components/chopsui/chops-dialog.html')
-rw-r--r-- | chromium/third_party/catapult/third_party/polymer2/bower_components/chopsui/chops-dialog.html | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/chromium/third_party/catapult/third_party/polymer2/bower_components/chopsui/chops-dialog.html b/chromium/third_party/catapult/third_party/polymer2/bower_components/chopsui/chops-dialog.html new file mode 100644 index 00000000000..a3ec993c968 --- /dev/null +++ b/chromium/third_party/catapult/third_party/polymer2/bower_components/chopsui/chops-dialog.html @@ -0,0 +1,235 @@ +<link rel="import" href="../polymer/polymer.html"> +<link rel="import" href="../iron-overlay-behavior/iron-focusables-helper.html"> + +<dom-module id="chops-dialog"> + <template> + <style> + :host { + position: fixed; + z-index: 9999; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0,0,0,0.4); + display: flex; + align-items: center; + justify-content: center; + } + :host(:not([opened])), [hidden] { + display: none; + visibility: hidden; + } + :host([close-on-outside-click]) { + cursor: pointer; + } + .dialog { + box-sizing: border-box; + background: white; + max-width: 90%; + max-height: 95%; + overflow: auto; + padding: 1em 16px; + cursor: default; + box-shadow: 0px 3px 20px 0px hsla(0, 0%, 0%, 0.4); + + /* + * Would have really preferred to just have the dialog itself be the + * :host element so that users could style it more easily, but this + * causes issues with the backdrop, because there's not a good way to + * make a child element appear behind its parent without using wrappers. + */ + @apply --chops-dialog-theme; + } + </style> + <div id="dialog" class="dialog" role="dialog"> + <slot></slot> + </div> + </template> + <script> + (function(window) { + 'use strict'; + + const ESC_KEYCODE = 27; + const TAB_KEYCODE = 9; + + /** + * `<chops-dialog>` displays a modal/dialog overlay. + * + * @customElement + * @polymer + * @demo /demo/chops-dialog_demo.html + */ + class ChopsDialog extends Polymer.mixinBehaviors([Polymer.IronFocusablesHelper], Polymer.Element) { + static get is() { return 'chops-dialog'; } + + static get properties() { + return { + /** + * Whether the dialog should currently be displayed or not. + */ + opened: { + type: Boolean, + notify: true, + value: false, + reflectToAttribute: true, + observer: '_openedChanged', + }, + /** + * A boolean that determines whether clicking outside of the dialog + * window should close it. + */ + closeOnOutsideClick: { + type: Boolean, + value: true, + reflectToAttribute: true, + }, + /** + * A function fired when the element tries to change its own opened + * state. This is useful if you want the dialog state managed outside + * of the dialog instead of with internal state. (ie: with Redux) + */ + onOpenedChange: Function, + /** + * Allow people to exit the dialog using keyboard shortcuts. Defaults + * to the escape key. + */ + exitKeys: { + type: Array, + value: [ESC_KEYCODE], + }, + _boundKeydownHandler: { + type: Function, + value: function() { + return this._keydownHandler.bind(this); + }, + }, + _previousFocusedElement: Object, + } + } + + ready() { + super.ready(); + this.addEventListener('click', (evt) => { + if (!this.opened || !this.closeOnOutsideClick) return; + + const hasDialog = evt.composedPath().find( + (node) => { + return node.classList && node.classList.contains('dialog'); + } + ); + if (hasDialog) return; + + this.close(); + }); + } + + connectedCallback() { + super.connectedCallback(); + window.addEventListener('keydown', this._boundKeydownHandler, true); + } + + disconnectedCallback() { + super.disconnectedCallback(); + window.removeEventListener('keydown', this._boundKeydownHandler, + true); + } + + _keydownHandler(event) { + if (!this.opened) return; + let keyCode = event.keyCode; + + // Handle closing hot keys. + if (this.exitKeys.includes(keyCode)) { + this.close(); + } + + // For accessibility, prevent tabbing outside of the dialog. + // Key code 9 is tab. + if (keyCode === TAB_KEYCODE) { + const tabbables = this.getTabbableNodes(this); + const active = this._getActiveElement(); + + if (!tabbables || !tabbables.length) { + event.preventDefault(); + } + + if (event.shiftKey) { + // backwards tab. + if (active === tabbables[0]) { + event.preventDefault(); + tabbables[tabbables.length - 1].focus(); + } + } else { + // forward tab. + if (active === tabbables[tabbables.length - 1]) { + event.preventDefault(); + tabbables[0].focus(); + } + } + } + } + + close() { + if (this.onOpenedChange) { + this.onOpenedChange(false); + } else { + this.opened = false; + } + } + + open() { + if (this.onOpenedChange) { + this.onOpenedChange(true); + } else { + this.opened = true; + } + } + + toggle() { + this.opened = !this.opened; + } + + _getActiveElement() { + // document.activeElement alone isn't sufficient to find the active + // element within shadow dom. + let active = document.activeElement || document.body; + while (active.root && Polymer.dom(active.root).activeElement) { + active = Polymer.dom(active.root).activeElement; + } + return active; + } + + _openedChanged(opened) { + if (opened) { + // For accessibility, we want to ensure we remember the element that was + // focused before this dialog opened. + this._previousFocusedElement = this._getActiveElement(); + + // Focus the first element within the dialog when it's opened. + const tabbables = this.getTabbableNodes(this); + if (tabbables && tabbables.length) { + tabbables[0].focus(); + } else if (this._previousFocusedElement) { + this._previousFocusedElement.blur(); + } + } else { + if (this._previousFocusedElement) { + const element = this._previousFocusedElement; + setTimeout(function() { + // HACK. This is to prevent a possible accessibility bug where + // using a keypress to trigger a button that exits a modal causes + // the modal to immediately re-open because the button that + // originally opened the modal refocuses, and the keypress + // propagates. + element.focus(); + }, 1); + } + } + } + } + customElements.define(ChopsDialog.is, ChopsDialog); + })(window); + </script> +<dom-module> |