fluentui-svelte 0.4.0 → 0.5.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.md +1 -1
- package/dist/components/auto-suggest-box/docs/llms.md +76 -15
- package/dist/components/card/card-header.svelte +2 -2
- package/dist/components/dropdown/dropdown.svelte +1 -0
- package/dist/components/list-view/docs/llms.md +79 -27
- package/dist/components/list-view/list-view-item.svelte +14 -11
- package/dist/components/list-view/list-view.svelte +3 -2
- package/dist/components/list-view/types.d.ts +5 -1
- package/dist/components/menu/docs/llms.md +16 -0
- package/dist/components/menu/menu-popover.svelte +7 -12
- package/dist/components/menu/menu-popover.svelte.d.ts +2 -10
- package/dist/components/menu/types.d.ts +21 -0
- package/dist/components/tree-view/docs/llms.md +166 -1
- package/dist/components/tree-view/tree-view-item-content.svelte +41 -8
- package/dist/components/tree-view/tree-view-item.svelte +10 -1
- package/dist/components/tree-view/tree-view.svelte +17 -2
- package/dist/components/tree-view/tree-view.svelte.js +4 -2
- package/dist/components/tree-view/types.d.ts +4 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -169,7 +169,7 @@ after changing a prop's JSDoc rather than editing the generated tables by hand.
|
|
|
169
169
|
>
|
|
170
170
|
> This app demonstrates all of the Windows UI 3 library controls and styles available to make a WinUI 3 app with the Windows App SDK.
|
|
171
171
|
|
|
172
|
-
> ### Documentation: [Microsoft's Fluent UI](https://
|
|
172
|
+
> ### Documentation: [Microsoft's Fluent UI](https://developer.microsoft.com/en-us/fluentui/)
|
|
173
173
|
>
|
|
174
174
|
> Design guidelines and UI code examples for creating Windows app experiences.
|
|
175
175
|
|
|
@@ -18,44 +18,105 @@ The AutoSuggestBox component provides an input field with dynamic suggestions as
|
|
|
18
18
|
|
|
19
19
|
## Examples
|
|
20
20
|
|
|
21
|
-
###
|
|
21
|
+
### Open on focus
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
Use `openOnFocus` to show suggestions as soon as the text box receives focus, before anything is typed.
|
|
24
|
+
|
|
25
|
+
```svelte
|
|
26
|
+
<AutoSuggestBox openOnFocus placeholder="Choose a fruit...">
|
|
27
|
+
{#each fruits as fruit, index (fruit.id)}
|
|
28
|
+
<AutoSuggestBoxOption {index} id={fruit.id} value={fruit.name}>
|
|
29
|
+
{fruit.name}
|
|
30
|
+
</AutoSuggestBoxOption>
|
|
31
|
+
{/each}
|
|
32
|
+
</AutoSuggestBox>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Virtualization
|
|
36
|
+
|
|
37
|
+
Window large suggestion sets by connecting AutoSuggestBox to a virtual list through the `virtualizer` prop. Its `size` is the total number of suggestions, while each option keeps its real `index`.
|
|
38
|
+
|
|
39
|
+
```svelte
|
|
40
|
+
<script>
|
|
41
|
+
import SvelteVirtualList from '@humanspeak/svelte-virtual-list';
|
|
42
|
+
|
|
43
|
+
let listRef;
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<AutoSuggestBox
|
|
47
|
+
placeholder="Type a name..."
|
|
48
|
+
virtualizer={{
|
|
49
|
+
size: people.length,
|
|
50
|
+
scrollToTop: () => listRef.scroll({ index: 0, align: 'top', smoothScroll: false }),
|
|
51
|
+
scrollToBottom: () => listRef.scroll({ index: people.length - 1, align: 'bottom', smoothScroll: false }),
|
|
52
|
+
scrollToIndex: (index) => listRef.scroll({ index, align: 'auto', smoothScroll: false })
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<SvelteVirtualList items={people} bind:this={listRef} defaultEstimatedItemHeight={30}>
|
|
56
|
+
{#snippet renderItem(person, index)}
|
|
57
|
+
<AutoSuggestBoxOption {index} id={person.id} value={person.name}>
|
|
58
|
+
{person.name}
|
|
59
|
+
</AutoSuggestBoxOption>
|
|
60
|
+
{/snippet}
|
|
61
|
+
</SvelteVirtualList>
|
|
62
|
+
</AutoSuggestBox>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Multiselect
|
|
66
|
+
|
|
67
|
+
Allow people to choose several suggestions and optionally show the selected values in the text box.
|
|
68
|
+
|
|
69
|
+
```svelte
|
|
70
|
+
<script>
|
|
71
|
+
let selectedOptions = $state([]);
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<AutoSuggestBox multiselect showTextualMultiselect bind:selectedOptions placeholder="Choose fruits...">
|
|
75
|
+
{#each fruits as fruit, index (fruit.id)}
|
|
76
|
+
<AutoSuggestBoxOption {index} id={fruit.id} value={fruit.name}>
|
|
77
|
+
{fruit.name}
|
|
78
|
+
</AutoSuggestBoxOption>
|
|
79
|
+
{/each}
|
|
80
|
+
</AutoSuggestBox>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Virtualization with filtering
|
|
84
|
+
|
|
85
|
+
A virtualized list must filter its source data itself and report the filtered length through `virtualizer.size`.
|
|
27
86
|
|
|
28
87
|
```svelte
|
|
29
88
|
<script>
|
|
30
89
|
import SvelteVirtualList from '@humanspeak/svelte-virtual-list';
|
|
31
90
|
|
|
32
|
-
let items = $state(
|
|
91
|
+
let items = $state(people);
|
|
33
92
|
let listRef;
|
|
34
93
|
</script>
|
|
35
94
|
|
|
36
95
|
<AutoSuggestBox
|
|
37
|
-
placeholder="Type a
|
|
96
|
+
placeholder="Type a name..."
|
|
38
97
|
virtualizer={{
|
|
39
98
|
size: items.length,
|
|
40
99
|
scrollToTop: () => listRef.scroll({ index: 0, align: 'top', smoothScroll: false }),
|
|
41
|
-
scrollToBottom: () =>
|
|
100
|
+
scrollToBottom: () => {
|
|
101
|
+
if (items.length) listRef.scroll({ index: items.length - 1, align: 'bottom', smoothScroll: false });
|
|
102
|
+
},
|
|
42
103
|
scrollToIndex: (index) => listRef.scroll({ index, align: 'auto', smoothScroll: false })
|
|
43
104
|
}}
|
|
44
|
-
textChanged={(
|
|
105
|
+
textChanged={(_, value) => {
|
|
106
|
+
const query = value.trim().toLowerCase();
|
|
107
|
+
items = people.filter(({ name }) => name.toLowerCase().includes(query));
|
|
108
|
+
}}
|
|
45
109
|
>
|
|
46
110
|
<SvelteVirtualList {items} bind:this={listRef} defaultEstimatedItemHeight={30}>
|
|
47
|
-
{#snippet renderItem(
|
|
48
|
-
<AutoSuggestBoxOption {index} id={
|
|
49
|
-
{
|
|
111
|
+
{#snippet renderItem(person, index)}
|
|
112
|
+
<AutoSuggestBoxOption {index} id={person.id} value={person.name}>
|
|
113
|
+
{person.name}
|
|
50
114
|
</AutoSuggestBoxOption>
|
|
51
115
|
{/snippet}
|
|
52
116
|
</SvelteVirtualList>
|
|
53
117
|
</AutoSuggestBox>
|
|
54
118
|
```
|
|
55
119
|
|
|
56
|
-
Filtering is yours to do in `textChanged`: a windowed list renders from your data, so the
|
|
57
|
-
built-in filter that a plain option list applies is skipped.
|
|
58
|
-
|
|
59
120
|
## Component Props
|
|
60
121
|
|
|
61
122
|
<!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
{#if typeof title === 'string'}
|
|
43
43
|
<h4 class="body" id={`${id}-title`}>{title}</h4>
|
|
44
44
|
{:else if title}
|
|
45
|
-
<RenderSoC SoC={title}
|
|
45
|
+
<RenderSoC SoC={title} args={{ id: `${id}-title` }} />
|
|
46
46
|
{/if}
|
|
47
47
|
{#if typeof description === 'string'}
|
|
48
48
|
<p class="caption">{description}</p>
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
{:else if typeof title === 'string'}
|
|
54
54
|
<h4 class="body" id={`${id}-title`}>{title}</h4>
|
|
55
55
|
{:else if title}
|
|
56
|
-
<RenderSoC SoC={title}
|
|
56
|
+
<RenderSoC SoC={title} args={{ id: `${id}-title` }} />
|
|
57
57
|
{/if}
|
|
58
58
|
{#if _action}
|
|
59
59
|
<RenderSoC SoC={_action} />
|
|
@@ -56,27 +56,78 @@ Use `selectionMode` (`single`, `multiselect`, or `extended`) to control how item
|
|
|
56
56
|
</ListView>
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
### Toggle on click
|
|
60
|
+
|
|
61
|
+
In a multiselect list, a plain click selects only that item and Ctrl/Cmd + click toggles it. Set `toggleOnClick` to make a plain click toggle the item directly.
|
|
62
|
+
|
|
63
|
+
```svelte
|
|
64
|
+
<ListView selectionMode="multiselect" toggleOnClick>
|
|
65
|
+
{#each ['Apple', 'Banana', 'Cherry'] as item (item)}
|
|
66
|
+
<ListViewItem value={item}>{item}</ListViewItem>
|
|
67
|
+
{/each}
|
|
68
|
+
</ListView>
|
|
69
|
+
```
|
|
70
|
+
|
|
59
71
|
### Checkmark
|
|
60
72
|
|
|
61
|
-
Add and configure a checkbox for the element. You can use the same props for the Checkbox component.
|
|
73
|
+
Add and configure a checkbox for the element. You can use the same props for the Checkbox component. The checkbox toggles the selection of its item without triggering `onAction`. Bind `selectedItems` to read or control the selection.
|
|
62
74
|
|
|
63
75
|
```svelte
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
<script>
|
|
77
|
+
let selected = $state(['Apple']);
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<ListView selectionMode="multiselect" bind:selectedItems={selected}>
|
|
81
|
+
{#each ['Apple', 'Banana', 'Cherry'] as item (item)}
|
|
82
|
+
<ListViewItem value={item} checkmark={{}}>{item}</ListViewItem>
|
|
83
|
+
{/each}
|
|
84
|
+
</ListView>
|
|
85
|
+
|
|
86
|
+
<p>Selected: {selected.join(', ')}</p>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Cards with a primary action
|
|
90
|
+
|
|
91
|
+
With `navigationMode` set to `composite` and a `selectionMode`, every item is a row. Clicking it runs its primary action (`onAction`), the checkbox toggles its selection, and any button inside is a secondary action. Use `checkmark.wrapperAttributes.style` to place the checkbox where you need it, like the top-right corner of the card. Note: every actionable element inside this ListViewItem, except the checkbox, must call e.stopPropagation().
|
|
92
|
+
|
|
93
|
+
```svelte
|
|
94
|
+
<script>
|
|
95
|
+
const cards = [
|
|
96
|
+
{ name: 'Fluent', description: 'Design system' },
|
|
97
|
+
{ name: 'Svelte', description: 'UI framework' },
|
|
98
|
+
{ name: 'Vite', description: 'Build tool' }
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
let selected = $state([]);
|
|
102
|
+
let lastAction = $state('');
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<ListView as="div" navigationMode="composite" selectionMode="multiselect" bind:selectedItems={selected}>
|
|
106
|
+
{#each cards as card (card.name)}
|
|
107
|
+
{#snippet install()}
|
|
108
|
+
<Button
|
|
109
|
+
onclick={(e) => {
|
|
110
|
+
e.stopPropagation();
|
|
111
|
+
lastAction = 'Installed ' + card.name;
|
|
112
|
+
}}>Install</Button
|
|
113
|
+
>
|
|
114
|
+
{/snippet}
|
|
115
|
+
<ListViewItem
|
|
116
|
+
value={card.name}
|
|
117
|
+
checkmark={{ wrapperAttributes: { style: 'position: absolute; top: 0.5rem; right: 0.5rem; z-index: 2;' } }}
|
|
118
|
+
style="padding: 0"
|
|
119
|
+
onAction={() => (lastAction = 'Opened ' + card.name)}
|
|
77
120
|
>
|
|
78
|
-
|
|
79
|
-
|
|
121
|
+
<Card orientation="vertical" style="width: 100%;">
|
|
122
|
+
<CardHeader title={card.name} description={card.description} />
|
|
123
|
+
<CardFooter action={install} />
|
|
124
|
+
</Card>
|
|
125
|
+
</ListViewItem>
|
|
126
|
+
{/each}
|
|
127
|
+
</ListView>
|
|
128
|
+
|
|
129
|
+
<p>Selected: {selected.join(', ')}</p>
|
|
130
|
+
<p>Last action: {lastAction}</p>
|
|
80
131
|
```
|
|
81
132
|
|
|
82
133
|
## ListView Props
|
|
@@ -84,17 +135,18 @@ Add and configure a checkbox for the element. You can use the same props for the
|
|
|
84
135
|
<!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
|
|
85
136
|
<!-- props:ListViewProps -->
|
|
86
137
|
|
|
87
|
-
| Name | Type | Default | Description
|
|
88
|
-
| -------------------------- | --------------------------------------------------------------------- | ----------- |
|
|
89
|
-
| `selectionMode` | `'none'` | `'single'` | `'multiselect'` | `'extended'` | `'none'` | How many items can be selected, and how the selection is made.
|
|
90
|
-
| `navigationMode` | `'items'` | `'composite'` | `'items'` | Whether the list itself takes a single tab stop, or every item takes its own.
|
|
91
|
-
| `selectedItems` _bindable_ | `string[]` | | The values of the selected items.
|
|
92
|
-
| `shape` | `'circular'` | `'rounded'` | `'square'` | `'rounded'` | The items can have a circular, rounded or square shape.
|
|
93
|
-
| `onSelectionChange` | `(e: Event, selectedItems: string[]` | `[]) => void` | | Called whenever the selection changes.
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
|
|
|
138
|
+
| Name | Type | Default | Description |
|
|
139
|
+
| -------------------------- | --------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
+
| `selectionMode` | `'none'` | `'single'` | `'multiselect'` | `'extended'` | `'none'` | How many items can be selected, and how the selection is made. |
|
|
141
|
+
| `navigationMode` | `'items'` | `'composite'` | `'items'` | Whether the list itself takes a single tab stop, or every item takes its own. |
|
|
142
|
+
| `selectedItems` _bindable_ | `string[]` | | The values of the selected items. |
|
|
143
|
+
| `shape` | `'circular'` | `'rounded'` | `'square'` | `'rounded'` | The items can have a circular, rounded or square shape. |
|
|
144
|
+
| `onSelectionChange` | `(e: Event, selectedItems: string[]` | `[]) => void` | | Called whenever the selection changes. |
|
|
145
|
+
| `toggleOnClick` | `boolean` | `false` | When `selectionMode` is `'multiselect'`, clicking an item toggles its selection directly instead of requiring Ctrl/Cmd + click. |
|
|
146
|
+
| `as` | `'ul'` | `'ol'` | `'div'` | `'ul'` | The HTML element to render the list as. |
|
|
147
|
+
| `ref` _bindable_ | `HTMLUListElement` | `HTMLOListElement` | `HTMLDivElement` | | The DOM reference of the list element. |
|
|
148
|
+
| `disableTabspot` | `boolean` | `false` | Opts the list out of the tabspot focus management, to wire your own. |
|
|
149
|
+
| HTML Attributes | | | |
|
|
98
150
|
|
|
99
151
|
<!-- /props:ListViewProps -->
|
|
100
152
|
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
const role = $derived(_role || getChildrenRole(as));
|
|
44
44
|
|
|
45
45
|
let active = $derived(_state.selectedItems.includes(value));
|
|
46
|
+
let checkmarkRef = $state<HTMLDivElement>();
|
|
46
47
|
|
|
47
48
|
// A row is a level of its own: right enters the cells, left comes back out. Anything
|
|
48
49
|
// that is not a row navigates as a plain item of the list above it.
|
|
@@ -82,21 +83,23 @@
|
|
|
82
83
|
{...tabspotAttrs}
|
|
83
84
|
onfocus={(e: FocusEvent) => invokeHandlers(e, disabled, [(event: FocusEvent) => onfocus?.(event, value || '')])}
|
|
84
85
|
onclick={(e: MouseEvent) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
role !== 'row'
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
(event: MouseEvent) => onAction?.(event, value || '')
|
|
92
|
-
]
|
|
93
|
-
: [(event: MouseEvent) => onAction?.(event, value || '')]
|
|
94
|
-
);
|
|
86
|
+
// The checkmark only toggles the selection, even on a row. The primary action is for the rest of the item.
|
|
87
|
+
const fromCheckmark = !!checkmarkRef?.contains(e.target as Node);
|
|
88
|
+
invokeHandlers(e, disabled, [
|
|
89
|
+
(role !== 'row' || fromCheckmark) && ((event: MouseEvent) => handleSelection?.(event, value, fromCheckmark)),
|
|
90
|
+
!fromCheckmark && ((event: MouseEvent) => onAction?.(event, value || ''))
|
|
91
|
+
]);
|
|
95
92
|
}}
|
|
96
93
|
{...attributes}
|
|
97
94
|
>
|
|
98
95
|
{#if checkmark}
|
|
99
|
-
<Checkbox
|
|
96
|
+
<Checkbox
|
|
97
|
+
{disabled}
|
|
98
|
+
bind:checked={active}
|
|
99
|
+
bind:wrapperRef={checkmarkRef}
|
|
100
|
+
tabindex={-1}
|
|
101
|
+
{...!isObjectEmpty(checkmark) ? checkmark : {}}
|
|
102
|
+
/>
|
|
100
103
|
{/if}
|
|
101
104
|
{@render children?.()}
|
|
102
105
|
</svelte:element>
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
selectedItems = $bindable([]),
|
|
13
13
|
onSelectionChange,
|
|
14
14
|
disableTabspot = false,
|
|
15
|
+
toggleOnClick = false,
|
|
15
16
|
children,
|
|
16
17
|
...attributes
|
|
17
18
|
}: ListViewProps<Tag> = $props();
|
|
@@ -72,7 +73,7 @@
|
|
|
72
73
|
},
|
|
73
74
|
events: null,
|
|
74
75
|
methods: {
|
|
75
|
-
handleSelection: (e: MouseEvent, value: string) => {
|
|
76
|
+
handleSelection: (e: MouseEvent, value: string, toggle = false) => {
|
|
76
77
|
if (selectionMode === 'none') return;
|
|
77
78
|
|
|
78
79
|
const itemIndex = items.findIndex((item) => item.value === value);
|
|
@@ -89,7 +90,7 @@
|
|
|
89
90
|
.filter((i) => !i.disabled)
|
|
90
91
|
.map((i) => i.value);
|
|
91
92
|
selectedItems = Array.from(new Set([...selectedItems, ...rangeValues]));
|
|
92
|
-
} else if (MULTISELECTABLE && (e.ctrlKey || e.metaKey)) {
|
|
93
|
+
} else if (MULTISELECTABLE && (toggle || e.ctrlKey || e.metaKey || toggleOnClick)) {
|
|
93
94
|
selectedItems = selectedItems.includes(value)
|
|
94
95
|
? selectedItems.filter((v) => v !== value)
|
|
95
96
|
: [...selectedItems, value];
|
|
@@ -23,6 +23,10 @@ export type ListViewProps<T extends 'ul' | 'ol' | 'div'> = {
|
|
|
23
23
|
shape?: Shapes;
|
|
24
24
|
/** Called whenever the selection changes. */
|
|
25
25
|
onSelectionChange?: (e: Event, selectedItems: string[] | []) => void;
|
|
26
|
+
/** When `selectionMode` is `'multiselect'`, clicking an item toggles its selection directly instead of requiring Ctrl/Cmd + click.
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
toggleOnClick?: boolean;
|
|
26
30
|
/** The HTML element to render the list as.
|
|
27
31
|
* @type 'ul' | 'ol' | 'div'
|
|
28
32
|
* @default 'ul'
|
|
@@ -87,7 +91,7 @@ export type ListViewContextState = {
|
|
|
87
91
|
anchorIndex: number | null;
|
|
88
92
|
};
|
|
89
93
|
export type ListViewContextMethods = {
|
|
90
|
-
handleSelection: (e: MouseEvent, value: string) => void;
|
|
94
|
+
handleSelection: (e: MouseEvent, value: string, toggle?: boolean) => void;
|
|
91
95
|
getChildrenRole: (tag: 'li' | 'a' | 'div') => 'listitem' | 'option' | 'row' | undefined;
|
|
92
96
|
registerItem: (id: string, value: string, disabled: boolean) => void;
|
|
93
97
|
unregisterItem: (id: string) => void;
|
|
@@ -140,6 +140,22 @@ This variant of the `MenuTrigger` component allows you to create context menus t
|
|
|
140
140
|
|
|
141
141
|
<!-- /props:MenuProps -->
|
|
142
142
|
|
|
143
|
+
## Component Props (MenuPopover)
|
|
144
|
+
|
|
145
|
+
<!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
|
|
146
|
+
<!-- props:MenuPopoverProps -->
|
|
147
|
+
|
|
148
|
+
| Name | Type | Default | Description |
|
|
149
|
+
| ----------------- | ------------------------------------ | ------- | -------------------------------------------------------- |
|
|
150
|
+
| `ref` | `HTMLElement` | | The element used to position the popover. |
|
|
151
|
+
| `open` _bindable_ | `boolean` | | Controls the open state of the popover. |
|
|
152
|
+
| `keepMounted` | `boolean` | `false` | Keeps the popover mounted in the DOM while it is closed. |
|
|
153
|
+
| `placement` | `ComputePositionConfig['placement']` | | Where the popover is positioned relative to its trigger. |
|
|
154
|
+
| `positionConfig` | `ComputePositionConfig` | | The position configuration handed over to floating-ui. |
|
|
155
|
+
| `children` | `Snippet` | | The content of the popover. |
|
|
156
|
+
|
|
157
|
+
<!-- /props:MenuPopoverProps -->
|
|
158
|
+
|
|
143
159
|
## Component Props (MenuTrigger)
|
|
144
160
|
|
|
145
161
|
<!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
import { useDebounce } from 'runed';
|
|
7
7
|
import { floating, flyToOffset, getCSSDuration, reactiveBoundingRect } from '../../internal/index.js';
|
|
8
8
|
import { getReducedMotion } from '../../providers/fluentui-svelte/fluentui-svelte.js';
|
|
9
|
-
import type {
|
|
10
|
-
import type { ComputePositionConfig } from '@floating-ui/dom';
|
|
9
|
+
import type { MenuPopoverProps } from './types.js';
|
|
11
10
|
|
|
12
11
|
let {
|
|
13
12
|
ref: _ref,
|
|
14
13
|
open: _open = $bindable(),
|
|
14
|
+
keepMounted = false,
|
|
15
15
|
placement,
|
|
16
16
|
positionConfig: floatingPosition = {
|
|
17
17
|
placement,
|
|
@@ -19,13 +19,7 @@
|
|
|
19
19
|
strategy: 'fixed'
|
|
20
20
|
},
|
|
21
21
|
children
|
|
22
|
-
}:
|
|
23
|
-
ref?: HTMLElement;
|
|
24
|
-
open?: boolean;
|
|
25
|
-
placement?: ComputePositionConfig['placement'];
|
|
26
|
-
positionConfig?: Partial<ComputePositionConfig>;
|
|
27
|
-
children: Snippet;
|
|
28
|
-
} = $props();
|
|
22
|
+
}: MenuPopoverProps = $props();
|
|
29
23
|
|
|
30
24
|
const context = getMenuContext();
|
|
31
25
|
|
|
@@ -90,16 +84,17 @@
|
|
|
90
84
|
});
|
|
91
85
|
</script>
|
|
92
86
|
|
|
93
|
-
{#if opened && ref}
|
|
87
|
+
{#if keepMounted || (opened && ref)}
|
|
94
88
|
{@const isRefAnElement = ref instanceof HTMLElement}
|
|
95
89
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
96
90
|
<div
|
|
97
91
|
bind:this={element}
|
|
98
92
|
class="fs-menu-popover"
|
|
93
|
+
hidden={!opened}
|
|
99
94
|
onmouseleave={openOnHover ? (e: MouseEvent) => debounced(e) : undefined}
|
|
100
|
-
{@attach ref ? floating(ref, config) : undefined}
|
|
95
|
+
{@attach opened && ref ? floating(ref, config) : undefined}
|
|
101
96
|
style={`--position=${isSubMenu ? 'absolute' : 'fixed'} --min-width=${isRefAnElement && !isSubMenu ? `${boundingElement.rect.width}px` : 'max-content'}`}
|
|
102
|
-
in:flyToOffset={isRefAnElement
|
|
97
|
+
in:flyToOffset={opened && isRefAnElement
|
|
103
98
|
? {
|
|
104
99
|
...getIntroTransition({ config, anchor: boundingElement.rect, element, ref, isSubMenu }),
|
|
105
100
|
duration: duration,
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
type $$ComponentProps = {
|
|
4
|
-
ref?: HTMLElement;
|
|
5
|
-
open?: boolean;
|
|
6
|
-
placement?: ComputePositionConfig['placement'];
|
|
7
|
-
positionConfig?: Partial<ComputePositionConfig>;
|
|
8
|
-
children: Snippet;
|
|
9
|
-
};
|
|
10
|
-
declare const MenuPopover: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
1
|
+
import type { MenuPopoverProps } from './types.ts';
|
|
2
|
+
declare const MenuPopover: import("svelte").Component<MenuPopoverProps, {}, "open">;
|
|
11
3
|
type MenuPopover = ReturnType<typeof MenuPopover>;
|
|
12
4
|
export default MenuPopover;
|
|
@@ -59,6 +59,27 @@ export type MenuProps = {
|
|
|
59
59
|
/** The parts of the menu: a trigger and a popover. */
|
|
60
60
|
children?: Snippet;
|
|
61
61
|
};
|
|
62
|
+
/** @propsmith MenuPopoverProps */
|
|
63
|
+
export type MenuPopoverProps = {
|
|
64
|
+
/** The element used to position the popover. */
|
|
65
|
+
ref?: HTMLElement;
|
|
66
|
+
/** Controls the open state of the popover.
|
|
67
|
+
* @bindable
|
|
68
|
+
*/
|
|
69
|
+
open?: boolean;
|
|
70
|
+
/** Keeps the popover mounted in the DOM while it is closed.
|
|
71
|
+
* @default false
|
|
72
|
+
*/
|
|
73
|
+
keepMounted?: boolean;
|
|
74
|
+
/** Where the popover is positioned relative to its trigger. */
|
|
75
|
+
placement?: ComputePositionConfig['placement'];
|
|
76
|
+
/** The position configuration handed over to floating-ui.
|
|
77
|
+
* @type ComputePositionConfig
|
|
78
|
+
*/
|
|
79
|
+
positionConfig?: Partial<ComputePositionConfig>;
|
|
80
|
+
/** The content of the popover. */
|
|
81
|
+
children: Snippet;
|
|
82
|
+
};
|
|
62
83
|
/** @propsmith MenuTriggerProps */
|
|
63
84
|
export type MenuTriggerProps = {
|
|
64
85
|
/** Disables the user interaction. */
|
|
@@ -35,7 +35,7 @@ Nesting a TreeView inside a TreeViewItem of type='branch' creates a subtree. Use
|
|
|
35
35
|
|
|
36
36
|
### Checkable Items
|
|
37
37
|
|
|
38
|
-
Set `selectionMode
|
|
38
|
+
Set `selectionMode` to `"multiple" | "single"` to render checkboxes on selectable items.
|
|
39
39
|
|
|
40
40
|
```svelte
|
|
41
41
|
<TreeView selectionMode="multiple">
|
|
@@ -50,6 +50,132 @@ Set `selectionMode="multiple"` to render checkboxes on selectable items.
|
|
|
50
50
|
</TreeView>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
### Single selection
|
|
54
|
+
|
|
55
|
+
Set `selectionMode` to `single` so checking an item unchecks the previous one, and branches no longer cascade to their children.
|
|
56
|
+
|
|
57
|
+
```svelte
|
|
58
|
+
<script>
|
|
59
|
+
let checked = $state([]);
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<TreeView selectionMode="single" bind:checkedItems={checked}>
|
|
63
|
+
<TreeViewItem id="fruits" type="branch" open>
|
|
64
|
+
<TreeViewItemContent>Fruits</TreeViewItemContent>
|
|
65
|
+
<TreeView>
|
|
66
|
+
<TreeViewItem id="apple">
|
|
67
|
+
<TreeViewItemContent>Apple</TreeViewItemContent>
|
|
68
|
+
</TreeViewItem>
|
|
69
|
+
<TreeViewItem id="banana">
|
|
70
|
+
<TreeViewItemContent>Banana</TreeViewItemContent>
|
|
71
|
+
</TreeViewItem>
|
|
72
|
+
</TreeView>
|
|
73
|
+
</TreeViewItem>
|
|
74
|
+
<TreeViewItem id="vegetables">
|
|
75
|
+
<TreeViewItemContent>Vegetables</TreeViewItemContent>
|
|
76
|
+
</TreeViewItem>
|
|
77
|
+
</TreeView>
|
|
78
|
+
|
|
79
|
+
<p>Checked: {checked.join(', ')}</p>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Icons
|
|
83
|
+
|
|
84
|
+
Use `iconBefore` and `iconAfter` to render icons around the label. Every item reserves the space of the expand chevron, so checkboxes and icons stay aligned between branches and leaves.
|
|
85
|
+
|
|
86
|
+
```svelte
|
|
87
|
+
<script>
|
|
88
|
+
import { TreeView, TreeViewItem, TreeViewItemContent } from 'fluentui-svelte';
|
|
89
|
+
import FolderRegular from 'fluentui-icons-svelte/FolderRegular.svelte';
|
|
90
|
+
import DocumentRegular from 'fluentui-icons-svelte/DocumentRegular.svelte';
|
|
91
|
+
import ImageRegular from 'fluentui-icons-svelte/ImageRegular.svelte';
|
|
92
|
+
import StarRegular from 'fluentui-icons-svelte/StarRegular.svelte';
|
|
93
|
+
import LockClosedRegular from 'fluentui-icons-svelte/LockClosedRegular.svelte';
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
{#snippet folder()}<FolderRegular width="1.25rem" />{/snippet}
|
|
97
|
+
{#snippet document()}<DocumentRegular width="1.25rem" />{/snippet}
|
|
98
|
+
{#snippet image()}<ImageRegular width="1.25rem" />{/snippet}
|
|
99
|
+
{#snippet star()}<StarRegular width="1rem" />{/snippet}
|
|
100
|
+
{#snippet lock()}<LockClosedRegular width="1rem" />{/snippet}
|
|
101
|
+
|
|
102
|
+
<TreeView selectionMode="multiple">
|
|
103
|
+
<TreeViewItem id="documents" type="branch" open>
|
|
104
|
+
<TreeViewItemContent iconBefore={folder}>Documents</TreeViewItemContent>
|
|
105
|
+
<TreeView>
|
|
106
|
+
<TreeViewItem id="report">
|
|
107
|
+
<TreeViewItemContent iconBefore={document} iconAfter={star}>Report</TreeViewItemContent>
|
|
108
|
+
</TreeViewItem>
|
|
109
|
+
<TreeViewItem id="photo">
|
|
110
|
+
<TreeViewItemContent iconBefore={image}>Photo</TreeViewItemContent>
|
|
111
|
+
</TreeViewItem>
|
|
112
|
+
</TreeView>
|
|
113
|
+
</TreeViewItem>
|
|
114
|
+
<TreeViewItem id="notes">
|
|
115
|
+
<TreeViewItemContent iconBefore={document} iconAfter={lock}>Notes</TreeViewItemContent>
|
|
116
|
+
</TreeViewItem>
|
|
117
|
+
</TreeView>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Aside and actions
|
|
121
|
+
|
|
122
|
+
`aside` is pinned to the end of the row. `actions` comes after it and is revealed when the row is hovered or focused.
|
|
123
|
+
|
|
124
|
+
```svelte
|
|
125
|
+
<script>
|
|
126
|
+
import { TreeView, TreeViewItem, TreeViewItemContent, Button } from 'fluentui-svelte';
|
|
127
|
+
import DocumentRegular from 'fluentui-icons-svelte/DocumentRegular.svelte';
|
|
128
|
+
import MoreHorizontalRegular from 'fluentui-icons-svelte/MoreHorizontalRegular.svelte';
|
|
129
|
+
</script>
|
|
130
|
+
|
|
131
|
+
{#snippet document()}<DocumentRegular width="1.25rem" />{/snippet}
|
|
132
|
+
{#snippet fileSize()}<span>2 KB</span>{/snippet}
|
|
133
|
+
{#snippet more()}
|
|
134
|
+
<Button appearance="subtle" onclick={(e) => e.stopPropagation()} aria-label="More actions"
|
|
135
|
+
><MoreHorizontalRegular width="1rem" /></Button
|
|
136
|
+
>
|
|
137
|
+
{/snippet}
|
|
138
|
+
|
|
139
|
+
<TreeView>
|
|
140
|
+
<TreeViewItem id="report">
|
|
141
|
+
<TreeViewItemContent iconBefore={document} aside={fileSize} actions={more}>Report</TreeViewItemContent>
|
|
142
|
+
</TreeViewItem>
|
|
143
|
+
<TreeViewItem id="annual-summary">
|
|
144
|
+
<TreeViewItemContent iconBefore={document} aside={fileSize} actions={more}>Annual summary</TreeViewItemContent>
|
|
145
|
+
</TreeViewItem>
|
|
146
|
+
</TreeView>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Sizes
|
|
150
|
+
|
|
151
|
+
Set `size` to `small`, `medium` or `large` on the root `TreeView`.
|
|
152
|
+
|
|
153
|
+
```svelte
|
|
154
|
+
<TreeView size="small">...</TreeView>
|
|
155
|
+
<TreeView size="medium">...</TreeView>
|
|
156
|
+
<TreeView size="large">...</TreeView>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Disabled items
|
|
160
|
+
|
|
161
|
+
A disabled item can't be opened or checked, and it is skipped when its parent checks its children, which leaves the parent in its mixed state.
|
|
162
|
+
|
|
163
|
+
```svelte
|
|
164
|
+
<TreeView selectionMode="multiple">
|
|
165
|
+
<TreeViewItem id="fruits" type="branch" open>
|
|
166
|
+
<TreeViewItemContent>Fruits</TreeViewItemContent>
|
|
167
|
+
<TreeView>
|
|
168
|
+
<TreeViewItem id="apple">
|
|
169
|
+
<TreeViewItemContent>Apple</TreeViewItemContent>
|
|
170
|
+
</TreeViewItem>
|
|
171
|
+
<TreeViewItem id="banana" disabled>
|
|
172
|
+
<TreeViewItemContent>Banana</TreeViewItemContent>
|
|
173
|
+
</TreeViewItem>
|
|
174
|
+
</TreeView>
|
|
175
|
+
</TreeViewItem>
|
|
176
|
+
</TreeView>
|
|
177
|
+
```
|
|
178
|
+
|
|
53
179
|
### Controlling Open and Checked State
|
|
54
180
|
|
|
55
181
|
Use the bindable `openItems` and `checkedItems` props on the root `TreeView` to control state programmatically.
|
|
@@ -70,6 +196,45 @@ Use the bindable `openItems` and `checkedItems` props on the root `TreeView` to
|
|
|
70
196
|
</TreeView>
|
|
71
197
|
</TreeViewItem>
|
|
72
198
|
</TreeView>
|
|
199
|
+
|
|
200
|
+
<p>Open: {openItems.join(', ')}</p>
|
|
201
|
+
<p>Checked: {checkedItems.join(', ')}</p>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Events
|
|
205
|
+
|
|
206
|
+
`onCheckedChange` and `onOpenChange` on the TreeView receive the resulting ids. The same props on a TreeViewItem receive the item and its new state, including the children a branch cascades to.
|
|
207
|
+
|
|
208
|
+
```svelte
|
|
209
|
+
<script>
|
|
210
|
+
let treeEvent = $state('');
|
|
211
|
+
let itemEvent = $state('');
|
|
212
|
+
</script>
|
|
213
|
+
|
|
214
|
+
<TreeView
|
|
215
|
+
selectionMode="multiple"
|
|
216
|
+
onCheckedChange={(e, checkedItems) => (treeEvent = 'Checked: ' + checkedItems.join(', '))}
|
|
217
|
+
onOpenChange={(e, openItems) => (treeEvent = 'Open: ' + openItems.join(', '))}
|
|
218
|
+
>
|
|
219
|
+
<TreeViewItem
|
|
220
|
+
id="fruits"
|
|
221
|
+
type="branch"
|
|
222
|
+
onOpenChange={(e, { id, open }) => (itemEvent = id + (open ? ' opened' : ' closed'))}
|
|
223
|
+
>
|
|
224
|
+
<TreeViewItemContent>Fruits</TreeViewItemContent>
|
|
225
|
+
<TreeView>
|
|
226
|
+
<TreeViewItem
|
|
227
|
+
id="apple"
|
|
228
|
+
onCheckedChange={(e, { id, checked }) => (itemEvent = id + (checked ? ' checked' : ' unchecked'))}
|
|
229
|
+
>
|
|
230
|
+
<TreeViewItemContent>Apple</TreeViewItemContent>
|
|
231
|
+
</TreeViewItem>
|
|
232
|
+
</TreeView>
|
|
233
|
+
</TreeViewItem>
|
|
234
|
+
</TreeView>
|
|
235
|
+
|
|
236
|
+
<p>Tree: {treeEvent}</p>
|
|
237
|
+
<p>Item: {itemEvent}</p>
|
|
73
238
|
```
|
|
74
239
|
|
|
75
240
|
## Component Props (TreeView)
|
|
@@ -47,10 +47,8 @@
|
|
|
47
47
|
wrapperAttributes={{
|
|
48
48
|
onclick: (e) => {
|
|
49
49
|
e.stopPropagation();
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
if (ITEM_CONTEXT.disabled) return;
|
|
54
52
|
handleCheck(e, ITEM_CONTEXT.id, !ITEM_CONTEXT.checked);
|
|
55
53
|
}
|
|
56
54
|
}}
|
|
@@ -87,9 +85,12 @@
|
|
|
87
85
|
|
|
88
86
|
<style>
|
|
89
87
|
.fs-tree-view-item-content {
|
|
88
|
+
--chevron-width: 1.2rem;
|
|
89
|
+
--gap: 0.75rem;
|
|
90
|
+
box-sizing: border-box;
|
|
90
91
|
display: flex;
|
|
91
92
|
align-items: center;
|
|
92
|
-
gap:
|
|
93
|
+
gap: var(--gap);
|
|
93
94
|
position: relative;
|
|
94
95
|
border-radius: var(--fs-control-border-radius);
|
|
95
96
|
font-size: var(--fs-body-font-size);
|
|
@@ -100,20 +101,26 @@
|
|
|
100
101
|
width: 100%;
|
|
101
102
|
text-align: left;
|
|
102
103
|
user-select: none;
|
|
104
|
+
&:not(:has(:global(.branch-indicator))) {
|
|
105
|
+
--leaf-offset: calc(var(--chevron-width) + var(--gap) + 0rem);
|
|
106
|
+
}
|
|
103
107
|
&.size-small {
|
|
104
108
|
font-size: var(--fs-body2-font-size);
|
|
105
109
|
padding: 0.125rem 0.5rem;
|
|
106
|
-
padding-left: calc(0.5rem +
|
|
110
|
+
padding-left: calc(0.5rem + 2rem * var(--depth, 0) + var(--leaf-offset, 0rem));
|
|
111
|
+
min-height: 1.75rem;
|
|
107
112
|
}
|
|
108
113
|
&.size-medium {
|
|
109
114
|
font-size: var(--fs-body-font-size);
|
|
110
115
|
padding: 0.25rem 0.625rem;
|
|
111
|
-
padding-left: calc(0.625rem +
|
|
116
|
+
padding-left: calc(0.625rem + 2rem * var(--depth, 0) + var(--leaf-offset, 0rem));
|
|
117
|
+
min-height: 2rem;
|
|
112
118
|
}
|
|
113
119
|
&.size-large {
|
|
114
120
|
font-size: var(--fs-subtitle2-font-size);
|
|
115
121
|
padding: 0.375rem 0.75rem;
|
|
116
|
-
padding-left: calc(0.75rem +
|
|
122
|
+
padding-left: calc(0.75rem + 2rem * var(--depth, 0) + var(--leaf-offset, 0rem));
|
|
123
|
+
min-height: 2.25rem;
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
&::after {
|
|
@@ -155,4 +162,30 @@
|
|
|
155
162
|
fill: currentColor;
|
|
156
163
|
}
|
|
157
164
|
}
|
|
165
|
+
.fs-tree-view-item-icon-before,
|
|
166
|
+
.fs-tree-view-item-icon-after,
|
|
167
|
+
.fs-tree-view-item-aside,
|
|
168
|
+
.fs-tree-view-item-actions {
|
|
169
|
+
display: inline-flex;
|
|
170
|
+
align-items: center;
|
|
171
|
+
flex-shrink: 0;
|
|
172
|
+
}
|
|
173
|
+
.fs-tree-view-item-aside,
|
|
174
|
+
.fs-tree-view-item-actions {
|
|
175
|
+
margin-left: auto;
|
|
176
|
+
}
|
|
177
|
+
.fs-tree-view-item-aside + .fs-tree-view-item-actions {
|
|
178
|
+
margin-left: 0;
|
|
179
|
+
}
|
|
180
|
+
.fs-tree-view-item-actions {
|
|
181
|
+
opacity: 0;
|
|
182
|
+
@media (hover: none) {
|
|
183
|
+
opacity: 1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
.fs-tree-view-item-content:hover .fs-tree-view-item-actions,
|
|
187
|
+
.fs-tree-view-item-content:focus-within .fs-tree-view-item-actions,
|
|
188
|
+
:global(.fs-tree-view-item:focus-visible) > .fs-tree-view-item-content .fs-tree-view-item-actions {
|
|
189
|
+
opacity: 1;
|
|
190
|
+
}
|
|
158
191
|
</style>
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
type = 'item',
|
|
33
33
|
open = (openItems instanceof Set ? openItems.has(id) : openItems.includes(id)) || undefined,
|
|
34
34
|
checked = (checkedItems instanceof Set ? checkedItems.has(id) : checkedItems.includes(id)) || undefined,
|
|
35
|
+
onCheckedChange,
|
|
36
|
+
onOpenChange,
|
|
35
37
|
children,
|
|
36
38
|
...attributes
|
|
37
39
|
}: TreeViewItemProps = $props();
|
|
@@ -61,6 +63,12 @@
|
|
|
61
63
|
get parentId() {
|
|
62
64
|
return itemContext?.id;
|
|
63
65
|
},
|
|
66
|
+
get onCheckedChange() {
|
|
67
|
+
return onCheckedChange;
|
|
68
|
+
},
|
|
69
|
+
get onOpenChange() {
|
|
70
|
+
return onOpenChange;
|
|
71
|
+
},
|
|
64
72
|
get open() {
|
|
65
73
|
return open;
|
|
66
74
|
},
|
|
@@ -190,7 +198,8 @@
|
|
|
190
198
|
outline: 2px solid var(--fs-focus-stroke-outer);
|
|
191
199
|
outline-offset: 2px;
|
|
192
200
|
}
|
|
193
|
-
|
|
201
|
+
/* Only the chevron of this item's own row, not the ones of the branches nested inside it. */
|
|
202
|
+
&.open > :global(.fs-tree-view-item-content .branch-indicator) {
|
|
194
203
|
rotate: 90deg;
|
|
195
204
|
}
|
|
196
205
|
}
|
|
@@ -89,12 +89,21 @@
|
|
|
89
89
|
await tick();
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
const wasChecked = new Map(state.TREE_NODES.entries().map(([nodeId, node]) => [nodeId, !!node.checked]));
|
|
93
|
+
|
|
92
94
|
if (selectionMode === 'single') {
|
|
93
95
|
traversalSyncSingleSelection(id, state.TREE_NODES, checkedItems);
|
|
94
|
-
|
|
96
|
+
} else {
|
|
97
|
+
traversalSync(id, state.TREE_NODES, checkedItems, checked);
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
// Every item the click reached reports its own change, the tree reports the resulting set.
|
|
101
|
+
for (const node of state.TREE_NODES.values()) {
|
|
102
|
+
if (!!node.checked !== wasChecked.get(node.id)) {
|
|
103
|
+
node.onCheckedChange?.(e as Event, { id: node.id, checked: !!node.checked });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
treeViewContext.events.onCheckedChange(e as Event, Array.from(checkedItems));
|
|
98
107
|
},
|
|
99
108
|
openItem: (e, id) => {
|
|
100
109
|
if (openItems instanceof SvelteSet) {
|
|
@@ -105,6 +114,9 @@
|
|
|
105
114
|
const node = treeViewContext.state.TREE_NODES.get(id)!;
|
|
106
115
|
|
|
107
116
|
node.open = true;
|
|
117
|
+
|
|
118
|
+
node.onOpenChange?.(e as Event, { id, open: true });
|
|
119
|
+
treeViewContext.events.onOpenChange(e as Event, Array.from(openItems));
|
|
108
120
|
},
|
|
109
121
|
closeItem: (e, id) => {
|
|
110
122
|
const { state } = treeViewContext;
|
|
@@ -122,6 +134,9 @@
|
|
|
122
134
|
if (virtualizer && state.forceVirtualRender) {
|
|
123
135
|
state.forceVirtualRender = false;
|
|
124
136
|
}
|
|
137
|
+
|
|
138
|
+
node.onOpenChange?.(e as Event, { id, open: false });
|
|
139
|
+
treeViewContext.events.onOpenChange(e as Event, Array.from(openItems));
|
|
125
140
|
},
|
|
126
141
|
registerItem: (node) => {
|
|
127
142
|
treeViewContext.state.TREE_NODES.set(node.id, node);
|
|
@@ -102,10 +102,12 @@ export function traversalSyncSingleSelection(itemId, state, iterable) {
|
|
|
102
102
|
node.indeterminate = false;
|
|
103
103
|
if (iterable instanceof Set) {
|
|
104
104
|
iterable.clear();
|
|
105
|
-
|
|
105
|
+
if (node.checked)
|
|
106
|
+
iterable.add(node.id);
|
|
106
107
|
}
|
|
107
108
|
else if (Array.isArray(iterable)) {
|
|
108
109
|
iterable.length = 0;
|
|
109
|
-
|
|
110
|
+
if (node.checked)
|
|
111
|
+
iterable.push(node.id);
|
|
110
112
|
}
|
|
111
113
|
}
|
|
@@ -14,6 +14,8 @@ export type TreeViewNode = {
|
|
|
14
14
|
checked?: boolean;
|
|
15
15
|
indeterminate?: boolean;
|
|
16
16
|
open?: boolean;
|
|
17
|
+
onCheckedChange?: TreeViewItemProps['onCheckedChange'];
|
|
18
|
+
onOpenChange?: TreeViewItemProps['onOpenChange'];
|
|
17
19
|
};
|
|
18
20
|
export type TreeViewEvent<T extends Record<string, unknown> | Iterable<unknown>> = (e: Event, data: T) => void;
|
|
19
21
|
/**
|
|
@@ -179,6 +181,8 @@ export type TreeViewItemContext = {
|
|
|
179
181
|
readonly id: string;
|
|
180
182
|
readonly type: TreeViewItemType;
|
|
181
183
|
readonly parentId?: string;
|
|
184
|
+
readonly onCheckedChange?: TreeViewItemProps['onCheckedChange'];
|
|
185
|
+
readonly onOpenChange?: TreeViewItemProps['onOpenChange'];
|
|
182
186
|
open?: boolean;
|
|
183
187
|
checked?: boolean;
|
|
184
188
|
indeterminate?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fluentui-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Modern Fluent UI 2 Components for Svelte 5",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Jorge Acosta",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"@humanspeak/svelte-virtual-list": "^0.5.14",
|
|
84
84
|
"@inlang/paraglide-js": "^2.24.1",
|
|
85
85
|
"@inlang/paraglide-js-svelte": "^1.0.4",
|
|
86
|
+
"@jlacostaec/paraglide-markup-svelte": "0.1.0",
|
|
86
87
|
"@jlacostaec/propsmith": "^0.2.0",
|
|
87
88
|
"@playwright/test": "^1.62.1",
|
|
88
89
|
"@sveltejs/adapter-auto": "^7.0.1",
|