fluentui-svelte 0.3.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.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/dist/components/auto-suggest-box/docs/llms.md +76 -15
  3. package/dist/components/breadcrumb/breadcrumb-button.svelte +130 -0
  4. package/dist/components/breadcrumb/breadcrumb-button.svelte.d.ts +35 -0
  5. package/dist/components/breadcrumb/breadcrumb-divider.svelte +39 -0
  6. package/dist/components/breadcrumb/breadcrumb-divider.svelte.d.ts +14 -0
  7. package/dist/components/breadcrumb/breadcrumb-item.svelte +30 -0
  8. package/dist/components/breadcrumb/breadcrumb-item.svelte.d.ts +16 -0
  9. package/dist/components/breadcrumb/breadcrumb.svelte +101 -0
  10. package/dist/components/breadcrumb/breadcrumb.svelte.d.ts +25 -0
  11. package/dist/components/breadcrumb/docs/llms.md +193 -0
  12. package/dist/components/breadcrumb/types.d.ts +80 -0
  13. package/dist/components/breadcrumb/types.js +1 -0
  14. package/dist/components/card/card-header.svelte +2 -2
  15. package/dist/components/dropdown/dropdown.svelte +1 -0
  16. package/dist/components/list-view/docs/llms.md +79 -27
  17. package/dist/components/list-view/list-view-item.svelte +14 -11
  18. package/dist/components/list-view/list-view.svelte +3 -2
  19. package/dist/components/list-view/types.d.ts +5 -1
  20. package/dist/components/menu/docs/llms.md +16 -0
  21. package/dist/components/menu/menu-popover.svelte +7 -12
  22. package/dist/components/menu/menu-popover.svelte.d.ts +2 -10
  23. package/dist/components/menu/types.d.ts +21 -0
  24. package/dist/components/tab-view/docs/llms.md +146 -0
  25. package/dist/components/tab-view/tab-view-tab.svelte +290 -0
  26. package/dist/components/tab-view/tab-view-tab.svelte.d.ts +20 -0
  27. package/dist/components/tab-view/tab-view.svelte +188 -0
  28. package/dist/components/tab-view/tab-view.svelte.d.ts +26 -0
  29. package/dist/components/tab-view/tab-view.svelte.js +15 -0
  30. package/dist/components/tab-view/types.d.ts +79 -0
  31. package/dist/components/tab-view/types.js +1 -0
  32. package/dist/components/top-nav/docs/llms.md +176 -0
  33. package/dist/components/top-nav/top-nav-item.svelte +234 -0
  34. package/dist/components/top-nav/top-nav-item.svelte.d.ts +41 -0
  35. package/dist/components/top-nav/top-nav.svelte +259 -0
  36. package/dist/components/top-nav/top-nav.svelte.d.ts +26 -0
  37. package/dist/components/top-nav/top-nav.svelte.js +18 -0
  38. package/dist/components/top-nav/types.d.ts +89 -0
  39. package/dist/components/top-nav/types.js +1 -0
  40. package/dist/components/tree-view/docs/llms.md +166 -1
  41. package/dist/components/tree-view/tree-view-item-content.svelte +41 -8
  42. package/dist/components/tree-view/tree-view-item.svelte +10 -1
  43. package/dist/components/tree-view/tree-view.svelte +17 -2
  44. package/dist/components/tree-view/tree-view.svelte.js +4 -2
  45. package/dist/components/tree-view/types.d.ts +4 -0
  46. package/dist/css/class-darkmode.css +1 -1
  47. package/dist/css/media-darkmode.css +1 -1
  48. package/dist/css/theme.css +1 -1
  49. package/dist/index.d.ts +8 -0
  50. package/dist/index.js +14 -0
  51. package/package.json +2 -1
