fix(primitives): eslint/tsconfig migration, asChild refactor, type fixes
- Migrate to eslint flat config + composite tsconfig. - Complete the asChild→as="template" refactor (remove asChild prop + :as-child bindings across components, matching Primitive's slot model). - Fix test type errors and source type-safety (useGraceArea hull/point math, FocusScope/util ref typing). Note: ~53 vue-tsc errors remain (HTML attr/event passthrough typing on transparent wrapper components + a couple of duplicate-export naming collisions) — not gated by CI (build/lint/test green); pending a component-attribute-typing design decision.
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
<script lang="ts">
|
||||
const DIGIT_RE = /\d/;
|
||||
const NON_DIGIT_G = /\D/g;
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, useTemplateRef, watch } from 'vue';
|
||||
import { usePinInputContext } from './context';
|
||||
|
||||
interface Props {
|
||||
index: number;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const ctx = usePinInputContext();
|
||||
const el = useTemplateRef<HTMLInputElement>('el');
|
||||
|
||||
watch(el, (curr, prev) => {
|
||||
if (prev)
|
||||
ctx.unregister(prev);
|
||||
if (curr)
|
||||
ctx.register(curr);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (el.value)
|
||||
ctx.unregister(el.value);
|
||||
});
|
||||
|
||||
const displayed = computed(() => {
|
||||
const ch = ctx.value.value[props.index] ?? '';
|
||||
if (!ch)
|
||||
return '';
|
||||
return ctx.mask.value ? '•' : ch;
|
||||
});
|
||||
|
||||
// `inputType` / `inputMode` were thin ternary wrappers — inline in template.
|
||||
|
||||
function onInput(e: Event): void {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const raw = target.value;
|
||||
// keep only the last typed character
|
||||
let ch = raw.length > 0 ? raw[raw.length - 1]! : '';
|
||||
if (ctx.type.value === 'number' && ch && !DIGIT_RE.test(ch))
|
||||
ch = '';
|
||||
ctx.setAt(props.index, ch);
|
||||
// re-sync DOM input since we overwrite with displayed
|
||||
target.value = ch ? (ctx.mask.value ? '•' : ch) : '';
|
||||
if (ch && props.index < ctx.length.value - 1)
|
||||
ctx.focusIndex(props.index + 1);
|
||||
}
|
||||
|
||||
function onKeyDown(e: KeyboardEvent): void {
|
||||
const i = props.index;
|
||||
const n = ctx.length.value;
|
||||
switch (e.key) {
|
||||
case 'Backspace': {
|
||||
const current = ctx.value.value[i] ?? '';
|
||||
if (current) {
|
||||
ctx.clearAt(i);
|
||||
}
|
||||
else if (i > 0) {
|
||||
ctx.focusIndex(i - 1);
|
||||
ctx.clearAt(i - 1);
|
||||
}
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
case 'Delete': {
|
||||
ctx.clearAt(i);
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
case 'ArrowLeft': {
|
||||
if (i > 0)
|
||||
ctx.focusIndex(i - 1);
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
case 'ArrowRight': {
|
||||
if (i < n - 1)
|
||||
ctx.focusIndex(i + 1);
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
case 'Home': {
|
||||
ctx.focusIndex(0);
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
case 'End': {
|
||||
ctx.focusIndex(n - 1);
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onPaste(e: ClipboardEvent): void {
|
||||
const data = e.clipboardData?.getData('text') ?? '';
|
||||
if (!data)
|
||||
return;
|
||||
e.preventDefault();
|
||||
const chars = ctx.type.value === 'number'
|
||||
? data.replace(NON_DIGIT_G, '').split('')
|
||||
: data.split('');
|
||||
let idx = props.index;
|
||||
for (const ch of chars) {
|
||||
if (idx >= ctx.length.value)
|
||||
break;
|
||||
ctx.setAt(idx, ch);
|
||||
idx++;
|
||||
}
|
||||
ctx.focusIndex(Math.min(idx, ctx.length.value - 1));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
ref="el"
|
||||
:type="ctx.mask.value ? 'password' : 'text'"
|
||||
:inputmode="ctx.type.value === 'number' ? 'numeric' : 'text'"
|
||||
:value="displayed"
|
||||
:placeholder="ctx.placeholder.value"
|
||||
:disabled="ctx.disabled.value"
|
||||
:autocomplete="ctx.otp.value ? 'one-time-code' : 'off'"
|
||||
:data-index="props.index"
|
||||
maxlength="1"
|
||||
@input="onInput"
|
||||
@keydown="onKeyDown"
|
||||
@paste="onPaste"
|
||||
>
|
||||
</template>
|
||||
Reference in New Issue
Block a user