@openreachtech/furo-vue 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +2 -0
- package/README.md +2 -0
- package/docs/design-tokens.md +419 -0
- package/docs/furo-accordion.md +109 -0
- package/docs/furo-alert-dialog.md +107 -0
- package/docs/furo-attachment.md +133 -0
- package/docs/furo-autocomplete-field.md +719 -0
- package/docs/furo-avatar-group.md +87 -0
- package/docs/furo-avatar.md +101 -0
- package/docs/furo-badge.md +126 -0
- package/docs/furo-breadcrumb.md +89 -0
- package/docs/furo-button.md +263 -0
- package/docs/furo-checkbox.md +384 -0
- package/docs/furo-collapsible.md +104 -0
- package/docs/furo-control-block.md +102 -0
- package/docs/furo-date-picker.md +270 -0
- package/docs/furo-date-time-picker.md +110 -0
- package/docs/furo-dialog.md +88 -0
- package/docs/furo-drawer.md +122 -0
- package/docs/furo-dropdown-menu.md +298 -0
- package/docs/furo-editable-field.md +391 -0
- package/docs/furo-editor.md +304 -0
- package/docs/furo-email-field.md +93 -0
- package/docs/furo-empty-state.md +97 -0
- package/docs/furo-error-state.md +97 -0
- package/docs/furo-file-field.md +163 -0
- package/docs/furo-icon.md +288 -0
- package/docs/furo-number-field.md +397 -0
- package/docs/furo-pagination.md +101 -0
- package/docs/furo-password-field.md +75 -0
- package/docs/furo-popover.md +114 -0
- package/docs/furo-radio.md +125 -0
- package/docs/furo-scroll-area.md +112 -0
- package/docs/furo-search-field.md +94 -0
- package/docs/furo-section-header.md +135 -0
- package/docs/furo-select.md +443 -0
- package/docs/furo-separator.md +87 -0
- package/docs/furo-sidebar.md +178 -0
- package/docs/furo-skeleton.md +92 -0
- package/docs/furo-splitter.md +103 -0
- package/docs/furo-stepper.md +127 -0
- package/docs/furo-switch.md +107 -0
- package/docs/furo-table.md +233 -0
- package/docs/furo-tabs.md +173 -0
- package/docs/furo-text-field.md +249 -0
- package/docs/furo-textarea.md +214 -0
- package/docs/furo-time-field.md +276 -0
- package/docs/furo-toast.md +217 -0
- package/docs/furo-toggle-group.md +123 -0
- package/docs/furo-toggle.md +104 -0
- package/docs/furo-tool-bar.md +88 -0
- package/docs/furo-tooltip.md +107 -0
- package/lib/components/atoms/FuroButton/FuroButtonContext.js +8 -8
- package/lib/components/atoms/FuroCheckbox/FuroCheckboxContext.js +1 -1
- package/lib/components/atoms/FuroSwitch/FuroSwitchContext.js +1 -1
- package/lib/components/atoms/FuroToggle/FuroToggleContext.js +1 -1
- package/lib/components/molecules/FuroAccordion/FuroAccordionContext.js +1 -1
- package/lib/components/molecules/FuroCollapsible/FuroCollapsibleContext.js +1 -1
- package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenuContext.js +1 -2
- package/lib/components/molecules/FuroPopover/FuroPopoverContext.js +1 -1
- package/lib/components/molecules/FuroSelect/FuroSelect.vue +8 -6
- package/lib/components/molecules/FuroSelect/FuroSelectContext.js +35 -0
- package/lib/components/molecules/FuroSelect/FuroSelectOptionText.vue +40 -0
- package/lib/components/molecules/FuroSelect/FuroSelectOptionTextContext.js +275 -0
- package/lib/components/molecules/FuroSplitter/FuroSplitterContext.js +1 -1
- package/lib/components/molecules/FuroToggleGroup/FuroToggleGroupContext.js +1 -1
- package/lib/components/molecules/FuroTooltip/FuroTooltipContext.js +1 -1
- package/lib/components/organisms/FuroTable/FuroTableContext.js +3 -3
- package/lib/components/organisms/FuroToast/FuroToastContext.js +6 -2
- package/lib/components/organisms/FuroToast/ToastQueue.js +4 -4
- package/nuxt/index.js +9 -9
- package/package.json +19 -17
- package/public/furo-vue/components.json +1 -1
- package/types/generated/components/atoms/FuroButton/FuroButtonContext.d.ts +4 -4
- package/types/generated/components/molecules/FuroSelect/FuroSelect.vue.d.ts +23 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectContext.d.ts +14 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectOptionText.vue.d.ts +50 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectOptionTextContext.d.ts +141 -0
- package/types/generated/components/organisms/FuroTable/FuroTableContext.d.ts +2 -2
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
# `<FuroAutocompleteField>` Interface Specification
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`<FuroAutocompleteField>` is a **shipped** autocomplete molecule in the Furo-Vue component system. Its **public contract is Furo-defined**: application developers pass `parcel`, listen to Furo emits, and use documented slots — without importing the underlying primitive library.
|
|
6
|
+
|
|
7
|
+
Implementation uses a headless combobox primitive inside `lib/components/molecules/FuroAutocompleteField/`* only. That dependency is **not** part of the application API.
|
|
8
|
+
|
|
9
|
+
Contract style aligns with `[FuroTextField](./furo-text-field.md)` (Furo-first `parcel` + fallthrough where applicable):
|
|
10
|
+
|
|
11
|
+
- behavior via `:parcel` (`FuroAutocompleteFieldParcel`)
|
|
12
|
+
- HTML / form attributes via fallthrough (`name`, `required`, `id`, `aria-`*, `disabled`, …)
|
|
13
|
+
- Furo emits (`change-value`, `update:value`, `search-keyword`, …)
|
|
14
|
+
- `v-model:value` syncs when the parent controls `parcel.value`
|
|
15
|
+
|
|
16
|
+
**Public SFC props:** `parcel` (behavior) and `inputParcel` (attributes of the inner `<input role="combobox">`).
|
|
17
|
+
|
|
18
|
+
**Model shape:** the combobox stores **full option objects** (leaf rows from `parcel.options`), not only `option.value`. `@update:value` and `parcel.value` use that same shape (`FuroAutocompleteOption`, primitive leaf, or arrays thereof in multi mode).
|
|
19
|
+
|
|
20
|
+
**Input display text:** the input part uses `extractDisplayValue()` → `option.label` (or `String(option)` for primitive leaves). There is no public display callback prop.
|
|
21
|
+
|
|
22
|
+
`@change-value` fires on **committed gestures** (list selection, select-all toggle, clear) — not on every keystroke. Use `@search-keyword` for debounced parent-driven search.
|
|
23
|
+
|
|
24
|
+
Read `[FuroTextField](./furo-text-field.md)` for the shared `parcel` mental model.
|
|
25
|
+
|
|
26
|
+
### Abstraction boundary
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
| Layer | Responsibility |
|
|
30
|
+
| ------------------------------ | -------------------------------------------------------------------------- |
|
|
31
|
+
| Application | `parcel`, listeners, slots; replaces `parcel.options` for API-driven lists |
|
|
32
|
+
| Molecule (`FuroAutocompleteField`) | Maps parcel → the underlying combobox primitive; DOM, debounce, client filter, emits |
|
|
33
|
+
| Underlying primitive (maintainers) | Internal only; not part of the application API |
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
**Rules:**
|
|
37
|
+
|
|
38
|
+
1. Documented `parcel` keys are the **Furo contract**. The getter merges defaults, then `...props.parcel`, then the template spreads `v-bind="context.parcel"` onto the root (additional keys may reach the underlying primitive but are **not** supported for apps).
|
|
39
|
+
2. Do **not** use the legacy `triggerParcel` / `portalParcel` / `contentParcel` props — they are **not** on the SFC. The one part-level prop is `inputParcel`, which carries plain input attributes only (see [Accessible name and form wiring](#accessible-name-and-form-wiring)).
|
|
40
|
+
3. `FuroSelect` still uses the older primitive passthrough pattern; autocomplete is Furo-first.
|
|
41
|
+
|
|
42
|
+
**Source files:**
|
|
43
|
+
|
|
44
|
+
- `lib/components/molecules/FuroAutocompleteField/FuroAutocompleteField.vue`
|
|
45
|
+
- `lib/components/molecules/FuroAutocompleteField/FuroAutocompleteFieldContext.js`
|
|
46
|
+
- `lib/components/molecules/FuroAutocompleteField/AutocompleteEmitPayload.js`
|
|
47
|
+
- `app/shares/TimerClerk.js`
|
|
48
|
+
|
|
49
|
+
**Live samples:** `pages/samples/components/playground/FuroAutocompleteFieldPlayground/index.vue`
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Public API at a glance
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
| Area | Status | Notes |
|
|
57
|
+
| ----------------------------------------------------------- | ------------------- | ------------------------------------------------------------------------- |
|
|
58
|
+
| **SFC surface** | | |
|
|
59
|
+
| Public props `parcel` and `inputParcel` (+ attribute fallthrough) | **Implemented** | See `[FuroAutocompleteFieldParcel](#furoautocompletefieldparcel)` |
|
|
60
|
+
| `inputParcel` → inner `<input role="combobox">` (`id`, `name`, `aria-*`, `required`, …) | **Implemented** | Gives the combobox an accessible name; see [Accessible name and form wiring](#accessible-name-and-form-wiring) |
|
|
61
|
+
| **Selection & options** | | |
|
|
62
|
+
| Single + multi; groups, separators, option `icon` | **Implemented** | Model = full leaf option object(s), not bare `value` |
|
|
63
|
+
| `parcel.value` + `v-model:value` | **Implemented** | Watches `parcel.value` and fallthrough `value` |
|
|
64
|
+
| Client filter (`localCompare: true`) | **Implemented** | `filteredOptions()` + the primitive's `useFilter` collator |
|
|
65
|
+
| Parent-owned list (`localCompare: false`) | **Implemented** | Full `parcel.options`; parent filters (e.g. API) |
|
|
66
|
+
| `loading`, `invalid`, `disabled`, `multiple` | **Implemented** | |
|
|
67
|
+
| `showClear`, `showSelectAll`, `selectAllText`, `emptyText` | **Implemented** | Defaults merged in context `parcel` getter |
|
|
68
|
+
| **Search & popover** | | |
|
|
69
|
+
| Debounced `search-keyword` (`string`) | **Implemented** | `TimerClerk`; only while popover open; also `''` on clear |
|
|
70
|
+
| `parcel.searchKeyword` → input | **Partial** | One-way watch into `internalSearchKeywordRef`; no `update:search-keyword` |
|
|
71
|
+
| `v-model:search-keyword` | **Not implemented** | |
|
|
72
|
+
| Popover open/close | **Internal only** | `internalOpenPopoverRef`; keyword cleared when popover opens |
|
|
73
|
+
| `parcel.open` / `v-model:open` | **Not implemented** | Template `:open` overrides `parcel.open` from `v-bind` |
|
|
74
|
+
| **Emits** | | |
|
|
75
|
+
| `update:value`, `change-value` | **Implemented** | Select row, select-all, clear, **chip remove** |
|
|
76
|
+
| `search-keyword` | **Implemented** | Payload is a **string** (not `{ searchKeyword }`) |
|
|
77
|
+
| `update:search-keyword`, `update:open`, `commit-value` | **Not implemented** | Listed on SFC `emits` only; never fired from context |
|
|
78
|
+
| **Slots** | | |
|
|
79
|
+
| Documented slots (`option-text`, `group-label`, `empty`, …) | **Implemented** | No `input` / root `trigger` slots |
|
|
80
|
+
| `select-all` scoped `{ checked, indeterminate, disabled }` | **Not implemented** | Typedef only; slot has no scoped props wired |
|
|
81
|
+
| **Spec debt (not in runtime)** | | |
|
|
82
|
+
| Free-form (`allowFreeForm`, `commit-value` on Enter/blur) | **Not implemented** | |
|
|
83
|
+
| Closed `parcel` (reject unknown keys before the primitive) | **Not implemented** | Extra keys still spread to the root |
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Props
|
|
89
|
+
|
|
90
|
+
### Rule: `parcel` vs attributes
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
| Mechanism | When to use | Application sees |
|
|
94
|
+
| --------------------------------------- | -------------------------- | --------------------------------------------------------------- |
|
|
95
|
+
| `:parcel` | Furo autocomplete behavior | `FuroAutocompleteFieldParcel` (+ merged defaults) |
|
|
96
|
+
| `:input-parcel` | `id`, `name`, `aria-label`, `aria-labelledby`, `aria-describedby`, `required`, … | Bound onto the inner `<input role="combobox">` |
|
|
97
|
+
| Attributes on `<FuroAutocompleteField>` | e.g. `class`, `style` | Vue fallthrough to the root wrapper (default `inheritAttrs`); **not** the input |
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
> `**parcel` must be JSON-serializable** — no functions inside `parcel`.
|
|
101
|
+
|
|
102
|
+
```html
|
|
103
|
+
<FuroAutocompleteField
|
|
104
|
+
v-model:value="context.assigneeRef.value"
|
|
105
|
+
:parcel="{
|
|
106
|
+
placeholder: 'Search assignee',
|
|
107
|
+
options: context.assigneeOptions,
|
|
108
|
+
localCompare: false,
|
|
109
|
+
debounceMilliseconds: 300,
|
|
110
|
+
loading: context.statusReactive.isLoadingAssignees,
|
|
111
|
+
invalid: context.statusReactive.isInvalidAssignee,
|
|
112
|
+
}"
|
|
113
|
+
name="assignee"
|
|
114
|
+
@search-keyword="context.onSearchAssignees({ searchKeyword: $event })"
|
|
115
|
+
/>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> `**@search-keyword` payload is a `string**`, not `{ searchKeyword }`. Handlers may accept either for forward compatibility (see playground).
|
|
119
|
+
|
|
120
|
+
### Component props (public)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
| Prop | Type | Default | Description |
|
|
124
|
+
| -------- | --------------------------------------- | ------- | ------------------------------- |
|
|
125
|
+
| `parcel` | `FuroAutocompleteFieldParcel` or `null` | `null` | Furo behavior (see table below) |
|
|
126
|
+
| `inputParcel` | `FuroAutocompleteFieldInputParcel` or `null` | `null` | Attributes bound onto the inner `<input role="combobox">`. Mirrors `FuroSelect`'s `triggerParcel` |
|
|
127
|
+
|
|
128
|
+
### Accessible name and form wiring
|
|
129
|
+
|
|
130
|
+
The real control is the inner `<input role="combobox">`, not the root wrapper (a `div`). A `label[for]`, `aria-label`, `aria-labelledby` or `aria-describedby` on the wrapper never reaches it, so route them through `inputParcel`:
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<label id="assignee-label" for="assignee-input">Assignee</label>
|
|
134
|
+
<FuroAutocompleteField
|
|
135
|
+
v-model:value="context.assigneeRef.value"
|
|
136
|
+
:parcel="{ options: context.assigneeOptions }"
|
|
137
|
+
:input-parcel="{
|
|
138
|
+
id: 'assignee-input',
|
|
139
|
+
name: 'assignee',
|
|
140
|
+
'aria-describedby': 'assignee-hint',
|
|
141
|
+
required: true,
|
|
142
|
+
}"
|
|
143
|
+
/>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`FuroControlBlock` emits `<label :for="controlId">`; pass the same id as `inputParcel.id`.
|
|
147
|
+
|
|
148
|
+
`inputParcel` is `InputHTMLAttributes` without `placeholder` and `value`. The component keeps the placeholder (`parcel.placeholder`), the display text and the search keyword for itself, and the primitive keeps `role`, `aria-expanded`, `aria-controls` and `aria-autocomplete`.
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
### `FuroAcceptableValue`
|
|
152
|
+
|
|
153
|
+
Primitive or object carried in option `value` and payloads:
|
|
154
|
+
|
|
155
|
+
`string | number | bigint | Record<string, unknown> | null`
|
|
156
|
+
|
|
157
|
+
The **model** for the field is usually a `**FuroAutocompleteOption` leaf object** (or array of them), not bare `FuroAcceptableValue`, unless options are primitive leaves.
|
|
158
|
+
|
|
159
|
+
### `FuroAutocompleteFieldParcel`
|
|
160
|
+
|
|
161
|
+
Keys read in `FuroAutocompleteFieldContext` and/or merged in the `parcel` getter (defaults shown).
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
| Key | Type | Default (getter) | Description |
|
|
165
|
+
| ---------------------- | ---------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------- |
|
|
166
|
+
| `value` | `FuroAutocompleteOption | FuroAcceptableValue | Array<…> | null` | — | Controlled selection; synced to `internalValueRef` (with fallthrough `value` as fallback) |
|
|
167
|
+
| `options` | `Array<FuroAutocompleteOption | FuroAcceptableValue>` | `[]` | List rows (groups + leaves) |
|
|
168
|
+
| `optionLabel` | `string \| ((option) => string)` | uses `option.label` | Key name or function that selects each option's display label; also drives local filtering. See [Custom option shape](#custom-option-shape) |
|
|
169
|
+
| `optionValue` | `string \| ((option) => value)` | whole option object | Key name or function that selects each option's model value. When unset, `update:value` emits the whole option object (historical default); when set, it emits that key's value or the function's result |
|
|
170
|
+
| `maxVisibleChips` | `number` | `null` | Multi only: render only the first N selected chips, then a `+{count}` overflow indicator. `null` means no cap |
|
|
171
|
+
| `chipOverflow` | `'wrap' \| 'ellipsis'` | `'wrap'` | How selected chips behave when they exceed the trigger width: `wrap` flows onto multiple lines; `ellipsis` keeps a single line and truncates |
|
|
172
|
+
| `placeholder` | `string` | `'Select an option'` | Input placeholder |
|
|
173
|
+
| `loading` | `boolean` | `false` | Input spinner; `filteredOptions()` returns `[]`; suppresses empty state |
|
|
174
|
+
| `invalid` | `boolean` | `false` | Root `.invalid` class |
|
|
175
|
+
| `disabled` | `boolean` | `false` | Disables combobox |
|
|
176
|
+
| `textDirection` | `'toRight' | 'toLeft'` | `'toRight'` | Maps to the primitive's `dir` (`ltr` / `rtl`) |
|
|
177
|
+
| `multiple` | `boolean` | `false` | Multi-select; chips in anchor |
|
|
178
|
+
| `showClear` | `boolean` | `true` | Clear control shown when value selected |
|
|
179
|
+
| `localCompare` | `boolean` | `true` | `true` → client filter via `useFilter` collator; `false` → parent-owned `options` |
|
|
180
|
+
| `debounceMilliseconds` | `number` | `300` | Debounce for `search-keyword` (`TimerClerk`) |
|
|
181
|
+
| `openOnClick` | `boolean` | `true` | Passed through to the root |
|
|
182
|
+
| `showSelectAll` | `boolean` | `false` | Multi only; sentinel select-all row |
|
|
183
|
+
| `selectAllText` | `string` | `'Select All'` | Select-all row label |
|
|
184
|
+
| `emptyText` | `string` | `'No options.'` | Default `#empty` copy |
|
|
185
|
+
| `creatable` | `boolean` | `false` | Show a "create new" row when the trimmed keyword has no exact label/value match (case-insensitive) |
|
|
186
|
+
| `createOptionText` | `string` | `'Create "{keyword}"'` | Create-row text template; `{keyword}` is replaced with the trimmed keyword. Override for i18n / word order |
|
|
187
|
+
| `searchKeyword` | `string` | — | **Partial** — parent → internal input via watch; no `update:search-keyword` |
|
|
188
|
+
| `open` | `boolean` | — | **Not implemented** — template `:open` uses `internalOpenPopoverRef` |
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
Additional keys on `props.parcel` are spread to the root (primitive passthrough). Only the keys above are the **supported Furo contract**.
|
|
192
|
+
|
|
193
|
+
#### `FuroAutocompleteOption` (leaf / group)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
| Key | Type | Description |
|
|
197
|
+
| ----------- | --------------------- | ------------------------------------------------------------------- |
|
|
198
|
+
| `value` | `FuroAcceptableValue` | Required on leaf rows |
|
|
199
|
+
| `label` | `string` | Display, filter text, and input display via `extractDisplayValue()` |
|
|
200
|
+
| `disabled` | `boolean` | Row disabled; skipped in select-all when `true` |
|
|
201
|
+
| `separator` | `boolean` | Renders `#separator` after row |
|
|
202
|
+
| `icon` | `IconProps` | Optional `#option-icon` |
|
|
203
|
+
| `options` | `Array<…>` | Non-empty → **group** (`isGroupOption()`) |
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
Extra JSON-serializable keys (e.g. `department`) are allowed for slots and parent logic.
|
|
207
|
+
|
|
208
|
+
#### Option equality (`compareOption`)
|
|
209
|
+
|
|
210
|
+
Selection matching uses `compareOption()`:
|
|
211
|
+
|
|
212
|
+
- Object leaves with `value` key: `JSON.stringify(optionA.value) === JSON.stringify(optionB.value)`
|
|
213
|
+
- Otherwise: strict equality (`===`)
|
|
214
|
+
|
|
215
|
+
#### Custom option shape
|
|
216
|
+
|
|
217
|
+
By default the field reads `option.label` for display / filter text and stores
|
|
218
|
+
the **whole option object** as the model value. When options do not follow that
|
|
219
|
+
shape (for example `{ id, name }`), map them with `optionLabel` / `optionValue`:
|
|
220
|
+
|
|
221
|
+
- `optionLabel` — a key name (`'name'`) or a function (`option => option.name`)
|
|
222
|
+
that selects the **display / filter label** for each option. When unset,
|
|
223
|
+
`option.label` is used.
|
|
224
|
+
- `optionValue` — a key name (`'id'`) or a function that selects the **model
|
|
225
|
+
value**. When unset, `update:value` emits the whole option object (the
|
|
226
|
+
historical default); when set, it emits that key's value or the function's
|
|
227
|
+
result.
|
|
228
|
+
|
|
229
|
+
Both keys are backward-compatible: with neither set, behavior is exactly as
|
|
230
|
+
before (label = `option.label`, value = the whole option object).
|
|
231
|
+
|
|
232
|
+
```html
|
|
233
|
+
<FuroAutocompleteField
|
|
234
|
+
v-model:value="context.memberRef.value"
|
|
235
|
+
:parcel="{
|
|
236
|
+
placeholder: 'Search member',
|
|
237
|
+
optionLabel: 'name',
|
|
238
|
+
optionValue: 'id',
|
|
239
|
+
options: [
|
|
240
|
+
{ id: 50001, name: 'Ada Lovelace' },
|
|
241
|
+
{ id: 50002, name: 'Alan Turing' },
|
|
242
|
+
],
|
|
243
|
+
}"
|
|
244
|
+
@change-value="context.onChangeMember({ payload: $event })"
|
|
245
|
+
/>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### Chip overflow (multiple mode)
|
|
249
|
+
|
|
250
|
+
In `multiple` mode, `maxVisibleChips` caps how many selected chips render before
|
|
251
|
+
a `+{count}` overflow indicator; `chipOverflow` controls layout when chips
|
|
252
|
+
exceed the trigger width (`wrap` flows onto multiple lines, `ellipsis` keeps a
|
|
253
|
+
single line and truncates). Defaults are backward-compatible: no cap and `wrap`.
|
|
254
|
+
|
|
255
|
+
```html
|
|
256
|
+
<FuroAutocompleteField
|
|
257
|
+
v-model:value="context.selectedTagsRef.value"
|
|
258
|
+
:parcel="{
|
|
259
|
+
multiple: true,
|
|
260
|
+
maxVisibleChips: 3,
|
|
261
|
+
chipOverflow: 'ellipsis',
|
|
262
|
+
placeholder: 'Search tags',
|
|
263
|
+
options: context.tagOptions,
|
|
264
|
+
}"
|
|
265
|
+
@change-value="context.onChangeTags({ payload: $event })"
|
|
266
|
+
/>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### Unified options
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
| Shape | Rendered as |
|
|
273
|
+
| ---------------------------- | --------------------------------------- |
|
|
274
|
+
| `option.options?.length > 0` | Group (group part + `group-label`) |
|
|
275
|
+
| Otherwise | Leaf (group part with `as-child`) |
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
List template iterates `context.filteredOptions()` — top-level entries after client filter (groups stay whole; children filtered inside `filteredOptions()` for object groups).
|
|
279
|
+
|
|
280
|
+
#### Disabled precedence
|
|
281
|
+
|
|
282
|
+
- Leaf: `option.disabled`
|
|
283
|
+
- Group child: `shouldDisableOption({ option: childOption })` checks the **child** only
|
|
284
|
+
- Root: `parcel.disabled` on the root
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Filtering
|
|
289
|
+
|
|
290
|
+
List visibility is computed in `filteredOptions()` (context).
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
| `parcel.localCompare` | `parcel.loading` | Visible list |
|
|
294
|
+
| --------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
295
|
+
| `true` (default) | `false` | Subset of `parcel.options` where any group child / leaf matches `internalSearchKeyword` (trimmed) via `useFilter().contains` on `label` and `String(value)` |
|
|
296
|
+
| `true` | `true` | `[]` (empty list while loading) |
|
|
297
|
+
| `false` | any | Full `parcel.options` (parent filters, e.g. API) |
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
### `hasEmptyOptions()`
|
|
301
|
+
|
|
302
|
+
```text
|
|
303
|
+
filteredOptions().length === 0 && !loading
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
While `loading`, `#empty` is hidden (`hasEmptyOptions()` is false) and `#loading` can show inside the viewport.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Search & debounce
|
|
311
|
+
|
|
312
|
+
Uses `[TimerClerk](../app/shares/TimerClerk.js)` in `setupComponent()` (`debounceMilliseconds` from merged parcel, default **300**).
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
| Step | Behavior |
|
|
316
|
+
| ------------- | -------------------------------------------------------------------------------- |
|
|
317
|
+
| User types | `onUpdateSearchKeyword` sets `internalSearchKeywordRef` |
|
|
318
|
+
| Debounce | `startTimer({ searchKeyword })` **only if** `internalOpenPopoverRef` is **open** |
|
|
319
|
+
| Tick | `emit('search-keyword', searchKeyword)` — **string** argument |
|
|
320
|
+
| Popover opens | `onUpdateOpenPopover({ isOpen: true })` resets `internalSearchKeyword` to `''` |
|
|
321
|
+
| Clear button | Also `emit('search-keyword', '')` |
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
**Not implemented:** `emit('update:search-keyword')`, controlled `parcel.searchKeyword`.
|
|
325
|
+
|
|
326
|
+
```text
|
|
327
|
+
Input change (popover open)
|
|
328
|
+
→ internalSearchKeywordRef
|
|
329
|
+
→ TimerClerk.startTimer
|
|
330
|
+
→ search-keyword "<string>"
|
|
331
|
+
→ parent updates parcel.options + parcel.loading
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Slots
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
| Slot | Scoped props | Default |
|
|
340
|
+
| ------------------ | ------------------------------------------------ | -------------------------------------------------------------------------------------- |
|
|
341
|
+
| `trigger-left` | — | Optional leading anchor content |
|
|
342
|
+
| `trigger-right` | — | Caret icon inside the trigger part |
|
|
343
|
+
| `cancel` | — | Clear icon in the clear control |
|
|
344
|
+
| `content-before` | — | Before viewport |
|
|
345
|
+
| `content-after` | — | After viewport |
|
|
346
|
+
| `select-all` | — | Select-all label text only (scoped `checked` / `indeterminate` / `disabled` not wired) |
|
|
347
|
+
| `group` | `group`, `groupIndex` | Group + children |
|
|
348
|
+
| `group-label` | `group`, `groupIndex` | Group header text |
|
|
349
|
+
| `option` | `option`, `optionIndex`, `group?`, `groupIndex?` | Row + indicator |
|
|
350
|
+
| `option-icon` | `option`, `optionIndex` | Icon when `option.icon` set |
|
|
351
|
+
| `option-text` | `option`, `optionIndex` | Label text |
|
|
352
|
+
| `option-indicator` | `option`, `optionIndex` | Check icon |
|
|
353
|
+
| `separator` | `option`, `optionIndex` | the separator part |
|
|
354
|
+
| `empty` | `searchKeyword`, `loading` | `emptyText` |
|
|
355
|
+
| `loading` | `searchKeyword` | Spinner in list |
|
|
356
|
+
| `create-option` | `searchKeyword` | `createOptionText` with `{keyword}` replaced — shown only when `creatable` and no exact match |
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
**Not present:** `input`, `trigger` (use `trigger-left` / `trigger-right`).
|
|
360
|
+
|
|
361
|
+
The input part is always rendered in the anchor (not slot-replaceable).
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Emits
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
| Event | Payload | When | Implemented |
|
|
369
|
+
| ----------------------- | ------------------------------- | ----------------------------------------------------- | ----------- |
|
|
370
|
+
| `change-value` | `AutocompleteEmitPayload` | Select option, select-all, clear | Yes |
|
|
371
|
+
| `update:value` | Option object, array, or `null` | Same as above | Yes |
|
|
372
|
+
| `search-keyword` | `string` | After debounce while popover open; also `''` on clear | Yes |
|
|
373
|
+
| `update:search-keyword` | `string` | — | **No** |
|
|
374
|
+
| `update:open` | `boolean` | — | **No** |
|
|
375
|
+
| `commit-value` | `AutocompleteEmitPayload` | — | **No** |
|
|
376
|
+
| `create-option` | `AutocompleteEmitPayload` | Select the create row (`creatable`); read the typed value via `payload.extractTrimmedSearchKeyword()` | Yes |
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
List selection closes the popover and clears `internalSearchKeyword`. Multi toggle on a row updates value without separate commit emit.
|
|
380
|
+
|
|
381
|
+
### Creatable (parent-owns)
|
|
382
|
+
|
|
383
|
+
When `parcel.creatable` is `true` and the trimmed keyword has no exact label/value match, a create row appears (text from `createOptionText`, `{keyword}` interpolated; override the string or the `create-option` slot for i18n). Selecting it emits **`create-option`** with an `AutocompleteEmitPayload`, then clears the keyword and closes the popover. The field never mutates its own `options`: the **parent owns** appending the new option to `parcel.options` and to the model.
|
|
384
|
+
|
|
385
|
+
```js
|
|
386
|
+
onCreateOption ({
|
|
387
|
+
payload,
|
|
388
|
+
}) {
|
|
389
|
+
const label = payload.extractTrimmedSearchKeyword()
|
|
390
|
+
|
|
391
|
+
if (!label) {
|
|
392
|
+
return
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const newOption = {
|
|
396
|
+
value: label,
|
|
397
|
+
label,
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
this.tagsReactive.options = [
|
|
401
|
+
...this.tagsReactive.options,
|
|
402
|
+
newOption,
|
|
403
|
+
]
|
|
404
|
+
this.tagsReactive.selectedValue = [
|
|
405
|
+
...this.tagsReactive.selectedValue,
|
|
406
|
+
newOption,
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
```html
|
|
412
|
+
@create-option="context.onCreateOption({ payload: $event })"
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Parent handler style
|
|
416
|
+
|
|
417
|
+
```js
|
|
418
|
+
onChangeValue ({
|
|
419
|
+
payload,
|
|
420
|
+
}) {
|
|
421
|
+
this.statusReactive.isInvalidAssignee = payload.hasEmptyValue()
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
onSearchAssignees ({
|
|
425
|
+
searchKeyword,
|
|
426
|
+
}) {
|
|
427
|
+
this.fetchAssignees({ searchKeyword })
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
```html
|
|
432
|
+
@search-keyword="context.onSearchAssignees({ searchKeyword: $event })"
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## `AutocompleteEmitPayload` API
|
|
438
|
+
|
|
439
|
+
`AutocompleteEmitPayload` extends `[BaseEmitPayload](../lib/emit-payload/BaseEmitPayload.js)`. Built from `CustomEvent` `detail` on selection/clear.
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
| Member | Type | Description |
|
|
443
|
+
| ------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- |
|
|
444
|
+
| `searchKeyword` | `string` | From `detail.searchKeyword` |
|
|
445
|
+
| `controlElement` | `HTMLElement | null` | `detail.target` |
|
|
446
|
+
| `selectedOption` | `FuroAutocompleteOption | FuroAcceptableValue | null` | `detail.selectedOption` |
|
|
447
|
+
| `selectedIndex` | `number | null` | `detail.selectedIndex` |
|
|
448
|
+
| `selectedOptions` | `Array<…>` | `detail.selectedOptions` |
|
|
449
|
+
| `selectedGroupIndex` | `number | null` | `detail.selectedGroupIndex` |
|
|
450
|
+
| `extractValue({ option })` | `FuroAcceptableValue` | `option.value` if object with `value`, else option |
|
|
451
|
+
| `extractValues()` | `Array<FuroAcceptableValue>` | Maps `selectedOptions` through `extractValue` |
|
|
452
|
+
| `hasEmptyValue()` | `boolean` | `extractValue()` is `null` or `extractValues()` empty |
|
|
453
|
+
| `hasSelectedValue()` | `boolean` | Negation of `hasEmptyValue()` |
|
|
454
|
+
| `extractTrimmedSearchKeyword()` | `string` | Trimmed `searchKeyword` |
|
|
455
|
+
| `matchesOption({ value })` | `boolean` | Strict equality of `extractValue` results |
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
**Not on class (spec debt):** `value`, `selectedIndices`, `selectedGroupIndices`, `isFreeFormCommit()`.
|
|
459
|
+
|
|
460
|
+
Use `payload.extractValue()` / `payload.extractValues()` for primitive `value` fields inside options.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## `v-model` and state handling
|
|
465
|
+
|
|
466
|
+
```html
|
|
467
|
+
<FuroAutocompleteField
|
|
468
|
+
v-model:value="context.assigneeRef.value"
|
|
469
|
+
:parcel="{
|
|
470
|
+
value: context.assigneeRef.value,
|
|
471
|
+
options: context.assigneeOptions,
|
|
472
|
+
placeholder: 'Search…',
|
|
473
|
+
}"
|
|
474
|
+
/>
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
| Binding | Implemented | Notes |
|
|
479
|
+
| ------------------------ | ----------- | --------------------------------------------------------------------------- |
|
|
480
|
+
| `v-model:value` | **Yes** | Watches `parcel.value` and fallthrough `value`; emits full option object(s) |
|
|
481
|
+
| `v-model:search-keyword` | **No** | `parcel.searchKeyword` syncs in only; no `update:search-keyword` |
|
|
482
|
+
| `v-model:open` | **No** | `internalOpenPopoverRef` bound on root; `parcel.open` ignored |
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
### State matrix
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
| State | Source |
|
|
489
|
+
| ----------------- | ------------------------------------------------------------------------ |
|
|
490
|
+
| Selected value(s) | `parcel.value` / `@update:value` |
|
|
491
|
+
| Filter string | `internalSearchKeywordRef`; optional one-way from `parcel.searchKeyword` |
|
|
492
|
+
| Popover open | `internalOpenPopoverRef` only (not `parcel.open`) |
|
|
493
|
+
| Disabled | `parcel.disabled` + fallthrough |
|
|
494
|
+
| Visible options | `filteredOptions()` from `parcel.options` + `localCompare` + `loading` |
|
|
495
|
+
| Loading UI | `parcel.loading` |
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Multiple selection + select all
|
|
501
|
+
|
|
502
|
+
Set `parcel.multiple: true`. Model is an **array of option objects** (can include primitive leaves if options are primitives).
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
| Topic | Behavior |
|
|
506
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
507
|
+
| `update:value` | Full array (or `[]` when cleared / deselect all) |
|
|
508
|
+
| `change-value` | Same gesture; payload lists current selection in `detail` |
|
|
509
|
+
| Row click | Toggles membership via `compareOption()` |
|
|
510
|
+
| Select all | `showSelectAll: true`; value `FURO_AUTOCOMPLETE_FIELD_SELECT_ALL_VALUE` (`__furo_autocomplete_field_select_all__`) |
|
|
511
|
+
| Select-all action | Toggles all non-disabled leaves from flattened `options`; closes popover |
|
|
512
|
+
| Chip remove | `onRemoveOption` emits `update:value` + `change-value` with updated array |
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Internal implementation (maintainers only)
|
|
518
|
+
|
|
519
|
+
### Template composition
|
|
520
|
+
|
|
521
|
+
```text
|
|
522
|
+
Root (v-bind parcel, model-value, :open internal)
|
|
523
|
+
Anchor.trigger
|
|
524
|
+
[trigger-left]
|
|
525
|
+
[multiple chips]
|
|
526
|
+
Input (display-value → extractDisplayValue / label)
|
|
527
|
+
Cancel? / loading icon / Trigger [trigger-right]
|
|
528
|
+
Portal
|
|
529
|
+
Content
|
|
530
|
+
[content-before]
|
|
531
|
+
Viewport
|
|
532
|
+
select-all option?
|
|
533
|
+
filteredOptions → groups / leaves / slots
|
|
534
|
+
Empty?
|
|
535
|
+
loading div?
|
|
536
|
+
[content-after]
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Primitive mapping notes
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
| Furo | Underlying primitive / behavior |
|
|
543
|
+
| ---------------------- | ------------------------------------------ |
|
|
544
|
+
| `textDirection` | `dir` `ltr` / `rtl` |
|
|
545
|
+
| `localCompare` | Furo-side filtering in `filteredOptions()` |
|
|
546
|
+
| `debounceMilliseconds` | `TimerClerk` only (not a primitive prop) |
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## Accessibility requirements
|
|
552
|
+
|
|
553
|
+
- Provide a visible label (`<label for>`, `aria-label`, or `aria-labelledby`).
|
|
554
|
+
- Set `textValue` on options when slot content is not plain text (underlying-primitive a11y; **filter still uses `label` / `value` in context**).
|
|
555
|
+
- Combobox roles delegated to the underlying primitive ([combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/)).
|
|
556
|
+
- `name` on component for forms; multi-value serialization is application-defined.
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## Styling convention
|
|
561
|
+
|
|
562
|
+
Styles in SFC `@layer furo`:
|
|
563
|
+
|
|
564
|
+
- Root: `.unit-form-control.autocomplete`
|
|
565
|
+
- Anchor: `.trigger`, `.input`, `.multiple`, `.trigger-right`
|
|
566
|
+
- Content: `.unit-autocomplete-content`, `.viewport`, `.group`, `.option`, `.empty`, `.loading`
|
|
567
|
+
- Tokens: field atoms (`--color-input`, `--size-input-height`, …)
|
|
568
|
+
|
|
569
|
+
Use `data-highlighted`, `data-disabled`, `data-state` from the underlying primitive for state styling.
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
## Examples
|
|
574
|
+
|
|
575
|
+
### Basic (controlled, client filter)
|
|
576
|
+
|
|
577
|
+
```html
|
|
578
|
+
<FuroAutocompleteField
|
|
579
|
+
v-model:value="context.countryRef.value"
|
|
580
|
+
:parcel="{
|
|
581
|
+
placeholder: 'Search country',
|
|
582
|
+
options: [
|
|
583
|
+
{ value: 'JP', label: 'Japan' },
|
|
584
|
+
{ value: 'VN', label: 'Vietnam' },
|
|
585
|
+
{ value: 'US', label: 'United States' },
|
|
586
|
+
],
|
|
587
|
+
invalid: context.statusReactive.isInvalidCountry,
|
|
588
|
+
}"
|
|
589
|
+
name="country"
|
|
590
|
+
@change-value="context.onChangeCountry({ payload: $event })"
|
|
591
|
+
/>
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Grouped options
|
|
595
|
+
|
|
596
|
+
```html
|
|
597
|
+
<FuroAutocompleteField
|
|
598
|
+
:parcel="{
|
|
599
|
+
placeholder: 'Search fruit',
|
|
600
|
+
options: [
|
|
601
|
+
{
|
|
602
|
+
label: 'Citrus',
|
|
603
|
+
options: [
|
|
604
|
+
{ value: 'orange', label: 'Orange' },
|
|
605
|
+
{ value: 'lemon', label: 'Lemon' },
|
|
606
|
+
],
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
}"
|
|
610
|
+
/>
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### API-driven options (`localCompare: false`)
|
|
614
|
+
|
|
615
|
+
```html
|
|
616
|
+
<FuroAutocompleteField
|
|
617
|
+
v-model:value="context.userRef.value"
|
|
618
|
+
:parcel="{
|
|
619
|
+
debounceMilliseconds: 300,
|
|
620
|
+
loading: context.statusReactive.isSearchingUsers,
|
|
621
|
+
localCompare: false,
|
|
622
|
+
options: context.userOptions,
|
|
623
|
+
placeholder: 'Search users',
|
|
624
|
+
}"
|
|
625
|
+
@search-keyword="context.fetchUsers({ searchKeyword: $event })"
|
|
626
|
+
/>
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### Object-valued options (model = full option row)
|
|
630
|
+
|
|
631
|
+
```html
|
|
632
|
+
<FuroAutocompleteField
|
|
633
|
+
v-model:value="context.personRef.value"
|
|
634
|
+
:parcel="{
|
|
635
|
+
options: context.peopleOptions,
|
|
636
|
+
placeholder: 'Search person',
|
|
637
|
+
}"
|
|
638
|
+
@update:value="context.personRef.value = $event"
|
|
639
|
+
/>
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
```js
|
|
643
|
+
// peopleOptions example: { value: { id: 50001, name: 'Ada' }, label: 'Ada' }
|
|
644
|
+
// personRef holds the selected option object; use option.label for display (built-in)
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### Multi-select + select all
|
|
648
|
+
|
|
649
|
+
```html
|
|
650
|
+
<FuroAutocompleteField
|
|
651
|
+
v-model:value="context.selectedTagsRef.value"
|
|
652
|
+
:parcel="{
|
|
653
|
+
multiple: true,
|
|
654
|
+
showSelectAll: true,
|
|
655
|
+
selectAllText: 'Select all tags',
|
|
656
|
+
placeholder: 'Search tags',
|
|
657
|
+
options: context.tagOptions,
|
|
658
|
+
}"
|
|
659
|
+
@change-value="context.onChangeTags({ payload: $event })"
|
|
660
|
+
/>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Custom option slot
|
|
664
|
+
|
|
665
|
+
```html
|
|
666
|
+
<FuroAutocompleteField
|
|
667
|
+
:parcel="{
|
|
668
|
+
placeholder: 'Search employee',
|
|
669
|
+
options: context.employeeOptions,
|
|
670
|
+
}"
|
|
671
|
+
>
|
|
672
|
+
<template #option-text="{ option }">
|
|
673
|
+
<strong>{{ option.label }}</strong>
|
|
674
|
+
<small>{{ option.department }}</small>
|
|
675
|
+
</template>
|
|
676
|
+
</FuroAutocompleteField>
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
## Implementation status
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
| Deliverable | Status |
|
|
685
|
+
| --------------------------------------------------------- | ----------------- |
|
|
686
|
+
| `FuroAutocompleteField.vue` + context + payload | Shipped |
|
|
687
|
+
| Public prop: `parcel` only | Shipped |
|
|
688
|
+
| Playground `FuroAutocompleteFieldPlayground` | Shipped |
|
|
689
|
+
| Unit tests `FuroAutocompleteFieldContext.js` (jsdom) | Shipped |
|
|
690
|
+
| `parcel.searchKeyword` one-way + `v-model:search-keyword` | Partial / pending |
|
|
691
|
+
| `v-model:open` / controlled `parcel.open` | Pending |
|
|
692
|
+
| Free-form + `commit-value` | Pending |
|
|
693
|
+
| Emit on chip remove | Shipped |
|
|
694
|
+
| Closed `parcel` (strip unknown primitive keys) | Pending |
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## Future / spec debt
|
|
700
|
+
|
|
701
|
+
Planned items **not** in runtime yet:
|
|
702
|
+
|
|
703
|
+
- `allowFreeForm`, `commit-value`, `generateFreeFormOptionFromKeyword`
|
|
704
|
+
- `resetSearchKeywordOnBlur`, `resetSearchKeywordOnSelect`, `openOnFocus`, `highlightOnHover`, `hideContentWhenEmpty`
|
|
705
|
+
- `update:search-keyword`, `update:open`, controlled `parcel.searchKeyword` / `parcel.open`
|
|
706
|
+
- `select-all` slot scoped `{ checked, indeterminate, disabled }`
|
|
707
|
+
- `AutocompleteEmitPayload.value`, `isFreeFormCommit()`, index arrays
|
|
708
|
+
- Remove pass-through of unknown keys on `v-bind="context.parcel"`
|
|
709
|
+
|
|
710
|
+
---
|
|
711
|
+
|
|
712
|
+
## Known gaps and follow-ups
|
|
713
|
+
|
|
714
|
+
- `**search-keyword` type:** runtime emits `string`; JSDoc on `EMIT_EVENT_HASH` still types `payload: { searchKeyword: string }` — align in code later.
|
|
715
|
+
- `**parcel.searchKeyword`:** inbound sync only; typing in the field does not emit `update:search-keyword` for parents.
|
|
716
|
+
- **Group `disabled`:** not propagated to children in `shouldDisableOption`.
|
|
717
|
+
- **Select-all:** no indeterminate UI state; toggle is all-or-none of enabled leaves.
|
|
718
|
+
- **API race:** parent should cancel in-flight fetches when a new keyword fires.
|
|
719
|
+
- **Form submit with `multiple`:** native `name` may not match array serialization expectations.
|