@@ -0,0 +1,193 @@
1
+ # Breadcrumb
2
+
3
+ A breadcrumb shows where a page sits in the hierarchy above it, and gives the reader a way back up. It renders a `nav` landmark around an ordered list, so a screen reader can jump straight to it.
4
+
5
+ The landmark names itself `Breadcrumb`, the trail is an `ol` so its order is part of the markup rather than of the styling, the last step carries `aria-current="page"`, and the dividers are held out of the accessibility tree — a trail of three steps reads as three, not as five. Every one of those can be overridden: pass your own `aria-label`, or an `aria-labelledby`, and the default steps aside.
6
+
7
+ To create a breadcrumb, you will need to use the following components:
8
+
9
+ - `Breadcrumb`: The landmark and the list. It creates the context the rest read.
10
+ - `BreadcrumbItem`: One step of the trail — the list item the step lives in. It takes anything, not only a step.
11
+ - `BreadcrumbButton`: A step that can be activated: an anchor when it goes somewhere, a button when it does something.
12
+ - `BreadcrumbDivider`: The mark between two steps. It is decoration, and is hidden from assistive technology.
13
+
14
+ ## Usage
15
+
16
+ A breadcrumb is a list of steps with a divider between them. The last step is the page the reader is already on, so it is marked `current` rather than linked away.
17
+
18
+ ```svelte
19
+ <script>
20
+ import { Breadcrumb, BreadcrumbItem, BreadcrumbButton, BreadcrumbDivider } from 'fluentui-svelte';
21
+ </script>
22
+
23
+ <Breadcrumb>
24
+ <BreadcrumbItem>
25
+ <BreadcrumbButton as="a" href="/">Home</BreadcrumbButton>
26
+ </BreadcrumbItem>
27
+ <BreadcrumbDivider />
28
+ <BreadcrumbItem>
29
+ <BreadcrumbButton as="a" href="/files">Files</BreadcrumbButton>
30
+ </BreadcrumbItem>
31
+ <BreadcrumbDivider />
32
+ <BreadcrumbItem>
33
+ <BreadcrumbButton current>Quarterly report</BreadcrumbButton>
34
+ </BreadcrumbItem>
35
+ </Breadcrumb>
36
+ ```
37
+
38
+ ## Examples
39
+
40
+ ### Size
41
+
42
+ `size` sets the type, the height and the divider of every step at once.
43
+
44
+ ```svelte
45
+ <Breadcrumb size="small">...</Breadcrumb>
46
+ <Breadcrumb size="medium">...</Breadcrumb>
47
+ <Breadcrumb size="large">...</Breadcrumb>
48
+ ```
49
+
50
+ ### Icon
51
+
52
+ `icon` takes a snippet or a component and renders it beside the label, on the side `iconPosition` asks for.
53
+
54
+ ```svelte
55
+ <BreadcrumbItem>
56
+ <BreadcrumbButton as="a" href="/files" icon={FolderRegular}>Files</BreadcrumbButton>
57
+ </BreadcrumbItem>
58
+ ```
59
+
60
+ ### Current Step
61
+
62
+ `current` marks the step as the page the trail ends at. It announces `aria-current="page"` and reads as text rather than as a way out, so the last step does not offer to take the reader where they already are.
63
+
64
+ ```svelte
65
+ <BreadcrumbItem>
66
+ <BreadcrumbButton current>Quarterly report</BreadcrumbButton>
67
+ </BreadcrumbItem>
68
+ ```
69
+
70
+ ### Overflow Menu
71
+
72
+ A step takes whatever you put in it, so a long trail folds its middle into a menu by making that menu a step of its own. Which steps fold is your call — slice the list wherever the layout needs it.
73
+
74
+ ```svelte
75
+ <script>
76
+ // Keep the root and the last two steps; everything between them goes into the menu.
77
+ const head = trail.slice(0, 1);
78
+ const folded = trail.slice(1, -2);
79
+ const tail = trail.slice(-2);
80
+ </script>
81
+
82
+ <Breadcrumb>
83
+ {#each head as item (item.name)}
84
+ <BreadcrumbItem>
85
+ <BreadcrumbButton as="a" href={item.href}>{item.name}</BreadcrumbButton>
86
+ </BreadcrumbItem>
87
+ <BreadcrumbDivider />
88
+ {/each}
89
+
90
+ <BreadcrumbItem>
91
+ <Menu>
92
+ <MenuTrigger>
93
+ {#snippet children({ state, menuTriggerProps })}
94
+ <Button bind:ref={state.ref} {...menuTriggerProps} appearance="subtle" aria-label="More steps">
95
+ <MoreHorizontalRegular />
96
+ </Button>
97
+ {/snippet}
98
+ </MenuTrigger>
99
+ <MenuPopover>
100
+ <MenuList>
101
+ <!-- A step that goes somewhere is a link, in the menu as much as in the trail. -->
102
+ {#each folded as item (item.name)}
103
+ <MenuItem href={item.href}>{item.name}</MenuItem>
104
+ {/each}
105
+ </MenuList>
106
+ </MenuPopover>
107
+ </Menu>
108
+ </BreadcrumbItem>
109
+ <BreadcrumbDivider />
110
+
111
+ {#each tail as item (item.name)}
112
+ <BreadcrumbItem>
113
+ <BreadcrumbButton current>{item.name}</BreadcrumbButton>
114
+ </BreadcrumbItem>
115
+ {/each}
116
+ </Breadcrumb>
117
+ ```
118
+
119
+ The label on the menu button is yours to write, and it is worth writing: a trigger with only an icon says nothing to a screen reader. Note also that what lives inside a `MenuPopover` is not rendered until the menu opens, so the folded steps are not in the HTML a crawler reads.
120
+
121
+ ### Focus Mode
122
+
123
+ `focusMode` decides how the focus walks the trail. `tab`, the default, leaves every step its own tab stop, which is what a reader expects of a row of links. `arrow` gives the whole trail a single tab stop and moves between steps with the arrow keys, with `Home` and `End` for the ends.
124
+
125
+ ```svelte
126
+ <Breadcrumb focusMode="arrow">...</Breadcrumb>
127
+ ```
128
+
129
+ ## Component Props (Breadcrumb)
130
+
131
+ <!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
132
+ <!-- props:BreadcrumbProps -->
133
+
134
+ | Name | Type | Default | Description |
135
+ | -------------------- | -------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
136
+ | `ref` _bindable_ | `HTMLElement` | | The DOM reference of the `nav`. |
137
+ | `listRef` _bindable_ | `HTMLOListElement` | | The DOM reference of the list. |
138
+ | `listProps` | `HTMLOlAttributes` | | The props to spread on the list element. |
139
+ | `size` | `'small'` &#124; `'medium'` &#124; `'large'` | `'medium'` | The size of every step and divider of the trail. |
140
+ | `focusMode` | `'tab'` &#124; `'arrow'` | `'tab'` | How the focus walks the trail: `tab` leaves every step its own tab stop, `arrow` gives the trail a single one and moves between steps with the arrow keys. |
141
+ | Element Attributes | | | |
142
+
143
+ <!-- /props:BreadcrumbProps -->
144
+
145
+ ## Component Props (BreadcrumbItem)
146
+
147
+ <!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
148
+ <!-- props:BreadcrumbItemProps -->
149
+
150
+ | Name | Type | Default | Description |
151
+ | ------------------------- | --------------- | ------- | ------------------------------ |
152
+ | `ref` _bindable_ | `HTMLLIElement` | | The DOM reference of the item. |
153
+ | Element Attributes (`li`) | | | |
154
+
155
+ <!-- /props:BreadcrumbItemProps -->
156
+
157
+ ## Component Props (BreadcrumbButton)
158
+
159
+ <!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
160
+ <!-- props:BreadcrumbButtonProps -->
161
+
162
+ | Name | Type | Default | Description |
163
+ | ------------------- | --------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------ |
164
+ | `ref` _bindable_ | `BreadcrumbItemElementDOMType[T]` | | The DOM reference of the step. |
165
+ | `as` | `'button'` &#124; `'a'` | `'button'` | The DOM element to render. A step that goes somewhere is an anchor and takes `href`. |
166
+ | `icon` | `Snippet` &#124; `Component` | | The icon to display beside the label. |
167
+ | `iconPosition` | `'before'` &#124; `'after'` | `'before'` | Which side of the label the icon sits on. |
168
+ | `current` | `boolean` | `false` | Marks the step as the page the trail ends at, which is what `aria-current` announces. |
169
+ | `disabled` | `boolean` | | Disables the user interaction. |
170
+ | `disabledFocusable` | `boolean` | `false` | Keeps a disabled step focusable, so the tab order stays the same for a keyboard or screen reader user. |
171
+ | HTML Attributes | | | |
172
+
173
+ <!-- /props:BreadcrumbButtonProps -->
174
+
175
+ ## Component Props (BreadcrumbDivider)
176
+
177
+ <!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
178
+ <!-- props:BreadcrumbDividerProps -->
179
+
180
+ | Name | Type | Default | Description |
181
+ | ------------------------- | --------------- | ------- | --------------------------------- |
182
+ | `ref` _bindable_ | `HTMLLIElement` | | The DOM reference of the divider. |
183
+ | Element Attributes (`li`) | | | |
184
+
185
+ <!-- /props:BreadcrumbDividerProps -->
186
+
187
+ ## Accessibility
188
+
189
+ The landmark is named `Breadcrumb` by default, which is what a screen reader announces when jumping to it. Give it your own `aria-label` when a page carries more than one trail, so each is distinguishable, or an `aria-labelledby` pointing at a heading you already render — the default steps aside for both.
190
+
191
+ The dividers are decoration and are hidden, so the list reads as the number of steps it has. The last step carries `aria-current="page"`. A step with an icon and no label needs an `aria-label` of its own.
192
+
193
+ Structured data is left to you. A breadcrumb is one of the few components a search engine reads directly, through `BreadcrumbList` from schema.org, but the text of a step lives in your markup and the URLs live in your routing — so the trail you publish is yours to build, in whichever shape your site already uses.
@@ -0,0 +1,80 @@
1
+ import type { BeforeOrAfter, PolymorphicProps, Sizes } from '../../types/index.js';
2
+ import type { Snippet, Component } from 'svelte';
3
+ import type { HTMLAttributes, HTMLOlAttributes } from 'svelte/elements';
4
+ /** How the focus walks the trail. */
5
+ export type BreadcrumbFocusMode = 'tab' | 'arrow';
6
+ export type BreadcrumbItemGenerics = 'button' | 'a';
7
+ export type BreadcrumbItemElementDOMType = {
8
+ button: HTMLButtonElement;
9
+ a: HTMLAnchorElement;
10
+ };
11
+ /** @propsmith BreadcrumbProps */
12
+ export type BreadcrumbProps = {
13
+ /** The DOM reference of the `nav`.
14
+ * @bindable
15
+ */
16
+ ref?: HTMLElement;
17
+ /** The DOM reference of the list.
18
+ * @bindable
19
+ */
20
+ listRef?: HTMLOListElement;
21
+ /** The props to spread on the list element.
22
+ * @type HTMLOlAttributes
23
+ */
24
+ listProps?: HTMLOlAttributes;
25
+ /** The size of every step and divider of the trail.
26
+ * @default 'medium'
27
+ */
28
+ size?: Sizes;
29
+ /** How the focus walks the trail: `tab` leaves every step its own tab stop, `arrow` gives the
30
+ * trail a single one and moves between steps with the arrow keys.
31
+ * @default 'tab'
32
+ */
33
+ focusMode?: BreadcrumbFocusMode;
34
+ } & HTMLAttributes<HTMLElement>;
35
+ /** @propsmith BreadcrumbItemProps */
36
+ export type BreadcrumbItemProps = {
37
+ /** The DOM reference of the item.
38
+ * @bindable
39
+ */
40
+ ref?: HTMLLIElement;
41
+ } & HTMLAttributes<HTMLLIElement>;
42
+ /** @propsmith BreadcrumbButtonProps */
43
+ export type BreadcrumbButtonProps<T extends BreadcrumbItemGenerics = 'button'> = {
44
+ /** The DOM reference of the step.
45
+ * @type BreadcrumbItemElementDOMType[T]
46
+ * @bindable
47
+ */
48
+ ref?: BreadcrumbItemElementDOMType[T];
49
+ /** The DOM element to render. A step that goes somewhere is an anchor and takes `href`.
50
+ * @type 'button' | 'a'
51
+ * @default 'button'
52
+ */
53
+ as?: T;
54
+ /** The icon to display beside the label.
55
+ * @type Snippet | Component
56
+ */
57
+ icon?: Snippet | Component;
58
+ /** Which side of the label the icon sits on.
59
+ * @default 'before'
60
+ */
61
+ iconPosition?: BeforeOrAfter;
62
+ /** Marks the step as the page the trail ends at, which is what `aria-current` announces.
63
+ * @default false
64
+ */
65
+ current?: boolean;
66
+ /** Disables the user interaction. */
67
+ disabled?: boolean;
68
+ /** Keeps a disabled step focusable, so the tab order stays the same for a keyboard or screen
69
+ * reader user.
70
+ * @default false
71
+ */
72
+ disabledFocusable?: boolean;
73
+ } & PolymorphicProps<T>;
74
+ /** @propsmith BreadcrumbDividerProps */
75
+ export type BreadcrumbDividerProps = {
76
+ /** The DOM reference of the divider.
77
+ * @bindable
78
+ */
79
+ ref?: HTMLLIElement;
80
+ } & HTMLAttributes<HTMLLIElement>;
@@ -0,0 +1 @@
1
+ export {};
@@ -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} id={`${id}-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} id={`${id}-title`} />
56
+ <RenderSoC SoC={title} args={{ id: `${id}-title` }} />
57
57
  {/if}
58
58
  {#if _action}
59
59
  <RenderSoC SoC={_action} />
@@ -195,6 +195,7 @@
195
195
  }
196
196
  }
197
197
  selectionMode={multiple ? 'multiselect' : 'single'}
198
+ toggleOnClick={multiple}
198
199
  onSelectionChange={() => {
199
200
  if (!multiple) closeDropdown();
200
201
  }}
@@ -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. Note: every actionable element inside this ListViewItem must call e.stopPropagation().
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
- <ListViewItem
65
- value="card"
66
- role="row"
67
- checkmark={{ style: 'position: absolute; top: 10px; left: 10px; z-index: 10;' }}
68
- onAction={(e, d) => alert('Triggered Primary Action')}
69
- >
70
- <div style="...">
71
- Card Content Inside ListItem
72
- <Button
73
- onclick={(e) => {
74
- e.stopPropagation();
75
- alert('Installing');
76
- }}>Install</Button
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
- </div>
79
- </ListViewItem>
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'` &#124; `'single'` &#124; `'multiselect'` &#124; `'extended'` | `'none'` | How many items can be selected, and how the selection is made. |
90
- | `navigationMode` | `'items'` &#124; `'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'` &#124; `'rounded'` &#124; `'square'` | `'rounded'` | The items can have a circular, rounded or square shape. |
93
- | `onSelectionChange` | `(e: Event, selectedItems: string[]` &#124; `[]) => void` | | Called whenever the selection changes. |
94
- | `as` | `'ul'` &#124; `'ol'` &#124; `'div'` | `'ul'` | The HTML element to render the list as. |
95
- | `ref` _bindable_ | `HTMLUListElement` &#124; `HTMLOListElement` &#124; `HTMLDivElement` | | The DOM reference of the list element. |
96
- | `disableTabspot` | `boolean` | `false` | Opts the list out of the tabspot focus management, to wire your own. |
97
- | HTML Attributes | | | |
138
+ | Name | Type | Default | Description |
139
+ | -------------------------- | --------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
140
+ | `selectionMode` | `'none'` &#124; `'single'` &#124; `'multiselect'` &#124; `'extended'` | `'none'` | How many items can be selected, and how the selection is made. |
141
+ | `navigationMode` | `'items'` &#124; `'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'` &#124; `'rounded'` &#124; `'square'` | `'rounded'` | The items can have a circular, rounded or square shape. |
144
+ | `onSelectionChange` | `(e: Event, selectedItems: string[]` &#124; `[]) => 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'` &#124; `'ol'` &#124; `'div'` | `'ul'` | The HTML element to render the list as. |
147
+ | `ref` _bindable_ | `HTMLUListElement` &#124; `HTMLOListElement` &#124; `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
- invokeHandlers(
86
- e,
87
- disabled,
88
- role !== 'row'
89
- ? [
90
- (event: MouseEvent) => handleSelection?.(event, value),
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 {disabled} bind:checked={active} tabindex={-1} {...!isObjectEmpty(checkmark) ? checkmark : {}} />
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 { Snippet } from 'svelte';
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 { Snippet } from 'svelte';
2
- import type { ComputePositionConfig } from '@floating-ui/dom';
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. */