UNPKG

bootstrap-vue-next

Version:

Seamless integration of Vue 3, Bootstrap 5, and TypeScript for modern, type-safe UI development

542 lines (541 loc) 17.7 kB
require("./chunk-CoQrYLCe.js"); const require_dist = require("./dist-BJ15ThEs.js"); const require_useDefaults = require("./useDefaults-DsLf4iRY.js"); const require_useId = require("./useId-DHrBgM7P.js"); const require_VisuallyHidden = require("./VisuallyHidden-DaN947I0.js"); const require_useForwardExpose = require("./useForwardExpose-DrJOy0jY.js"); const require_VisuallyHiddenInput = require("./VisuallyHiddenInput-Biq4Qv5j.js"); const require_BFormInput = require("./BFormInput-BuRSGYdY.js"); let vue = require("vue"); //#region ../../node_modules/.pnpm/reka-ui@2.9.2_vue@3.5.31_typescript@5.9.3_/node_modules/reka-ui/dist/shared/useArrowNavigation.js var ignoredElement = ["INPUT", "TEXTAREA"]; /** * Allow arrow navigation for every html element with data-reka-collection-item tag * * @param e Keyboard event * @param currentElement Event initiator element or any element that wants to handle the navigation * @param parentElement Parent element where contains all the collection items, this will collect every item to be used when nav * @param options further options * @returns the navigated html element or null if none */ function useArrowNavigation(e, currentElement, parentElement, options = {}) { if (!currentElement || options.enableIgnoredElement && ignoredElement.includes(currentElement.nodeName)) return null; const { arrowKeyOptions = "both", attributeName = "[data-reka-collection-item]", itemsArray = [], loop = true, dir = "ltr", preventScroll = true, focus = false } = options; const [right, left, up, down, home, end] = [ e.key === "ArrowRight", e.key === "ArrowLeft", e.key === "ArrowUp", e.key === "ArrowDown", e.key === "Home", e.key === "End" ]; const goingVertical = up || down; const goingHorizontal = right || left; if (!home && !end && (!goingVertical && !goingHorizontal || arrowKeyOptions === "vertical" && goingHorizontal || arrowKeyOptions === "horizontal" && goingVertical)) return null; const allCollectionItems = parentElement ? Array.from(parentElement.querySelectorAll(attributeName)) : itemsArray; if (!allCollectionItems.length) return null; if (preventScroll) e.preventDefault(); let item = null; if (goingHorizontal || goingVertical) item = findNextFocusableElement(allCollectionItems, currentElement, { goForward: goingVertical ? down : dir === "ltr" ? right : left, loop }); else if (home) item = allCollectionItems.at(0) || null; else if (end) item = allCollectionItems.at(-1) || null; if (focus) item?.focus(); return item; } /** * Recursive function to find the next focusable element to avoid disabled elements * * @param elements Elements to navigate * @param currentElement Current active element * @param options * @returns next focusable element */ function findNextFocusableElement(elements, currentElement, options, iterations = !elements.includes(currentElement) ? elements.length + 1 : elements.length) { if (--iterations === 0) return null; const index = elements.indexOf(currentElement); let newIndex; if (index === -1) newIndex = options.goForward ? 0 : elements.length - 1; else newIndex = options.goForward ? index + 1 : index - 1; if (!options.loop && (newIndex < 0 || newIndex >= elements.length)) return null; const candidate = elements[(newIndex + elements.length) % elements.length]; if (!candidate) return null; if (candidate.hasAttribute("disabled") && candidate.getAttribute("disabled") !== "false") return findNextFocusableElement(elements, candidate, options, iterations); return candidate; } //#endregion //#region ../../node_modules/.pnpm/reka-ui@2.9.2_vue@3.5.31_typescript@5.9.3_/node_modules/reka-ui/dist/PinInput/PinInputRoot.js var [injectPinInputRootContext, providePinInputRootContext] = require_VisuallyHidden.createContext("PinInputRoot"); var PinInputRoot_default = /* @__PURE__ */ (0, vue.defineComponent)({ inheritAttrs: false, __name: "PinInputRoot", props: { modelValue: { type: null, required: false }, defaultValue: { type: null, required: false }, placeholder: { type: String, required: false, default: "" }, mask: { type: Boolean, required: false }, otp: { type: Boolean, required: false }, type: { type: null, required: false, default: "text" }, dir: { type: String, required: false }, disabled: { type: Boolean, required: false }, id: { type: String, required: false }, asChild: { type: Boolean, required: false }, as: { type: null, required: false }, name: { type: String, required: false }, required: { type: Boolean, required: false } }, emits: ["update:modelValue", "complete"], setup(__props, { emit: __emit }) { const props = __props; const emits = __emit; const { mask, otp, placeholder, type, disabled, dir: propDir } = (0, vue.toRefs)(props); const { forwardRef } = require_useForwardExpose.useForwardExpose(); const dir = require_VisuallyHidden.useDirection(propDir); const modelValue = require_dist.useVModel(props, "modelValue", emits, { defaultValue: props.defaultValue ?? [], passive: true, deep: true }); const currentModelValue = (0, vue.computed)(() => Array.isArray(modelValue.value) ? [...modelValue.value] : []); const inputElements = (0, vue.ref)(/* @__PURE__ */ new Set()); function onInputElementChange(el) { inputElements.value.add(el); } const isNumericMode = (0, vue.computed)(() => props.type === "number"); const isCompleted = (0, vue.computed)(() => { return currentModelValue.value.filter((i) => !!i || isNumericMode.value && i === 0).length === inputElements.value.size; }); (0, vue.watch)(modelValue, () => { if (isCompleted.value) emits("complete", modelValue.value); }, { deep: true }); providePinInputRootContext({ modelValue, currentModelValue, mask, otp, placeholder, type, dir, disabled, isCompleted, inputElements, onInputElementChange, isNumericMode }); return (_ctx, _cache) => { return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(require_VisuallyHidden.Primitive), (0, vue.mergeProps)(_ctx.$attrs, { ref: (0, vue.unref)(forwardRef), dir: (0, vue.unref)(dir), "data-complete": isCompleted.value ? "" : void 0, "data-disabled": (0, vue.unref)(disabled) ? "" : void 0 }), { default: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "default", { modelValue: (0, vue.unref)(modelValue) }), (0, vue.createVNode)(require_VisuallyHiddenInput.VisuallyHiddenInput_default, { id: _ctx.id, as: "input", feature: "focusable", tabindex: "-1", value: currentModelValue.value.join(""), name: _ctx.name ?? "", disabled: (0, vue.unref)(disabled), required: _ctx.required, onFocus: _cache[0] || (_cache[0] = ($event) => Array.from(inputElements.value)?.[0]?.focus()) }, null, 8, [ "id", "value", "name", "disabled", "required" ])]), _: 3 }, 16, [ "dir", "data-complete", "data-disabled" ]); }; } }); //#endregion //#region ../../node_modules/.pnpm/reka-ui@2.9.2_vue@3.5.31_typescript@5.9.3_/node_modules/reka-ui/dist/PinInput/PinInputInput.js var PinInputInput_default = /* @__PURE__ */ (0, vue.defineComponent)({ __name: "PinInputInput", props: { index: { type: Number, required: true }, disabled: { type: Boolean, required: false }, asChild: { type: Boolean, required: false }, as: { type: null, required: false, default: "input" } }, setup(__props) { const props = __props; const context = injectPinInputRootContext(); const inputElements = (0, vue.computed)(() => Array.from(context.inputElements.value)); const currentValue = (0, vue.computed)(() => context.currentModelValue.value[props.index]); const disabled = (0, vue.computed)(() => props.disabled || context.disabled.value); const isOtpMode = (0, vue.computed)(() => context.otp.value); const isPasswordMode = (0, vue.computed)(() => context.mask.value); const { primitiveElement, currentElement } = require_VisuallyHidden.usePrimitiveElement(); function handleInput(event) { const target = event.target; if ((event.data?.length ?? 0) > 1) { handleMultipleCharacter(target.value); return; } if (context.isNumericMode.value && !/^\d*$/.test(target.value)) { target.value = target.value.replace(/\D/g, ""); return; } target.value = event.data || target.value.slice(-1); updateModelValueAt(props.index, target.value); const nextEl = inputElements.value[props.index + 1]; if (nextEl) nextEl.focus(); } function updatePlaceholder() { (0, vue.nextTick)(() => { const target = currentElement.value; if (!target) return; if (!target.value && target === require_useForwardExpose.getActiveElement()) target.placeholder = ""; else target.placeholder = context.placeholder.value; }); } function handleKeydown(event) { useArrowNavigation(event, require_useForwardExpose.getActiveElement(), void 0, { itemsArray: inputElements.value, focus: true, loop: false, arrowKeyOptions: "horizontal", dir: context.dir.value }); } function handleBackspace(event) { event.preventDefault(); if (event.target.value) updateModelValueAt(props.index, ""); else { const prevEl = inputElements.value[props.index - 1]; if (prevEl) { prevEl.focus(); updateModelValueAt(props.index - 1, ""); } } } function handleDelete(event) { if (event.key === "Delete") { event.preventDefault(); updateModelValueAt(props.index, ""); } } function handleFocus(event) { if (context.otp.value) { const firstEmptyInputIdx = inputElements.value.findIndex((_, idx) => context.currentModelValue.value[idx] === "" || context.currentModelValue.value[idx] === void 0); if (firstEmptyInputIdx !== -1 && firstEmptyInputIdx < props.index) { inputElements.value[firstEmptyInputIdx].focus(); return; } } event.target.setSelectionRange(1, 1); updatePlaceholder(); } function handleBlur(event) { updatePlaceholder(); } function handlePaste(event) { event.preventDefault(); const clipboardData = event.clipboardData; if (!clipboardData) return; handleMultipleCharacter(clipboardData.getData("text")); } function handleMultipleCharacter(values) { const tempModelValue = [...context.currentModelValue.value]; const initialIndex = values.length >= inputElements.value.length ? 0 : props.index; const lastIndex = Math.min(initialIndex + values.length, inputElements.value.length); for (let i = initialIndex; i < lastIndex; i++) { const input = inputElements.value[i]; const value = values[i - initialIndex]; if (context.isNumericMode.value && !/^\d*$/.test(value)) continue; tempModelValue[i] = value; input.focus(); } context.modelValue.value = tempModelValue; inputElements.value[lastIndex]?.focus(); } function removeTrailingEmptyStrings(input) { let i = input.length - 1; while (i >= 0 && input[i] === "") { input.pop(); i--; } return input; } function updateModelValueAt(index, value) { const tempModelValue = [...context.currentModelValue.value]; if (context.isNumericMode.value) { const num = +value; if (value === "" || isNaN(num)) delete tempModelValue[index]; else tempModelValue[index] = num; } else tempModelValue[index] = value; context.modelValue.value = removeTrailingEmptyStrings(tempModelValue); } (0, vue.watch)(currentValue, updatePlaceholder); (0, vue.onMounted)(() => { context.onInputElementChange(currentElement.value); }); (0, vue.onUnmounted)(() => { context.inputElements?.value.delete(currentElement.value); }); return (_ctx, _cache) => { return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(require_VisuallyHidden.Primitive), { ref_key: "primitiveElement", ref: primitiveElement, autocapitalize: "none", as: _ctx.as, "as-child": _ctx.asChild, autocomplete: isOtpMode.value ? "one-time-code" : "false", type: isPasswordMode.value ? "password" : "text", inputmode: (0, vue.unref)(context).isNumericMode.value ? "numeric" : "text", pattern: (0, vue.unref)(context).isNumericMode.value ? "[0-9]*" : void 0, placeholder: (0, vue.unref)(context).placeholder.value, value: currentValue.value, disabled: disabled.value, "data-disabled": disabled.value ? "" : void 0, "data-complete": (0, vue.unref)(context).isCompleted.value ? "" : void 0, "aria-label": `pin input ${_ctx.index + 1} of ${inputElements.value.length}`, onInput: _cache[0] || (_cache[0] = ($event) => handleInput($event)), onKeydown: [ (0, vue.withKeys)(handleKeydown, [ "left", "right", "up", "down", "home", "end" ]), (0, vue.withKeys)(handleBackspace, ["backspace"]), (0, vue.withKeys)(handleDelete, ["delete"]) ], onFocus: handleFocus, onBlur: handleBlur, onPaste: handlePaste }, { default: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "default")]), _: 3 }, 8, [ "as", "as-child", "autocomplete", "type", "inputmode", "pattern", "placeholder", "value", "disabled", "data-disabled", "data-complete", "aria-label" ]); }; } }); //#endregion //#region src/components/BOtpInput/BOtpInput.vue?vue&type=script&setup=true&lang.ts var lengthDefault = 6; //#endregion //#region src/components/BOtpInput/BOtpInput.vue var BOtpInput_default = /* @__PURE__ */ (0, vue.defineComponent)({ __name: "BOtpInput", props: /* @__PURE__ */ (0, vue.mergeModels)({ ariaInvalid: { type: [Boolean, String], default: void 0 }, ariaLabel: { default: void 0 }, autofocus: { type: Boolean, default: false }, dir: { default: void 0 }, disabled: { type: Boolean, default: false }, form: { default: void 0 }, id: { default: void 0 }, length: { default: lengthDefault }, mask: { type: Boolean, default: false }, name: { default: void 0 }, otp: { type: Boolean, default: false }, placeholder: { default: "" }, plaintext: { type: Boolean, default: false }, readonly: { type: Boolean, default: false }, required: { type: Boolean, default: false }, separator: {}, size: { default: void 0 }, state: { type: [Boolean, null], default: null }, type: { default: "text" } }, { "modelValue": { default: () => [] }, "modelModifiers": {} }), emits: /* @__PURE__ */ (0, vue.mergeModels)(["complete"], ["update:modelValue"]), setup(__props, { emit: __emit }) { const props = require_useDefaults.useDefaults(__props, "BOtpInput"); const emit = __emit; const modelValue = (0, vue.useModel)(__props, "modelValue"); const computedId = require_useId.useId(() => props.id); const lengthNumber = require_dist.useToNumber(() => props.length, { nanToZero: true, method: "parseInt" }); const computedLength = (0, vue.computed)(() => lengthNumber.value > 0 ? lengthNumber.value : lengthDefault); const rootClasses = (0, vue.computed)(() => [ "b-otp-input", "d-flex", "gap-2", "align-items-center", { [`b-otp-input-${props.size}`]: !!props.size } ]); return (_ctx, _cache) => { return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(PinInputRoot_default), { id: (0, vue.unref)(computedId), modelValue: modelValue.value, "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => modelValue.value = $event), class: (0, vue.normalizeClass)(rootClasses.value), disabled: (0, vue.unref)(props).disabled, dir: (0, vue.unref)(props).dir, mask: (0, vue.unref)(props).mask, name: (0, vue.unref)(props).name, otp: (0, vue.unref)(props).otp, placeholder: (0, vue.unref)(props).placeholder, required: (0, vue.unref)(props).required, type: (0, vue.unref)(props).type, onComplete: _cache[1] || (_cache[1] = ($event) => emit("complete", $event)) }, { default: (0, vue.withCtx)(() => [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(computedLength.value, (_, i) => { return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(PinInputInput_default), { key: i, index: i, disabled: (0, vue.unref)(props).disabled, "as-child": "" }, { default: (0, vue.withCtx)(() => [(0, vue.createVNode)(require_BFormInput.BFormInput_default, { class: "b-otp-input-field", "aria-label": `${(0, vue.unref)(props).ariaLabel || "Pin"} ${i + 1} of ${computedLength.value}`, "aria-invalid": (0, vue.unref)(props).ariaInvalid, autofocus: (0, vue.unref)(props).autofocus && i === 0, disabled: (0, vue.unref)(props).disabled, form: (0, vue.unref)(props).form, plaintext: (0, vue.unref)(props).plaintext, readonly: (0, vue.unref)(props).readonly, size: (0, vue.unref)(props).size, state: (0, vue.unref)(props).state }, null, 8, [ "aria-label", "aria-invalid", "autofocus", "disabled", "form", "plaintext", "readonly", "size", "state" ])]), _: 2 }, 1032, ["index", "disabled"]); }), 128))]), _: 1 }, 8, [ "id", "modelValue", "class", "disabled", "dir", "mask", "name", "otp", "placeholder", "required", "type" ]); }; } }); //#endregion Object.defineProperty(exports, "BOtpInput_default", { enumerable: true, get: function() { return BOtpInput_default; } }); //# sourceMappingURL=BOtpInput-CSCoKHP9.js.map