Select
A Select component allows users pick a value from predefined options.
Features
Install
Install the component from your command line.
Anatomy
Import all parts and piece them together.
<script setup lang="ts">import * as select from '@destyler/select'import { normalizeProps, useMachine } from '@destyler/vue'import { computed, useId } from 'vue'
const [state, send] = useMachine( select.machine({ id: useId(), }),)
const api = computed(() => select.connect(state.value, send, normalizeProps))</script>
<template> <button v-bind="api.getTriggerProps()"></button> <div v-bind="api.getPositionerProps()"> <ul v-bind="api.getContentProps()" > <li v-bind="api.getItemProps({ item: '0' })" > <span v-bind="api.getItemIndicatorProps({ item: '0' })"></span> </li> </ul> </div></template>import { normalizeProps, useMachine } from '@destyler/react'import * as select from '@destyler/select'import { useId } from 'react'
export default function Select() { const [state, send] = useMachine( select.machine({ id: useId(), }), )
const api = select.connect(state, send, normalizeProps)
return ( <> <button {...api.getTriggerProps()} ></button> <div {...api.getPositionerProps()}> <ul {...api.getContentProps()} > <li {...api.getItemProps({ item: '0' })} > <span {...api.getItemIndicatorProps({ item: '0' })} ></span> </li> </ul> </div> </> )}<script lang="ts"> import * as select from '@destyler/select' import { normalizeProps, useMachine } from '@destyler/svelte'
const id = $props.id()
const [state, send] = useMachine( select.machine({ id, }), )
const api = $derived(select.connect(state, send, normalizeProps))</script>
<button {...api.getTriggerProps()} ></button><div {...api.getPositionerProps()} > <ul {...api.getContentProps()} > <li {...api.getItemProps({ item:'0' })} > <span {...api.getItemIndicatorProps({ item:'0' })} ></span> </li> </ul></div>import * as select from '@destyler/select'import { normalizeProps, useMachine } from '@destyler/solid'import { createMemo, createUniqueId } from 'solid-js'
export default function Select() { const [state, send] = useMachine( select.machine({ id: createUniqueId(), }), )
const api = createMemo(() => select.connect(state, send, normalizeProps))
return ( <> <button {...api().getTriggerProps()}></button> <div {...api().getPositionerProps()}> <ul {...api().getContentProps()}> <li {...api().getItemProps({ item: '0' })} > <span {...api().getItemIndicatorProps({ item: '0' })}></span> </li> </ul> </div> </> )}Setting the initial value
To set the initial value of the select, pass the value property to the select destyler’s context.
The value property must be an array of strings.
If selecting a single value, pass an array with a single string.
const [state, send] = useMachine( select.machine({ value: ["vue"], }),)Selecting multiple values
To allow selecting multiple values, set the multiple property in the destyler’s context to true.
const [state, send] = useMachine( select.machine({ multiple: true, }),)Use custom object format
By default, the combobox collection expects an array of items with label and value properties.
To use a custom object format, pass the itemToString and itemToValue properties to the collection function.
-
itemToString— A function that returns the string representation of an item. Used to compare items when filtering. -
itemToValue— A function that returns the unique value of an item. -
itemToDisabled— A function that returns the disabled state of an item.
const collection = combobox.collection({ // custom object format items: [ { id: 1, fruit: "Vue", available: true, quantity: 10 }, { id: 2, fruit: "React", available: false, quantity: 5 }, { id: 3, fruit: "Svelte", available: true, quantity: 3 }, { id: 4, fruit: "Solid", available: false, quantity: 0 }, //... ], // convert item to string itemToString(item) { return item.fruit }, // convert item to value itemToValue(item) { return item.id }, // convert item to disabled state itemToDisabled(item) { return !item.available || item.quantity === 0 },})
// use the collectionconst [state, send] = useMachine( select.machine({ id: useId(), collection, }),)Usage within a form
To use select within a form, you’ll need to:
-
Pass the
nameproperty to the select destyler’s context -
Render a
hidden selectelement usingapi.getSelectProps()
<script setup lang="ts">import * as select from '@destyler/select'import { normalizeProps, useMachine } from '@destyler/vue'import { computed, useId } from 'vue'
const [state, send] = useMachine( select.machine({ id: useId(), name: "country", }),)
const api = computed(() => select.connect(state.value, send, normalizeProps))</script>
<template> <select v-bind="api.getHiddenSelectProps()"></select> <button v-bind="api.getTriggerProps()"></button> <div v-bind="api.getPositionerProps()"> <ul v-bind="api.getContentProps()" > <li v-bind="api.getItemProps({ item: '0' })" > <span v-bind="api.getItemIndicatorProps({ item: '0' })"></span> </li> </ul> </div></template>import { normalizeProps, useMachine } from '@destyler/react'import * as select from '@destyler/select'import { useId } from 'react'
export default function Select() { const [state, send] = useMachine( select.machine({ id: useId(), name: "country", }), )
const api = select.connect(state, send, normalizeProps)
return ( <> <select {...api.getHiddenSelectProps()}></select> <button {...api.getTriggerProps()} ></button> <div {...api.getPositionerProps()}> <ul {...api.getContentProps()} > <li {...api.getItemProps({ item: '0' })} > <span {...api.getItemIndicatorProps({ item: '0' })} ></span> </li> </ul> </div> </> )}<script lang="ts"> import * as select from '@destyler/select' import { normalizeProps, useMachine } from '@destyler/svelte'
const id = $props.id()
const [state, send] = useMachine( select.machine({ id, name: "country", }), )
const api = $derived(select.connect(state, send, normalizeProps))</script>
<select {...api.getHiddenSelectProps()}></select><button {...api.getTriggerProps()} ></button><div {...api.getPositionerProps()} > <ul {...api.getContentProps()} > <li {...api.getItemProps({ item:'0' })} > <span {...api.getItemIndicatorProps({ item:'0' })} ></span> </li> </ul></div>import * as select from '@destyler/select'import { normalizeProps, useMachine } from '@destyler/solid'import { createMemo, createUniqueId } from 'solid-js'
export default function Select() { const [state, send] = useMachine( select.machine({ id: createUniqueId(), name: "country", }), )
const api = createMemo(() => select.connect(state, send, normalizeProps))
return ( <> <select {...api().getHiddenSelectProps()}></select> <button {...api().getTriggerProps()}></button> <div {...api().getPositionerProps()}> <ul {...api().getContentProps()}> <li {...api().getItemProps({ item: '0' })} > <span {...api().getItemIndicatorProps({ item: '0' })}></span> </li> </ul> </div> </> )}Disabling the select
To disable the select, set the disabled property in the destyler’s context to true.
const [state, send] = useMachine( select.machine({ id: useId(), collection: select.collection({ items: countries, isItemDisabled(item) { return item.disabled }, }), }),)Close on select
This behaviour ensures that the menu is closed when an item is selected
and is true by default. It’s only concerned with when an item is selected
with pointer, space key or enter key. To disable the behaviour,
set the closeOnSelect property in the destyler’s context to false.
const [state, send] = useMachine( select.machine({ closeOnSelect: false, }),)Looping the keyboard navigation
When navigating with the select using the arrow down and up keys,
the select stops at the first and last options.
If you need want the navigation to loop back to the first or last option,
set the loop: true in the destyler’s context.
const [state, send] = useMachine( select.machine({ loop: true, }),)Listening for highlight changes
When an item is highlighted with the pointer or keyboard, use the onHighlightChange to listen for the change and do something with it.
const [state, send] = useMachine( select.machine({ onHighlightChange(details) { // details => { // highlightedValue: string | null, // highlightedItem: CollectionItem | null // } console.log(details) }, }),)Listening for selection changes
When an item is selected, use the onValueChange property to listen for the change and do something with it.
const [state, send] = useMachine( select.machine({ onValueChange(details) { // details => { value: string[], items: Item[] } console.log(details) }, }),)Listening for open and close events
When the select is opened or closed, the onOpenChange callback is called. You can listen for these events and do something with it.
const [state, send] = useMachine( select.machine({ onOpenChange(details) { // details => { open: boolean } console.log("Select opened") }, }),)Styling guide
Earlier, we mentioned that each Select part has a
data-partattribute added to them to select and style them in the DOM.
Open and closed state
When the select is open, the trigger and content is given a data-state attribute.
[data-part="trigger"][data-state="open"] { /* styles for open or closed state */}
[data-part="content"][data-state="open"] { /* styles for open or closed state */}Selected state
Items are given a data-state attribute, indicating whether they are selected.
[data-part="item"][data-state="checked"] { /* styles for selected or unselected state */}Highlighted state
When an item is highlighted, via keyboard navigation or pointer, it is given a data-highlighted attribute.
[data-part="item"][data-highlighted] { /* styles for highlighted state */}Invalid state
When the select is invalid, the label and trigger is given a data-invalid attribute.
[data-part="label"][data-invalid] { /* styles for invalid state */}
[data-part="trigger"][data-invalid] { /* styles for invalid state */}Disabled state
When the select is disabled, the trigger and label is given a data-disabled attribute.
[data-part="trigger"][data-disabled] { /* styles for disabled select state */}
[data-part="label"][data-disabled] { /* styles for disabled label state */}
[data-part="item"][data-disabled] { /* styles for disabled option state */}Empty state
When no option is selected, the trigger is given a data-placeholder-shown attribute.
[data-part="trigger"][data-placeholder-shown] { /* styles for empty select state */}Methods and Properties
Machine Context
The combobox machine exposes the following context properties:
ListCollection<any>Partial<{ root: string; content: string; control: string; trigger: string; clearTrigger: string; label: string; hiddenSelect: string; positioner: string; item: (id: string | number) => string; itemGroup: (id: string | number) => string; itemGroupLabel: (id: string | number) => string; }>stringstringbooleanbooleanbooleanbooleanboolean(details: HighlightChangeDetails<T>) => void(details: ValueChangeDetails<T>) => void(details: OpenChangeDetails) => voidPositioningOptionsstring[]stringbooleanbooleanbooleanboolean(details: ScrollToIndexDetails) => voidbooleanboolean"ltr" | "rtl"string() => ShadowRoot | Node | Document(event: PointerDownOutsideEvent) => void(event: FocusOutsideEvent) => void(event: InteractOutsideEvent) => voidMachine API
The select api exposes the following methods:
booleanbooleanbooleanstringV(value: string) => voidV[]booleanstring[]string(value: string) => void() => void(value: string[]) => void(value?: string) => void() => void(props: ItemProps<any>) => ItemState(open: boolean) => voidListCollection<V>(collection: ListCollection<V>) => void(options?: Partial<PositioningOptions>) => voidbooleanbooleanData Attributes
Root
data-scopedata-partdata-invaliddata-readonlyLabel
data-scopedata-partdata-disableddata-invaliddata-readonlyControl
data-scopedata-partdata-statedata-focusdata-disableddata-invalidValueText
data-scopedata-partdata-disableddata-invaliddata-focusTrigger
data-scopedata-partdata-statedata-disableddata-invaliddata-readonlydata-placementdata-placeholder-shownIndicator
data-scopedata-partdata-statedata-disableddata-invaliddata-readonlyItem
data-scopedata-partdata-valuedata-statedata-highlighteddata-disabledItemText
data-scopedata-partdata-statedata-disableddata-highlightedItemIndicator
data-scopedata-partdata-stateItemGroup
data-scopedata-partdata-disabledClearTrigger
data-scopedata-partdata-invalidContent
data-scopedata-partdata-statedata-placementdata-activedescendantAccessibility
Keyboard Interaction
SpaceEnterArrowDownArrowUpEscA-Z a-z