UNPKG

bootstrap-vue-next

Version:

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

536 lines (535 loc) 17 kB
import { C as useVModel, z as useToNumber } from "./dist-B10a-gZ8.mjs"; import { t as useDefaults } from "./useDefaults-BKgBaqOV.mjs"; import { t as useId$1 } from "./useId-BKZFSYm8.mjs"; import { a as useDirection, c as createContext, n as usePrimitiveElement, r as Primitive } from "./VisuallyHidden-Bbwok8oL.mjs"; import { n as getActiveElement, t as useForwardExpose } from "./useForwardExpose-4OUimdPL.mjs"; import { t as VisuallyHiddenInput_default } from "./VisuallyHiddenInput-D1SjTCVH.mjs"; import { t as BFormInput_default } from "./BFormInput-Dg9dbwHp.mjs"; import { Fragment, computed, createBlock, createElementBlock, createVNode, defineComponent, mergeModels, mergeProps, nextTick, normalizeClass, onMounted, onUnmounted, openBlock, ref, renderList, renderSlot, toRefs, unref, useModel, watch, withCtx, withKeys } from "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] = createContext("PinInputRoot"); var PinInputRoot_default = /* @__PURE__ */ 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 } = toRefs(props); const { forwardRef } = useForwardExpose(); const dir = useDirection(propDir); const modelValue = useVModel(props, "modelValue", emits, { defaultValue: props.defaultValue ?? [], passive: true, deep: true }); const currentModelValue = computed(() => Array.isArray(modelValue.value) ? [...modelValue.value] : []); const inputElements = ref(/* @__PURE__ */ new Set()); function onInputElementChange(el) { inputElements.value.add(el); } const isNumericMode = computed(() => props.type === "number"); const isCompleted = computed(() => { return currentModelValue.value.filter((i) => !!i || isNumericMode.value && i === 0).length === inputElements.value.size; }); 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 openBlock(), createBlock(unref(Primitive), mergeProps(_ctx.$attrs, { ref: unref(forwardRef), dir: unref(dir), "data-complete": isCompleted.value ? "" : void 0, "data-disabled": unref(disabled) ? "" : void 0 }), { default: withCtx(() => [renderSlot(_ctx.$slots, "default", { modelValue: unref(modelValue) }), createVNode(VisuallyHiddenInput_default, { id: _ctx.id, as: "input", feature: "focusable", tabindex: "-1", value: currentModelValue.value.join(""), name: _ctx.name ?? "", disabled: 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__ */ 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 = computed(() => Array.from(context.inputElements.value)); const currentValue = computed(() => context.currentModelValue.value[props.index]); const disabled = computed(() => props.disabled || context.disabled.value); const isOtpMode = computed(() => context.otp.value); const isPasswordMode = computed(() => context.mask.value); const { primitiveElement, currentElement } = 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() { nextTick(() => { const target = currentElement.value; if (!target) return; if (!target.value && target === getActiveElement()) target.placeholder = ""; else target.placeholder = context.placeholder.value; }); } function handleKeydown(event) { useArrowNavigation(event, 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); } watch(currentValue, updatePlaceholder); onMounted(() => { context.onInputElementChange(currentElement.value); }); onUnmounted(() => { context.inputElements?.value.delete(currentElement.value); }); return (_ctx, _cache) => { return openBlock(), createBlock(unref(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: unref(context).isNumericMode.value ? "numeric" : "text", pattern: unref(context).isNumericMode.value ? "[0-9]*" : void 0, placeholder: unref(context).placeholder.value, value: currentValue.value, disabled: disabled.value, "data-disabled": disabled.value ? "" : void 0, "data-complete": 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: [ withKeys(handleKeydown, [ "left", "right", "up", "down", "home", "end" ]), withKeys(handleBackspace, ["backspace"]), withKeys(handleDelete, ["delete"]) ], onFocus: handleFocus, onBlur: handleBlur, onPaste: handlePaste }, { default: withCtx(() => [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/BFormOtp/BFormOtp.vue?vue&type=script&setup=true&lang.ts var lengthDefault = 6; //#endregion //#region src/components/BFormOtp/BFormOtp.vue var BFormOtp_default = /* @__PURE__ */ defineComponent({ __name: "BFormOtp", props: /* @__PURE__ */ 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__ */ mergeModels(["complete"], ["update:modelValue"]), setup(__props, { emit: __emit }) { const props = useDefaults(__props, "BFormOtp"); const emit = __emit; const modelValue = useModel(__props, "modelValue"); const computedId = useId$1(() => props.id); const lengthNumber = useToNumber(() => props.length, { nanToZero: true, method: "parseInt" }); const computedLength = computed(() => lengthNumber.value > 0 ? lengthNumber.value : lengthDefault); const rootClasses = computed(() => [ "b-form-otp", "d-flex", "gap-2", "align-items-center", { [`b-form-otp-${props.size}`]: !!props.size } ]); return (_ctx, _cache) => { return openBlock(), createBlock(unref(PinInputRoot_default), { id: unref(computedId), modelValue: modelValue.value, "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => modelValue.value = $event), class: normalizeClass(rootClasses.value), disabled: unref(props).disabled, dir: unref(props).dir, mask: unref(props).mask, name: unref(props).name, otp: unref(props).otp, placeholder: unref(props).placeholder, required: unref(props).required, type: unref(props).type, onComplete: _cache[1] || (_cache[1] = ($event) => emit("complete", $event)) }, { default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(computedLength.value, (_, i) => { return openBlock(), createBlock(unref(PinInputInput_default), { key: i, index: i, disabled: unref(props).disabled, "as-child": "" }, { default: withCtx(() => [createVNode(BFormInput_default, { class: "b-form-otp-field", "aria-label": `${unref(props).ariaLabel || "Pin"} ${i + 1} of ${computedLength.value}`, "aria-invalid": unref(props).ariaInvalid, autofocus: unref(props).autofocus && i === 0, disabled: unref(props).disabled, form: unref(props).form, plaintext: unref(props).plaintext, readonly: unref(props).readonly, size: unref(props).size, state: 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 export { BFormOtp_default as t }; //# sourceMappingURL=BFormOtp-DYjsPhQN.mjs.map