fluentui-svelte 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/dialog/dialog-actions.svelte +17 -2
- package/dist/components/dialog/dialog-content.svelte +2 -2
- package/dist/components/dialog/dialog-content.svelte.d.ts +1 -1
- package/dist/components/dialog/dialog-surface.svelte +32 -3
- package/dist/components/dialog/dialog-title.svelte +23 -6
- package/dist/components/dialog/dialog-trigger.svelte +8 -2
- package/dist/components/dialog/dialog-trigger.svelte.d.ts +1 -1
- package/dist/components/dialog/docs/llms.md +2 -2
- package/dist/components/dialog/types.d.ts +6 -2
- package/dist/components/dialog/utils.d.ts +2 -0
- package/dist/components/dialog/utils.js +1 -0
- package/dist/components/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/rating/docs/llms.md +110 -0
- package/dist/components/rating/rating.svelte +210 -0
- package/dist/components/rating/rating.svelte.d.ts +19 -0
- package/dist/components/rating/types.d.ts +44 -0
- package/dist/components/rating/types.js +1 -0
- package/dist/components/tag/docs/llms.md +383 -0
- package/dist/components/tag/tag-group.d.ts +2 -0
- package/dist/components/tag/tag-group.js +2 -0
- package/dist/components/tag/tag-group.svelte +117 -0
- package/dist/components/tag/tag-group.svelte.d.ts +20 -0
- package/dist/components/tag/tag.svelte +458 -0
- package/dist/components/tag/tag.svelte.d.ts +19 -0
- package/dist/components/tag/types.d.ts +135 -0
- package/dist/components/tag/types.js +1 -0
- package/dist/components/tag-picker/docs/llms.md +292 -0
- package/dist/components/tag-picker/option-group.svelte +38 -0
- package/dist/components/tag-picker/option-group.svelte.d.ts +4 -0
- package/dist/components/tag-picker/option.svelte +71 -0
- package/dist/components/tag-picker/option.svelte.d.ts +4 -0
- package/dist/components/tag-picker/tag-picker.d.ts +2 -0
- package/dist/components/tag-picker/tag-picker.js +2 -0
- package/dist/components/tag-picker/tag-picker.svelte +644 -0
- package/dist/components/tag-picker/tag-picker.svelte.d.ts +21 -0
- package/dist/components/tag-picker/types.d.ts +122 -0
- package/dist/components/tag-picker/types.js +1 -0
- package/dist/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/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/internal/constants.d.ts +0 -4
- package/dist/internal/constants.js +0 -4002
- package/package.json +3 -2
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} />
|
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
import { getTabspotAttributes } from 'tabspot';
|
|
3
3
|
import type { DialogActionsProps } from './types.js';
|
|
4
4
|
|
|
5
|
-
let {
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(),
|
|
7
|
+
position = 'end',
|
|
8
|
+
fluid,
|
|
9
|
+
class: classes,
|
|
10
|
+
children,
|
|
11
|
+
...attributes
|
|
12
|
+
}: DialogActionsProps = $props();
|
|
6
13
|
|
|
7
14
|
// The bar is one tab stop holding a row of buttons: without the mover the root would
|
|
8
15
|
// take the tab stop and then offer no way to reach the rest of the buttons.
|
|
@@ -12,7 +19,12 @@
|
|
|
12
19
|
});
|
|
13
20
|
</script>
|
|
14
21
|
|
|
15
|
-
<div
|
|
22
|
+
<div
|
|
23
|
+
class={['dialog-actions', `justify-${position}`, { fluid }, classes]}
|
|
24
|
+
bind:this={ref}
|
|
25
|
+
{...tabspotAttributes}
|
|
26
|
+
{...attributes}
|
|
27
|
+
>
|
|
16
28
|
{@render children?.()}
|
|
17
29
|
</div>
|
|
18
30
|
|
|
@@ -33,5 +45,8 @@
|
|
|
33
45
|
&.justify-end {
|
|
34
46
|
justify-content: flex-end;
|
|
35
47
|
}
|
|
48
|
+
&.fluid > :global(*) {
|
|
49
|
+
flex: 1 1 0;
|
|
50
|
+
}
|
|
36
51
|
}
|
|
37
52
|
</style>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { DialogContentProps } from './types.js';
|
|
3
3
|
|
|
4
|
-
let { children }: DialogContentProps = $props();
|
|
4
|
+
let { ref = $bindable(), class: classes, children, ...attributes }: DialogContentProps = $props();
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
|
-
<div class=
|
|
7
|
+
<div class={['dialog-content', classes]} bind:this={ref} {...attributes}>
|
|
8
8
|
{@render children?.()}
|
|
9
9
|
</div>
|
|
10
10
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { DialogContentProps } from './types.ts';
|
|
2
|
-
declare const DialogContent: import("svelte").Component<DialogContentProps, {}, "">;
|
|
2
|
+
declare const DialogContent: import("svelte").Component<DialogContentProps, {}, "ref">;
|
|
3
3
|
type DialogContent = ReturnType<typeof DialogContent>;
|
|
4
4
|
export default DialogContent;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onClickOutside } from 'runed';
|
|
3
|
+
import { on } from 'svelte/events';
|
|
3
4
|
import type { DialogSurfaceProps } from './types.js';
|
|
4
5
|
import { getDialogContext } from './dialog.svelte.js';
|
|
5
6
|
import { Button } from '../../index.js';
|
|
6
7
|
import DismissFilled from 'fluentui-icons-svelte/DismissFilled.svelte';
|
|
7
8
|
|
|
8
|
-
let { ref = $bindable(), children, ...attributes }: DialogSurfaceProps = $props();
|
|
9
|
+
let { ref = $bindable(), class: classes, oncancel, children, ...attributes }: DialogSurfaceProps = $props();
|
|
9
10
|
|
|
10
11
|
const CONTEXT = getDialogContext();
|
|
11
12
|
|
|
@@ -17,22 +18,50 @@
|
|
|
17
18
|
|
|
18
19
|
const { closeDialog } = methods;
|
|
19
20
|
|
|
21
|
+
// runed reports an outside press 10ms after the pointer goes down. By then the press that opens the
|
|
22
|
+
// dialog has already opened it, and a press while it is closed has nothing to dismiss, so remember
|
|
23
|
+
// whether the dialog was open at the moment the pointer went down.
|
|
24
|
+
let openOnPointerDown = false;
|
|
25
|
+
|
|
26
|
+
$effect(() => {
|
|
27
|
+
const doc = ref?.ownerDocument;
|
|
28
|
+
if (!doc) return;
|
|
29
|
+
return on(doc, 'pointerdown', () => (openOnPointerDown = !!ref?.open), { capture: true });
|
|
30
|
+
});
|
|
31
|
+
|
|
20
32
|
// Registered unconditionally and guarded from inside, so switching `type` after mount is honored.
|
|
21
33
|
onClickOutside(
|
|
22
34
|
() => ref,
|
|
23
35
|
() => {
|
|
24
|
-
if (config.type === 'alert') return;
|
|
36
|
+
if (config.type === 'alert' || !openOnPointerDown) return;
|
|
25
37
|
closeDialog();
|
|
26
38
|
}
|
|
27
39
|
);
|
|
28
40
|
|
|
41
|
+
// Escape cancels a modal dialog natively. An alert has to be answered with one of its actions, and any
|
|
42
|
+
// other modal closes through `closeDialog` so `onOpenChange` hears about it. A consumer's `oncancel`
|
|
43
|
+
// can still veto it with `preventDefault`.
|
|
44
|
+
function handleCancel(e: Event & { currentTarget: EventTarget & HTMLDialogElement }) {
|
|
45
|
+
oncancel?.(e);
|
|
46
|
+
if (e.defaultPrevented) return;
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
if (config.type !== 'alert') closeDialog();
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
$effect(() => {
|
|
30
52
|
if (!ref) return;
|
|
31
53
|
_state.dialogRef = ref;
|
|
32
54
|
});
|
|
33
55
|
</script>
|
|
34
56
|
|
|
35
|
-
<dialog
|
|
57
|
+
<dialog
|
|
58
|
+
open={_state.open}
|
|
59
|
+
class={['fs-dialog', classes]}
|
|
60
|
+
aria-labelledby={_state.titleId}
|
|
61
|
+
bind:this={ref}
|
|
62
|
+
oncancel={handleCancel}
|
|
63
|
+
{...attributes}
|
|
64
|
+
>
|
|
36
65
|
<div class="dialog-wrapper">
|
|
37
66
|
{#if config.type === 'non-modal'}
|
|
38
67
|
<Button class="close-icon" appearance="subtle" onclick={() => methods.closeDialog()} aria-label="Close dialog">
|
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends DialogTitleTag">
|
|
2
2
|
import { PREFIX } from '../../internal/constants.js';
|
|
3
3
|
import { getDialogContext } from './dialog.svelte.js';
|
|
4
|
+
import { TITLE_TAG } from './utils.js';
|
|
4
5
|
import type { DialogTitleProps, DialogTitleTag } from './types.js';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const _ID = $props.id();
|
|
8
|
+
const FALLBACK_ID = `${PREFIX}dialog-title-${_ID}`;
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
as = 'h3' as T,
|
|
12
|
+
ref = $bindable(),
|
|
13
|
+
id = FALLBACK_ID,
|
|
14
|
+
class: classes,
|
|
15
|
+
children,
|
|
16
|
+
...attributes
|
|
17
|
+
}: DialogTitleProps<T> = $props();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The tag the invariant rules over. The markup renders it through here, so it is checked
|
|
21
|
+
* during SSR and on every prop update rather than only once on mount.
|
|
22
|
+
*/
|
|
23
|
+
const _as = $derived.by(() => {
|
|
24
|
+
if (!TITLE_TAG.includes(as)) throw new Error(`Invalid tag: ${as}. Must be one of ${TITLE_TAG.join(', ')}`);
|
|
25
|
+
return as;
|
|
26
|
+
});
|
|
10
27
|
|
|
11
28
|
const context = getDialogContext();
|
|
12
29
|
|
|
13
30
|
$effect.pre(() => {
|
|
14
31
|
if (!context) return;
|
|
15
32
|
|
|
16
|
-
context.state.titleId =
|
|
33
|
+
context.state.titleId = id ?? undefined;
|
|
17
34
|
|
|
18
35
|
return () => {
|
|
19
36
|
context.state.titleId = undefined;
|
|
@@ -21,7 +38,7 @@
|
|
|
21
38
|
});
|
|
22
39
|
</script>
|
|
23
40
|
|
|
24
|
-
<svelte:element this={
|
|
41
|
+
<svelte:element this={_as} {id} class={['dialog-title', classes]} bind:this={ref} {...attributes}>
|
|
25
42
|
{@render children?.()}
|
|
26
43
|
</svelte:element>
|
|
27
44
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Button } from '../../index.js';
|
|
3
|
+
import { invokeHandlers } from '../../internal/index.js';
|
|
3
4
|
import { getDialogContext } from './dialog.svelte.js';
|
|
4
5
|
import type { DialogTriggerProps } from './types.js';
|
|
5
6
|
|
|
6
|
-
let { children }: DialogTriggerProps = $props();
|
|
7
|
+
let { ref = $bindable(), class: classes, onclick, children, ...attributes }: DialogTriggerProps = $props();
|
|
7
8
|
|
|
8
9
|
const CONTEXT = getDialogContext();
|
|
9
10
|
|
|
@@ -14,7 +15,12 @@
|
|
|
14
15
|
const { methods } = CONTEXT;
|
|
15
16
|
</script>
|
|
16
17
|
|
|
17
|
-
<Button
|
|
18
|
+
<Button
|
|
19
|
+
bind:ref
|
|
20
|
+
class={['dialog-trigger', classes]}
|
|
21
|
+
onclick={(e: MouseEvent) => invokeHandlers(e, [], [onclick, methods.openDialog])}
|
|
22
|
+
{...attributes}
|
|
23
|
+
>
|
|
18
24
|
{#if children}
|
|
19
25
|
{@render children()}
|
|
20
26
|
{:else}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { DialogTriggerProps } from './types.ts';
|
|
2
|
-
declare const DialogTrigger: import("svelte").Component<DialogTriggerProps, {}, "">;
|
|
2
|
+
declare const DialogTrigger: import("svelte").Component<DialogTriggerProps, {}, "ref">;
|
|
3
3
|
type DialogTrigger = ReturnType<typeof DialogTrigger>;
|
|
4
4
|
export default DialogTrigger;
|
|
@@ -99,7 +99,7 @@ Add a `DialogActions` region to render action buttons in the dialog footer. Use
|
|
|
99
99
|
|
|
100
100
|
| Name | Type | Default | Description |
|
|
101
101
|
| ----------------------- | ------------------- | ------- | ----------------------------------------- |
|
|
102
|
-
| `ref`
|
|
102
|
+
| `ref` _bindable_ | `HTMLButtonElement` | | The DOM reference of the trigger element. |
|
|
103
103
|
| `ButtonProps<'button'>` | | | |
|
|
104
104
|
|
|
105
105
|
<!-- /props:DialogTriggerProps -->
|
|
@@ -136,7 +136,7 @@ Add a `DialogActions` region to render action buttons in the dialog footer. Use
|
|
|
136
136
|
|
|
137
137
|
| Name | Type | Default | Description |
|
|
138
138
|
| -------------------------- | ---------------- | ------- | ----------------------------------------- |
|
|
139
|
-
| `ref`
|
|
139
|
+
| `ref` _bindable_ | `HTMLDivElement` | | The DOM reference of the content element. |
|
|
140
140
|
| Element Attributes (`div`) | | | |
|
|
141
141
|
|
|
142
142
|
<!-- /props:DialogContentProps -->
|
|
@@ -35,12 +35,16 @@ export type DialogSurfaceProps = {
|
|
|
35
35
|
} & HTMLAttributes<HTMLDialogElement>;
|
|
36
36
|
/** @propsmith DialogTriggerProps */
|
|
37
37
|
export type DialogTriggerProps = {
|
|
38
|
-
/** The DOM reference of the trigger element.
|
|
38
|
+
/** The DOM reference of the trigger element.
|
|
39
|
+
* @bindable
|
|
40
|
+
*/
|
|
39
41
|
ref?: HTMLButtonElement;
|
|
40
42
|
} & ButtonProps<'button'>;
|
|
41
43
|
/** @propsmith DialogContentProps */
|
|
42
44
|
export type DialogContentProps = {
|
|
43
|
-
/** The DOM reference of the content element.
|
|
45
|
+
/** The DOM reference of the content element.
|
|
46
|
+
* @bindable
|
|
47
|
+
*/
|
|
44
48
|
ref?: HTMLDivElement;
|
|
45
49
|
} & HTMLAttributes<HTMLDivElement>;
|
|
46
50
|
/** @propsmith DialogActionsProps */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const TITLE_TAG = ['div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
|
|
@@ -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;
|