"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { OTPInput: () => OTPInput_default, REGEXP_ONLY_CHARS: () => REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS: () => REGEXP_ONLY_DIGITS, REGEXP_ONLY_DIGITS_AND_CHARS: () => REGEXP_ONLY_DIGITS_AND_CHARS }); module.exports = __toCommonJS(src_exports); // src/OTPInput.vue var import_vue4 = require("vue"); var import_vue5 = require("vue"); var import_vue6 = require("vue"); // src/regexp.ts var REGEXP_ONLY_DIGITS = "^\\d+$"; var REGEXP_ONLY_CHARS = "^[a-zA-Z]+$"; var REGEXP_ONLY_DIGITS_AND_CHARS = "^[a-zA-Z0-9]+$"; // src/sync-timeouts.ts function syncTimeouts(cb) { const t1 = setTimeout(cb, 0); const t2 = setTimeout(cb, 10); const t3 = setTimeout(cb, 50); return [t1, t2, t3]; } // src/use-pwm-badge.ts var import_vue = require("vue"); var PWM_BADGE_MARGIN_RIGHT = 18; var PWM_BADGE_SPACE_WIDTH_PX = 40; var PWM_BADGE_SPACE_WIDTH = `${PWM_BADGE_SPACE_WIDTH_PX}px`; var PASSWORD_MANAGERS_SELECTORS = [ "[data-lastpass-icon-root]", // LastPass "com-1password-button", // 1Password "[data-dashlanecreated]", // Dashlane '[style$="2147483647 !important;"]' // Bitwarden ].join(","); function usePasswordManagerBadge({ containerRef, inputRef, pushPasswordManagerStrategy, isFocused }) { const pwmMetadata = (0, import_vue.ref)({ done: false, refocused: false }); const hasPWMBadge = (0, import_vue.ref)(false); const hasPWMBadgeSpace = (0, import_vue.ref)(false); const done = (0, import_vue.ref)(false); const willPushPWMBadge = (0, import_vue.computed)(() => { if (pushPasswordManagerStrategy === "none") { return false; } const increaseWidthCase = (pushPasswordManagerStrategy === "increase-width" || pushPasswordManagerStrategy === "experimental-no-flickering") && hasPWMBadge.value && hasPWMBadgeSpace.value; return increaseWidthCase; }); const trackPWMBadge = () => { const container = containerRef.value; const input = inputRef.value; if (!container || !input || done.value || pushPasswordManagerStrategy === "none") { return; } const elementToCompare = container; const rightCornerX = elementToCompare.getBoundingClientRect().left + elementToCompare.offsetWidth; const centereredY = elementToCompare.getBoundingClientRect().top + elementToCompare.offsetHeight / 2; const x = rightCornerX - PWM_BADGE_MARGIN_RIGHT; const y = centereredY; const pmws = document.querySelectorAll(PASSWORD_MANAGERS_SELECTORS); if (pmws.length === 0) { const maybeBadgeEl = document.elementFromPoint(x, y); if (maybeBadgeEl === container) { return; } } hasPWMBadge.value = true; done.value = true; if (!pwmMetadata.value.refocused && document.activeElement === input) { const sel = [input.selectionStart, input.selectionEnd]; input.blur(); input.focus(); input.setSelectionRange(sel[0], sel[1]); pwmMetadata.value.refocused = true; } }; const checkHasSpace = () => { const container = containerRef.value; if (!container || pushPasswordManagerStrategy === "none") { return; } const viewportWidth = window.innerWidth; const distanceToRightEdge = viewportWidth - container.getBoundingClientRect().right; hasPWMBadgeSpace.value = distanceToRightEdge >= PWM_BADGE_SPACE_WIDTH_PX; }; let spaceInterval; (0, import_vue.onMounted)(() => { checkHasSpace(); spaceInterval = setInterval(checkHasSpace, 1e3); }); (0, import_vue.onUnmounted)(() => { clearInterval(spaceInterval); }); (0, import_vue.watch)([isFocused, inputRef], (newValues, _, onInvalidate) => { const [newIsFocused, newInputRef] = newValues; const _isFocused = newIsFocused || document.activeElement === newInputRef; if (pushPasswordManagerStrategy === "none" || !_isFocused) { return; } const t1 = setTimeout(trackPWMBadge, 0); const t2 = setTimeout(trackPWMBadge, 2e3); const t3 = setTimeout(trackPWMBadge, 5e3); const t4 = setTimeout(() => { done.value = true; }, 6e3); onInvalidate(() => { clearTimeout(t1); clearTimeout(t2); clearTimeout(t3); clearTimeout(t4); }); }); return { hasPWMBadge, willPushPWMBadge, PWM_BADGE_SPACE_WIDTH }; } // src/use-previous.ts var import_vue2 = require("vue"); function usePrevious(value, initialValue) { const previous = (0, import_vue2.shallowRef)(initialValue); (0, import_vue2.watch)(value, (_, oldValue) => { previous.value = oldValue; }, { flush: "sync" }); return (0, import_vue2.readonly)(previous); } // src/NoSciptCssFallback.ts var import_vue3 = require("vue"); var NOSCRIPT_CSS_FALLBACK = ` [data-input-otp] { --nojs-bg: white !important; --nojs-fg: black !important; background-color: var(--nojs-bg) !important; color: var(--nojs-fg) !important; caret-color: var(--nojs-fg) !important; letter-spacing: .25em !important; text-align: center !important; border: 1px solid var(--nojs-fg) !important; border-radius: 4px !important; width: 100% !important; } @media (prefers-color-scheme: dark) { [data-input-otp] { --nojs-bg: black !important; --nojs-fg: white !important; } }`; var NoSciptCssFallback = (0, import_vue3.defineComponent)({ props: { fallback: { type: String, required: true } }, setup(props) { return () => (0, import_vue3.h)("noscript", { innerHTML: `` }); } }); // src/OTPInput.vue var _hoisted_1 = { style: { "position": "absolute", "inset": "0", "pointer-events": "none" } }; var _hoisted_2 = ["value", "data-input-otp-mss", "data-input-otp-mse"]; var _sfc_main = /* @__PURE__ */ (0, import_vue4.defineComponent)({ ...{ name: "OTPInput", inheritAttrs: false }, __name: "OTPInput", props: /* @__PURE__ */ (0, import_vue4.mergeModels)({ maxlength: {}, textAlign: { default: "left" }, inputmode: { default: "numeric" }, containerClass: {}, pushPasswordManagerStrategy: { default: "increase-width" }, noScriptCssFallback: { default: NOSCRIPT_CSS_FALLBACK }, accept: {}, alt: {}, autocomplete: { default: "one-time-code" }, autofocus: { type: Boolean }, capture: { type: [Boolean, String] }, checked: { type: [Boolean, Array, Set] }, crossorigin: {}, disabled: { type: Boolean }, enterKeyHint: {}, form: {}, formaction: {}, formenctype: {}, formmethod: {}, formnovalidate: { type: Boolean }, formtarget: {}, height: {}, indeterminate: { type: Boolean }, list: {}, max: {}, min: {}, minlength: {}, multiple: { type: Boolean }, name: {}, pattern: { default: REGEXP_ONLY_DIGITS }, placeholder: {}, readonly: { type: Boolean }, required: { type: Boolean }, size: {}, src: {}, step: {}, type: {}, value: {}, width: {} }, { "modelValue": { default: "" }, "modelModifiers": {} }), emits: /* @__PURE__ */ (0, import_vue4.mergeModels)(["complete", "change", "select", "input", "focus", "blur", "mouseover", "mouseleave", "paste"], ["update:modelValue"]), setup(__props, { expose: __expose, emit: __emit }) { const props = __props; const emit = __emit; const internalValue = (0, import_vue4.useModel)(__props, "modelValue"); const previousValue = usePrevious(internalValue); const regexp = (0, import_vue6.computed)(() => props.pattern ? typeof props.pattern === "string" ? new RegExp(props.pattern) : props.pattern : null); const isHoveringInput = (0, import_vue6.ref)(false); const isFocused = (0, import_vue6.ref)(false); const mirrorSelectionStart = (0, import_vue6.ref)(null); const mirrorSelectionEnd = (0, import_vue6.ref)(null); const inputRef = (0, import_vue6.ref)(null); __expose({ ref: inputRef }); const containerRef = (0, import_vue6.ref)(null); const inputMetadataRef = (0, import_vue6.ref)({ prev: [ inputRef.value?.selectionStart, inputRef.value?.selectionEnd, inputRef.value?.selectionDirection ] }); function safeInsertRule(sheet, rule) { try { sheet.insertRule(rule); } catch { console.error("input-otp could not insert CSS rule:", rule); } } (0, import_vue6.onMounted)(() => { const input = inputRef.value; const container = containerRef.value; if (!input || !container) { return; } inputMetadataRef.value.prev = [ input.selectionStart, input.selectionEnd, input.selectionDirection ]; function onDocumentSelectionChange() { if (!input) { return; } if (document.activeElement !== input) { mirrorSelectionStart.value = null; mirrorSelectionEnd.value = null; return; } const _s = input.selectionStart; const _e = input.selectionEnd; const _dir = input.selectionDirection; const _ml = input.maxLength; const _val = input.value; const _prev = inputMetadataRef.value.prev; let start = -1; let end = -1; let direction; if (_val.length !== 0 && _s !== null && _e !== null) { const isSingleCaret = _s === _e; const isInsertMode = _s === _val.length && _val.length < _ml; if (isSingleCaret && !isInsertMode) { const c = _s; if (c === 0) { start = 0; end = 1; direction = "forward"; } else if (c === _ml) { start = c - 1; end = c; direction = "backward"; } else if (_ml > 1 && _val.length > 1) { let offset = 0; if (_prev[0] !== null && _prev[1] !== null) { direction = c < _prev[1] ? "backward" : "forward"; const wasPreviouslyInserting = _prev[0] === _prev[1] && _prev[0] < _ml; if (direction === "backward" && !wasPreviouslyInserting) { offset = -1; } } start = offset + c; end = offset + c + 1; } } if (start !== -1 && end !== -1 && start !== end) { input.setSelectionRange(start, end, direction); } } const s = start !== -1 ? start : _s; const e = end !== -1 ? end : _e; const dir = direction ?? _dir; mirrorSelectionStart.value = s; mirrorSelectionEnd.value = e; inputMetadataRef.value.prev = [s, e, dir]; } document.addEventListener("selectionchange", onDocumentSelectionChange, { capture: true }); onDocumentSelectionChange(); if (document.activeElement === input) { isFocused.value = true; } if (!document.getElementById("input-otp-style")) { const styleEl = document.createElement("style"); styleEl.id = "input-otp-style"; document.head.appendChild(styleEl); if (styleEl.sheet) { const autofillStyles = "background: transparent !important; color: transparent !important; border-color: transparent !important; opacity: 0 !important; box-shadow: none !important; -webkit-box-shadow: none !important; -webkit-text-fill-color: transparent !important;"; safeInsertRule( styleEl.sheet, "[data-input-otp]::selection { background: transparent !important; color: transparent !important; }" ); safeInsertRule( styleEl.sheet, `[data-input-otp]:autofill { ${autofillStyles} }` ); safeInsertRule( styleEl.sheet, `[data-input-otp]:-webkit-autofill { ${autofillStyles} }` ); safeInsertRule( styleEl.sheet, `@supports (-webkit-touch-callout: none) { [data-input-otp] { letter-spacing: -.6em !important; font-weight: 100 !important; font-stretch: ultra-condensed; font-optical-sizing: none !important; left: -1px !important; right: 1px !important; } }` ); safeInsertRule( styleEl.sheet, `[data-input-otp] + * { pointer-events: all !important; }` ); } } const updateRootHeight = () => { if (container) { container.style.setProperty( "--root-height", `${input.clientHeight}px` ); } }; updateRootHeight(); const resizeObserver = new ResizeObserver(updateRootHeight); resizeObserver.observe(input); (0, import_vue6.onUnmounted)(() => { document.removeEventListener( "selectionchange", onDocumentSelectionChange, { capture: true } ); resizeObserver.disconnect(); }); }); (0, import_vue6.watch)([internalValue, isFocused], () => { syncTimeouts(() => { inputRef.value?.dispatchEvent(new Event("input")); const s = inputRef.value?.selectionStart; const e = inputRef.value?.selectionEnd; const dir = inputRef.value?.selectionDirection; if (s !== null && e !== null) { mirrorSelectionStart.value = s; mirrorSelectionEnd.value = e; inputMetadataRef.value.prev = [s, e, dir]; } }); }, { immediate: true }); (0, import_vue6.watchEffect)(() => { if (previousValue.value === void 0) { return; } if (internalValue.value !== previousValue.value && previousValue.value.length < props.maxlength && internalValue.value.length === props.maxlength) { emit("complete", internalValue.value); } }); const pwmb = usePasswordManagerBadge({ containerRef, inputRef, pushPasswordManagerStrategy: props.pushPasswordManagerStrategy, isFocused }); function _inputListener(e) { const newValue = e.currentTarget.value.slice(0, props.maxlength); if (newValue.length > 0 && regexp.value && !regexp.value.test(newValue)) { e.preventDefault(); return; } const maybeHasDeleted = typeof previousValue.value === "string" && newValue.length < previousValue.value.length; if (maybeHasDeleted) { document.dispatchEvent(new Event("selectionchange")); } internalValue.value = newValue; emit("input", newValue); } function _focusListener() { if (inputRef.value) { const start = Math.min(inputRef.value.value.length, props.maxlength - 1); const end = inputRef.value.value.length; inputRef.value?.setSelectionRange(start, end); mirrorSelectionStart.value = start; mirrorSelectionEnd.value = end; } isFocused.value = true; } function _pasteListener(e) { const input = inputRef.value; if (!e.clipboardData || !input) { return; } const content = e.clipboardData.getData("text/plain"); e.preventDefault(); const start = inputRef.value?.selectionStart; const end = inputRef.value?.selectionEnd; const isReplacing = start !== end; const newValueUncapped = isReplacing ? internalValue.value.slice(0, start) + content + internalValue.value.slice(end) : internalValue.value.slice(0, start) + content + internalValue.value.slice(start); const newValue = newValueUncapped.slice(0, props.maxlength); if (newValue.length > 0 && regexp.value && !regexp.value.test(newValue)) { return; } internalValue.value = newValue; emit("input", newValue); const _start = Math.min(newValue.length, props.maxlength - 1); const _end = newValue.length; input.setSelectionRange(_start, _end); mirrorSelectionStart.value = _start; mirrorSelectionEnd.value = _end; } const attrs = (0, import_vue6.useAttrs)(); const inputProps = (0, import_vue6.computed)(() => { const { containerClass, value, ...rest } = props; return { ...attrs, // putting attrs for now until I can extract the input props from Vue ...rest, pattern: regexp.value?.source }; }); const rootStyle = (0, import_vue6.computed)( () => ({ position: "relative", cursor: props.disabled ? "default" : "text", userSelect: "none", WebkitUserSelect: "none", pointerEvents: "none" }) ); const inputStyle = (0, import_vue6.computed)( () => ({ position: "absolute", inset: 0, width: pwmb.willPushPWMBadge.value ? `calc(100% + ${pwmb.PWM_BADGE_SPACE_WIDTH})` : "100%", clipPath: pwmb.willPushPWMBadge.value ? `inset(0 ${pwmb.PWM_BADGE_SPACE_WIDTH} 0 0)` : void 0, height: "100%", display: "flex", textAlign: props.textAlign, opacity: "1", // Mandatory for iOS hold-paste color: "transparent", pointerEvents: "all", background: "transparent", caretColor: "transparent", border: "0 solid transparent", outline: "0 solid transparent", boxShadow: "none", lineHeight: "1", letterSpacing: "-.5em", fontSize: "var(--root-height)", fontFamily: "monospace", fontVariantNumeric: "tabular-nums" // letterSpacing: '-1em', // transform: 'scale(1.5)', // paddingRight: '100%', // paddingBottom: '100%', // debugging purposes // inset: undefined, // position: undefined, // color: 'black', // background: 'white', // opacity: '1', // caretColor: 'black', // padding: '0', // letterSpacing: 'unset', // fontSize: 'unset', // paddingInline: '.5rem', }) ); const contextValue = (0, import_vue6.computed)(() => { return Array.from({ length: Number(props.maxlength) }).map((_, slotIdx) => { const isActive = isFocused.value && mirrorSelectionStart.value !== null && mirrorSelectionEnd.value !== null && (mirrorSelectionStart.value === mirrorSelectionEnd.value && slotIdx === mirrorSelectionStart.value || slotIdx >= mirrorSelectionStart.value && slotIdx < mirrorSelectionEnd.value); const char = internalValue.value[slotIdx] !== void 0 ? internalValue.value[slotIdx] : null; return { char, isActive, hasFakeCaret: isActive && char === null }; }); }); return (_ctx, _cache) => { return (0, import_vue5.openBlock)(), (0, import_vue5.createElementBlock)( import_vue5.Fragment, null, [ _ctx.noScriptCssFallback !== null ? ((0, import_vue5.openBlock)(), (0, import_vue5.createBlock)((0, import_vue5.unref)(NoSciptCssFallback), { key: 0, fallback: _ctx.noScriptCssFallback }, null, 8, ["fallback"])) : (0, import_vue5.createCommentVNode)("v-if", true), (0, import_vue5.createElementVNode)( "div", { ref_key: "containerRef", ref: containerRef, "data-input-otp-container": "", style: (0, import_vue5.normalizeStyle)(rootStyle.value), class: (0, import_vue5.normalizeClass)(_ctx.containerClass) }, [ (0, import_vue5.renderSlot)(_ctx.$slots, "default", { slots: contextValue.value, isFocused: isFocused.value, isHovering: !_ctx.disabled && isHoveringInput.value }), (0, import_vue5.createElementVNode)("div", _hoisted_1, [ (0, import_vue5.createElementVNode)("input", (0, import_vue5.mergeProps)({ ref_key: "inputRef", ref: inputRef, value: internalValue.value, "data-input-otp": "", "data-input-otp-mss": mirrorSelectionStart.value, "data-input-otp-mse": mirrorSelectionEnd.value, style: inputStyle.value }, inputProps.value, { onMouseover: _cache[0] || (_cache[0] = (e) => { isHoveringInput.value = true; emit("mouseover", e); }), onMouseleave: _cache[1] || (_cache[1] = (e) => { isHoveringInput.value = false; emit("mouseleave", e); }), onPaste: _cache[2] || (_cache[2] = (e) => { _pasteListener(e); emit("paste", e); }), onInput: _inputListener, onFocus: _cache[3] || (_cache[3] = (e) => { _focusListener(); emit("focus", e); }), onBlur: _cache[4] || (_cache[4] = (e) => { isFocused.value = false; emit("blur", e); }) }), null, 16, _hoisted_2) ]) ], 6 /* CLASS, STYLE */ ) ], 64 /* STABLE_FRAGMENT */ ); }; } }); var OTPInput_default = _sfc_main; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { OTPInput, REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS, REGEXP_ONLY_DIGITS_AND_CHARS }); //# sourceMappingURL=index.cjs.map