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
JavaScript
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