From f15e330c278738603222ec87712b3ab8ebd86b32 Mon Sep 17 00:00:00 2001 From: Hunter Johnston Date: Tue, 4 Feb 2025 15:42:01 -0500 Subject: [PATCH 1/3] formatting --- .../lib/bits/pin-input/pin-input.svelte.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/bits-ui/src/lib/bits/pin-input/pin-input.svelte.ts b/packages/bits-ui/src/lib/bits/pin-input/pin-input.svelte.ts index 107d5d03b..56d88ac3a 100644 --- a/packages/bits-ui/src/lib/bits/pin-input/pin-input.svelte.ts +++ b/packages/bits-ui/src/lib/bits/pin-input/pin-input.svelte.ts @@ -355,7 +355,7 @@ class PinInputRootState { this.#prevInputMetadata.prev = [s, e, dir]; }; - oninput = (e: BitsEvent) => { + oninput = (e: BitsEvent) => { const newValue = e.currentTarget.value.slice(0, this.opts.maxLength.current); if (newValue.length > 0 && this.#regexPattern && !this.#regexPattern.test(newValue)) { e.preventDefault(); @@ -396,6 +396,22 @@ class PinInputRootState { !this.opts.onPaste?.current && (!this.#initialLoad.isIOS || !e.clipboardData || !input) ) { + const contentData = e.clipboardData?.getData("text/plain"); + const start = input.selectionStart === null ? undefined : input.selectionStart; + const end = input.selectionEnd === null ? undefined : input.selectionEnd; + + const isReplacing = start !== end; + + const initNewVal = this.opts.value.current; + + const newValueUncapped = isReplacing + ? initNewVal.slice(0, start) + contentData + initNewVal.slice(end) + : initNewVal.slice(0, start) + contentData + initNewVal.slice(start); + const newValue = newValueUncapped.slice(0, this.opts.maxLength.current); + + if (newValue.length > 0 && this.#regexPattern && !this.#regexPattern.test(newValue)) { + e.preventDefault(); + } return; } @@ -454,7 +470,7 @@ class PinInputRootState { "data-pin-input-input-mss": this.#mirrorSelectionStart, "data-pin-input-input-mse": this.#mirrorSelectionEnd, inputmode: this.opts.inputmode.current, - // pattern: this.#regexPattern?.source, + pattern: this.#regexPattern?.source, maxlength: this.opts.maxLength.current, value: this.opts.value.current, disabled: getDisabled(this.opts.disabled.current), From 40438c29c817b3d83d5ade3773afc3f293b9a10c Mon Sep 17 00:00:00 2001 From: Hunter Johnston Date: Tue, 4 Feb 2025 15:42:28 -0500 Subject: [PATCH 2/3] add changeset --- .changeset/few-coats-greet.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/few-coats-greet.md diff --git a/.changeset/few-coats-greet.md b/.changeset/few-coats-greet.md new file mode 100644 index 000000000..20fcd2dd0 --- /dev/null +++ b/.changeset/few-coats-greet.md @@ -0,0 +1,5 @@ +--- +"bits-ui": patch +--- + +fix: Pin Input allowing paste on non-matching From ac875ffb50727f3492fed55669ebc163049a7a64 Mon Sep 17 00:00:00 2001 From: Hunter Johnston Date: Tue, 4 Feb 2025 15:54:10 -0500 Subject: [PATCH 3/3] housekeeping --- .../lib/bits/pin-input/pin-input.svelte.ts | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/packages/bits-ui/src/lib/bits/pin-input/pin-input.svelte.ts b/packages/bits-ui/src/lib/bits/pin-input/pin-input.svelte.ts index 56d88ac3a..8b04d5155 100644 --- a/packages/bits-ui/src/lib/bits/pin-input/pin-input.svelte.ts +++ b/packages/bits-ui/src/lib/bits/pin-input/pin-input.svelte.ts @@ -392,24 +392,27 @@ class PinInputRootState { const input = this.#inputRef.current; if (!input) return; - if ( - !this.opts.onPaste?.current && - (!this.#initialLoad.isIOS || !e.clipboardData || !input) - ) { - const contentData = e.clipboardData?.getData("text/plain"); + const getNewValue = (finalContent: string | undefined) => { const start = input.selectionStart === null ? undefined : input.selectionStart; const end = input.selectionEnd === null ? undefined : input.selectionEnd; - const isReplacing = start !== end; - const initNewVal = this.opts.value.current; - const newValueUncapped = isReplacing - ? initNewVal.slice(0, start) + contentData + initNewVal.slice(end) - : initNewVal.slice(0, start) + contentData + initNewVal.slice(start); - const newValue = newValueUncapped.slice(0, this.opts.maxLength.current); + ? initNewVal.slice(0, start) + finalContent + initNewVal.slice(end) + : initNewVal.slice(0, start) + finalContent + initNewVal.slice(start); + return newValueUncapped.slice(0, this.opts.maxLength.current); + }; + + const isValueInvalid = (newValue: string) => { + return newValue.length > 0 && this.#regexPattern && !this.#regexPattern.test(newValue); + }; - if (newValue.length > 0 && this.#regexPattern && !this.#regexPattern.test(newValue)) { + if ( + !this.opts.onPaste?.current && + (!this.#initialLoad.isIOS || !e.clipboardData || !input) + ) { + const newValue = getNewValue(e.clipboardData?.getData("text/plain")); + if (isValueInvalid(newValue)) { e.preventDefault(); } return; @@ -419,21 +422,9 @@ class PinInputRootState { const content = this.opts.onPaste?.current ? this.opts.onPaste.current(_content) : _content; e.preventDefault(); - const start = input.selectionStart === null ? undefined : input.selectionStart; - const end = input.selectionEnd === null ? undefined : input.selectionEnd; - - const isReplacing = start !== end; + const newValue = getNewValue(content); - const initNewVal = this.opts.value.current; - - const newValueUncapped = isReplacing - ? initNewVal.slice(0, start) + content + initNewVal.slice(end) - : initNewVal.slice(0, start) + content + initNewVal.slice(start); - const newValue = newValueUncapped.slice(0, this.opts.maxLength.current); - - if (newValue.length > 0 && this.#regexPattern && !this.#regexPattern.test(newValue)) { - return; - } + if (isValueInvalid(newValue)) return; input.value = newValue; this.opts.value.current = newValue;