fluentui-svelte 0.5.0 → 0.6.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/dist/components/dialog/dialog-actions.svelte +17 -2
- package/dist/components/dialog/dialog-content.svelte +2 -2
- package/dist/components/dialog/dialog-content.svelte.d.ts +1 -1
- package/dist/components/dialog/dialog-surface.svelte +32 -3
- package/dist/components/dialog/dialog-title.svelte +23 -6
- package/dist/components/dialog/dialog-trigger.svelte +8 -2
- package/dist/components/dialog/dialog-trigger.svelte.d.ts +1 -1
- package/dist/components/dialog/docs/llms.md +2 -2
- package/dist/components/dialog/types.d.ts +6 -2
- package/dist/components/dialog/utils.d.ts +2 -0
- package/dist/components/dialog/utils.js +1 -0
- package/dist/components/rating/docs/llms.md +110 -0
- package/dist/components/rating/rating.svelte +210 -0
- package/dist/components/rating/rating.svelte.d.ts +19 -0
- package/dist/components/rating/types.d.ts +44 -0
- package/dist/components/rating/types.js +1 -0
- package/dist/components/tag/docs/llms.md +383 -0
- package/dist/components/tag/tag-group.d.ts +2 -0
- package/dist/components/tag/tag-group.js +2 -0
- package/dist/components/tag/tag-group.svelte +117 -0
- package/dist/components/tag/tag-group.svelte.d.ts +20 -0
- package/dist/components/tag/tag.svelte +458 -0
- package/dist/components/tag/tag.svelte.d.ts +19 -0
- package/dist/components/tag/types.d.ts +135 -0
- package/dist/components/tag/types.js +1 -0
- package/dist/components/tag-picker/docs/llms.md +292 -0
- package/dist/components/tag-picker/option-group.svelte +38 -0
- package/dist/components/tag-picker/option-group.svelte.d.ts +4 -0
- package/dist/components/tag-picker/option.svelte +71 -0
- package/dist/components/tag-picker/option.svelte.d.ts +4 -0
- package/dist/components/tag-picker/tag-picker.d.ts +2 -0
- package/dist/components/tag-picker/tag-picker.js +2 -0
- package/dist/components/tag-picker/tag-picker.svelte +644 -0
- package/dist/components/tag-picker/tag-picker.svelte.d.ts +21 -0
- package/dist/components/tag-picker/types.d.ts +122 -0
- package/dist/components/tag-picker/types.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/internal/constants.d.ts +0 -4
- package/dist/internal/constants.js +0 -4002
- package/package.json +2 -2
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# TagPicker
|
|
2
|
+
|
|
3
|
+
A TagPicker combines a text field and a dropdown, giving people a way to choose several options from a list or to enter their own choice. Every choice becomes a dismissible `Tag` shown in the control. It is made of `TagPicker`, `TagPickerOption` and `TagPickerOptionGroup`.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```svelte
|
|
8
|
+
<script>
|
|
9
|
+
import { TagPicker, TagPickerOption } from 'fluentui-svelte';
|
|
10
|
+
|
|
11
|
+
const options = ['John Doe', 'Jane Doe', 'Max Mustermann'];
|
|
12
|
+
|
|
13
|
+
let selectedOptions = $state([]);
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<TagPicker
|
|
17
|
+
bind:selectedOptions
|
|
18
|
+
inputProps={{ 'aria-label': 'Select Employees' }}
|
|
19
|
+
tagGroupProps={{ 'aria-label': 'Selected Employees' }}
|
|
20
|
+
>
|
|
21
|
+
{#each options as option (option)}
|
|
22
|
+
<TagPickerOption value={option}>{option}</TagPickerOption>
|
|
23
|
+
{/each}
|
|
24
|
+
</TagPicker>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## TagPicker API
|
|
28
|
+
|
|
29
|
+
- `selectedOptions` holds the values of the chosen options. It is bindable, and every value shows as a tag.
|
|
30
|
+
- Selecting an option adds a tag and clears the text. The option is not offered again until its tag is removed.
|
|
31
|
+
- The options are filtered by what is typed in the input: a case-insensitive substring of the `text` of the option (its `value` when there is no `text`). The first match is highlighted.
|
|
32
|
+
- A multiple picker keeps the list open after each choice. With `single` a new choice replaces the tag and closes the list.
|
|
33
|
+
- Give the input an accessible name with `inputProps={{ 'aria-label': '...' }}`, and the group of tags one with `tagGroupProps`.
|
|
34
|
+
- Pressing Backspace in an empty input removes the last tag. Tell your users about it.
|
|
35
|
+
- A tag shows the `text` of its option, and keeps it once the option leaves the screen. This holds for a tag preselected through `selectedOptions` too, as long as the options are children of the TagPicker. A value that no option has is shown as it is, and so is every value during server-side rendering. To control what a tag says, render the tags yourself with the `tag` snippet.
|
|
36
|
+
- Do not use an interactive `Tag` inside a TagPicker.
|
|
37
|
+
- Keys: ArrowDown and ArrowUp open the list and move through the options, Enter chooses the option under the cursor, Escape and Tab close the list, Home and End move the caret of the input.
|
|
38
|
+
|
|
39
|
+
## Examples
|
|
40
|
+
|
|
41
|
+
### Default
|
|
42
|
+
|
|
43
|
+
The `tag` snippet renders the tag of a chosen option, here with an `Avatar` as its media. Render a `Tag` with the given `value` and `role="listitem"`.
|
|
44
|
+
|
|
45
|
+
```svelte
|
|
46
|
+
<script>
|
|
47
|
+
let selectedOptions = $state([]);
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
{#snippet tag({ value, text })}
|
|
51
|
+
{#snippet media()}
|
|
52
|
+
<Avatar aria-hidden="true" name={text} size={16} color="colorful" />
|
|
53
|
+
{/snippet}
|
|
54
|
+
<Tag role="listitem" {value} {media} title={text}>{text}</Tag>
|
|
55
|
+
{/snippet}
|
|
56
|
+
|
|
57
|
+
<TagPicker
|
|
58
|
+
bind:selectedOptions
|
|
59
|
+
{tag}
|
|
60
|
+
inputProps={{ 'aria-label': 'Select Employees' }}
|
|
61
|
+
tagGroupProps={{ 'aria-label': 'Selected Employees' }}
|
|
62
|
+
>
|
|
63
|
+
{#each options as option (option)}
|
|
64
|
+
<TagPickerOption value={option}>
|
|
65
|
+
<Avatar aria-hidden="true" shape="square" name={option} size={20} color="colorful" />
|
|
66
|
+
{option}
|
|
67
|
+
</TagPickerOption>
|
|
68
|
+
{/each}
|
|
69
|
+
</TagPicker>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Button
|
|
73
|
+
|
|
74
|
+
Make the input read only with `inputProps={{ readonly: true }}` to use the TagPicker as a button that opens the list.
|
|
75
|
+
|
|
76
|
+
```svelte
|
|
77
|
+
<TagPicker
|
|
78
|
+
bind:selectedOptions
|
|
79
|
+
placeholder="Select employees"
|
|
80
|
+
inputProps={{ 'aria-label': 'Select Employees', readonly: true }}
|
|
81
|
+
>
|
|
82
|
+
{#each options as option (option)}
|
|
83
|
+
<TagPickerOption value={option}>{option}</TagPickerOption>
|
|
84
|
+
{/each}
|
|
85
|
+
</TagPicker>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Disabled
|
|
89
|
+
|
|
90
|
+
A disabled TagPicker cannot be opened, typed in or changed, and its tags cannot be dismissed.
|
|
91
|
+
|
|
92
|
+
```svelte
|
|
93
|
+
<TagPicker disabled selectedOptions={[options[0]]} inputProps={{ 'aria-label': 'Select Employees' }}>
|
|
94
|
+
{#each options as option (option)}
|
|
95
|
+
<TagPickerOption value={option}>{option}</TagPickerOption>
|
|
96
|
+
{/each}
|
|
97
|
+
</TagPicker>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Expand icon
|
|
101
|
+
|
|
102
|
+
Replace the dropdown arrow with `expandIcon`, a component or a snippet, or remove it by passing `null`.
|
|
103
|
+
|
|
104
|
+
```svelte
|
|
105
|
+
<script>
|
|
106
|
+
import { ArrowDownFilled } from 'fluentui-icons-svelte';
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<!-- A custom icon -->
|
|
110
|
+
<TagPicker bind:selectedOptions expandIcon={ArrowDownFilled} inputProps={{ 'aria-label': 'Select Employees' }}>
|
|
111
|
+
...
|
|
112
|
+
</TagPicker>
|
|
113
|
+
|
|
114
|
+
<!-- No icon -->
|
|
115
|
+
<TagPicker bind:selectedOptions expandIcon={null} inputProps={{ 'aria-label': 'Select Employees' }}>...</TagPicker>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Filtering
|
|
119
|
+
|
|
120
|
+
The options are filtered as the user types, and the first match is highlighted so that Enter chooses it. `notFoundText` is shown when no option is left.
|
|
121
|
+
|
|
122
|
+
```svelte
|
|
123
|
+
<TagPicker
|
|
124
|
+
bind:selectedOptions
|
|
125
|
+
notFoundText="We couldn't find any matches"
|
|
126
|
+
inputProps={{ 'aria-label': 'Select Employees' }}
|
|
127
|
+
>
|
|
128
|
+
{#each options as option (option)}
|
|
129
|
+
<TagPickerOption value={option}>{option}</TagPickerOption>
|
|
130
|
+
{/each}
|
|
131
|
+
</TagPicker>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Grouped
|
|
135
|
+
|
|
136
|
+
Group options with `TagPickerOptionGroup`. A group with no option left on screen hides itself.
|
|
137
|
+
|
|
138
|
+
```svelte
|
|
139
|
+
<TagPicker bind:selectedOptions inputProps={{ 'aria-label': 'Select Employees' }}>
|
|
140
|
+
<TagPickerOptionGroup label="Managers">
|
|
141
|
+
{#each managers as option (option)}
|
|
142
|
+
<TagPickerOption value={option}>{option}</TagPickerOption>
|
|
143
|
+
{/each}
|
|
144
|
+
</TagPickerOptionGroup>
|
|
145
|
+
<TagPickerOptionGroup label="Developers">
|
|
146
|
+
{#each devs as option (option)}
|
|
147
|
+
<TagPickerOption value={option}>{option}</TagPickerOption>
|
|
148
|
+
{/each}
|
|
149
|
+
</TagPickerOptionGroup>
|
|
150
|
+
</TagPicker>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### No popover
|
|
154
|
+
|
|
155
|
+
Leave out the options to get a TagPicker without a list, to let users enter their own tags. Bind `value` and add the tag in `querySubmitted`, called when Enter is pressed with text in the input.
|
|
156
|
+
|
|
157
|
+
```svelte
|
|
158
|
+
<script>
|
|
159
|
+
let selectedOptions = $state([]);
|
|
160
|
+
let value = $state('');
|
|
161
|
+
</script>
|
|
162
|
+
|
|
163
|
+
<TagPicker
|
|
164
|
+
bind:selectedOptions
|
|
165
|
+
bind:value
|
|
166
|
+
placeholder="Type and press Enter"
|
|
167
|
+
inputProps={{ 'aria-label': 'Add Employees' }}
|
|
168
|
+
querySubmitted={(e, query) => {
|
|
169
|
+
if (!selectedOptions.includes(query)) selectedOptions = [...selectedOptions, query];
|
|
170
|
+
value = '';
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Secondary action
|
|
176
|
+
|
|
177
|
+
Add a button next to the dropdown arrow with the `secondaryAction` snippet.
|
|
178
|
+
|
|
179
|
+
```svelte
|
|
180
|
+
{#snippet allClear()}
|
|
181
|
+
<Button appearance="subtle" onclick={() => (selectedOptions = [])}>All Clear</Button>
|
|
182
|
+
{/snippet}
|
|
183
|
+
|
|
184
|
+
<TagPicker bind:selectedOptions secondaryAction={allClear} inputProps={{ 'aria-label': 'Select Employees' }}>
|
|
185
|
+
{#each options as option (option)}
|
|
186
|
+
<TagPickerOption value={option}>{option}</TagPickerOption>
|
|
187
|
+
{/each}
|
|
188
|
+
</TagPicker>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Single select
|
|
192
|
+
|
|
193
|
+
With `single` a new choice replaces the current tag and closes the list.
|
|
194
|
+
|
|
195
|
+
```svelte
|
|
196
|
+
<TagPicker single bind:selectedOptions inputProps={{ 'aria-label': 'Select Employee' }}>
|
|
197
|
+
{#each options as option (option)}
|
|
198
|
+
<TagPickerOption value={option}>{option}</TagPickerOption>
|
|
199
|
+
{/each}
|
|
200
|
+
</TagPicker>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Size
|
|
204
|
+
|
|
205
|
+
A TagPicker is `medium` by default, or `large` or `extra-large`. The size of the control also picks the size of its tags.
|
|
206
|
+
|
|
207
|
+
```svelte
|
|
208
|
+
{#each ['medium', 'large', 'extra-large'] as size}
|
|
209
|
+
<TagPicker {size} selectedOptions={[options[0]]} inputProps={{ 'aria-label': size }}>
|
|
210
|
+
{#each options as option (option)}
|
|
211
|
+
<TagPickerOption value={option}>{option}</TagPickerOption>
|
|
212
|
+
{/each}
|
|
213
|
+
</TagPicker>
|
|
214
|
+
{/each}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Truncated text
|
|
218
|
+
|
|
219
|
+
A tag that is wider than the control, or than its own `max-width`, shortens its text with an ellipsis. Give the tag a `title` so the full text stays reachable, and truncate the text of the options yourself.
|
|
220
|
+
|
|
221
|
+
```svelte
|
|
222
|
+
{#snippet tag({ value, text })}
|
|
223
|
+
<Tag role="listitem" {value} title={text} style="max-width: 100px;">{text}</Tag>
|
|
224
|
+
{/snippet}
|
|
225
|
+
|
|
226
|
+
<TagPicker bind:selectedOptions {tag} inputProps={{ 'aria-label': 'Select Employees' }}>
|
|
227
|
+
{#each [...longOptions, ...options] as option (option)}
|
|
228
|
+
<TagPickerOption value={option}>
|
|
229
|
+
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;">{option}</span>
|
|
230
|
+
</TagPickerOption>
|
|
231
|
+
{/each}
|
|
232
|
+
</TagPicker>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Component Props (TagPicker)
|
|
236
|
+
|
|
237
|
+
<!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
|
|
238
|
+
<!-- props:TagPickerProps -->
|
|
239
|
+
|
|
240
|
+
| Name | Type | Default | Description |
|
|
241
|
+
| --------------------------------------------------- | -------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
242
|
+
| `ref` _bindable_ | `HTMLDivElement` | | The DOM reference of the wrapper element. |
|
|
243
|
+
| `inputRef` _bindable_ | `HTMLInputElement` | | The DOM reference of the input element. |
|
|
244
|
+
| `selectedOptions` _bindable_ | `string[]` | `[]` | The values of the chosen options, shown as tags in the control. A value no option has is shown as it is. |
|
|
245
|
+
| `value` _bindable_ | `string` | `''` | The text typed in the input. It filters the options, and is cleared when one is chosen. |
|
|
246
|
+
| `open` _bindable_ | `boolean` | `false` | Controls the open state of the option list. |
|
|
247
|
+
| `single` | `boolean` | | Limits the picker to one tag: choosing an option replaces the current one and closes the list. |
|
|
248
|
+
| `disabled` | `boolean` | | Disables the user interaction: the input, the tags and the list. |
|
|
249
|
+
| `size` | `'medium'` | `'large'` | `'extra-large'` | `'medium'` | The size of the control. It also picks the size of the tags. |
|
|
250
|
+
| `placeholder` | `string` | | The placeholder text of the input. |
|
|
251
|
+
| `notFoundText` | `string` | `'No options available'` | The message shown in place of the list when there is no option left to choose. |
|
|
252
|
+
| `expandIcon` | `Snippet` | `Component` | `null` | `ChevronDownRegular` | The dropdown arrow. Pass `null` to remove it. |
|
|
253
|
+
| `secondaryAction` | `Snippet` | | A button-like element rendered at the end of the control, before the dropdown arrow. |
|
|
254
|
+
| `tag` | `Snippet<[{ value: string; text: string }]>` | | Renders the tag of a chosen option, to give it media or custom text. Falls back to a plain tag. Render a `Tag` with the given `value`, and `role="listitem"`. |
|
|
255
|
+
| `tagGroupProps` | `TagGroupProps` | | The props to spread on the group holding the tags. This is where its `aria-label` goes. |
|
|
256
|
+
| `inputProps` | `HTMLInputAttributes` | | The props to spread on the input. This is where its accessible name (`aria-label`) and `readonly` (to turn the input into a plain trigger) go. |
|
|
257
|
+
| `onSelectionChange` | `(e: Event, selectedOptions: string[]) => void` | | Called when an option is chosen, or a tag is removed. |
|
|
258
|
+
| `querySubmitted` | `(e: KeyboardEvent, query: string) => void` | | Called when Enter is pressed with text in the input and no option under the cursor. This is where a free-form tag is added. |
|
|
259
|
+
| `children` | `Snippet` | | The options. Leave it out to get a picker without a list. |
|
|
260
|
+
| `HTMLAttributes<HTMLDivElement>` without `children` | | | |
|
|
261
|
+
|
|
262
|
+
<!-- /props:TagPickerProps -->
|
|
263
|
+
|
|
264
|
+
## Component Props (TagPickerOption)
|
|
265
|
+
|
|
266
|
+
<!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
|
|
267
|
+
<!-- props:TagPickerOptionProps -->
|
|
268
|
+
|
|
269
|
+
| Name | Type | Default | Description |
|
|
270
|
+
| ------------------------------------------------------------------------ | --------------- | ------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
271
|
+
| `value` | `string` | | The value added to `selectedOptions` when the option is chosen. |
|
|
272
|
+
| `text` | `string` | | The text the option is filtered by, and the text of its tag. Falls back to `value`. |
|
|
273
|
+
| `id` | `string` | | The id of the option element, referenced by the input through `aria-activedescendant`. Falls back to a generated id. |
|
|
274
|
+
| `ref` _bindable_ | `HTMLLIElement` | | The DOM reference of the option element. |
|
|
275
|
+
| `disabled` | `boolean` | | Takes the option out of reach: it is shown, but the cursor steps over it. |
|
|
276
|
+
| `ListViewItemProps<'li'>` without `as`, `value`, `onAction`, `checkmark` | | | |
|
|
277
|
+
|
|
278
|
+
<!-- /props:TagPickerOptionProps -->
|
|
279
|
+
|
|
280
|
+
## Component Props (TagPickerOptionGroup)
|
|
281
|
+
|
|
282
|
+
<!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
|
|
283
|
+
<!-- props:TagPickerOptionGroupProps -->
|
|
284
|
+
|
|
285
|
+
| Name | Type | Default | Description |
|
|
286
|
+
| ----------------------------------------------------- | ------------------ | ------- | --------------------------------------- |
|
|
287
|
+
| `label` | `string` | | The label of the group. |
|
|
288
|
+
| `ref` _bindable_ | `HTMLUListElement` | | The DOM reference of the group element. |
|
|
289
|
+
| `children` | `Snippet` | | The options of the group. |
|
|
290
|
+
| `HTMLAttributes<HTMLUListElement>` without `children` | | | |
|
|
291
|
+
|
|
292
|
+
<!-- /props:TagPickerOptionGroupProps -->
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { PREFIX } from '../../internal/constants.js';
|
|
3
|
+
import type { TagPickerOptionGroupProps } from './types.js';
|
|
4
|
+
|
|
5
|
+
const FALLBACK_ID = $props.id();
|
|
6
|
+
const LABEL_ID = `${PREFIX}-tagpicker-group-${FALLBACK_ID}-label`;
|
|
7
|
+
|
|
8
|
+
let { label, ref = $bindable(), class: classes, children, ...attributes }: TagPickerOptionGroupProps = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<li class="fs-tagpicker-group" role="presentation">
|
|
12
|
+
<span class="fs-tagpicker-group-label" id={LABEL_ID}>{label}</span>
|
|
13
|
+
<ul bind:this={ref} class={classes} role="group" aria-labelledby={LABEL_ID} {...attributes}>
|
|
14
|
+
{@render children?.()}
|
|
15
|
+
</ul>
|
|
16
|
+
</li>
|
|
17
|
+
|
|
18
|
+
<style>
|
|
19
|
+
.fs-tagpicker-group {
|
|
20
|
+
list-style: none;
|
|
21
|
+
/* A group left without an option on screen hides itself. */
|
|
22
|
+
&:not(:has(:global(.fs-tagpicker-option))) {
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
.fs-tagpicker-group-label {
|
|
27
|
+
display: block;
|
|
28
|
+
padding: 0.375rem 0.713rem;
|
|
29
|
+
font-size: var(--fs-caption-font-size);
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
color: var(--fs-text-secondary);
|
|
32
|
+
}
|
|
33
|
+
ul {
|
|
34
|
+
list-style: none;
|
|
35
|
+
margin: 0;
|
|
36
|
+
padding: 0;
|
|
37
|
+
}
|
|
38
|
+
</style>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { PREFIX } from '../../internal/constants.js';
|
|
3
|
+
import ListViewItem from '../list-view/list-view-item.svelte';
|
|
4
|
+
import { getTagPickerContext } from './tag-picker.js';
|
|
5
|
+
import type { TagPickerOptionProps } from './types.js';
|
|
6
|
+
|
|
7
|
+
const FALLBACK_ID = $props.id();
|
|
8
|
+
const ID = `${PREFIX}-${FALLBACK_ID}`;
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
disabled,
|
|
12
|
+
ref = $bindable(),
|
|
13
|
+
id = ID,
|
|
14
|
+
value,
|
|
15
|
+
text,
|
|
16
|
+
class: classes,
|
|
17
|
+
onclick,
|
|
18
|
+
children,
|
|
19
|
+
...attributes
|
|
20
|
+
}: TagPickerOptionProps = $props();
|
|
21
|
+
|
|
22
|
+
const context = getTagPickerContext();
|
|
23
|
+
|
|
24
|
+
if (!context) throw new Error('TagPickerOption must be used within a TagPicker');
|
|
25
|
+
|
|
26
|
+
const { state: _state, methods } = context;
|
|
27
|
+
const label = $derived(text ?? value);
|
|
28
|
+
|
|
29
|
+
// A chosen option is not offered again, and a typed query leaves only the options that contain it.
|
|
30
|
+
const isVisible = $derived(
|
|
31
|
+
!_state.selectedOptions.includes(value) && label.toLowerCase().includes(_state.query.toLowerCase())
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
function handleClick(e: Parameters<NonNullable<typeof onclick>>[0]) {
|
|
35
|
+
onclick?.(e);
|
|
36
|
+
|
|
37
|
+
if (!disabled) methods.chooseOption(e, id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The tag of the option keeps its text even when the option itself is not on screen.
|
|
41
|
+
$effect(() => methods.setLabel(value, label));
|
|
42
|
+
|
|
43
|
+
// An option counts only while it is on screen: that is what makes the empty list detectable
|
|
44
|
+
// and keeps keyboard navigation off options nobody can see.
|
|
45
|
+
$effect(() => {
|
|
46
|
+
if (!isVisible || !_state.open) return;
|
|
47
|
+
|
|
48
|
+
// Capture current values to avoid them being stale in the callback
|
|
49
|
+
const _id = id;
|
|
50
|
+
|
|
51
|
+
methods.setOption({ id, value, disabled });
|
|
52
|
+
|
|
53
|
+
return () => methods.deleteOption(_id);
|
|
54
|
+
});
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
{#if isVisible}
|
|
58
|
+
<ListViewItem
|
|
59
|
+
{id}
|
|
60
|
+
role="option"
|
|
61
|
+
{value}
|
|
62
|
+
tabindex={-1}
|
|
63
|
+
class={['fs-tagpicker-option', classes]}
|
|
64
|
+
bind:ref
|
|
65
|
+
{disabled}
|
|
66
|
+
{...attributes}
|
|
67
|
+
onclick={handleClick}
|
|
68
|
+
>
|
|
69
|
+
{@render children?.()}
|
|
70
|
+
</ListViewItem>
|
|
71
|
+
{/if}
|