diff options
Diffstat (limited to 'ext/js/dom')
-rw-r--r-- | ext/js/dom/document-util.js | 798 | ||||
-rw-r--r-- | ext/js/dom/dom-data-binder.js | 4 | ||||
-rw-r--r-- | ext/js/dom/text-source-element.js | 4 | ||||
-rw-r--r-- | ext/js/dom/text-source-generator.js | 8 | ||||
-rw-r--r-- | ext/js/dom/text-source-range.js | 16 |
5 files changed, 415 insertions, 415 deletions
diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index a98bfe86..bf11f421 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -15,466 +15,466 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ - /** - * This class contains utility functions related to the HTML document. - * TODO : This class should be made non-static + * This variable is stateful, but it is only used to do feature detection, + * and its value should be constant for the lifetime of the extension. + * @type {?boolean} */ -// eslint-disable-next-line unicorn/no-static-only-class -export class DocumentUtil { - /** @type {?boolean} */ - static _cssZoomSupported = null; +let cssZoomSupported = null; - /** - * Computes the scaling adjustment that is necessary for client space coordinates based on the - * CSS zoom level. - * @param {?Node} node A node in the document. - * @returns {number} The scaling factor. - */ - static computeZoomScale(node) { - if (this._cssZoomSupported === null) { - this._cssZoomSupported = this._computeCssZoomSupported(); - } - if (!this._cssZoomSupported) { return 1; } - // documentElement must be excluded because the computer style of its zoom property is inconsistent. - // * If CSS `:root{zoom:X;}` is specified, the computed zoom will always report `X`. - // * If CSS `:root{zoom:X;}` is not specified, the computed zoom report the browser's zoom level. - // Therefor, if CSS root zoom is specified as a value other than 1, the adjusted {x, y} values - // would be incorrect, which is not new behaviour. - let scale = 1; - const {ELEMENT_NODE, DOCUMENT_FRAGMENT_NODE} = Node; - const {documentElement} = document; - for (; node !== null && node !== documentElement; node = node.parentNode) { - const {nodeType} = node; - if (nodeType === DOCUMENT_FRAGMENT_NODE) { - const {host} = /** @type {ShadowRoot} */ (node); - if (typeof host !== 'undefined') { - node = host; - } - continue; - } else if (nodeType !== ELEMENT_NODE) { - continue; +/** + * Computes the scaling adjustment that is necessary for client space coordinates based on the + * CSS zoom level. + * @param {?Node} node A node in the document. + * @returns {number} The scaling factor. + */ +export function computeZoomScale(node) { + if (cssZoomSupported === null) { + cssZoomSupported = computeCssZoomSupported(); + } + if (!cssZoomSupported) { return 1; } + // documentElement must be excluded because the computer style of its zoom property is inconsistent. + // * If CSS `:root{zoom:X;}` is specified, the computed zoom will always report `X`. + // * If CSS `:root{zoom:X;}` is not specified, the computed zoom report the browser's zoom level. + // Therefor, if CSS root zoom is specified as a value other than 1, the adjusted {x, y} values + // would be incorrect, which is not new behaviour. + let scale = 1; + const {ELEMENT_NODE, DOCUMENT_FRAGMENT_NODE} = Node; + const {documentElement} = document; + for (; node !== null && node !== documentElement; node = node.parentNode) { + const {nodeType} = node; + if (nodeType === DOCUMENT_FRAGMENT_NODE) { + const {host} = /** @type {ShadowRoot} */ (node); + if (typeof host !== 'undefined') { + node = host; } - const zoomString = getComputedStyle(/** @type {HTMLElement} */ (node)).getPropertyValue('zoom'); - if (typeof zoomString !== 'string' || zoomString.length === 0) { continue; } - const zoom = Number.parseFloat(zoomString); - if (!Number.isFinite(zoom) || zoom === 0) { continue; } - scale *= zoom; + continue; + } else if (nodeType !== ELEMENT_NODE) { + continue; } - return scale; + const zoomString = getComputedStyle(/** @type {HTMLElement} */ (node)).getPropertyValue('zoom'); + if (typeof zoomString !== 'string' || zoomString.length === 0) { continue; } + const zoom = Number.parseFloat(zoomString); + if (!Number.isFinite(zoom) || zoom === 0) { continue; } + scale *= zoom; } + return scale; +} - /** - * Converts a rect based on the CSS zoom scaling for a given node. - * @param {DOMRect} rect The rect to convert. - * @param {Node} node The node to compute the zoom from. - * @returns {DOMRect} The updated rect, or the same rect if no change is needed. - */ - static convertRectZoomCoordinates(rect, node) { - const scale = this.computeZoomScale(node); - return (scale === 1 ? rect : new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale)); - } +/** + * Converts a rect based on the CSS zoom scaling for a given node. + * @param {DOMRect} rect The rect to convert. + * @param {Node} node The node to compute the zoom from. + * @returns {DOMRect} The updated rect, or the same rect if no change is needed. + */ +export function convertRectZoomCoordinates(rect, node) { + const scale = computeZoomScale(node); + return (scale === 1 ? rect : new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale)); +} - /** - * Converts multiple rects based on the CSS zoom scaling for a given node. - * @param {DOMRect[]|DOMRectList} rects The rects to convert. - * @param {Node} node The node to compute the zoom from. - * @returns {DOMRect[]} The updated rects, or the same rects array if no change is needed. - */ - static convertMultipleRectZoomCoordinates(rects, node) { - const scale = this.computeZoomScale(node); - if (scale === 1) { return [...rects]; } - const results = []; - for (const rect of rects) { - results.push(new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale)); - } - return results; +/** + * Converts multiple rects based on the CSS zoom scaling for a given node. + * @param {DOMRect[]|DOMRectList} rects The rects to convert. + * @param {Node} node The node to compute the zoom from. + * @returns {DOMRect[]} The updated rects, or the same rects array if no change is needed. + */ +export function convertMultipleRectZoomCoordinates(rects, node) { + const scale = computeZoomScale(node); + if (scale === 1) { return [...rects]; } + const results = []; + for (const rect of rects) { + results.push(new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale)); } + return results; +} - /** - * Checks whether a given point is contained within a rect. - * @param {number} x The horizontal coordinate. - * @param {number} y The vertical coordinate. - * @param {DOMRect} rect The rect to check. - * @returns {boolean} `true` if the point is inside the rect, `false` otherwise. - */ - static isPointInRect(x, y, rect) { - return ( - x >= rect.left && x < rect.right && - y >= rect.top && y < rect.bottom - ); - } +/** + * Checks whether a given point is contained within a rect. + * @param {number} x The horizontal coordinate. + * @param {number} y The vertical coordinate. + * @param {DOMRect} rect The rect to check. + * @returns {boolean} `true` if the point is inside the rect, `false` otherwise. + */ +export function isPointInRect(x, y, rect) { + return ( + x >= rect.left && x < rect.right && + y >= rect.top && y < rect.bottom + ); +} - /** - * Checks whether a given point is contained within any rect in a list. - * @param {number} x The horizontal coordinate. - * @param {number} y The vertical coordinate. - * @param {DOMRect[]|DOMRectList} rects The rect to check. - * @returns {boolean} `true` if the point is inside any of the rects, `false` otherwise. - */ - static isPointInAnyRect(x, y, rects) { - for (const rect of rects) { - if (this.isPointInRect(x, y, rect)) { - return true; - } +/** + * Checks whether a given point is contained within any rect in a list. + * @param {number} x The horizontal coordinate. + * @param {number} y The vertical coordinate. + * @param {DOMRect[]|DOMRectList} rects The rect to check. + * @returns {boolean} `true` if the point is inside any of the rects, `false` otherwise. + */ +export function isPointInAnyRect(x, y, rects) { + for (const rect of rects) { + if (isPointInRect(x, y, rect)) { + return true; } - return false; } + return false; +} - /** - * Checks whether a given point is contained within a selection range. - * @param {number} x The horizontal coordinate. - * @param {number} y The vertical coordinate. - * @param {Selection} selection The selection to check. - * @returns {boolean} `true` if the point is inside the selection, `false` otherwise. - */ - static isPointInSelection(x, y, selection) { - for (let i = 0; i < selection.rangeCount; ++i) { - const range = selection.getRangeAt(i); - if (this.isPointInAnyRect(x, y, range.getClientRects())) { - return true; - } +/** + * Checks whether a given point is contained within a selection range. + * @param {number} x The horizontal coordinate. + * @param {number} y The vertical coordinate. + * @param {Selection} selection The selection to check. + * @returns {boolean} `true` if the point is inside the selection, `false` otherwise. + */ +export function isPointInSelection(x, y, selection) { + for (let i = 0; i < selection.rangeCount; ++i) { + const range = selection.getRangeAt(i); + if (isPointInAnyRect(x, y, range.getClientRects())) { + return true; } - return false; } + return false; +} - /** - * Gets an array of the active modifier keys. - * @param {KeyboardEvent|MouseEvent|TouchEvent} event The event to check. - * @returns {import('input').ModifierKey[]} An array of modifiers. - */ - static getActiveModifiers(event) { - /** @type {import('input').ModifierKey[]} */ - const modifiers = []; - if (event.altKey) { modifiers.push('alt'); } - if (event.ctrlKey) { modifiers.push('ctrl'); } - if (event.metaKey) { modifiers.push('meta'); } - if (event.shiftKey) { modifiers.push('shift'); } - return modifiers; - } +/** + * Gets an array of the active modifier keys. + * @param {KeyboardEvent|MouseEvent|TouchEvent} event The event to check. + * @returns {import('input').ModifierKey[]} An array of modifiers. + */ +export function getActiveModifiers(event) { + /** @type {import('input').ModifierKey[]} */ + const modifiers = []; + if (event.altKey) { modifiers.push('alt'); } + if (event.ctrlKey) { modifiers.push('ctrl'); } + if (event.metaKey) { modifiers.push('meta'); } + if (event.shiftKey) { modifiers.push('shift'); } + return modifiers; +} - /** - * Gets an array of the active modifier keys and buttons. - * @param {KeyboardEvent|MouseEvent|TouchEvent} event The event to check. - * @returns {import('input').Modifier[]} An array of modifiers and buttons. - */ - static getActiveModifiersAndButtons(event) { - /** @type {import('input').Modifier[]} */ - const modifiers = this.getActiveModifiers(event); - if (event instanceof MouseEvent) { - this._getActiveButtons(event, modifiers); - } - return modifiers; +/** + * Gets an array of the active modifier keys and buttons. + * @param {KeyboardEvent|MouseEvent|TouchEvent} event The event to check. + * @returns {import('input').Modifier[]} An array of modifiers and buttons. + */ +export function getActiveModifiersAndButtons(event) { + /** @type {import('input').Modifier[]} */ + const modifiers = getActiveModifiers(event); + if (event instanceof MouseEvent) { + getActiveButtonsInternal(event, modifiers); } + return modifiers; +} - /** - * Gets an array of the active buttons. - * @param {MouseEvent} event The event to check. - * @returns {import('input').ModifierMouseButton[]} An array of modifiers and buttons. - */ - static getActiveButtons(event) { - /** @type {import('input').ModifierMouseButton[]} */ - const buttons = []; - this._getActiveButtons(event, buttons); - return buttons; - } +/** + * Gets an array of the active buttons. + * @param {MouseEvent} event The event to check. + * @returns {import('input').ModifierMouseButton[]} An array of modifiers and buttons. + */ +export function getActiveButtons(event) { + /** @type {import('input').ModifierMouseButton[]} */ + const buttons = []; + getActiveButtonsInternal(event, buttons); + return buttons; +} - /** - * Adds a fullscreen change event listener. This function handles all of the browser-specific variants. - * @param {EventListener} onFullscreenChanged The event callback. - * @param {?import('../core/event-listener-collection.js').EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. - */ - static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection = null) { - const target = document; - const options = false; - const fullscreenEventNames = [ - 'fullscreenchange', - 'MSFullscreenChange', - 'mozfullscreenchange', - 'webkitfullscreenchange' - ]; - for (const eventName of fullscreenEventNames) { - if (eventListenerCollection === null) { - target.addEventListener(eventName, onFullscreenChanged, options); - } else { - eventListenerCollection.addEventListener(target, eventName, onFullscreenChanged, options); - } +/** + * Adds a fullscreen change event listener. This function handles all of the browser-specific variants. + * @param {EventListener} onFullscreenChanged The event callback. + * @param {?import('../core/event-listener-collection.js').EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. + */ +export function addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection = null) { + const target = document; + const options = false; + const fullscreenEventNames = [ + 'fullscreenchange', + 'MSFullscreenChange', + 'mozfullscreenchange', + 'webkitfullscreenchange' + ]; + for (const eventName of fullscreenEventNames) { + if (eventListenerCollection === null) { + target.addEventListener(eventName, onFullscreenChanged, options); + } else { + eventListenerCollection.addEventListener(target, eventName, onFullscreenChanged, options); } } +} - /** - * Returns the current fullscreen element. This function handles all of the browser-specific variants. - * @returns {?Element} The current fullscreen element, or `null` if the window is not fullscreen. - */ - static getFullscreenElement() { - return ( - document.fullscreenElement || - // @ts-expect-error - vendor prefix - document.msFullscreenElement || - // @ts-expect-error - vendor prefix - document.mozFullScreenElement || - // @ts-expect-error - vendor prefix - document.webkitFullscreenElement || - null - ); - } +/** + * Returns the current fullscreen element. This function handles all of the browser-specific variants. + * @returns {?Element} The current fullscreen element, or `null` if the window is not fullscreen. + */ +export function getFullscreenElement() { + return ( + document.fullscreenElement || + // @ts-expect-error - vendor prefix + document.msFullscreenElement || + // @ts-expect-error - vendor prefix + document.mozFullScreenElement || + // @ts-expect-error - vendor prefix + document.webkitFullscreenElement || + null + ); +} - /** - * Gets all of the nodes within a `Range`. - * @param {Range} range The range to check. - * @returns {Node[]} The list of nodes. - */ - static getNodesInRange(range) { - const end = range.endContainer; - const nodes = []; - for (let node = /** @type {?Node} */ (range.startContainer); node !== null; node = this.getNextNode(node)) { - nodes.push(node); - if (node === end) { break; } - } - return nodes; +/** + * Gets all of the nodes within a `Range`. + * @param {Range} range The range to check. + * @returns {Node[]} The list of nodes. + */ +export function getNodesInRange(range) { + const end = range.endContainer; + const nodes = []; + for (let node = /** @type {?Node} */ (range.startContainer); node !== null; node = getNextNode(node)) { + nodes.push(node); + if (node === end) { break; } } + return nodes; +} - /** - * Gets the next node after a specified node. This traverses the DOM in its logical order. - * @param {Node} node The node to start at. - * @returns {?Node} The next node, or `null` if there is no next node. - */ - static getNextNode(node) { - let next = /** @type {?Node} */ (node.firstChild); - if (next === null) { - while (true) { - next = node.nextSibling; - if (next !== null) { break; } +/** + * Gets the next node after a specified node. This traverses the DOM in its logical order. + * @param {Node} node The node to start at. + * @returns {?Node} The next node, or `null` if there is no next node. + */ +export function getNextNode(node) { + let next = /** @type {?Node} */ (node.firstChild); + if (next === null) { + while (true) { + next = node.nextSibling; + if (next !== null) { break; } - next = node.parentNode; - if (next === null) { break; } + next = node.parentNode; + if (next === null) { break; } - node = next; - } + node = next; } - return next; } + return next; +} - /** - * Checks whether any node in a list of nodes matches a selector. - * @param {Node[]} nodes The list of ndoes to check. - * @param {string} selector The selector to test. - * @returns {boolean} `true` if any element node matches the selector, `false` otherwise. - */ - static anyNodeMatchesSelector(nodes, selector) { - const ELEMENT_NODE = Node.ELEMENT_NODE; - // This is a rather ugly way of getting the "node" variable to be a nullable - for (let node of /** @type {(?Node)[]} */ (nodes)) { - while (node !== null) { - if (node.nodeType !== ELEMENT_NODE) { - node = node.parentNode; - continue; - } - if (/** @type {HTMLElement} */ (node).matches(selector)) { return true; } - break; +/** + * Checks whether any node in a list of nodes matches a selector. + * @param {Node[]} nodes The list of ndoes to check. + * @param {string} selector The selector to test. + * @returns {boolean} `true` if any element node matches the selector, `false` otherwise. + */ +export function anyNodeMatchesSelector(nodes, selector) { + const ELEMENT_NODE = Node.ELEMENT_NODE; + // This is a rather ugly way of getting the "node" variable to be a nullable + for (let node of /** @type {(?Node)[]} */ (nodes)) { + while (node !== null) { + if (node.nodeType !== ELEMENT_NODE) { + node = node.parentNode; + continue; } + if (/** @type {HTMLElement} */ (node).matches(selector)) { return true; } + break; } - return false; } + return false; +} - /** - * Checks whether every node in a list of nodes matches a selector. - * @param {Node[]} nodes The list of ndoes to check. - * @param {string} selector The selector to test. - * @returns {boolean} `true` if every element node matches the selector, `false` otherwise. - */ - static everyNodeMatchesSelector(nodes, selector) { - const ELEMENT_NODE = Node.ELEMENT_NODE; - // This is a rather ugly way of getting the "node" variable to be a nullable - for (let node of /** @type {(?Node)[]} */ (nodes)) { - while (true) { - if (node === null) { return false; } - if (node.nodeType === ELEMENT_NODE && /** @type {HTMLElement} */ (node).matches(selector)) { break; } - node = node.parentNode; - } +/** + * Checks whether every node in a list of nodes matches a selector. + * @param {Node[]} nodes The list of ndoes to check. + * @param {string} selector The selector to test. + * @returns {boolean} `true` if every element node matches the selector, `false` otherwise. + */ +export function everyNodeMatchesSelector(nodes, selector) { + const ELEMENT_NODE = Node.ELEMENT_NODE; + // This is a rather ugly way of getting the "node" variable to be a nullable + for (let node of /** @type {(?Node)[]} */ (nodes)) { + while (true) { + if (node === null) { return false; } + if (node.nodeType === ELEMENT_NODE && /** @type {HTMLElement} */ (node).matches(selector)) { break; } + node = node.parentNode; } - return true; } + return true; +} - /** - * Checks whether the meta key is supported in the browser on the specified operating system. - * @param {string} os The operating system to check. - * @param {string} browser The browser to check. - * @returns {boolean} `true` if supported, `false` otherwise. - */ - static isMetaKeySupported(os, browser) { - return !(browser === 'firefox' || browser === 'firefox-mobile') || os === 'mac'; - } +/** + * Checks whether the meta key is supported in the browser on the specified operating system. + * @param {string} os The operating system to check. + * @param {string} browser The browser to check. + * @returns {boolean} `true` if supported, `false` otherwise. + */ +export function isMetaKeySupported(os, browser) { + return !(browser === 'firefox' || browser === 'firefox-mobile') || os === 'mac'; +} - /** - * Checks whether an element on the page that can accept input is focused. - * @returns {boolean} `true` if an input element is focused, `false` otherwise. - */ - static isInputElementFocused() { - const element = document.activeElement; - if (element === null) { return false; } - const type = element.nodeName.toUpperCase(); - switch (type) { - case 'INPUT': - case 'TEXTAREA': - case 'SELECT': - return true; - default: - return element instanceof HTMLElement && element.isContentEditable; - } +/** + * Checks whether an element on the page that can accept input is focused. + * @returns {boolean} `true` if an input element is focused, `false` otherwise. + */ +export function isInputElementFocused() { + const element = document.activeElement; + if (element === null) { return false; } + const type = element.nodeName.toUpperCase(); + switch (type) { + case 'INPUT': + case 'TEXTAREA': + case 'SELECT': + return true; + default: + return element instanceof HTMLElement && element.isContentEditable; } +} - /** - * Offsets an array of DOMRects by a given amount. - * @param {DOMRect[]} rects The DOMRects to offset. - * @param {number} x The horizontal offset amount. - * @param {number} y The vertical offset amount. - * @returns {DOMRect[]} The DOMRects with the offset applied. - */ - static offsetDOMRects(rects, x, y) { - const results = []; - for (const rect of rects) { - results.push(new DOMRect(rect.left + x, rect.top + y, rect.width, rect.height)); - } - return results; +/** + * Offsets an array of DOMRects by a given amount. + * @param {DOMRect[]} rects The DOMRects to offset. + * @param {number} x The horizontal offset amount. + * @param {number} y The vertical offset amount. + * @returns {DOMRect[]} The DOMRects with the offset applied. + */ +export function offsetDOMRects(rects, x, y) { + const results = []; + for (const rect of rects) { + results.push(new DOMRect(rect.left + x, rect.top + y, rect.width, rect.height)); } + return results; +} - /** - * Gets the parent writing mode of an element. - * See: https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode. - * @param {?Element} element The HTML element to check. - * @returns {import('document-util').NormalizedWritingMode} The writing mode. - */ - static getElementWritingMode(element) { - if (element !== null) { - const {writingMode} = getComputedStyle(element); - if (typeof writingMode === 'string') { - return this.normalizeWritingMode(writingMode); - } +/** + * Gets the parent writing mode of an element. + * See: https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode. + * @param {?Element} element The HTML element to check. + * @returns {import('document-util').NormalizedWritingMode} The writing mode. + */ +export function getElementWritingMode(element) { + if (element !== null) { + const {writingMode} = getComputedStyle(element); + if (typeof writingMode === 'string') { + return normalizeWritingMode(writingMode); } - return 'horizontal-tb'; } + return 'horizontal-tb'; +} - /** - * Normalizes a CSS writing mode value by converting non-standard and deprecated values - * into their corresponding standard vaules. - * @param {string} writingMode The writing mode to normalize. - * @returns {import('document-util').NormalizedWritingMode} The normalized writing mode. - */ - static normalizeWritingMode(writingMode) { - switch (writingMode) { - case 'tb': - return 'vertical-lr'; - case 'tb-rl': - return 'vertical-rl'; - case 'horizontal-tb': - case 'vertical-rl': - case 'vertical-lr': - case 'sideways-rl': - case 'sideways-lr': - return writingMode; - default: // 'lr', 'lr-tb', 'rl' - return 'horizontal-tb'; - } +/** + * Normalizes a CSS writing mode value by converting non-standard and deprecated values + * into their corresponding standard vaules. + * @param {string} writingMode The writing mode to normalize. + * @returns {import('document-util').NormalizedWritingMode} The normalized writing mode. + */ +export function normalizeWritingMode(writingMode) { + switch (writingMode) { + case 'tb': + return 'vertical-lr'; + case 'tb-rl': + return 'vertical-rl'; + case 'horizontal-tb': + case 'vertical-rl': + case 'vertical-lr': + case 'sideways-rl': + case 'sideways-lr': + return writingMode; + default: // 'lr', 'lr-tb', 'rl' + return 'horizontal-tb'; } +} - /** - * Converts a value from an element to a number. - * @param {string} valueString A string representation of a number. - * @param {import('document-util').ToNumberConstraints} constraints An object which might contain `min`, `max`, and `step` fields which are used to constrain the value. - * @returns {number} The parsed and constrained number. - */ - static convertElementValueToNumber(valueString, constraints) { - let value = Number.parseFloat(valueString); - if (!Number.isFinite(value)) { value = 0; } +/** + * Converts a value from an element to a number. + * @param {string} valueString A string representation of a number. + * @param {import('document-util').ToNumberConstraints} constraints An object which might contain `min`, `max`, and `step` fields which are used to constrain the value. + * @returns {number} The parsed and constrained number. + */ +export function convertElementValueToNumber(valueString, constraints) { + let value = Number.parseFloat(valueString); + if (!Number.isFinite(value)) { value = 0; } - const min = this._convertToNumberOrNull(constraints.min); - const max = this._convertToNumberOrNull(constraints.max); - const step = this._convertToNumberOrNull(constraints.step); - if (typeof min === 'number') { value = Math.max(value, min); } - if (typeof max === 'number') { value = Math.min(value, max); } - if (typeof step === 'number' && step !== 0) { value = Math.round(value / step) * step; } - return value; - } + const min = convertToNumberOrNull(constraints.min); + const max = convertToNumberOrNull(constraints.max); + const step = convertToNumberOrNull(constraints.step); + if (typeof min === 'number') { value = Math.max(value, min); } + if (typeof max === 'number') { value = Math.min(value, max); } + if (typeof step === 'number' && step !== 0) { value = Math.round(value / step) * step; } + return value; +} - /** - * @param {string} value - * @returns {?import('input').Modifier} - */ - static normalizeModifier(value) { - switch (value) { - case 'alt': - case 'ctrl': - case 'meta': - case 'shift': - case 'mouse0': - case 'mouse1': - case 'mouse2': - case 'mouse3': - case 'mouse4': - case 'mouse5': - return value; - default: - return null; - } +/** + * @param {string} value + * @returns {?import('input').Modifier} + */ +export function normalizeModifier(value) { + switch (value) { + case 'alt': + case 'ctrl': + case 'meta': + case 'shift': + case 'mouse0': + case 'mouse1': + case 'mouse2': + case 'mouse3': + case 'mouse4': + case 'mouse5': + return value; + default: + return null; } +} - /** - * @param {string} value - * @returns {?import('input').ModifierKey} - */ - static normalizeModifierKey(value) { - switch (value) { - case 'alt': - case 'ctrl': - case 'meta': - case 'shift': - return value; - default: - return null; - } +/** + * @param {string} value + * @returns {?import('input').ModifierKey} + */ +export function normalizeModifierKey(value) { + switch (value) { + case 'alt': + case 'ctrl': + case 'meta': + case 'shift': + return value; + default: + return null; } +} - /** - * @param {MouseEvent} event The event to check. - * @param {import('input').ModifierMouseButton[]|import('input').Modifier[]} array - */ - static _getActiveButtons(event, array) { - let {buttons} = event; - if (typeof buttons === 'number' && buttons > 0) { - for (let i = 0; i < 6; ++i) { - const buttonFlag = (1 << i); - if ((buttons & buttonFlag) !== 0) { - array.push(/** @type {import('input').ModifierMouseButton} */ (`mouse${i}`)); - buttons &= ~buttonFlag; - if (buttons === 0) { break; } - } +/** + * @param {MouseEvent} event The event to check. + * @param {import('input').ModifierMouseButton[]|import('input').Modifier[]} array + */ +function getActiveButtonsInternal(event, array) { + let {buttons} = event; + if (typeof buttons === 'number' && buttons > 0) { + for (let i = 0; i < 6; ++i) { + const buttonFlag = (1 << i); + if ((buttons & buttonFlag) !== 0) { + array.push(/** @type {import('input').ModifierMouseButton} */ (`mouse${i}`)); + buttons &= ~buttonFlag; + if (buttons === 0) { break; } } } } +} - /** - * @param {string|number|undefined} value - * @returns {?number} - */ - static _convertToNumberOrNull(value) { - if (typeof value !== 'number') { - if (typeof value !== 'string' || value.length === 0) { - return null; - } - value = Number.parseFloat(value); +/** + * @param {string|number|undefined} value + * @returns {?number} + */ +function convertToNumberOrNull(value) { + if (typeof value !== 'number') { + if (typeof value !== 'string' || value.length === 0) { + return null; } - return !Number.isNaN(value) ? value : null; + value = Number.parseFloat(value); } + return !Number.isNaN(value) ? value : null; +} - /** - * Computes whether or not this browser and document supports CSS zoom, which is primarily a legacy Chromium feature. - * @returns {boolean} - */ - static _computeCssZoomSupported() { - // 'style' can be undefined in certain contexts, such as when document is an SVG document. - const {style} = document.createElement('div'); +/** + * Computes whether or not this browser and document supports CSS zoom, which is primarily a legacy Chromium feature. + * @returns {boolean} + */ +function computeCssZoomSupported() { + // 'style' can be undefined in certain contexts, such as when document is an SVG document. + const {style} = document.createElement('div'); + return ( + typeof style === 'object' && + style !== null && // @ts-expect-error - zoom is a non-standard property. - return (typeof style === 'object' && style !== null && typeof style.zoom === 'string'); - } + typeof style.zoom === 'string' + ); } diff --git a/ext/js/dom/dom-data-binder.js b/ext/js/dom/dom-data-binder.js index 7523e434..be5633d7 100644 --- a/ext/js/dom/dom-data-binder.js +++ b/ext/js/dom/dom-data-binder.js @@ -17,7 +17,7 @@ */ import {TaskAccumulator} from '../general/task-accumulator.js'; -import {DocumentUtil} from './document-util.js'; +import {convertElementValueToNumber} from './document-util.js'; import {SelectorObserver} from './selector-observer.js'; /** @@ -264,7 +264,7 @@ export class DOMDataBinder { case 'text': return `${/** @type {HTMLInputElement} */ (element).value}`; case 'number': - return DocumentUtil.convertElementValueToNumber(/** @type {HTMLInputElement} */ (element).value, /** @type {HTMLInputElement} */ (element)); + return convertElementValueToNumber(/** @type {HTMLInputElement} */ (element).value, /** @type {HTMLInputElement} */ (element)); case 'textarea': return /** @type {HTMLTextAreaElement} */ (element).value; case 'select': diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js index 927783d1..b2829e75 100644 --- a/ext/js/dom/text-source-element.js +++ b/ext/js/dom/text-source-element.js @@ -17,7 +17,7 @@ */ import {readCodePointsBackward, readCodePointsForward} from '../data/sandbox/string-util.js'; -import {DocumentUtil} from './document-util.js'; +import {convertMultipleRectZoomCoordinates} from './document-util.js'; /** * This class represents a text source that is attached to a HTML element, such as an <img> @@ -145,7 +145,7 @@ export class TextSourceElement { * @returns {DOMRect[]} The rects. */ getRects() { - return DocumentUtil.convertMultipleRectZoomCoordinates(this._element.getClientRects(), this._element); + return convertMultipleRectZoomCoordinates(this._element.getClientRects(), this._element); } /** diff --git a/ext/js/dom/text-source-generator.js b/ext/js/dom/text-source-generator.js index 83c7271c..68bf036a 100644 --- a/ext/js/dom/text-source-generator.js +++ b/ext/js/dom/text-source-generator.js @@ -15,7 +15,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import {DocumentUtil} from './document-util.js'; +import {computeZoomScale, isPointInAnyRect} from './document-util.js'; import {DOMTextScanner} from './dom-text-scanner.js'; import {TextSourceElement} from './text-source-element.js'; import {TextSourceRange} from './text-source-range.js'; @@ -358,7 +358,7 @@ export class TextSourceGenerator { // Convert CSS zoom coordinates if (normalizeCssZoom) { - const scale = DocumentUtil.computeZoomScale(startContainer); + const scale = computeZoomScale(startContainer); x /= scale; y /= scale; } @@ -370,7 +370,7 @@ export class TextSourceGenerator { const {node, offset, content} = new DOMTextScanner(nodePre, offsetPre, true, false).seek(1); range.setEnd(node, offset); - if (!this._isWhitespace(content) && DocumentUtil.isPointInAnyRect(x, y, range.getClientRects())) { + if (!this._isWhitespace(content) && isPointInAnyRect(x, y, range.getClientRects())) { return true; } } finally { @@ -381,7 +381,7 @@ export class TextSourceGenerator { const {node, offset, content} = new DOMTextScanner(startContainer, range.startOffset, true, false).seek(-1); range.setStart(node, offset); - if (!this._isWhitespace(content) && DocumentUtil.isPointInAnyRect(x, y, range.getClientRects())) { + if (!this._isWhitespace(content) && isPointInAnyRect(x, y, range.getClientRects())) { // This purposefully leaves the starting offset as modified and sets the range length to 0. range.setEnd(node, offset); return true; diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index 05e7b6fb..2012af7a 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -17,7 +17,7 @@ */ import {toError} from '../core/to-error.js'; -import {DocumentUtil} from './document-util.js'; +import {convertMultipleRectZoomCoordinates, convertRectZoomCoordinates, getElementWritingMode, getNodesInRange, offsetDOMRects} from './document-util.js'; import {DOMTextScanner} from './dom-text-scanner.js'; /** @@ -170,7 +170,7 @@ export class TextSourceRange { */ getRects() { if (this._isImposterDisconnected()) { return this._getCachedRects(); } - return DocumentUtil.convertMultipleRectZoomCoordinates(this._range.getClientRects(), this._range.startContainer); + return convertMultipleRectZoomCoordinates(this._range.getClientRects(), this._range.startContainer); } /** @@ -181,7 +181,7 @@ export class TextSourceRange { getWritingMode() { let node = this._isImposterDisconnected() ? this._imposterSourceElement : this._range.startContainer; if (node !== null && node.nodeType !== Node.ELEMENT_NODE) { node = node.parentElement; } - return DocumentUtil.getElementWritingMode(/** @type {?Element} */ (node)); + return getElementWritingMode(/** @type {?Element} */ (node)); } /** @@ -243,7 +243,7 @@ export class TextSourceRange { * @returns {Node[]} The nodes in the range. */ getNodesInRange() { - return DocumentUtil.getNodesInRange(this._range); + return getNodesInRange(this._range); } /** @@ -263,8 +263,8 @@ export class TextSourceRange { * @returns {TextSourceRange} A new instance of the class corresponding to the range. */ static createFromImposter(range, imposterElement, imposterSourceElement) { - const cachedRects = DocumentUtil.convertMultipleRectZoomCoordinates(range.getClientRects(), range.startContainer); - const cachedSourceRect = DocumentUtil.convertRectZoomCoordinates(imposterSourceElement.getBoundingClientRect(), imposterSourceElement); + const cachedRects = convertMultipleRectZoomCoordinates(range.getClientRects(), range.startContainer); + const cachedSourceRect = convertRectZoomCoordinates(imposterSourceElement.getBoundingClientRect(), imposterSourceElement); return new TextSourceRange(range, range.startOffset, range.toString(), imposterElement, imposterSourceElement, cachedRects, cachedSourceRect); } @@ -289,8 +289,8 @@ export class TextSourceRange { ) { throw new Error('Cached rects not valid for this instance'); } - const sourceRect = DocumentUtil.convertRectZoomCoordinates(this._imposterSourceElement.getBoundingClientRect(), this._imposterSourceElement); - return DocumentUtil.offsetDOMRects( + const sourceRect = convertRectZoomCoordinates(this._imposterSourceElement.getBoundingClientRect(), this._imposterSourceElement); + return offsetDOMRects( this._cachedRects, sourceRect.left - this._cachedSourceRect.left, sourceRect.top - this._cachedSourceRect.top |