diff options
| author | Darius Jahandarie <djahandarie@gmail.com> | 2023-12-06 03:53:16 +0000 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-12-06 03:53:16 +0000 | 
| commit | bd5bc1a5db29903bc098995cd9262c4576bf76af (patch) | |
| tree | c9214189e0214480fcf6539ad1c6327aef6cbd1c /ext/js/dom/selector-observer.js | |
| parent | fd6bba8a2a869eaf2b2c1fa49001f933fce3c618 (diff) | |
| parent | 23e6fb76319c9ed7c9bcdc3efba39bc5dd38f288 (diff) | |
Merge pull request #339 from toasted-nutbread/type-annotations
Type annotations
Diffstat (limited to 'ext/js/dom/selector-observer.js')
| -rw-r--r-- | ext/js/dom/selector-observer.js | 113 | 
1 files changed, 64 insertions, 49 deletions
| diff --git a/ext/js/dom/selector-observer.js b/ext/js/dom/selector-observer.js index e0e3d4ff..2cf46543 100644 --- a/ext/js/dom/selector-observer.js +++ b/ext/js/dom/selector-observer.js @@ -18,55 +18,35 @@  /**   * Class which is used to observe elements matching a selector in specific element. + * @template T   */  export class SelectorObserver {      /** -     * @function OnAddedCallback -     * @param {Element} element The element which was added. -     * @returns {*} Custom data which is assigned to element and passed to callbacks. -     */ - -    /** -     * @function OnRemovedCallback -     * @param {Element} element The element which was removed. -     * @param {*} data The custom data corresponding to the element. -     */ - -    /** -     * @function OnChildrenUpdatedCallback -     * @param {Element} element The element which had its children updated. -     * @param {*} data The custom data corresponding to the element. -     */ - -    /** -     * @function IsStaleCallback -     * @param {Element} element The element which had its children updated. -     * @param {*} data The custom data corresponding to the element. -     * @returns {boolean} Whether or not the data is stale for the element. -     */ - -    /**       * Creates a new instance. -     * @param {object} details The configuration for the object. -     * @param {string} details.selector A string CSS selector used to find elements. -     * @param {?string} details.ignoreSelector A string CSS selector used to filter elements, or `null` for no filtering. -     * @param {OnAddedCallback} details.onAdded A function which is invoked for each element that is added that matches the selector. -     * @param {?OnRemovedCallback} details.onRemoved A function which is invoked for each element that is removed, or `null`. -     * @param {?OnChildrenUpdatedCallback} details.onChildrenUpdated A function which is invoked for each element which has its children updated, or `null`. -     * @param {?IsStaleCallback} details.isStale A function which checks if the data is stale for a given element, or `null`. -     *   If the element is stale, it will be removed and potentially re-added. +     * @param {import('selector-observer').ConstructorDetails<T>} details The configuration for the object.       */      constructor({selector, ignoreSelector=null, onAdded=null, onRemoved=null, onChildrenUpdated=null, isStale=null}) { +        /** @type {string} */          this._selector = selector; +        /** @type {?string} */          this._ignoreSelector = ignoreSelector; +        /** @type {?import('selector-observer').OnAddedCallback<T>} */          this._onAdded = onAdded; +        /** @type {?import('selector-observer').OnRemovedCallback<T>} */          this._onRemoved = onRemoved; +        /** @type {?import('selector-observer').OnChildrenUpdatedCallback<T>} */          this._onChildrenUpdated = onChildrenUpdated; +        /** @type {?import('selector-observer').IsStaleCallback<T>} */          this._isStale = isStale; +        /** @type {?Element} */          this._observingElement = null; +        /** @type {MutationObserver} */          this._mutationObserver = new MutationObserver(this._onMutation.bind(this)); +        /** @type {Map<Node, import('selector-observer').Observer<T>>} */          this._elementMap = new Map(); // Map([element => observer]...) +        /** @type {Map<Node, Set<import('selector-observer').Observer<T>>>} */          this._elementAncestorMap = new Map(); // Map([element => Set([observer]...)]...) +        /** @type {boolean} */          this._isObserving = false;      } @@ -100,9 +80,10 @@ export class SelectorObserver {              subtree: true          }); +        const {parentNode} = element;          this._onMutation([{              type: 'childList', -            target: element.parentNode, +            target: parentNode !== null ? parentNode : element,              addedNodes: [element],              removedNodes: []          }]); @@ -124,17 +105,19 @@ export class SelectorObserver {      /**       * Returns an iterable list of [element, data] pairs. -     * @yields A sequence of [element, data] pairs. +     * @yields {[element: Element, data: T]} A sequence of [element, data] pairs. +     * @returns {Generator<[element: Element, data: T], void, unknown>}       */      *entries() { -        for (const [element, {data}] of this._elementMap) { +        for (const {element, data} of this._elementMap.values()) {              yield [element, data];          }      }      /**       * Returns an iterable list of data for every element. -     * @yields A sequence of data values. +     * @yields {T} A sequence of data values. +     * @returns {Generator<T, void, unknown>}       */      *datas() {          for (const {data} of this._elementMap.values()) { @@ -144,6 +127,9 @@ export class SelectorObserver {      // Private +    /** +     * @param {(MutationRecord|import('selector-observer').MutationRecordLike)[]} mutationList +     */      _onMutation(mutationList) {          for (const mutation of mutationList) {              switch (mutation.type) { @@ -157,6 +143,9 @@ export class SelectorObserver {          }      } +    /** +     * @param {MutationRecord|import('selector-observer').MutationRecordLike} record +     */      _onChildListMutation({addedNodes, removedNodes, target}) {          const selector = this._selector;          const ELEMENT_NODE = Node.ELEMENT_NODE; @@ -171,10 +160,10 @@ export class SelectorObserver {          for (const node of addedNodes) {              if (node.nodeType !== ELEMENT_NODE) { continue; } -            if (node.matches(selector)) { -                this._createObserver(node); +            if (/** @type {Element} */ (node).matches(selector)) { +                this._createObserver(/** @type {Element} */ (node));              } -            for (const childNode of node.querySelectorAll(selector)) { +            for (const childNode of /** @type {Element} */ (node).querySelectorAll(selector)) {                  this._createObserver(childNode);              }          } @@ -183,7 +172,7 @@ export class SelectorObserver {              this._onChildrenUpdated !== null &&              (addedNodes.length !== 0 || addedNodes.length !== 0)          ) { -            for (let node = target; node !== null; node = node.parentNode) { +            for (let node = /** @type {?Node} */ (target); node !== null; node = node.parentNode) {                  const observer = this._elementMap.get(node);                  if (typeof observer !== 'undefined') {                      this._onObserverChildrenUpdated(observer); @@ -192,9 +181,12 @@ export class SelectorObserver {          }      } +    /** +     * @param {MutationRecord|import('selector-observer').MutationRecordLike} record +     */      _onAttributeMutation({target}) {          const selector = this._selector; -        const observers = this._elementAncestorMap.get(target); +        const observers = this._elementAncestorMap.get(/** @type {Element} */ (target));          if (typeof observers !== 'undefined') {              for (const observer of observers) {                  const element = observer.element; @@ -208,15 +200,19 @@ export class SelectorObserver {              }          } -        if (target.matches(selector)) { -            this._createObserver(target); +        if (/** @type {Element} */ (target).matches(selector)) { +            this._createObserver(/** @type {Element} */ (target));          }      } +    /** +     * @param {Element} element +     */      _createObserver(element) {          if (this._elementMap.has(element) || this._shouldIgnoreElement(element) || this._onAdded === null) { return; }          const data = this._onAdded(element); +        if (typeof data === 'undefined') { return; }          const ancestors = this._getAncestors(element);          const observer = {element, ancestors, data}; @@ -232,6 +228,9 @@ export class SelectorObserver {          }      } +    /** +     * @param {import('selector-observer').Observer<T>} observer +     */      _removeObserver(observer) {          const {element, ancestors, data} = observer; @@ -252,26 +251,42 @@ export class SelectorObserver {          }      } +    /** +     * @param {import('selector-observer').Observer<T>} observer +     */      _onObserverChildrenUpdated(observer) { +        if (this._onChildrenUpdated === null) { return; }          this._onChildrenUpdated(observer.element, observer.data);      } +    /** +     * @param {import('selector-observer').Observer<T>} observer +     * @returns {boolean} +     */      _isObserverStale(observer) {          return (this._isStale !== null && this._isStale(observer.element, observer.data));      } +    /** +     * @param {Element} element +     * @returns {boolean} +     */      _shouldIgnoreElement(element) {          return (this._ignoreSelector !== null && element.matches(this._ignoreSelector));      } +    /** +     * @param {Node} node +     * @returns {Node[]} +     */      _getAncestors(node) {          const root = this._observingElement;          const results = []; -        while (true) { -            results.push(node); -            if (node === root) { break; } -            node = node.parentNode; -            if (node === null) { break; } +        let n = /** @type {?Node} */ (node); +        while (n !== null) { +            results.push(n); +            if (n === root) { break; } +            n = n.parentNode;          }          return results;      } |