@github/text-expander-element
Advanced tools
+62
-83
@@ -215,11 +215,2 @@ const ctrlBindings = !!navigator.userAgent.match(/Macintosh/); | ||
| /** | ||
| * A custom element is implemented as a class which extends HTMLElement (in the | ||
| * case of autonomous elements) or the interface you want to customize (in the | ||
| * case of customized built-in elements). | ||
| * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks | ||
| */ | ||
| class CustomHTMLElement extends HTMLElement { | ||
| } | ||
| class InputStyleCloneUpdateEvent extends Event { | ||
@@ -232,3 +223,3 @@ constructor() { | ||
| /** | ||
| * Create an element that exactly matches an input pixel-for-pixel and automatically stays in sync with it. This | ||
| * Creates an element that exactly matches an input pixel-for-pixel and automatically stays in sync with it. This | ||
| * is a non-interactive overlay on to the input and can be used to affect the visual appearance of the input | ||
@@ -250,3 +241,3 @@ * without modifying its behavior. The clone element is hidden by default. | ||
| // - koddsson/textarea-caret-position (Copyright (c) 2015 Jonathan Ong me@jongleberry.com): https://github.com/koddsson/textarea-caret-position/blob/eba40ec8488eed4d77815f109af22e1d9c0751d3/index.js | ||
| class InputStyleCloneElement extends CustomHTMLElement { | ||
| class InputStyleClone extends EventTarget { | ||
| #styleObserver = new MutationObserver(() => this.#updateStyles()); | ||
@@ -259,10 +250,11 @@ #resizeObserver = new ResizeObserver(() => this.#requestUpdateLayout()); | ||
| #inputRef; | ||
| #container; | ||
| #container = document.createElement("div"); | ||
| #cloneElement = document.createElement("div"); | ||
| /** | ||
| * Get the clone for an input, reusing an existing one if available. This avoids creating unecessary clones, which | ||
| * have a performance cost due to their high-frequency event-based updates. Because these elements are shared, they | ||
| * should be mutated with caution. | ||
| * should be mutated with caution. If you're planning to mutate the clone, consider constructing a new one instead. | ||
| * | ||
| * Upon initial creation the clone element will automatically be inserted into the DOM and begin observing the | ||
| * linked input. Only one clone per input can ever exist at a time. | ||
| * linked input. | ||
| * @param input The target input to clone. | ||
@@ -273,4 +265,3 @@ */ | ||
| if (!clone) { | ||
| clone = new InputStyleCloneElement(); | ||
| clone.connect(input); | ||
| clone = new InputStyleClone(input); | ||
| CloneRegistry.set(input, clone); | ||
@@ -283,17 +274,50 @@ } | ||
| * | ||
| * NOTE: calling the static `for` method is nearly always preferable as it will reuse an existing clone if available. | ||
| * NOTE: calling the static `for` method is usually preferable as it will reuse an existing clone if available. | ||
| * However, if reusing clones is problematic (ie, if the clone needs to be mutated), a clone can be constructed | ||
| * directly with `new InputStyleCloneElement()` and then bound to an input and inserted into the DOM with | ||
| * `clone.connect(target)`. | ||
| * directly with `new InputStyleClone(target)`. | ||
| */ | ||
| connect(input) { | ||
| constructor(input) { | ||
| super(); | ||
| this.#inputRef = new WeakRef(input); | ||
| // We want position:absolute so it doesn't take space in the layout, but that doesn't work with display:table-cell | ||
| // used in the HTMLInputElement approach. So we need a wrapper. | ||
| this.#container = document.createElement("div"); | ||
| this.#container.style.position = "absolute"; | ||
| this.#container.style.pointerEvents = "none"; | ||
| this.#container.setAttribute("aria-hidden", "true"); | ||
| this.#container.appendChild(this.#cloneElement); | ||
| this.#cloneElement.style.pointerEvents = "none"; | ||
| this.#cloneElement.style.userSelect = "none"; | ||
| this.#cloneElement.style.overflow = "hidden"; | ||
| this.#cloneElement.style.display = "block"; | ||
| // Important not to use display:none which would not render the content at all | ||
| this.#cloneElement.style.visibility = "hidden"; | ||
| if (input instanceof HTMLTextAreaElement) { | ||
| this.#cloneElement.style.whiteSpace = "pre-wrap"; | ||
| this.#cloneElement.style.wordWrap = "break-word"; | ||
| } | ||
| else { | ||
| this.#cloneElement.style.whiteSpace = "nowrap"; | ||
| // text in single-line inputs is vertically centered | ||
| this.#cloneElement.style.display = "table-cell"; | ||
| this.#cloneElement.style.verticalAlign = "middle"; | ||
| } | ||
| input.after(this.#container); | ||
| this.#container.appendChild(this); | ||
| this.#updateStyles(); | ||
| this.#updateText(); | ||
| this.#styleObserver.observe(input, { | ||
| attributeFilter: [ | ||
| "style", | ||
| "dir", // users can right-click in some browsers to change the text direction dynamically | ||
| ], | ||
| }); | ||
| this.#resizeObserver.observe(input); | ||
| document.addEventListener("scroll", this.#onDocumentScrollOrResize, { capture: true }); | ||
| window.addEventListener("resize", this.#onDocumentScrollOrResize, { capture: true }); | ||
| // capture so this happens first, so other things can respond to `input` events after this data updates | ||
| input.addEventListener("input", this.#onInput, { capture: true }); | ||
| } | ||
| /** Get the clone element. */ | ||
| get element() { | ||
| return this.#cloneElement; | ||
| } | ||
| /** | ||
@@ -307,39 +331,3 @@ * Force a recalculation. Will emit an `update` event. This is typically not needed unless the input has changed in | ||
| } | ||
| /** @private */ | ||
| connectedCallback() { | ||
| this.#usingInput((input) => { | ||
| this.style.pointerEvents = "none"; | ||
| this.style.userSelect = "none"; | ||
| this.style.overflow = "hidden"; | ||
| this.style.display = "block"; | ||
| // Important not to use display:none which would not render the content at all | ||
| this.style.visibility = "hidden"; | ||
| if (input instanceof HTMLTextAreaElement) { | ||
| this.style.whiteSpace = "pre-wrap"; | ||
| this.style.wordWrap = "break-word"; | ||
| } | ||
| else { | ||
| this.style.whiteSpace = "nowrap"; | ||
| // text in single-line inputs is vertically centered | ||
| this.style.display = "table-cell"; | ||
| this.style.verticalAlign = "middle"; | ||
| } | ||
| this.setAttribute("aria-hidden", "true"); | ||
| this.#updateStyles(); | ||
| this.#updateText(); | ||
| this.#styleObserver.observe(input, { | ||
| attributeFilter: [ | ||
| "style", | ||
| "dir", // users can right-click in some browsers to change the text direction dynamically | ||
| ], | ||
| }); | ||
| this.#resizeObserver.observe(input); | ||
| document.addEventListener("scroll", this.#onDocumentScrollOrResize, { capture: true }); | ||
| window.addEventListener("resize", this.#onDocumentScrollOrResize, { capture: true }); | ||
| // capture so this happens first, so other things can respond to `input` events after this data updates | ||
| input.addEventListener("input", this.#onInput, { capture: true }); | ||
| }); | ||
| } | ||
| /** @private */ | ||
| disconnectedCallback() { | ||
| disconnect() { | ||
| this.#container?.remove(); | ||
@@ -365,3 +353,3 @@ this.#styleObserver.disconnect(); | ||
| if (!input) | ||
| return this.remove(); | ||
| return this.disconnect(); | ||
| return fn(input); | ||
@@ -381,17 +369,17 @@ } | ||
| const inputStyle = window.getComputedStyle(input); | ||
| this.style.height = inputStyle.height; | ||
| this.style.width = inputStyle.width; | ||
| this.#cloneElement.style.height = inputStyle.height; | ||
| this.#cloneElement.style.width = inputStyle.width; | ||
| // Immediately re-adjust for browser inconsistencies in scrollbar handling, if necessary | ||
| if (input.clientHeight !== this.clientHeight) | ||
| this.style.height = `calc(${inputStyle.height} + ${input.clientHeight - this.clientHeight}px)`; | ||
| if (input.clientWidth !== this.clientWidth) | ||
| this.style.width = `calc(${inputStyle.width} + ${input.clientWidth - this.clientWidth}px)`; | ||
| if (input.clientHeight !== this.#cloneElement.clientHeight) | ||
| this.#cloneElement.style.height = `calc(${inputStyle.height} + ${input.clientHeight - this.#cloneElement.clientHeight}px)`; | ||
| if (input.clientWidth !== this.#cloneElement.clientWidth) | ||
| this.#cloneElement.style.width = `calc(${inputStyle.width} + ${input.clientWidth - this.#cloneElement.clientWidth}px)`; | ||
| // Position on top of the input | ||
| const inputRect = input.getBoundingClientRect(); | ||
| const cloneRect = this.getBoundingClientRect(); | ||
| const cloneRect = this.#cloneElement.getBoundingClientRect(); | ||
| this.#xOffset = this.#xOffset + inputRect.left - cloneRect.left; | ||
| this.#yOffset = this.#yOffset + inputRect.top - cloneRect.top; | ||
| this.style.transform = `translate(${this.#xOffset}px, ${this.#yOffset}px)`; | ||
| this.scrollTop = input.scrollTop; | ||
| this.scrollLeft = input.scrollLeft; | ||
| this.#cloneElement.style.transform = `translate(${this.#xOffset}px, ${this.#yOffset}px)`; | ||
| this.#cloneElement.scrollTop = input.scrollTop; | ||
| this.#cloneElement.scrollLeft = input.scrollLeft; | ||
| this.dispatchEvent(new InputStyleCloneUpdateEvent()); | ||
@@ -416,3 +404,3 @@ }); | ||
| for (const prop of propertiesToCopy) | ||
| this.style[prop] = inputStyle[prop]; | ||
| this.#cloneElement.style[prop] = inputStyle[prop]; | ||
| this.#requestUpdateLayout(); | ||
@@ -427,3 +415,3 @@ }); | ||
| this.#usingInput((input) => { | ||
| this.textContent = input.value; | ||
| this.#cloneElement.textContent = input.value; | ||
| // This is often unecessary on a pure text update, but text updates could potentially cause layout updates like | ||
@@ -483,11 +471,2 @@ // scrolling or resizing. And we run the update on _every frame_ when scrolling, so this isn't that expensive. | ||
| ]; | ||
| // Inspired by https://github.com/github/catalyst/blob/dc284dcf4f82329a9cac5c867462a8fa529b6c40/src/register.ts | ||
| try { | ||
| customElements.define("input-style-clone", InputStyleCloneElement); | ||
| } | ||
| catch (e) { | ||
| // Throws DOMException with NotSupportedError if already defined | ||
| if (!(e instanceof DOMException && e.name === "NotSupportedError")) | ||
| throw e; | ||
| } | ||
@@ -598,3 +577,3 @@ class InputRange { | ||
| get #styleClone() { | ||
| return InputStyleCloneElement.for(this.#inputElement); | ||
| return InputStyleClone.for(this.#inputElement); | ||
| } | ||
@@ -612,3 +591,3 @@ get #cloneElement() { | ||
| const range = document.createRange(); | ||
| const textNode = this.#cloneElement.childNodes[0]; | ||
| const textNode = this.#cloneElement.element.childNodes[0]; | ||
| if (textNode) { | ||
@@ -615,0 +594,0 @@ range.setStart(textNode, this.startOffset); |
+62
-83
@@ -45,11 +45,2 @@ import Combobox from '@github/combobox-nav'; | ||
| /** | ||
| * A custom element is implemented as a class which extends HTMLElement (in the | ||
| * case of autonomous elements) or the interface you want to customize (in the | ||
| * case of customized built-in elements). | ||
| * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks | ||
| */ | ||
| class CustomHTMLElement extends HTMLElement { | ||
| } | ||
| class InputStyleCloneUpdateEvent extends Event { | ||
@@ -62,3 +53,3 @@ constructor() { | ||
| /** | ||
| * Create an element that exactly matches an input pixel-for-pixel and automatically stays in sync with it. This | ||
| * Creates an element that exactly matches an input pixel-for-pixel and automatically stays in sync with it. This | ||
| * is a non-interactive overlay on to the input and can be used to affect the visual appearance of the input | ||
@@ -80,3 +71,3 @@ * without modifying its behavior. The clone element is hidden by default. | ||
| // - koddsson/textarea-caret-position (Copyright (c) 2015 Jonathan Ong me@jongleberry.com): https://github.com/koddsson/textarea-caret-position/blob/eba40ec8488eed4d77815f109af22e1d9c0751d3/index.js | ||
| class InputStyleCloneElement extends CustomHTMLElement { | ||
| class InputStyleClone extends EventTarget { | ||
| #styleObserver = new MutationObserver(() => this.#updateStyles()); | ||
@@ -89,10 +80,11 @@ #resizeObserver = new ResizeObserver(() => this.#requestUpdateLayout()); | ||
| #inputRef; | ||
| #container; | ||
| #container = document.createElement("div"); | ||
| #cloneElement = document.createElement("div"); | ||
| /** | ||
| * Get the clone for an input, reusing an existing one if available. This avoids creating unecessary clones, which | ||
| * have a performance cost due to their high-frequency event-based updates. Because these elements are shared, they | ||
| * should be mutated with caution. | ||
| * should be mutated with caution. If you're planning to mutate the clone, consider constructing a new one instead. | ||
| * | ||
| * Upon initial creation the clone element will automatically be inserted into the DOM and begin observing the | ||
| * linked input. Only one clone per input can ever exist at a time. | ||
| * linked input. | ||
| * @param input The target input to clone. | ||
@@ -103,4 +95,3 @@ */ | ||
| if (!clone) { | ||
| clone = new InputStyleCloneElement(); | ||
| clone.connect(input); | ||
| clone = new InputStyleClone(input); | ||
| CloneRegistry.set(input, clone); | ||
@@ -113,17 +104,50 @@ } | ||
| * | ||
| * NOTE: calling the static `for` method is nearly always preferable as it will reuse an existing clone if available. | ||
| * NOTE: calling the static `for` method is usually preferable as it will reuse an existing clone if available. | ||
| * However, if reusing clones is problematic (ie, if the clone needs to be mutated), a clone can be constructed | ||
| * directly with `new InputStyleCloneElement()` and then bound to an input and inserted into the DOM with | ||
| * `clone.connect(target)`. | ||
| * directly with `new InputStyleClone(target)`. | ||
| */ | ||
| connect(input) { | ||
| constructor(input) { | ||
| super(); | ||
| this.#inputRef = new WeakRef(input); | ||
| // We want position:absolute so it doesn't take space in the layout, but that doesn't work with display:table-cell | ||
| // used in the HTMLInputElement approach. So we need a wrapper. | ||
| this.#container = document.createElement("div"); | ||
| this.#container.style.position = "absolute"; | ||
| this.#container.style.pointerEvents = "none"; | ||
| this.#container.setAttribute("aria-hidden", "true"); | ||
| this.#container.appendChild(this.#cloneElement); | ||
| this.#cloneElement.style.pointerEvents = "none"; | ||
| this.#cloneElement.style.userSelect = "none"; | ||
| this.#cloneElement.style.overflow = "hidden"; | ||
| this.#cloneElement.style.display = "block"; | ||
| // Important not to use display:none which would not render the content at all | ||
| this.#cloneElement.style.visibility = "hidden"; | ||
| if (input instanceof HTMLTextAreaElement) { | ||
| this.#cloneElement.style.whiteSpace = "pre-wrap"; | ||
| this.#cloneElement.style.wordWrap = "break-word"; | ||
| } | ||
| else { | ||
| this.#cloneElement.style.whiteSpace = "nowrap"; | ||
| // text in single-line inputs is vertically centered | ||
| this.#cloneElement.style.display = "table-cell"; | ||
| this.#cloneElement.style.verticalAlign = "middle"; | ||
| } | ||
| input.after(this.#container); | ||
| this.#container.appendChild(this); | ||
| this.#updateStyles(); | ||
| this.#updateText(); | ||
| this.#styleObserver.observe(input, { | ||
| attributeFilter: [ | ||
| "style", | ||
| "dir", // users can right-click in some browsers to change the text direction dynamically | ||
| ], | ||
| }); | ||
| this.#resizeObserver.observe(input); | ||
| document.addEventListener("scroll", this.#onDocumentScrollOrResize, { capture: true }); | ||
| window.addEventListener("resize", this.#onDocumentScrollOrResize, { capture: true }); | ||
| // capture so this happens first, so other things can respond to `input` events after this data updates | ||
| input.addEventListener("input", this.#onInput, { capture: true }); | ||
| } | ||
| /** Get the clone element. */ | ||
| get element() { | ||
| return this.#cloneElement; | ||
| } | ||
| /** | ||
@@ -137,39 +161,3 @@ * Force a recalculation. Will emit an `update` event. This is typically not needed unless the input has changed in | ||
| } | ||
| /** @private */ | ||
| connectedCallback() { | ||
| this.#usingInput((input) => { | ||
| this.style.pointerEvents = "none"; | ||
| this.style.userSelect = "none"; | ||
| this.style.overflow = "hidden"; | ||
| this.style.display = "block"; | ||
| // Important not to use display:none which would not render the content at all | ||
| this.style.visibility = "hidden"; | ||
| if (input instanceof HTMLTextAreaElement) { | ||
| this.style.whiteSpace = "pre-wrap"; | ||
| this.style.wordWrap = "break-word"; | ||
| } | ||
| else { | ||
| this.style.whiteSpace = "nowrap"; | ||
| // text in single-line inputs is vertically centered | ||
| this.style.display = "table-cell"; | ||
| this.style.verticalAlign = "middle"; | ||
| } | ||
| this.setAttribute("aria-hidden", "true"); | ||
| this.#updateStyles(); | ||
| this.#updateText(); | ||
| this.#styleObserver.observe(input, { | ||
| attributeFilter: [ | ||
| "style", | ||
| "dir", // users can right-click in some browsers to change the text direction dynamically | ||
| ], | ||
| }); | ||
| this.#resizeObserver.observe(input); | ||
| document.addEventListener("scroll", this.#onDocumentScrollOrResize, { capture: true }); | ||
| window.addEventListener("resize", this.#onDocumentScrollOrResize, { capture: true }); | ||
| // capture so this happens first, so other things can respond to `input` events after this data updates | ||
| input.addEventListener("input", this.#onInput, { capture: true }); | ||
| }); | ||
| } | ||
| /** @private */ | ||
| disconnectedCallback() { | ||
| disconnect() { | ||
| this.#container?.remove(); | ||
@@ -195,3 +183,3 @@ this.#styleObserver.disconnect(); | ||
| if (!input) | ||
| return this.remove(); | ||
| return this.disconnect(); | ||
| return fn(input); | ||
@@ -211,17 +199,17 @@ } | ||
| const inputStyle = window.getComputedStyle(input); | ||
| this.style.height = inputStyle.height; | ||
| this.style.width = inputStyle.width; | ||
| this.#cloneElement.style.height = inputStyle.height; | ||
| this.#cloneElement.style.width = inputStyle.width; | ||
| // Immediately re-adjust for browser inconsistencies in scrollbar handling, if necessary | ||
| if (input.clientHeight !== this.clientHeight) | ||
| this.style.height = `calc(${inputStyle.height} + ${input.clientHeight - this.clientHeight}px)`; | ||
| if (input.clientWidth !== this.clientWidth) | ||
| this.style.width = `calc(${inputStyle.width} + ${input.clientWidth - this.clientWidth}px)`; | ||
| if (input.clientHeight !== this.#cloneElement.clientHeight) | ||
| this.#cloneElement.style.height = `calc(${inputStyle.height} + ${input.clientHeight - this.#cloneElement.clientHeight}px)`; | ||
| if (input.clientWidth !== this.#cloneElement.clientWidth) | ||
| this.#cloneElement.style.width = `calc(${inputStyle.width} + ${input.clientWidth - this.#cloneElement.clientWidth}px)`; | ||
| // Position on top of the input | ||
| const inputRect = input.getBoundingClientRect(); | ||
| const cloneRect = this.getBoundingClientRect(); | ||
| const cloneRect = this.#cloneElement.getBoundingClientRect(); | ||
| this.#xOffset = this.#xOffset + inputRect.left - cloneRect.left; | ||
| this.#yOffset = this.#yOffset + inputRect.top - cloneRect.top; | ||
| this.style.transform = `translate(${this.#xOffset}px, ${this.#yOffset}px)`; | ||
| this.scrollTop = input.scrollTop; | ||
| this.scrollLeft = input.scrollLeft; | ||
| this.#cloneElement.style.transform = `translate(${this.#xOffset}px, ${this.#yOffset}px)`; | ||
| this.#cloneElement.scrollTop = input.scrollTop; | ||
| this.#cloneElement.scrollLeft = input.scrollLeft; | ||
| this.dispatchEvent(new InputStyleCloneUpdateEvent()); | ||
@@ -246,3 +234,3 @@ }); | ||
| for (const prop of propertiesToCopy) | ||
| this.style[prop] = inputStyle[prop]; | ||
| this.#cloneElement.style[prop] = inputStyle[prop]; | ||
| this.#requestUpdateLayout(); | ||
@@ -257,3 +245,3 @@ }); | ||
| this.#usingInput((input) => { | ||
| this.textContent = input.value; | ||
| this.#cloneElement.textContent = input.value; | ||
| // This is often unecessary on a pure text update, but text updates could potentially cause layout updates like | ||
@@ -313,11 +301,2 @@ // scrolling or resizing. And we run the update on _every frame_ when scrolling, so this isn't that expensive. | ||
| ]; | ||
| // Inspired by https://github.com/github/catalyst/blob/dc284dcf4f82329a9cac5c867462a8fa529b6c40/src/register.ts | ||
| try { | ||
| customElements.define("input-style-clone", InputStyleCloneElement); | ||
| } | ||
| catch (e) { | ||
| // Throws DOMException with NotSupportedError if already defined | ||
| if (!(e instanceof DOMException && e.name === "NotSupportedError")) | ||
| throw e; | ||
| } | ||
@@ -428,3 +407,3 @@ class InputRange { | ||
| get #styleClone() { | ||
| return InputStyleCloneElement.for(this.#inputElement); | ||
| return InputStyleClone.for(this.#inputElement); | ||
| } | ||
@@ -442,3 +421,3 @@ get #cloneElement() { | ||
| const range = document.createRange(); | ||
| const textNode = this.#cloneElement.childNodes[0]; | ||
| const textNode = this.#cloneElement.element.childNodes[0]; | ||
| if (textNode) { | ||
@@ -445,0 +424,0 @@ range.setStart(textNode, this.startOffset); |
+2
-2
| { | ||
| "name": "@github/text-expander-element", | ||
| "version": "2.9.1", | ||
| "version": "2.9.2", | ||
| "description": "Activates a suggestion menu to expand text snippets as you type.", | ||
@@ -33,3 +33,3 @@ "repository": "github/text-expander-element", | ||
| "@github/combobox-nav": "^2.0.2", | ||
| "dom-input-range": "^1.2.0" | ||
| "dom-input-range": "^2.0.0" | ||
| }, | ||
@@ -36,0 +36,0 @@ "devDependencies": { |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
6
-45.45%78425
-1.53%1772
-2.21%1
Infinity%+ Added
- Removed
Updated