| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @extends {UI.XElement} |
| */ |
| export default class XWidget extends UI.XElement { |
| constructor() { |
| super(); |
| this.style.setProperty('display', 'flex'); |
| this.style.setProperty('flex-direction', 'column'); |
| this.style.setProperty('align-items', 'stretch'); |
| this.style.setProperty('justify-content', 'flex-start'); |
| this.style.setProperty('contain', 'layout style'); |
| |
| this._visible = false; |
| /** @type {?DocumentFragment} */ |
| this._shadowRoot; |
| /** @type {?Element} */ |
| this._defaultFocusedElement = null; |
| /** @type {!Array<!Element>} */ |
| this._elementsToRestoreScrollPositionsFor = []; |
| /** @type {?function()} */ |
| this._onShownCallback; |
| /** @type {?function()} */ |
| this._onHiddenCallback; |
| /** @type {?function()} */ |
| this._onResizedCallback; |
| |
| if (!XWidget._observer) { |
| XWidget._observer = new ResizeObserver(entries => { |
| for (const entry of entries) { |
| if (entry.target._visible && entry.target._onResizedCallback) { |
| entry.target._onResizedCallback.call(null); |
| } |
| } |
| }); |
| } |
| XWidget._observer.observe(this); |
| |
| this.setElementsToRestoreScrollPositionsFor([this]); |
| } |
| |
| /** |
| * @param {?Node} node |
| */ |
| static focusWidgetForNode(node) { |
| node = node && node.parentNodeOrShadowHost(); |
| let widget = null; |
| while (node) { |
| if (node instanceof XWidget) { |
| if (widget) { |
| node._defaultFocusedElement = widget; |
| } |
| widget = node; |
| } |
| node = node.parentNodeOrShadowHost(); |
| } |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isShowing() { |
| return this._visible; |
| } |
| |
| /** |
| * @param {string} cssFile |
| */ |
| registerRequiredCSS(cssFile) { |
| UI.appendStyle(this._shadowRoot || this, cssFile); |
| } |
| |
| /** |
| * @param {?function()} callback |
| */ |
| setOnShown(callback) { |
| this._onShownCallback = callback; |
| } |
| |
| /** |
| * @param {?function()} callback |
| */ |
| setOnHidden(callback) { |
| this._onHiddenCallback = callback; |
| } |
| |
| /** |
| * @param {?function()} callback |
| */ |
| setOnResized(callback) { |
| this._onResizedCallback = callback; |
| } |
| |
| /** |
| * @param {!Array<!Element>} elements |
| */ |
| setElementsToRestoreScrollPositionsFor(elements) { |
| for (const element of this._elementsToRestoreScrollPositionsFor) { |
| element.removeEventListener('scroll', XWidget._storeScrollPosition, {passive: true, capture: false}); |
| } |
| this._elementsToRestoreScrollPositionsFor = elements; |
| for (const element of this._elementsToRestoreScrollPositionsFor) { |
| element.addEventListener('scroll', XWidget._storeScrollPosition, {passive: true, capture: false}); |
| } |
| } |
| |
| restoreScrollPositions() { |
| for (const element of this._elementsToRestoreScrollPositionsFor) { |
| if (element._scrollTop) { |
| element.scrollTop = element._scrollTop; |
| } |
| if (element._scrollLeft) { |
| element.scrollLeft = element._scrollLeft; |
| } |
| } |
| } |
| |
| /** |
| * @param {!Event} event |
| */ |
| static _storeScrollPosition(event) { |
| const element = event.currentTarget; |
| element._scrollTop = element.scrollTop; |
| element._scrollLeft = element.scrollLeft; |
| } |
| |
| /** |
| * @param {?Element} element |
| */ |
| setDefaultFocusedElement(element) { |
| if (element && !this.isSelfOrAncestor(element)) { |
| throw new Error('Default focus must be descendant'); |
| } |
| this._defaultFocusedElement = element; |
| } |
| |
| /** |
| * @override |
| */ |
| focus() { |
| if (!this._visible) { |
| return; |
| } |
| |
| let element; |
| if (this._defaultFocusedElement && this.isSelfOrAncestor(this._defaultFocusedElement)) { |
| element = this._defaultFocusedElement; |
| } else if (this.tabIndex !== -1) { |
| element = this; |
| } else { |
| let child = this.traverseNextNode(this); |
| while (child) { |
| if ((child instanceof XWidget) && child._visible) { |
| element = child; |
| break; |
| } |
| child = child.traverseNextNode(this); |
| } |
| } |
| |
| if (!element || element.hasFocus()) { |
| return; |
| } |
| if (element === this) { |
| HTMLElement.prototype.focus.call(this); |
| } else { |
| element.focus(); |
| } |
| } |
| |
| /** |
| * @override |
| */ |
| connectedCallback() { |
| this._visible = true; |
| this.restoreScrollPositions(); |
| if (this._onShownCallback) { |
| this._onShownCallback.call(null); |
| } |
| } |
| |
| /** |
| * @override |
| */ |
| disconnectedCallback() { |
| this._visible = false; |
| if (this._onHiddenCallback) { |
| this._onHiddenCallback.call(null); |
| } |
| } |
| } |
| |
| self.customElements.define('x-widget', XWidget); |
| |
| /* Legacy exported object*/ |
| self.UI = self.UI || {}; |
| |
| /* Legacy exported object*/ |
| UI = UI || {}; |
| |
| /** @constructor */ |
| UI.XWidget = XWidget; |