fix(primitives): component fixes and test cleanup

This commit is contained in:
2026-06-08 15:51:30 +07:00
parent 9ef8125965
commit def1db8b6c
12 changed files with 18 additions and 19 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ function load(nextSrc: string | undefined) {
setStatus('error');
return;
}
if (typeof globalThis.window === 'undefined') {
if (globalThis.window === undefined) {
setStatus('loading');
return;
}
+1 -2
View File
@@ -7,8 +7,6 @@ export interface ComboboxRootProps<T extends AcceptableValue = AcceptableValue>
modelValue?: T | T[];
/** Uncontrolled initial value. */
defaultValue?: T | T[];
/** Controlled open state. Use `v-model:open`. */
open?: boolean;
/** Uncontrolled default open state. */
defaultOpen?: boolean;
/** Allow selecting multiple values. */
@@ -77,6 +75,7 @@ const config = useConfig();
const direction = computed(() => dir ?? config.dir.value);
const localOpen = ref<boolean>(defaultOpen);
/** Controlled open state. Use `v-model:open`. */
const open = defineModel<boolean>('open', {
default: undefined,
get: v => v ?? localOpen.value,
+1 -1
View File
@@ -138,7 +138,7 @@ const filteredItems = computed<Map<string, number>>(() => {
function escapeAttr(v: string): string {
if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') return CSS.escape(v);
return v.replace(/["\\]/g, '\\$&');
return v.replaceAll(/["\\]/g, '\\$&');
}
function getSelectableItems(): string[] {
+2 -6
View File
@@ -5,13 +5,9 @@ declare global {
}
declare module 'vue' {
interface ComponentCustomProps {
[key: `data${string}`]: unknown;
}
type ComponentCustomProps = Record<`data${string}`, unknown>;
}
declare module 'vue' {
interface HTMLAttributes {
[key: `data-${string}`]: unknown;
}
type HTMLAttributes = Record<`data-${string}`, unknown>;
}
@@ -21,6 +21,9 @@ const { openDelay = 700, closeDelay = 300, defaultOpen = false } = defineProps<H
const openModel = defineModel<boolean | undefined>('open', { default: undefined });
const uncontrolled = ref(defaultOpen);
// `open` intentionally shares the model name: it's a local read-only computed that
// resolves the controlled model against the uncontrolled fallback. Safe in script-setup.
// eslint-disable-next-line vue/no-dupe-keys
const open = computed(() => openModel.value ?? uncontrolled.value);
function setOpen(value: boolean) {
@@ -1,4 +1,7 @@
<script lang="ts">
// This component takes no props of its own (it renders a fixed wrapper + RovingFocusGroup
// and forwards $attrs). The empty interface is the intentional public prop contract.
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface NavigationMenuListProps {}
</script>
@@ -3,8 +3,6 @@ import type { Direction } from '../config-provider';
import type { Orientation } from '../roving-focus';
export interface NavigationMenuRootProps {
/** Controlled active item value. Use `v-model`. */
modelValue?: string;
/** Uncontrolled initial value. */
defaultValue?: string;
/** Reading direction. Falls back to `ConfigProvider`. */
@@ -72,6 +70,7 @@ const config = useConfig();
const dirRef = computed<Direction>(() => dir ?? config.dir.value);
const localValue = ref<string>(defaultValue ?? '');
/** Controlled active item value. Use `v-model`. */
const modelValue = defineModel<string | undefined>({
default: undefined,
get: v => v ?? localValue.value,
@@ -212,7 +211,7 @@ provideNavigationMenuContext({
<Primitive
:ref="forwardRef"
as="nav"
:aria-label="$attrs['aria-label'] as string | undefined ?? 'Main'"
:aria-label="($attrs['aria-label'] ?? 'Main') as string"
:data-orientation="orientation"
:dir="dirRef"
data-primitives-navigation-menu
@@ -2,8 +2,6 @@
import type { Orientation } from '../roving-focus';
export interface NavigationMenuSubProps {
/** Controlled active value of the submenu. Use `v-model`. */
modelValue?: string;
/** Uncontrolled initial value. */
defaultValue?: string;
/** Submenu orientation. @default 'horizontal' */
@@ -36,6 +34,7 @@ defineSlots<{
}>();
const localValue = ref<string>(defaultValue ?? '');
/** Controlled active value of the submenu. Use `v-model`. */
const modelValue = defineModel<string | undefined>({
default: undefined,
get: v => v ?? localValue.value,
@@ -102,7 +102,7 @@ function onPaste(e: ClipboardEvent): void {
return;
e.preventDefault();
const chars = ctx.type.value === 'number'
? data.replace(NON_DIGIT_G, '').split('')
? data.replaceAll(NON_DIGIT_G, '').split('')
: data.split('');
let idx = props.index;
for (const ch of chars) {
@@ -17,7 +17,7 @@ const { forwardRef } = useForwardExpose();
function onClick(): void {
if (item.disabled.value) return;
const idx = ctx.modelValue.value.findIndex(v => v === item.value.value);
const idx = ctx.modelValue.value.indexOf(item.value.value);
if (idx !== -1) ctx.onRemoveValue(idx);
}
</script>
@@ -120,8 +120,8 @@ describe('TagsInput', () => {
it('delete trigger removes its tag', async () => {
const w = createTagsInput({ defaultValue: ['a', 'b'] });
const deleteBtns = w.findAll('button').filter(b => b.text() === '×');
await deleteBtns[0]!.trigger('click');
const deleteBtn = w.findAll('button').find(b => b.text() === '×');
await deleteBtn!.trigger('click');
await nextTick();
const items = w.findAllComponents(TagsInputItem as Component);
expect(items).toHaveLength(1);