Files
tools/vue/primitives/src/listbox/ListboxItem.vue
T
robonen 626fbc70d8 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.
2026-06-07 16:29:56 +07:00

86 lines
3.0 KiB
Vue

<script lang="ts" generic="T extends ListboxValue = ListboxValue">
import type { ListboxValue } from './utils';
import type { PrimitiveProps } from '../primitive';
export interface ListboxItemProps<U extends ListboxValue = ListboxValue> extends PrimitiveProps {
/** The value of the item. */
value: U;
/** Disable this item. */
disabled?: boolean;
}
export interface ListboxItemEmits<U extends ListboxValue = ListboxValue> {
/** Fired before the value change commits. Call `event.preventDefault()` to cancel. */
select: [event: CustomEvent<{ originalEvent: Event; value: U }>];
}
</script>
<script setup lang="ts" generic="T extends ListboxValue = ListboxValue">
import { provideListboxItemContext, useListboxRootContext } from './context';
import { Primitive } from '../primitive';
import { computed } from 'vue';
import { useCollectionInjector } from '../collection';
import { useForwardExpose } from '@robonen/vue';
import { useId } from '../config-provider';
const { as = 'div', value, disabled = false } = defineProps<ListboxItemProps<T>>();
const emit = defineEmits<ListboxItemEmits<T>>();
const ctx = useListboxRootContext();
const { forwardRef, currentElement } = useForwardExpose();
const { CollectionItem } = useCollectionInjector();
const id = useId(undefined, 'listbox-item').value;
const isHighlighted = computed(() => currentElement.value === ctx.highlightedElement.value);
const isSelected = computed(() => ctx.isSelected(value));
const isDisabled = computed(() => disabled || ctx.disabled.value);
function handleSelect(originalEvent: Event): void {
if (isDisabled.value) return;
const detail = { originalEvent, value };
const custom = new CustomEvent('listbox.select', { bubbles: true, cancelable: true, detail });
emit('select', custom);
if (custom.defaultPrevented) return;
ctx.onValueChange(value);
if (currentElement.value) ctx.changeHighlight(currentElement.value);
}
function onClick(event: MouseEvent): void {
handleSelect(event);
}
function onKeyDown(event: KeyboardEvent): void {
if (event.key !== ' ') return;
event.preventDefault();
handleSelect(event);
}
function onPointerMove(): void {
if (!ctx.highlightOnHover.value) return;
if (!currentElement.value || ctx.highlightedElement.value === currentElement.value) return;
ctx.changeHighlight(currentElement.value, false, false);
}
provideListboxItemContext({ isSelected });
</script>
<template>
<CollectionItem :value="value">
<Primitive
:ref="forwardRef"
:as="as"
:id="id"
role="option"
:tabindex="ctx.focusable.value ? (isHighlighted ? '0' : '-1') : '-1'"
:aria-selected="isSelected"
:data-state="isSelected ? 'checked' : 'unchecked'"
:data-highlighted="isHighlighted ? '' : undefined"
:data-disabled="isDisabled ? '' : undefined"
:disabled="isDisabled || undefined"
@click="onClick"
@keydown="onKeyDown"
@pointermove="onPointerMove"
>
<slot :is-selected="isSelected" :is-highlighted="isHighlighted" />
</Primitive>
</CollectionItem>
</template>