fluentui-svelte 0.1.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/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/components/auto-suggest-box/auto-suggest-box.d.ts +2 -0
- package/dist/components/auto-suggest-box/auto-suggest-box.js +2 -0
- package/dist/components/auto-suggest-box/auto-suggest-box.svelte +457 -0
- package/dist/components/auto-suggest-box/auto-suggest-box.svelte.d.ts +4 -0
- package/dist/components/auto-suggest-box/docs/llms.md +73 -0
- package/dist/components/auto-suggest-box/option.svelte +82 -0
- package/dist/components/auto-suggest-box/option.svelte.d.ts +4 -0
- package/dist/components/auto-suggest-box/types.d.ts +140 -0
- package/dist/components/auto-suggest-box/types.js +1 -0
- package/dist/components/avatar/avatar.svelte +269 -0
- package/dist/components/avatar/avatar.svelte.d.ts +20 -0
- package/dist/components/avatar/docs/llms.md +92 -0
- package/dist/components/avatar/type.d.ts +63 -0
- package/dist/components/avatar/type.js +1 -0
- package/dist/components/badge/badge-icon.svelte +103 -0
- package/dist/components/badge/badge-icon.svelte.d.ts +25 -0
- package/dist/components/badge/badge.svelte +163 -0
- package/dist/components/badge/badge.svelte.d.ts +20 -0
- package/dist/components/badge/docs/llms.md +133 -0
- package/dist/components/badge/types.d.ts +70 -0
- package/dist/components/badge/types.js +1 -0
- package/dist/components/badge/utils.svelte.d.ts +16 -0
- package/dist/components/badge/utils.svelte.js +35 -0
- package/dist/components/button/button.svelte +203 -0
- package/dist/components/button/button.svelte.d.ts +38 -0
- package/dist/components/button/docs/llms.md +102 -0
- package/dist/components/button/split-button.svelte +66 -0
- package/dist/components/button/split-button.svelte.d.ts +4 -0
- package/dist/components/button/types.d.ts +84 -0
- package/dist/components/button/types.js +1 -0
- package/dist/components/calendar-date-picker/calendar-date-picker.svelte +119 -0
- package/dist/components/calendar-date-picker/calendar-date-picker.svelte.d.ts +4 -0
- package/dist/components/calendar-date-picker/docs/llms.md +33 -0
- package/dist/components/calendar-date-picker/types.d.ts +36 -0
- package/dist/components/calendar-date-picker/types.js +1 -0
- package/dist/components/calendar-view/calendar-view-controls.svelte +115 -0
- package/dist/components/calendar-view/calendar-view-controls.svelte.d.ts +3 -0
- package/dist/components/calendar-view/calendar-view-days.svelte +210 -0
- package/dist/components/calendar-view/calendar-view-days.svelte.d.ts +3 -0
- package/dist/components/calendar-view/calendar-view-grid.d.ts +38 -0
- package/dist/components/calendar-view/calendar-view-grid.js +140 -0
- package/dist/components/calendar-view/calendar-view-header.svelte +66 -0
- package/dist/components/calendar-view/calendar-view-header.svelte.d.ts +18 -0
- package/dist/components/calendar-view/calendar-view-item.svelte +181 -0
- package/dist/components/calendar-view/calendar-view-item.svelte.d.ts +4 -0
- package/dist/components/calendar-view/calendar-view-months.svelte +165 -0
- package/dist/components/calendar-view/calendar-view-months.svelte.d.ts +3 -0
- package/dist/components/calendar-view/calendar-view-years.svelte +161 -0
- package/dist/components/calendar-view/calendar-view-years.svelte.d.ts +3 -0
- package/dist/components/calendar-view/calendar-view.svelte +451 -0
- package/dist/components/calendar-view/calendar-view.svelte.d.ts +9 -0
- package/dist/components/calendar-view/calendar-view.svelte.js +196 -0
- package/dist/components/calendar-view/docs/llms.md +121 -0
- package/dist/components/calendar-view/index.d.ts +1 -0
- package/dist/components/calendar-view/index.js +1 -0
- package/dist/components/calendar-view/types.d.ts +174 -0
- package/dist/components/calendar-view/types.js +1 -0
- package/dist/components/checkbox/checkbox.svelte +171 -0
- package/dist/components/checkbox/checkbox.svelte.d.ts +40 -0
- package/dist/components/checkbox/docs/llms.md +78 -0
- package/dist/components/checkbox/types.d.ts +50 -0
- package/dist/components/checkbox/types.js +1 -0
- package/dist/components/date-picker/date-picker.svelte +381 -0
- package/dist/components/date-picker/date-picker.svelte.d.ts +4 -0
- package/dist/components/date-picker/docs/llms.md +52 -0
- package/dist/components/date-picker/index.d.ts +1 -0
- package/dist/components/date-picker/index.js +1 -0
- package/dist/components/date-picker/types.d.ts +74 -0
- package/dist/components/date-picker/types.js +1 -0
- package/dist/components/dialog/dialog-actions.svelte +37 -0
- package/dist/components/dialog/dialog-actions.svelte.d.ts +4 -0
- package/dist/components/dialog/dialog-content.svelte +17 -0
- package/dist/components/dialog/dialog-content.svelte.d.ts +4 -0
- package/dist/components/dialog/dialog-surface.svelte +95 -0
- package/dist/components/dialog/dialog-surface.svelte.d.ts +4 -0
- package/dist/components/dialog/dialog-title.svelte +33 -0
- package/dist/components/dialog/dialog-title.svelte.d.ts +25 -0
- package/dist/components/dialog/dialog-trigger.svelte +23 -0
- package/dist/components/dialog/dialog-trigger.svelte.d.ts +4 -0
- package/dist/components/dialog/dialog.svelte +64 -0
- package/dist/components/dialog/dialog.svelte.d.ts +7 -0
- package/dist/components/dialog/dialog.svelte.js +2 -0
- package/dist/components/dialog/docs/llms.md +131 -0
- package/dist/components/dialog/types.d.ts +92 -0
- package/dist/components/dialog/types.js +1 -0
- package/dist/components/divider/divider.svelte +120 -0
- package/dist/components/divider/divider.svelte.d.ts +39 -0
- package/dist/components/divider/docs/llms.md +83 -0
- package/dist/components/divider/types.d.ts +32 -0
- package/dist/components/divider/types.js +1 -0
- package/dist/components/dropdown/docs/llms.md +70 -0
- package/dist/components/dropdown/dropdown-context.d.ts +2 -0
- package/dist/components/dropdown/dropdown-context.js +2 -0
- package/dist/components/dropdown/dropdown-option.svelte +31 -0
- package/dist/components/dropdown/dropdown-option.svelte.d.ts +4 -0
- package/dist/components/dropdown/dropdown.svelte +309 -0
- package/dist/components/dropdown/dropdown.svelte.d.ts +4 -0
- package/dist/components/dropdown/types.d.ts +82 -0
- package/dist/components/dropdown/types.js +1 -0
- package/dist/components/dropdown-select/docs/llms.md +72 -0
- package/dist/components/dropdown-select/dropdown-select-option.svelte +87 -0
- package/dist/components/dropdown-select/dropdown-select-option.svelte.d.ts +4 -0
- package/dist/components/dropdown-select/dropdown-select.svelte +165 -0
- package/dist/components/dropdown-select/dropdown-select.svelte.d.ts +4 -0
- package/dist/components/dropdown-select/dropdown-select.svelte.js +48 -0
- package/dist/components/dropdown-select/index.d.ts +2 -0
- package/dist/components/dropdown-select/index.js +2 -0
- package/dist/components/dropdown-select/types.d.ts +46 -0
- package/dist/components/dropdown-select/types.js +1 -0
- package/dist/components/expander/docs/llms.md +88 -0
- package/dist/components/expander/expander.svelte +249 -0
- package/dist/components/expander/expander.svelte.d.ts +21 -0
- package/dist/components/expander/types.d.ts +47 -0
- package/dist/components/expander/types.js +1 -0
- package/dist/components/flyout/docs/llms.md +31 -0
- package/dist/components/flyout/flyout.svelte +148 -0
- package/dist/components/flyout/flyout.svelte.d.ts +19 -0
- package/dist/components/flyout/types.d.ts +36 -0
- package/dist/components/flyout/types.js +1 -0
- package/dist/components/hyperlink/docs/llms.md +33 -0
- package/dist/components/hyperlink/hyperlink.svelte +66 -0
- package/dist/components/hyperlink/hyperlink.svelte.d.ts +16 -0
- package/dist/components/hyperlink/types.d.ts +14 -0
- package/dist/components/hyperlink/types.js +1 -0
- package/dist/components/info-bar/docs/llms.md +81 -0
- package/dist/components/info-bar/info-bar.svelte +140 -0
- package/dist/components/info-bar/info-bar.svelte.d.ts +19 -0
- package/dist/components/info-bar/types.d.ts +27 -0
- package/dist/components/info-bar/types.js +1 -0
- package/dist/components/label/docs/llms.md +67 -0
- package/dist/components/label/label.svelte +95 -0
- package/dist/components/label/label.svelte.d.ts +4 -0
- package/dist/components/label/types.d.ts +32 -0
- package/dist/components/label/types.js +1 -0
- package/dist/components/list-view/docs/llms.md +110 -0
- package/dist/components/list-view/list-view-item.svelte +168 -0
- package/dist/components/list-view/list-view-item.svelte.d.ts +25 -0
- package/dist/components/list-view/list-view.svelte +137 -0
- package/dist/components/list-view/list-view.svelte.d.ts +25 -0
- package/dist/components/list-view/types.d.ts +95 -0
- package/dist/components/list-view/types.js +1 -0
- package/dist/components/list-view/utils.d.ts +6 -0
- package/dist/components/list-view/utils.js +12 -0
- package/dist/components/menu/context-menu-trigger.svelte +84 -0
- package/dist/components/menu/context-menu-trigger.svelte.d.ts +9 -0
- package/dist/components/menu/docs/llms.md +169 -0
- package/dist/components/menu/menu-divider.svelte +9 -0
- package/dist/components/menu/menu-divider.svelte.d.ts +26 -0
- package/dist/components/menu/menu-group.svelte +31 -0
- package/dist/components/menu/menu-group.svelte.d.ts +4 -0
- package/dist/components/menu/menu-item-checkbox.svelte +143 -0
- package/dist/components/menu/menu-item-checkbox.svelte.d.ts +15 -0
- package/dist/components/menu/menu-item-radio.svelte +147 -0
- package/dist/components/menu/menu-item-radio.svelte.d.ts +15 -0
- package/dist/components/menu/menu-item-switch.svelte +194 -0
- package/dist/components/menu/menu-item-switch.svelte.d.ts +15 -0
- package/dist/components/menu/menu-item.svelte +124 -0
- package/dist/components/menu/menu-item.svelte.d.ts +18 -0
- package/dist/components/menu/menu-list.svelte +38 -0
- package/dist/components/menu/menu-list.svelte.d.ts +5 -0
- package/dist/components/menu/menu-popover.svelte +125 -0
- package/dist/components/menu/menu-popover.svelte.d.ts +12 -0
- package/dist/components/menu/menu-trigger.svelte +121 -0
- package/dist/components/menu/menu-trigger.svelte.d.ts +4 -0
- package/dist/components/menu/menu.svelte +124 -0
- package/dist/components/menu/menu.svelte.d.ts +4 -0
- package/dist/components/menu/menu.svelte.js +77 -0
- package/dist/components/menu/types.d.ts +127 -0
- package/dist/components/menu/types.js +1 -0
- package/dist/components/number-box/docs/llms.md +37 -0
- package/dist/components/number-box/number-box.svelte +115 -0
- package/dist/components/number-box/number-box.svelte.d.ts +15 -0
- package/dist/components/number-box/types.d.ts +16 -0
- package/dist/components/number-box/types.js +1 -0
- package/dist/components/persona/docs/llms.md +107 -0
- package/dist/components/persona/persona.svelte +79 -0
- package/dist/components/persona/persona.svelte.d.ts +4 -0
- package/dist/components/persona/types.d.ts +42 -0
- package/dist/components/persona/types.js +1 -0
- package/dist/components/progress-bar/docs/llms.md +67 -0
- package/dist/components/progress-bar/progress-bar.svelte +167 -0
- package/dist/components/progress-bar/progress-bar.svelte.d.ts +15 -0
- package/dist/components/progress-bar/types.d.ts +39 -0
- package/dist/components/progress-bar/types.js +1 -0
- package/dist/components/progress-ring/docs/llms.md +76 -0
- package/dist/components/progress-ring/progress-ring.svelte +145 -0
- package/dist/components/progress-ring/progress-ring.svelte.d.ts +15 -0
- package/dist/components/progress-ring/types.d.ts +42 -0
- package/dist/components/progress-ring/types.js +1 -0
- package/dist/components/radio-button/docs/llms.md +64 -0
- package/dist/components/radio-button/radio-button.svelte +129 -0
- package/dist/components/radio-button/radio-button.svelte.d.ts +19 -0
- package/dist/components/radio-button/types.d.ts +24 -0
- package/dist/components/radio-button/types.js +1 -0
- package/dist/components/skeleton/docs/llms.md +67 -0
- package/dist/components/skeleton/skeleton.svelte +106 -0
- package/dist/components/skeleton/skeleton.svelte.d.ts +39 -0
- package/dist/components/skeleton/types.d.ts +24 -0
- package/dist/components/skeleton/types.js +1 -0
- package/dist/components/slider/docs/llms.md +75 -0
- package/dist/components/slider/slider.svelte +486 -0
- package/dist/components/slider/slider.svelte.d.ts +19 -0
- package/dist/components/slider/types.d.ts +102 -0
- package/dist/components/slider/types.js +1 -0
- package/dist/components/text-box/docs/llms.md +86 -0
- package/dist/components/text-box/text-box-button.svelte +65 -0
- package/dist/components/text-box/text-box-button.svelte.d.ts +9 -0
- package/dist/components/text-box/text-box.svelte +299 -0
- package/dist/components/text-box/text-box.svelte.d.ts +16 -0
- package/dist/components/text-box/types.d.ts +60 -0
- package/dist/components/text-box/types.js +1 -0
- package/dist/components/textarea/docs/llms.md +55 -0
- package/dist/components/textarea/textarea.svelte +151 -0
- package/dist/components/textarea/textarea.svelte.d.ts +17 -0
- package/dist/components/textarea/types.d.ts +33 -0
- package/dist/components/textarea/types.js +1 -0
- package/dist/components/time-picker/docs/llms.md +47 -0
- package/dist/components/time-picker/index.d.ts +1 -0
- package/dist/components/time-picker/index.js +1 -0
- package/dist/components/time-picker/time-picker.svelte +336 -0
- package/dist/components/time-picker/time-picker.svelte.d.ts +4 -0
- package/dist/components/time-picker/types.d.ts +54 -0
- package/dist/components/time-picker/types.js +1 -0
- package/dist/components/toggle-switch/docs/llms.md +39 -0
- package/dist/components/toggle-switch/toggle-switch.svelte +151 -0
- package/dist/components/toggle-switch/toggle-switch.svelte.d.ts +17 -0
- package/dist/components/toggle-switch/types.d.ts +20 -0
- package/dist/components/toggle-switch/types.js +1 -0
- package/dist/components/tooltip/docs/llms.md +74 -0
- package/dist/components/tooltip/tooltip.svelte +246 -0
- package/dist/components/tooltip/tooltip.svelte.d.ts +18 -0
- package/dist/components/tooltip/types.d.ts +67 -0
- package/dist/components/tooltip/types.js +1 -0
- package/dist/components/tree-view/docs/llms.md +117 -0
- package/dist/components/tree-view/tree-view-item-content.svelte +158 -0
- package/dist/components/tree-view/tree-view-item-content.svelte.d.ts +4 -0
- package/dist/components/tree-view/tree-view-item.svelte +197 -0
- package/dist/components/tree-view/tree-view-item.svelte.d.ts +4 -0
- package/dist/components/tree-view/tree-view.svelte +220 -0
- package/dist/components/tree-view/tree-view.svelte.d.ts +4 -0
- package/dist/components/tree-view/tree-view.svelte.js +111 -0
- package/dist/components/tree-view/types.d.ts +185 -0
- package/dist/components/tree-view/types.js +1 -0
- package/dist/css/class-darkmode.css +186 -0
- package/dist/css/media-darkmode.css +189 -0
- package/dist/css/reset.css +53 -0
- package/dist/css/theme.css +276 -0
- package/dist/css/typography.css +67 -0
- package/dist/css/utils.d.ts +1 -0
- package/dist/css/utils.js +153 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +129 -0
- package/dist/internal/components/dynamic-carousel/dynamic-carousel.svelte +449 -0
- package/dist/internal/components/dynamic-carousel/dynamic-carousel.svelte.d.ts +22 -0
- package/dist/internal/components/render-soc/render-soc.svelte +29 -0
- package/dist/internal/components/render-soc/render-soc.svelte.d.ts +30 -0
- package/dist/internal/constants.d.ts +26 -0
- package/dist/internal/constants.js +4036 -0
- package/dist/internal/context.d.ts +14 -0
- package/dist/internal/context.js +13 -0
- package/dist/internal/dom-utils.svelte.d.ts +6 -0
- package/dist/internal/dom-utils.svelte.js +46 -0
- package/dist/internal/floating-ui/floating.svelte.d.ts +15 -0
- package/dist/internal/floating-ui/floating.svelte.js +26 -0
- package/dist/internal/floating-ui/utils.d.ts +3 -0
- package/dist/internal/floating-ui/utils.js +30 -0
- package/dist/internal/index.d.ts +9 -0
- package/dist/internal/index.js +9 -0
- package/dist/internal/polyfills/expander.svelte.d.ts +21 -0
- package/dist/internal/polyfills/expander.svelte.js +96 -0
- package/dist/internal/transitions.d.ts +32 -0
- package/dist/internal/transitions.js +37 -0
- package/dist/internal/utils.d.ts +22 -0
- package/dist/internal/utils.js +63 -0
- package/dist/providers/fluentui-svelte/fluentui-svelte.d.ts +19 -0
- package/dist/providers/fluentui-svelte/fluentui-svelte.js +11 -0
- package/dist/providers/fluentui-svelte/fluentui-svelte.svelte +68 -0
- package/dist/providers/fluentui-svelte/fluentui-svelte.svelte.d.ts +9 -0
- package/dist/types/index.d.ts +117 -0
- package/dist/types/index.js +1 -0
- package/package.json +142 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Tree View
|
|
2
|
+
|
|
3
|
+
The Tree View displays hierarchical data in a nested list where each item can be expanded, checked or selected. Items are composed with the TreeViewItem and TreeViewItemContent components — the tree does not accept a data prop.
|
|
4
|
+
|
|
5
|
+
> **Early access:** The API, features, and behavior of the component are subject to significant changes. We strongly advise against using this component in production environments at this time.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```svelte
|
|
10
|
+
<script>
|
|
11
|
+
import { TreeView, TreeViewItem, TreeViewItemContent } from 'fluentui-svelte';
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<TreeView selectionMode="multiple">
|
|
15
|
+
<TreeViewItem id="fruits" type="branch">
|
|
16
|
+
<TreeViewItemContent>Fruits</TreeViewItemContent>
|
|
17
|
+
<TreeView>
|
|
18
|
+
<TreeViewItem id="apple">
|
|
19
|
+
<TreeViewItemContent>Apple</TreeViewItemContent>
|
|
20
|
+
</TreeViewItem>
|
|
21
|
+
<TreeViewItem id="banana" disabled>
|
|
22
|
+
<TreeViewItemContent>Banana</TreeViewItemContent>
|
|
23
|
+
</TreeViewItem>
|
|
24
|
+
</TreeView>
|
|
25
|
+
</TreeViewItem>
|
|
26
|
+
<TreeViewItem id="vegetables">
|
|
27
|
+
<TreeViewItemContent>Vegetables</TreeViewItemContent>
|
|
28
|
+
</TreeViewItem>
|
|
29
|
+
</TreeView>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Nesting a TreeView inside a TreeViewItem of type='branch' creates a subtree. Use openItems / checkedItems on the root TreeView (bindable) to control open and checked state programmatically.
|
|
33
|
+
|
|
34
|
+
## Examples
|
|
35
|
+
|
|
36
|
+
### Checkable Items
|
|
37
|
+
|
|
38
|
+
Set `selectionMode="multiple"` to render checkboxes on selectable items.
|
|
39
|
+
|
|
40
|
+
```svelte
|
|
41
|
+
<TreeView selectionMode="multiple">
|
|
42
|
+
<TreeViewItem id="fruits" type="branch">
|
|
43
|
+
<TreeViewItemContent>Fruits</TreeViewItemContent>
|
|
44
|
+
<TreeView>
|
|
45
|
+
<TreeViewItem id="apple">
|
|
46
|
+
<TreeViewItemContent>Apple</TreeViewItemContent>
|
|
47
|
+
</TreeViewItem>
|
|
48
|
+
</TreeView>
|
|
49
|
+
</TreeViewItem>
|
|
50
|
+
</TreeView>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Controlling Open and Checked State
|
|
54
|
+
|
|
55
|
+
Use the bindable `openItems` and `checkedItems` props on the root `TreeView` to control state programmatically.
|
|
56
|
+
|
|
57
|
+
```svelte
|
|
58
|
+
<script>
|
|
59
|
+
let openItems = $state(['fruits']);
|
|
60
|
+
let checkedItems = $state([]);
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<TreeView bind:openItems bind:checkedItems selectionMode="multiple">
|
|
64
|
+
<TreeViewItem id="fruits" type="branch">
|
|
65
|
+
<TreeViewItemContent>Fruits</TreeViewItemContent>
|
|
66
|
+
<TreeView>
|
|
67
|
+
<TreeViewItem id="apple">
|
|
68
|
+
<TreeViewItemContent>Apple</TreeViewItemContent>
|
|
69
|
+
</TreeViewItem>
|
|
70
|
+
</TreeView>
|
|
71
|
+
</TreeViewItem>
|
|
72
|
+
</TreeView>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Component Props (TreeView)
|
|
76
|
+
|
|
77
|
+
| Name | Type | Description |
|
|
78
|
+
| ----------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
79
|
+
| `ref` | `HTMLUListElement` | Bindable DOM reference of the root ul element. |
|
|
80
|
+
| `size` | `'small' \| 'medium' \| 'large'` | Visual size of the tree. Default is 'medium'. |
|
|
81
|
+
| `navigationMode` | `'tree' \| 'treegrid'` | Keyboard navigation mode. Default is 'tree'. |
|
|
82
|
+
| `selectionMode` | `'single' \| 'multiple'` | Selection mode for checkable items. Default is 'multiple'. |
|
|
83
|
+
| `openItems` | `string[] \| SvelteSet<string>` | Bindable list or set of open item ids. Uses SvelteSet when a virtualizer is provided. |
|
|
84
|
+
| `checkedItems` | `string[] \| SvelteSet<string>` | Bindable list or set of checked item ids. Uses SvelteSet when a virtualizer is provided. |
|
|
85
|
+
| `onOpenChange` | `(e: Event, openItems: string[]) => void` | Callback fired when an item is expanded or collapsed. |
|
|
86
|
+
| `onCheckedChange` | `(e: Event, checkedItems: string[]) => void` | Callback fired when an item is checked or unchecked. |
|
|
87
|
+
| `virtualizer` | `TreeViewVirtualizer` | Optional adapter for windowing or virtual list libraries. When provided, only the rendered slice is in the DOM. |
|
|
88
|
+
| `children` | `Snippet` | The TreeViewItem children of the tree. |
|
|
89
|
+
|
|
90
|
+
## Component Props (TreeViewItem)
|
|
91
|
+
|
|
92
|
+
| Name | Type | Description |
|
|
93
|
+
| ----------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
|
|
94
|
+
| `id` | `string` | Unique identifier of the item. Required for open and checked state tracking. |
|
|
95
|
+
| `type` | `'item' \| 'branch'` | Whether the item is a leaf ('item') or a branch that can contain a nested TreeView. |
|
|
96
|
+
| `ref` | `HTMLLIElement` | Bindable DOM reference of the li element. |
|
|
97
|
+
| `value` | `string \| number` | Optional value associated with the item. |
|
|
98
|
+
| `text` | `string` | Optional accessible text label used by the tree. |
|
|
99
|
+
| `open` | `boolean` | Bindable open state for type='branch' items. |
|
|
100
|
+
| `checked` | `boolean` | Bindable checked state for selectable items. |
|
|
101
|
+
| `indeterminate` | `boolean` | Whether the item is in an indeterminate check state. |
|
|
102
|
+
| `disabled` | `boolean` | Disables the item. |
|
|
103
|
+
| `index` | `number` | Explicit index — used with a virtualizer to keep keyboard navigation in sync. |
|
|
104
|
+
| `onOpenChange` | `(e: Event, data: { id: string; open: boolean }) => void` | Callback fired when this item is expanded or collapsed. |
|
|
105
|
+
| `onCheckedChange` | `(e: Event, data: { id: string; checked: boolean }) => void` | Callback fired when this item is checked or unchecked. |
|
|
106
|
+
|
|
107
|
+
## Component Props (TreeViewItemContent)
|
|
108
|
+
|
|
109
|
+
| Name | Type | Description |
|
|
110
|
+
| ------------ | ---------------------- | ----------------------------------------------------------- |
|
|
111
|
+
| `ref` | `HTMLDivElement` | Bindable DOM reference of the layout element. |
|
|
112
|
+
| `expandIcon` | `Snippet \| Component` | Custom icon rendered for the expand or collapse affordance. |
|
|
113
|
+
| `iconBefore` | `Snippet \| Component` | Icon rendered before the item text. |
|
|
114
|
+
| `iconAfter` | `Snippet \| Component` | Icon rendered after the item text. |
|
|
115
|
+
| `aside` | `Snippet \| Component` | Content rendered on the trailing side of the row. |
|
|
116
|
+
| `actions` | `Snippet \| Component` | Interactive actions rendered on hover or focus. |
|
|
117
|
+
| `children` | `Snippet` | The item label content. |
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Checkbox } from '../../index.js';
|
|
3
|
+
import { RenderSoC } from '../../internal/index.js';
|
|
4
|
+
import ChevronRightRegular from 'fluentui-icons-svelte/ChevronRightRegular.svelte';
|
|
5
|
+
import { getTreeViewContext, getTreeViewItemContext } from './tree-view.svelte.js';
|
|
6
|
+
import type { TreeViewItemLayoutProps } from './types.js';
|
|
7
|
+
|
|
8
|
+
let { actions, aside, expandIcon, iconBefore, iconAfter, ref, children, ...attributes }: TreeViewItemLayoutProps =
|
|
9
|
+
$props();
|
|
10
|
+
|
|
11
|
+
const TREE_VIEW_CONTEXT = getTreeViewContext();
|
|
12
|
+
const ITEM_CONTEXT = getTreeViewItemContext();
|
|
13
|
+
|
|
14
|
+
if (!TREE_VIEW_CONTEXT) throw new Error('TreeViewItemLayout must be used within a TreeView');
|
|
15
|
+
if (!ITEM_CONTEXT) throw new Error('TreeViewItemContent must be used within a TreeViewItem');
|
|
16
|
+
|
|
17
|
+
const { config, methods } = TREE_VIEW_CONTEXT;
|
|
18
|
+
|
|
19
|
+
const { size } = config;
|
|
20
|
+
|
|
21
|
+
const { handleCheck } = methods;
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<!-- Presentational row. The interactive element is the parent treeitem (`<li>`); this
|
|
25
|
+
div is plain markup so it is not exposed as a separate control. -->
|
|
26
|
+
<div
|
|
27
|
+
class={['fs-tree-view-item-content', `size-${size}`, ITEM_CONTEXT.disabled && 'disabled']}
|
|
28
|
+
style="--depth: {ITEM_CONTEXT.depth}"
|
|
29
|
+
bind:this={ref}
|
|
30
|
+
{...attributes}
|
|
31
|
+
>
|
|
32
|
+
{#if expandIcon}
|
|
33
|
+
<span class="fs-tree-view-item-expand-icon">
|
|
34
|
+
<RenderSoC SoC={expandIcon} />
|
|
35
|
+
</span>
|
|
36
|
+
{/if}
|
|
37
|
+
|
|
38
|
+
{#if ITEM_CONTEXT.type === 'branch'}
|
|
39
|
+
<ChevronRightRegular width="1.2rem" class="branch-indicator" />
|
|
40
|
+
{/if}
|
|
41
|
+
|
|
42
|
+
<Checkbox
|
|
43
|
+
hidden
|
|
44
|
+
checked={ITEM_CONTEXT.checked}
|
|
45
|
+
indeterminate={ITEM_CONTEXT.indeterminate}
|
|
46
|
+
disabled={ITEM_CONTEXT.disabled}
|
|
47
|
+
wrapperAttributes={{
|
|
48
|
+
onclick: (e) => {
|
|
49
|
+
e.stopPropagation();
|
|
50
|
+
if (ITEM_CONTEXT.disabled) {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
handleCheck(e, ITEM_CONTEXT.id, !ITEM_CONTEXT.checked);
|
|
55
|
+
}
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
{#if iconBefore}
|
|
60
|
+
<span class="fs-tree-view-item-icon-before">
|
|
61
|
+
<RenderSoC SoC={iconBefore} />
|
|
62
|
+
</span>
|
|
63
|
+
{/if}
|
|
64
|
+
|
|
65
|
+
<span class="fs-tree-view-item-label" id="{ITEM_CONTEXT.id}-label">
|
|
66
|
+
{@render children?.()}
|
|
67
|
+
</span>
|
|
68
|
+
|
|
69
|
+
{#if iconAfter}
|
|
70
|
+
<span class="fs-tree-view-item-icon-after">
|
|
71
|
+
<RenderSoC SoC={iconAfter} />
|
|
72
|
+
</span>
|
|
73
|
+
{/if}
|
|
74
|
+
|
|
75
|
+
{#if aside}
|
|
76
|
+
<span class="fs-tree-view-item-aside">
|
|
77
|
+
<RenderSoC SoC={aside} />
|
|
78
|
+
</span>
|
|
79
|
+
{/if}
|
|
80
|
+
|
|
81
|
+
{#if actions}
|
|
82
|
+
<span class="fs-tree-view-item-actions">
|
|
83
|
+
<RenderSoC SoC={actions} />
|
|
84
|
+
</span>
|
|
85
|
+
{/if}
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<style>
|
|
89
|
+
.fs-tree-view-item-content {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: 0.75rem;
|
|
93
|
+
position: relative;
|
|
94
|
+
border-radius: var(--fs-control-border-radius);
|
|
95
|
+
font-size: var(--fs-body-font-size);
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
outline: none;
|
|
98
|
+
background: none;
|
|
99
|
+
border: none;
|
|
100
|
+
width: 100%;
|
|
101
|
+
text-align: left;
|
|
102
|
+
user-select: none;
|
|
103
|
+
&.size-small {
|
|
104
|
+
font-size: var(--fs-body2-font-size);
|
|
105
|
+
padding: 0.125rem 0.5rem;
|
|
106
|
+
padding-left: calc(0.5rem + 1.5rem * var(--depth, 0));
|
|
107
|
+
}
|
|
108
|
+
&.size-medium {
|
|
109
|
+
font-size: var(--fs-body-font-size);
|
|
110
|
+
padding: 0.25rem 0.625rem;
|
|
111
|
+
padding-left: calc(0.625rem + 1.5rem * var(--depth, 0));
|
|
112
|
+
}
|
|
113
|
+
&.size-large {
|
|
114
|
+
font-size: var(--fs-subtitle2-font-size);
|
|
115
|
+
padding: 0.375rem 0.75rem;
|
|
116
|
+
padding-left: calc(0.75rem + 1.5rem * var(--depth, 0));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&::after {
|
|
120
|
+
content: '';
|
|
121
|
+
display: block;
|
|
122
|
+
width: 0.188rem;
|
|
123
|
+
height: 0;
|
|
124
|
+
background-color: var(--fs-accent-fill-default);
|
|
125
|
+
position: absolute;
|
|
126
|
+
left: 0;
|
|
127
|
+
top: 50%;
|
|
128
|
+
transform: translateY(-50%);
|
|
129
|
+
border-radius: 1rem;
|
|
130
|
+
transition: height 0.2s ease-in-out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
&:hover {
|
|
134
|
+
background-color: var(--fs-subtle-fill-secondary);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
&.selected {
|
|
138
|
+
background-color: var(--fs-subtle-fill-secondary);
|
|
139
|
+
|
|
140
|
+
&::after {
|
|
141
|
+
height: 50%;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
&.disabled {
|
|
146
|
+
opacity: 0.4;
|
|
147
|
+
cursor: not-allowed;
|
|
148
|
+
pointer-events: none;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
:global(.tree-view-item-icon) {
|
|
152
|
+
width: 1.25rem;
|
|
153
|
+
height: 1.25rem;
|
|
154
|
+
flex-shrink: 0;
|
|
155
|
+
fill: currentColor;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
</style>
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, tick } from 'svelte';
|
|
3
|
+
import { PREFIX } from '../../internal/constants.js';
|
|
4
|
+
import {
|
|
5
|
+
getTreeViewDepth,
|
|
6
|
+
getTreeViewContext,
|
|
7
|
+
setTreeViewItemContext,
|
|
8
|
+
getTreeViewItemContext
|
|
9
|
+
} from './tree-view.svelte.js';
|
|
10
|
+
import type { TreeViewItemContext, TreeViewItemProps } from './types.js';
|
|
11
|
+
|
|
12
|
+
const COMPONENT_NAME = 'TreeViewItem';
|
|
13
|
+
const FALLBACK_ID = $props.id();
|
|
14
|
+
const ID = `${PREFIX}-${COMPONENT_NAME}-${FALLBACK_ID}`;
|
|
15
|
+
|
|
16
|
+
const context = getTreeViewContext();
|
|
17
|
+
|
|
18
|
+
const itemContext = getTreeViewItemContext();
|
|
19
|
+
|
|
20
|
+
if (!context) throw new Error('TreeViewItem must be used within a TreeView');
|
|
21
|
+
|
|
22
|
+
// Honor the `checked` and `open` props if provided, otherwise derive them from the parent tree's state.
|
|
23
|
+
const { checkedItems, openItems } = context.state;
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
ref,
|
|
27
|
+
index,
|
|
28
|
+
id = ID,
|
|
29
|
+
disabled,
|
|
30
|
+
value = id,
|
|
31
|
+
indeterminate,
|
|
32
|
+
type = 'item',
|
|
33
|
+
open = (openItems instanceof Set ? openItems.has(id) : openItems.includes(id)) || undefined,
|
|
34
|
+
checked = (checkedItems instanceof Set ? checkedItems.has(id) : checkedItems.includes(id)) || undefined,
|
|
35
|
+
children,
|
|
36
|
+
...attributes
|
|
37
|
+
}: TreeViewItemProps = $props();
|
|
38
|
+
|
|
39
|
+
const depth = getTreeViewDepth() ?? 0;
|
|
40
|
+
|
|
41
|
+
const { config, methods } = context;
|
|
42
|
+
|
|
43
|
+
const { size } = config;
|
|
44
|
+
|
|
45
|
+
const itemState: TreeViewItemContext = {
|
|
46
|
+
get disabled() {
|
|
47
|
+
return itemContext?.disabled ?? disabled;
|
|
48
|
+
},
|
|
49
|
+
get depth() {
|
|
50
|
+
return depth;
|
|
51
|
+
},
|
|
52
|
+
get value() {
|
|
53
|
+
return value;
|
|
54
|
+
},
|
|
55
|
+
get id() {
|
|
56
|
+
return id;
|
|
57
|
+
},
|
|
58
|
+
get type() {
|
|
59
|
+
return type;
|
|
60
|
+
},
|
|
61
|
+
get parentId() {
|
|
62
|
+
return itemContext?.id;
|
|
63
|
+
},
|
|
64
|
+
get open() {
|
|
65
|
+
return open;
|
|
66
|
+
},
|
|
67
|
+
set open(v) {
|
|
68
|
+
open = v;
|
|
69
|
+
},
|
|
70
|
+
get checked() {
|
|
71
|
+
return checked;
|
|
72
|
+
},
|
|
73
|
+
set checked(v) {
|
|
74
|
+
checked = v;
|
|
75
|
+
},
|
|
76
|
+
get indeterminate() {
|
|
77
|
+
return indeterminate;
|
|
78
|
+
},
|
|
79
|
+
set indeterminate(v) {
|
|
80
|
+
indeterminate = v;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
setTreeViewItemContext(itemState);
|
|
85
|
+
|
|
86
|
+
// Virtualized tree: the windowing library mounts/unmounts (and may reuse) rows as it
|
|
87
|
+
// scrolls, so register via an effect that re-runs when the identity changes and cleans
|
|
88
|
+
// up the previous id.
|
|
89
|
+
if (config.virtualized && !itemContext) {
|
|
90
|
+
$effect(() => {
|
|
91
|
+
const _id: string = id;
|
|
92
|
+
|
|
93
|
+
methods.registerItem(itemState);
|
|
94
|
+
return () => methods.unregisterItem(_id);
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
// Static tree: register this node once so a branch's tri-state can be derived from its
|
|
98
|
+
// descendants.
|
|
99
|
+
onMount(() => {
|
|
100
|
+
methods.registerItem(itemState);
|
|
101
|
+
return () => methods.unregisterItem(id);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const isBranch = $derived(type === 'branch');
|
|
106
|
+
const isVirtualRoot = $derived(config.virtualized && !itemContext);
|
|
107
|
+
|
|
108
|
+
const handleClick = (e: MouseEvent) => {
|
|
109
|
+
if (itemState.disabled) return;
|
|
110
|
+
// Ignore clicks bubbling up from a descendant treeitem's own row.
|
|
111
|
+
if ((e.target as HTMLElement).closest('.fs-tree-view-item') !== e.currentTarget) return;
|
|
112
|
+
if (isBranch) {
|
|
113
|
+
if (open) methods.closeItem(e, id);
|
|
114
|
+
else methods.openItem(e, id);
|
|
115
|
+
} else {
|
|
116
|
+
methods.handleCheck(e, id, !checked);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const handleKeydown = async (e: KeyboardEvent) => {
|
|
121
|
+
if (e.target !== e.currentTarget || itemState.disabled) return;
|
|
122
|
+
|
|
123
|
+
switch (e.key) {
|
|
124
|
+
case 'Enter':
|
|
125
|
+
case ' ':
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
methods.handleCheck(e, id, !checked);
|
|
128
|
+
break;
|
|
129
|
+
case 'ArrowRight':
|
|
130
|
+
if (isBranch && !open) {
|
|
131
|
+
methods.openItem(e, id);
|
|
132
|
+
|
|
133
|
+
// NOTE: This apparently works, maybe the problem is that TabSpot is losing the order of the items...
|
|
134
|
+
// TODO: investigate further.
|
|
135
|
+
if (config.virtualized) {
|
|
136
|
+
await tick();
|
|
137
|
+
|
|
138
|
+
const firstChildElement = ref?.querySelector(
|
|
139
|
+
`:scope .fs-tree-view-item:not([aria-disabled="true"])[aria-level="${depth + 2}"]`
|
|
140
|
+
) as HTMLElement;
|
|
141
|
+
|
|
142
|
+
firstChildElement?.focus();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
case 'ArrowLeft':
|
|
148
|
+
if (itemContext?.id) {
|
|
149
|
+
methods.closeItem(e, itemContext?.id);
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
<li
|
|
157
|
+
{id}
|
|
158
|
+
class={['fs-tree-view-item', `fs-tree-item-size-${size}`, { open }]}
|
|
159
|
+
style="--depth: {depth}"
|
|
160
|
+
role="treeitem"
|
|
161
|
+
aria-expanded={type === 'branch' ? open : undefined}
|
|
162
|
+
aria-checked={indeterminate ? 'mixed' : checked}
|
|
163
|
+
aria-level={depth + 1}
|
|
164
|
+
aria-labelledby="{id}-label"
|
|
165
|
+
data-index={isVirtualRoot ? index : undefined}
|
|
166
|
+
data-value={value}
|
|
167
|
+
aria-disabled={itemState.disabled ? 'true' : undefined}
|
|
168
|
+
onclick={handleClick}
|
|
169
|
+
onkeydown={handleKeydown}
|
|
170
|
+
bind:this={ref}
|
|
171
|
+
{...attributes}
|
|
172
|
+
>
|
|
173
|
+
{@render children?.()}
|
|
174
|
+
</li>
|
|
175
|
+
|
|
176
|
+
<style>
|
|
177
|
+
.fs-tree-view-item {
|
|
178
|
+
list-style: none;
|
|
179
|
+
display: flex;
|
|
180
|
+
flex-direction: column;
|
|
181
|
+
/* The treeitem itself is the focus target; suppress its outline and show the
|
|
182
|
+
focus ring on its row instead. */
|
|
183
|
+
outline: none;
|
|
184
|
+
&:not(.open) :global(.fs-tree-view) {
|
|
185
|
+
height: 0;
|
|
186
|
+
overflow: hidden;
|
|
187
|
+
opacity: 0;
|
|
188
|
+
}
|
|
189
|
+
&:focus-visible > :global(.fs-tree-view-item-content) {
|
|
190
|
+
outline: 2px solid var(--fs-focus-stroke-outer);
|
|
191
|
+
outline-offset: 2px;
|
|
192
|
+
}
|
|
193
|
+
&.open :global(.branch-indicator) {
|
|
194
|
+
rotate: 90deg;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
</style>
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, tick } from 'svelte';
|
|
3
|
+
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
4
|
+
import { getTabspotAttributes, tabspotVirtual } from 'tabspot';
|
|
5
|
+
import {
|
|
6
|
+
getTreeViewContext,
|
|
7
|
+
getTreeViewDepth,
|
|
8
|
+
setTreeViewContext,
|
|
9
|
+
setTreeViewDepth,
|
|
10
|
+
getTreeViewItemContext,
|
|
11
|
+
traversalSync,
|
|
12
|
+
traversalSyncSingleSelection
|
|
13
|
+
} from './tree-view.svelte.js';
|
|
14
|
+
import type { TreeViewProps, TreeViewContext, TreeViewNode } from './types.js';
|
|
15
|
+
|
|
16
|
+
const CONTEXT = getTreeViewContext();
|
|
17
|
+
const TREE_NODES = new SvelteMap<string, TreeViewNode>();
|
|
18
|
+
const DEPTH = getTreeViewDepth() || 0;
|
|
19
|
+
const ITEM_CONTEXT = getTreeViewItemContext();
|
|
20
|
+
const IS_SUB_TREE = $derived(!!CONTEXT);
|
|
21
|
+
|
|
22
|
+
if (CONTEXT) setTreeViewDepth(DEPTH + 1);
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
virtualizer,
|
|
26
|
+
navigationMode = CONTEXT?.config.navigationMode ?? 'tree',
|
|
27
|
+
checkedItems = $bindable(CONTEXT?.state.checkedItems ?? (virtualizer !== undefined ? new SvelteSet<string>() : [])),
|
|
28
|
+
openItems = $bindable(CONTEXT?.state.openItems ?? (virtualizer !== undefined ? new SvelteSet<string>() : [])),
|
|
29
|
+
ref = $bindable(),
|
|
30
|
+
onOpenChange,
|
|
31
|
+
onCheckedChange,
|
|
32
|
+
selectionMode = CONTEXT?.state.selectionMode ?? 'multiple',
|
|
33
|
+
size = 'medium',
|
|
34
|
+
children,
|
|
35
|
+
...attributes
|
|
36
|
+
}: TreeViewProps = $props();
|
|
37
|
+
|
|
38
|
+
const config: TreeViewContext['config'] = $derived({
|
|
39
|
+
navigationMode: CONTEXT?.config.navigationMode || navigationMode,
|
|
40
|
+
size: CONTEXT?.config.size || size,
|
|
41
|
+
virtualized: CONTEXT?.config.virtualized ?? !!virtualizer,
|
|
42
|
+
virtualCount: CONTEXT?.config.virtualCount ?? virtualizer?.size
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
let forceVirtualRender = $state(false);
|
|
46
|
+
|
|
47
|
+
const _state: TreeViewContext['state'] = {
|
|
48
|
+
get openItems() {
|
|
49
|
+
return openItems;
|
|
50
|
+
},
|
|
51
|
+
get checkedItems() {
|
|
52
|
+
return checkedItems;
|
|
53
|
+
},
|
|
54
|
+
get selectionMode() {
|
|
55
|
+
return selectionMode;
|
|
56
|
+
},
|
|
57
|
+
get TREE_NODES() {
|
|
58
|
+
return CONTEXT?.state.TREE_NODES ?? TREE_NODES;
|
|
59
|
+
},
|
|
60
|
+
get forceVirtualRender() {
|
|
61
|
+
return CONTEXT?.state.forceVirtualRender ?? forceVirtualRender;
|
|
62
|
+
},
|
|
63
|
+
set forceVirtualRender(v) {
|
|
64
|
+
if (CONTEXT?.state.forceVirtualRender) {
|
|
65
|
+
CONTEXT.state.forceVirtualRender = v;
|
|
66
|
+
} else {
|
|
67
|
+
forceVirtualRender = v;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const events: TreeViewContext['events'] = $derived({
|
|
73
|
+
onCheckedChange: CONTEXT?.events.onCheckedChange ?? ((e, data) => onCheckedChange?.(e, data)),
|
|
74
|
+
onOpenChange: CONTEXT?.events.onOpenChange ?? ((e, data) => onOpenChange?.(e, data))
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const methods: TreeViewContext['methods'] = {
|
|
78
|
+
handleCheck: async (e, id, checked) => {
|
|
79
|
+
if (virtualizer) {
|
|
80
|
+
_state.forceVirtualRender = true;
|
|
81
|
+
await tick();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (_state.selectionMode === 'single') {
|
|
85
|
+
traversalSyncSingleSelection(id, _state.TREE_NODES, checkedItems);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
traversalSync(id, _state.TREE_NODES, checkedItems, checked);
|
|
90
|
+
},
|
|
91
|
+
openItem: (e, id) => {
|
|
92
|
+
if (openItems instanceof SvelteSet) {
|
|
93
|
+
openItems.add(id);
|
|
94
|
+
} else {
|
|
95
|
+
openItems = [...openItems, id];
|
|
96
|
+
}
|
|
97
|
+
const node = _state.TREE_NODES.get(id)!;
|
|
98
|
+
|
|
99
|
+
node.open = true;
|
|
100
|
+
},
|
|
101
|
+
closeItem: (e, id) => {
|
|
102
|
+
if (openItems instanceof SvelteSet) {
|
|
103
|
+
openItems.delete(id);
|
|
104
|
+
} else {
|
|
105
|
+
openItems = openItems.filter((itemId) => itemId !== id);
|
|
106
|
+
}
|
|
107
|
+
const node = _state.TREE_NODES.get(id)!;
|
|
108
|
+
|
|
109
|
+
node.open = false;
|
|
110
|
+
|
|
111
|
+
// If
|
|
112
|
+
if (virtualizer && _state.forceVirtualRender) {
|
|
113
|
+
_state.forceVirtualRender = false;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
registerItem: (node) => {
|
|
117
|
+
_state.TREE_NODES.set(node.id, node);
|
|
118
|
+
},
|
|
119
|
+
unregisterItem: (id) => {
|
|
120
|
+
_state.TREE_NODES.delete(id);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const treeViewContext: TreeViewContext = {
|
|
125
|
+
get config() {
|
|
126
|
+
return config;
|
|
127
|
+
},
|
|
128
|
+
get state() {
|
|
129
|
+
return _state;
|
|
130
|
+
},
|
|
131
|
+
get events() {
|
|
132
|
+
return events;
|
|
133
|
+
},
|
|
134
|
+
get methods() {
|
|
135
|
+
return methods;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
setTreeViewContext(treeViewContext);
|
|
140
|
+
|
|
141
|
+
const tabspotAttributes = $derived(
|
|
142
|
+
!IS_SUB_TREE
|
|
143
|
+
? getTabspotAttributes({
|
|
144
|
+
root: {
|
|
145
|
+
manageSpecialKeys: true
|
|
146
|
+
},
|
|
147
|
+
mover: { axis: 'vertical', items: '.fs-tree-view-item', skip: '[aria-disabled="true"]' }
|
|
148
|
+
})
|
|
149
|
+
: getTabspotAttributes({
|
|
150
|
+
mover: { axis: 'vertical', items: '.fs-tree-view-item', skip: '[aria-disabled="true"]' },
|
|
151
|
+
grouper: { enterDirection: 'right', exitDirection: 'left' }
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
let typeBuffer = $state('');
|
|
156
|
+
let typeTimer: ReturnType<typeof setTimeout> | undefined = $state();
|
|
157
|
+
|
|
158
|
+
// APG type-ahead: typing printable characters moves focus to the next visible
|
|
159
|
+
// treeitem whose label starts with the typed string. Only the root tree listens;
|
|
160
|
+
// nav/activation keys are handled by tabspot and the treeitems themselves.
|
|
161
|
+
const handleTypeahead = (e: KeyboardEvent) => {
|
|
162
|
+
if (IS_SUB_TREE || !ref) return;
|
|
163
|
+
if (e.key.length !== 1 || e.key === ' ' || e.altKey || e.ctrlKey || e.metaKey) return;
|
|
164
|
+
|
|
165
|
+
typeBuffer += e.key.toLowerCase();
|
|
166
|
+
clearTimeout(typeTimer);
|
|
167
|
+
typeTimer = setTimeout(() => {
|
|
168
|
+
typeBuffer = '';
|
|
169
|
+
}, 500);
|
|
170
|
+
|
|
171
|
+
const items = Array.from(ref.querySelectorAll<HTMLElement>('.fs-tree-view-item')).filter((el) =>
|
|
172
|
+
el.checkVisibility ? el.checkVisibility() : el.offsetParent !== null
|
|
173
|
+
);
|
|
174
|
+
const start = items.indexOf(document.activeElement as HTMLElement);
|
|
175
|
+
|
|
176
|
+
for (let i = 1; i <= items.length; i++) {
|
|
177
|
+
const item = items[(start + i) % items.length];
|
|
178
|
+
const label = item.querySelector('.fs-tree-view-item-label')?.textContent?.trim().toLowerCase();
|
|
179
|
+
if (label?.startsWith(typeBuffer)) {
|
|
180
|
+
item.focus();
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Add tabspot virtualizer for correct focus management
|
|
187
|
+
onMount(() => {
|
|
188
|
+
if (!IS_SUB_TREE || !ref || !virtualizer) return;
|
|
189
|
+
return tabspotVirtual(ref, {
|
|
190
|
+
scrollToIndex: (index) => virtualizer.scrollToIndex?.(index),
|
|
191
|
+
count: () => virtualizer.size
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
</script>
|
|
195
|
+
|
|
196
|
+
<!-- Render only when necessary (virtualizer perf) -->
|
|
197
|
+
{#if _state.forceVirtualRender || (config.virtualized && (!IS_SUB_TREE || ITEM_CONTEXT?.open)) || !config.virtualized}
|
|
198
|
+
<ul
|
|
199
|
+
class="fs-tree-view"
|
|
200
|
+
role={IS_SUB_TREE ? 'group' : 'tree'}
|
|
201
|
+
aria-multiselectable={selectionMode !== 'single'}
|
|
202
|
+
onkeydown={!IS_SUB_TREE ? handleTypeahead : undefined}
|
|
203
|
+
bind:this={ref}
|
|
204
|
+
{...tabspotAttributes}
|
|
205
|
+
{...attributes}
|
|
206
|
+
>
|
|
207
|
+
{@render children?.()}
|
|
208
|
+
</ul>
|
|
209
|
+
{/if}
|
|
210
|
+
|
|
211
|
+
<style>
|
|
212
|
+
.fs-tree-view {
|
|
213
|
+
display: flex;
|
|
214
|
+
flex-direction: column;
|
|
215
|
+
list-style: none;
|
|
216
|
+
padding: 0;
|
|
217
|
+
margin: 0;
|
|
218
|
+
gap: 0.125rem;
|
|
219
|
+
}
|
|
220
|
+
</style>
|