fluentui-svelte 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/auto-suggest-box/auto-suggest-box.svelte +68 -56
- package/dist/components/auto-suggest-box/docs/llms.md +46 -14
- package/dist/components/auto-suggest-box/option.svelte +2 -1
- package/dist/components/avatar/docs/llms.md +20 -14
- package/dist/components/badge/docs/llms.md +29 -16
- package/dist/components/button/button.svelte +11 -5
- package/dist/components/button/docs/llms.md +33 -24
- package/dist/components/calendar-date-picker/docs/llms.md +17 -7
- package/dist/components/calendar-view/calendar-view-header.svelte +2 -2
- package/dist/components/calendar-view/calendar-view-header.svelte.d.ts +2 -17
- package/dist/components/calendar-view/calendar-view-months.svelte +1 -1
- package/dist/components/calendar-view/calendar-view-years.svelte +1 -1
- package/dist/components/calendar-view/calendar-view.svelte +66 -52
- package/dist/components/calendar-view/docs/llms.md +42 -30
- package/dist/components/card/docs/llms.md +54 -34
- package/dist/components/checkbox/docs/llms.md +18 -12
- package/dist/components/date-picker/docs/llms.md +25 -15
- package/dist/components/dialog/dialog-surface.svelte +8 -8
- package/dist/components/dialog/dialog.svelte +41 -39
- package/dist/components/dialog/docs/llms.md +59 -34
- package/dist/components/divider/docs/llms.md +14 -9
- package/dist/components/dropdown/docs/llms.md +34 -15
- package/dist/components/dropdown/dropdown.svelte +4 -2
- package/dist/components/dropdown-select/docs/llms.md +28 -14
- package/dist/components/expander/docs/llms.md +19 -12
- package/dist/components/flyout/docs/llms.md +17 -11
- package/dist/components/hyperlink/docs/llms.md +11 -7
- package/dist/components/info-bar/docs/llms.md +15 -9
- package/dist/components/label/docs/llms.md +15 -11
- package/dist/components/list-view/docs/llms.md +34 -24
- package/dist/components/list-view/list-view-item.svelte +10 -4
- package/dist/components/list-view/list-view.svelte +68 -63
- package/dist/components/menu/context-menu-trigger.svelte +45 -40
- package/dist/components/menu/docs/llms.md +63 -34
- package/dist/components/menu/menu-item-checkbox.svelte +3 -1
- package/dist/components/menu/menu-item-radio.svelte +3 -1
- package/dist/components/menu/menu-item-switch.svelte +3 -1
- package/dist/components/menu/menu-item.svelte +5 -1
- package/dist/components/menu/menu-popover.svelte +21 -10
- package/dist/components/menu/menu-trigger.svelte +22 -9
- package/dist/components/menu/menu.svelte +98 -87
- package/dist/components/number-box/docs/llms.md +10 -6
- package/dist/components/persona/docs/llms.md +19 -14
- package/dist/components/progress-bar/docs/llms.md +18 -10
- package/dist/components/progress-ring/docs/llms.md +19 -12
- package/dist/components/radio-button/docs/llms.md +13 -8
- package/dist/components/skeleton/docs/llms.md +13 -8
- package/dist/components/slider/docs/llms.md +33 -21
- package/dist/components/slider/slider.svelte +13 -11
- package/dist/components/table/docs/llms.md +531 -0
- package/dist/components/table/table-body.svelte +31 -0
- package/dist/components/table/table-body.svelte.d.ts +25 -0
- package/dist/components/table/table-cell-actions.svelte +48 -0
- package/dist/components/table/table-cell-actions.svelte.d.ts +17 -0
- package/dist/components/table/table-cell-layout.svelte +88 -0
- package/dist/components/table/table-cell-layout.svelte.d.ts +14 -0
- package/dist/components/table/table-cell.svelte +74 -0
- package/dist/components/table/table-cell.svelte.d.ts +25 -0
- package/dist/components/table/table-header-cell.svelte +257 -0
- package/dist/components/table/table-header-cell.svelte.d.ts +25 -0
- package/dist/components/table/table-header.svelte +28 -0
- package/dist/components/table/table-header.svelte.d.ts +25 -0
- package/dist/components/table/table-row.svelte +57 -0
- package/dist/components/table/table-row.svelte.d.ts +25 -0
- package/dist/components/table/table-selection-cell.svelte +89 -0
- package/dist/components/table/table-selection-cell.svelte.d.ts +25 -0
- package/dist/components/table/table.svelte +189 -0
- package/dist/components/table/table.svelte.d.ts +49 -0
- package/dist/components/table/table.svelte.js +138 -0
- package/dist/components/table/types.d.ts +287 -0
- package/dist/components/table/types.js +1 -0
- package/dist/components/text-box/docs/llms.md +23 -17
- package/dist/components/textarea/docs/llms.md +16 -7
- package/dist/components/time-picker/docs/llms.md +20 -10
- package/dist/components/toggle-switch/docs/llms.md +13 -9
- package/dist/components/tooltip/docs/llms.md +24 -10
- package/dist/components/tree-view/docs/llms.md +53 -35
- package/dist/components/tree-view/tree-view-item-content.svelte +1 -1
- package/dist/components/tree-view/tree-view-item.svelte +3 -3
- package/dist/components/tree-view/tree-view.svelte +89 -93
- package/dist/index.d.ts +9 -0
- package/dist/index.js +11 -0
- package/dist/providers/fluentui-svelte/fluentui-svelte.svelte +15 -15
- package/package.json +1 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts" generics="Tag extends 'td' | 'div' = 'td'">
|
|
2
|
+
import Checkbox from '../checkbox/checkbox.svelte';
|
|
3
|
+
import RadioButton from '../radio-button/radio-button.svelte';
|
|
4
|
+
import { colIndex, getTag, requireTableContext, selectOnSpace } from './table.svelte.js';
|
|
5
|
+
import type { TableDOM, TableSelectionCellProps } from './types.js';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
as,
|
|
9
|
+
ref = $bindable(),
|
|
10
|
+
type = 'checkbox',
|
|
11
|
+
checked = false,
|
|
12
|
+
subtle = false,
|
|
13
|
+
invisible = false,
|
|
14
|
+
checkboxIndicator,
|
|
15
|
+
radioIndicator,
|
|
16
|
+
class: classes,
|
|
17
|
+
onclick,
|
|
18
|
+
...attributes
|
|
19
|
+
}: TableSelectionCellProps<Tag> = $props();
|
|
20
|
+
|
|
21
|
+
const context = requireTableContext();
|
|
22
|
+
|
|
23
|
+
const noNativeElements = $derived(context.config.noNativeElements);
|
|
24
|
+
|
|
25
|
+
const _as = $derived(getTag(as, 'td', noNativeElements));
|
|
26
|
+
|
|
27
|
+
/** A `grid` has no `cell`, only `gridcell`, so arrow navigation renames what the cell reports. */
|
|
28
|
+
const enableTabspot = $derived(context.config.enableTabspot);
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<svelte:element
|
|
32
|
+
this={_as}
|
|
33
|
+
bind:this={ref as TableDOM[Tag]}
|
|
34
|
+
role={enableTabspot ? 'gridcell' : _as === 'div' ? 'cell' : undefined}
|
|
35
|
+
class={['fs-table-selection-cell', { flex: _as === 'div', subtle, invisible, checked: !!checked }, classes]}
|
|
36
|
+
{@attach enableTabspot ? colIndex() : undefined}
|
|
37
|
+
{@attach onclick ? selectOnSpace : undefined}
|
|
38
|
+
{onclick}
|
|
39
|
+
{...attributes}
|
|
40
|
+
>
|
|
41
|
+
<!--
|
|
42
|
+
The indicator answers the pointer itself, and the click still reaches the row underneath, so
|
|
43
|
+
hitting it selects the row rather than only ticking a box the consumer never hears about.
|
|
44
|
+
-->
|
|
45
|
+
{#if type === 'radio'}
|
|
46
|
+
<RadioButton checked={checked === true} {...radioIndicator} />
|
|
47
|
+
{:else}
|
|
48
|
+
<Checkbox wrapperAs="div" checked={checked === true} indeterminate={checked === 'mixed'} {...checkboxIndicator} />
|
|
49
|
+
{/if}
|
|
50
|
+
</svelte:element>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
.fs-table-selection-cell {
|
|
54
|
+
height: var(--fs-table-row-height);
|
|
55
|
+
padding: 0 var(--fs-table-cell-padding);
|
|
56
|
+
vertical-align: middle;
|
|
57
|
+
/* Shrink to the indicator: a native table hands the leftover width to the data columns. */
|
|
58
|
+
width: 1%;
|
|
59
|
+
white-space: nowrap;
|
|
60
|
+
& :global(.fs-checkbox),
|
|
61
|
+
& :global(.fs-radio-button) {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
}
|
|
65
|
+
&.subtle :global(.fs-checkbox),
|
|
66
|
+
&.subtle :global(.fs-radio-button) {
|
|
67
|
+
/* Set by the row, so the indicator surfaces on hover and while the focus is inside. */
|
|
68
|
+
opacity: var(--fs-table-row-active, 0);
|
|
69
|
+
transition: opacity var(--fs-fast-duration) var(--fs-point-to-point);
|
|
70
|
+
@media (prefers-reduced-motion: reduce) {
|
|
71
|
+
transition: none;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
&.subtle.checked :global(.fs-checkbox),
|
|
75
|
+
&.subtle.checked :global(.fs-radio-button) {
|
|
76
|
+
opacity: 1;
|
|
77
|
+
}
|
|
78
|
+
&.invisible :global(.fs-checkbox),
|
|
79
|
+
&.invisible :global(.fs-radio-button) {
|
|
80
|
+
visibility: hidden;
|
|
81
|
+
}
|
|
82
|
+
&.flex {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
flex: 0 0 auto;
|
|
86
|
+
width: auto;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TableSelectionCellProps } from './types.ts';
|
|
2
|
+
declare function $$render<Tag extends 'td' | 'div' = 'td'>(): {
|
|
3
|
+
props: TableSelectionCellProps<Tag>;
|
|
4
|
+
exports: {};
|
|
5
|
+
bindings: "ref";
|
|
6
|
+
slots: {};
|
|
7
|
+
events: {};
|
|
8
|
+
};
|
|
9
|
+
declare class __sveltets_Render<Tag extends 'td' | 'div' = 'td'> {
|
|
10
|
+
props(): ReturnType<typeof $$render<Tag>>['props'];
|
|
11
|
+
events(): ReturnType<typeof $$render<Tag>>['events'];
|
|
12
|
+
slots(): ReturnType<typeof $$render<Tag>>['slots'];
|
|
13
|
+
bindings(): "ref";
|
|
14
|
+
exports(): {};
|
|
15
|
+
}
|
|
16
|
+
interface $$IsomorphicComponent {
|
|
17
|
+
new <Tag extends 'td' | 'div' = 'td'>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Tag>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Tag>['props']>, ReturnType<__sveltets_Render<Tag>['events']>, ReturnType<__sveltets_Render<Tag>['slots']>> & {
|
|
18
|
+
$$bindings?: ReturnType<__sveltets_Render<Tag>['bindings']>;
|
|
19
|
+
} & ReturnType<__sveltets_Render<Tag>['exports']>;
|
|
20
|
+
<Tag extends 'td' | 'div' = 'td'>(internal: unknown, props: ReturnType<__sveltets_Render<Tag>['props']> & {}): ReturnType<__sveltets_Render<Tag>['exports']>;
|
|
21
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
22
|
+
}
|
|
23
|
+
declare const TableSelectionCell: $$IsomorphicComponent;
|
|
24
|
+
type TableSelectionCell<Tag extends 'td' | 'div' = 'td'> = InstanceType<typeof TableSelectionCell<Tag>>;
|
|
25
|
+
export default TableSelectionCell;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<script lang="ts" generics="Tag extends 'table' | 'div' = 'table'">
|
|
2
|
+
import { tick } from 'svelte';
|
|
3
|
+
import { getTabspotAttributes, tabspotVirtual } from 'tabspot';
|
|
4
|
+
import { getTag, setTableContext, TABSPOT_ITEMS, TABSPOT_SKIP } from './table.svelte.js';
|
|
5
|
+
import type { TableContext, TableDOM, TableProps } from './types.js';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
as,
|
|
9
|
+
ref = $bindable(),
|
|
10
|
+
size = 'medium',
|
|
11
|
+
noNativeElements = false,
|
|
12
|
+
sortable = false,
|
|
13
|
+
noRowBorders = false,
|
|
14
|
+
enableTabspot = false,
|
|
15
|
+
virtualizer,
|
|
16
|
+
resizableColumns = false,
|
|
17
|
+
columnWidths = $bindable([]),
|
|
18
|
+
columnSizing,
|
|
19
|
+
onColumnResize,
|
|
20
|
+
class: classes,
|
|
21
|
+
children,
|
|
22
|
+
...attributes
|
|
23
|
+
}: TableProps<Tag> = $props();
|
|
24
|
+
|
|
25
|
+
/** Narrow enough that a column never collapses to nothing, wide enough to stay grabbable. */
|
|
26
|
+
const MIN_COLUMN_WIDTH = 40;
|
|
27
|
+
|
|
28
|
+
const context: TableContext = $state({
|
|
29
|
+
config: {
|
|
30
|
+
get size() {
|
|
31
|
+
return size;
|
|
32
|
+
},
|
|
33
|
+
get noNativeElements() {
|
|
34
|
+
return noNativeElements;
|
|
35
|
+
},
|
|
36
|
+
get sortable() {
|
|
37
|
+
return sortable;
|
|
38
|
+
},
|
|
39
|
+
get enableTabspot() {
|
|
40
|
+
return enableTabspot;
|
|
41
|
+
},
|
|
42
|
+
get resizableColumns() {
|
|
43
|
+
return resizableColumns;
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
state: {
|
|
47
|
+
get columnWidths() {
|
|
48
|
+
return columnWidths;
|
|
49
|
+
},
|
|
50
|
+
set columnWidths(v) {
|
|
51
|
+
columnWidths = v;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
events: null,
|
|
55
|
+
methods: {
|
|
56
|
+
getColumnWidth: (index: number) => context.state.columnWidths[index] ?? columnSizing?.[index]?.defaultWidth,
|
|
57
|
+
resizeColumn: (e: Event, index: number, width: number) => {
|
|
58
|
+
const { state } = context;
|
|
59
|
+
const min = columnSizing?.[index]?.minWidth ?? MIN_COLUMN_WIDTH;
|
|
60
|
+
const next = Math.round(Math.max(min, width));
|
|
61
|
+
if (state.columnWidths[index] === next) return;
|
|
62
|
+
|
|
63
|
+
// A fresh array, so a consumer binding to it sees the change however they store it.
|
|
64
|
+
const widths = [...state.columnWidths];
|
|
65
|
+
widths[index] = next;
|
|
66
|
+
state.columnWidths = widths;
|
|
67
|
+
onColumnResize?.(e, { index, width: next });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
setTableContext(context);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The tag the invariant rules over. The markup renders it through here, so it is checked during
|
|
76
|
+
* SSR and on every prop update rather than only once on mount.
|
|
77
|
+
*/
|
|
78
|
+
const _as = $derived(getTag(as, 'table', noNativeElements));
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A grid mover: the arrows walk the cells, rows are read off the DOM parent of each cell, and
|
|
82
|
+
* the whole table takes a single tab stop. `Escape` is left alone, a grid has nothing to exit to.
|
|
83
|
+
*/
|
|
84
|
+
const tabspotAttributes = $derived(
|
|
85
|
+
enableTabspot &&
|
|
86
|
+
getTabspotAttributes({
|
|
87
|
+
root: { manageSpecialKeys: { Home: true, End: true, PageUp: true, PageDown: true } },
|
|
88
|
+
mover: { layout: 'grid', rows: { by: 'parent' }, items: TABSPOT_ITEMS, skip: TABSPOT_SKIP }
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Tabspot navigates the DOM, and a windowed list only renders a slice of it. The adapter is what
|
|
94
|
+
* lets a move aim at a row that is not there yet: it scrolls the row in, then waits for Svelte to
|
|
95
|
+
* flush before looking for it.
|
|
96
|
+
*/
|
|
97
|
+
$effect(() => {
|
|
98
|
+
if (!enableTabspot || !virtualizer || !ref) return;
|
|
99
|
+
return tabspotVirtual(ref as HTMLElement, {
|
|
100
|
+
scrollToIndex: (index) => virtualizer.scrollToIndex?.(index),
|
|
101
|
+
count: () => virtualizer.size,
|
|
102
|
+
tick
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
</script>
|
|
106
|
+
|
|
107
|
+
<!--
|
|
108
|
+
@component
|
|
109
|
+
A table displays sets of two dimensional data. Every part is a primitive, so sorting and
|
|
110
|
+
selection stay the responsibility of the consumer.
|
|
111
|
+
|
|
112
|
+
- Usage:
|
|
113
|
+
```tsx
|
|
114
|
+
<script>
|
|
115
|
+
import { Table, TableHeader, TableHeaderCell, TableBody, TableRow, TableCell } from 'fluentui-svelte';
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<Table aria-label="Files">
|
|
119
|
+
<TableHeader>
|
|
120
|
+
<TableRow>
|
|
121
|
+
<TableHeaderCell>File</TableHeaderCell>
|
|
122
|
+
</TableRow>
|
|
123
|
+
</TableHeader>
|
|
124
|
+
<TableBody>
|
|
125
|
+
<TableRow>
|
|
126
|
+
<TableCell>Meeting notes</TableCell>
|
|
127
|
+
</TableRow>
|
|
128
|
+
</TableBody>
|
|
129
|
+
</Table>
|
|
130
|
+
```
|
|
131
|
+
-->
|
|
132
|
+
<svelte:element
|
|
133
|
+
this={_as}
|
|
134
|
+
bind:this={ref as TableDOM[Tag]}
|
|
135
|
+
role={enableTabspot ? 'grid' : _as === 'div' ? 'table' : undefined}
|
|
136
|
+
class={[
|
|
137
|
+
'fs-table',
|
|
138
|
+
size,
|
|
139
|
+
{ flex: _as === 'div', 'no-row-borders': noRowBorders, resizable: resizableColumns },
|
|
140
|
+
classes
|
|
141
|
+
]}
|
|
142
|
+
{...tabspotAttributes}
|
|
143
|
+
{...attributes}
|
|
144
|
+
>
|
|
145
|
+
{@render children?.()}
|
|
146
|
+
</svelte:element>
|
|
147
|
+
|
|
148
|
+
<style>
|
|
149
|
+
.fs-table {
|
|
150
|
+
width: 100%;
|
|
151
|
+
border-collapse: collapse;
|
|
152
|
+
text-align: left;
|
|
153
|
+
color: var(--fs-text-primary);
|
|
154
|
+
font-family: var(--fs-font-family-base);
|
|
155
|
+
font-size: var(--fs-body2-font-size);
|
|
156
|
+
line-height: var(--fs-body2-line-height);
|
|
157
|
+
/* Every part reads these, so a size change ripples out of the root alone. */
|
|
158
|
+
--fs-table-row-height: 2.75rem;
|
|
159
|
+
--fs-table-cell-padding: 0.5rem;
|
|
160
|
+
--fs-table-row-border: 1px solid var(--fs-divider-stroke-default);
|
|
161
|
+
&.no-row-borders {
|
|
162
|
+
--fs-table-row-border: none;
|
|
163
|
+
}
|
|
164
|
+
/*
|
|
165
|
+
* The auto algorithm re-measures every column from its content, which undoes a drag. A fixed
|
|
166
|
+
* table also has to size to the sum of its columns: a percentage width inside a shrink-to-fit
|
|
167
|
+
* container is circular, and the browser resolves that circle into a runaway width.
|
|
168
|
+
*/
|
|
169
|
+
&.resizable {
|
|
170
|
+
table-layout: fixed;
|
|
171
|
+
width: max-content;
|
|
172
|
+
max-width: 100%;
|
|
173
|
+
}
|
|
174
|
+
&.small {
|
|
175
|
+
--fs-table-row-height: 2.125rem;
|
|
176
|
+
}
|
|
177
|
+
&.extra-small {
|
|
178
|
+
--fs-table-row-height: 1.5rem;
|
|
179
|
+
--fs-table-cell-padding: 0.375rem;
|
|
180
|
+
font-size: var(--fs-caption-font-size);
|
|
181
|
+
line-height: var(--fs-caption-line-height);
|
|
182
|
+
}
|
|
183
|
+
/* A `div` table has no table layout to lean on, so the rows stack and the cells flex. */
|
|
184
|
+
&.flex {
|
|
185
|
+
display: flex;
|
|
186
|
+
flex-direction: column;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
</style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { TableProps } from './types.ts';
|
|
2
|
+
declare function $$render<Tag extends 'table' | 'div' = 'table'>(): {
|
|
3
|
+
props: TableProps<Tag>;
|
|
4
|
+
exports: {};
|
|
5
|
+
bindings: "ref" | "columnWidths";
|
|
6
|
+
slots: {};
|
|
7
|
+
events: {};
|
|
8
|
+
};
|
|
9
|
+
declare class __sveltets_Render<Tag extends 'table' | 'div' = 'table'> {
|
|
10
|
+
props(): ReturnType<typeof $$render<Tag>>['props'];
|
|
11
|
+
events(): ReturnType<typeof $$render<Tag>>['events'];
|
|
12
|
+
slots(): ReturnType<typeof $$render<Tag>>['slots'];
|
|
13
|
+
bindings(): "ref" | "columnWidths";
|
|
14
|
+
exports(): {};
|
|
15
|
+
}
|
|
16
|
+
interface $$IsomorphicComponent {
|
|
17
|
+
new <Tag extends 'table' | 'div' = 'table'>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Tag>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Tag>['props']>, ReturnType<__sveltets_Render<Tag>['events']>, ReturnType<__sveltets_Render<Tag>['slots']>> & {
|
|
18
|
+
$$bindings?: ReturnType<__sveltets_Render<Tag>['bindings']>;
|
|
19
|
+
} & ReturnType<__sveltets_Render<Tag>['exports']>;
|
|
20
|
+
<Tag extends 'table' | 'div' = 'table'>(internal: unknown, props: ReturnType<__sveltets_Render<Tag>['props']> & {}): ReturnType<__sveltets_Render<Tag>['exports']>;
|
|
21
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A table displays sets of two dimensional data. Every part is a primitive, so sorting and
|
|
25
|
+
* selection stay the responsibility of the consumer.
|
|
26
|
+
*
|
|
27
|
+
* - Usage:
|
|
28
|
+
* ```tsx
|
|
29
|
+
* <script>
|
|
30
|
+
* import { Table, TableHeader, TableHeaderCell, TableBody, TableRow, TableCell } from 'fluentui-svelte';
|
|
31
|
+
* </script>
|
|
32
|
+
*
|
|
33
|
+
* <Table aria-label="Files">
|
|
34
|
+
* <TableHeader>
|
|
35
|
+
* <TableRow>
|
|
36
|
+
* <TableHeaderCell>File</TableHeaderCell>
|
|
37
|
+
* </TableRow>
|
|
38
|
+
* </TableHeader>
|
|
39
|
+
* <TableBody>
|
|
40
|
+
* <TableRow>
|
|
41
|
+
* <TableCell>Meeting notes</TableCell>
|
|
42
|
+
* </TableRow>
|
|
43
|
+
* </TableBody>
|
|
44
|
+
* </Table>
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare const Table: $$IsomorphicComponent;
|
|
48
|
+
type Table<Tag extends 'table' | 'div' = 'table'> = InstanceType<typeof Table<Tag>>;
|
|
49
|
+
export default Table;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { PREFIX } from '../../internal/constants.js';
|
|
2
|
+
import { createFSContext } from '../../internal/index.js';
|
|
3
|
+
import { on } from 'svelte/events';
|
|
4
|
+
export const COMPONENT_NAME = `${PREFIX}-table-`;
|
|
5
|
+
export const [getTableContext, setTableContext] = createFSContext();
|
|
6
|
+
/** The cells a grid mover lands on. Every kind of cell takes a turn, header and selection included. */
|
|
7
|
+
export const TABSPOT_ITEMS = '.fs-table-header-cell, .fs-table-cell, .fs-table-selection-cell';
|
|
8
|
+
/** Cells the cursor passes over instead of landing on, which is what `focusMode: 'none'` asks for. */
|
|
9
|
+
export const TABSPOT_SKIP = '[data-fs-focus-mode="none"]';
|
|
10
|
+
/**
|
|
11
|
+
* Everything that would answer the Tab key on its own. A disabled control takes no focus, so
|
|
12
|
+
* holding it would be pointless and entering the cell would land on something that cannot be left.
|
|
13
|
+
*/
|
|
14
|
+
const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex], [contenteditable="true"]';
|
|
15
|
+
/** Controls that spend `Space` themselves, so a row must not take it from under them. */
|
|
16
|
+
const SPACE_CONSUMERS = 'a[href], button, input, select, textarea, [contenteditable="true"]';
|
|
17
|
+
/**
|
|
18
|
+
* `Space` activates a row or a cell the consumer wired, the same way a pointer click does. The grid
|
|
19
|
+
* pattern asks for it: the arrow keys park the focus on a cell, and a cell answers no key by itself.
|
|
20
|
+
*
|
|
21
|
+
* Only a wired element claims the key — anywhere else `Space` stays the page scroll it always was.
|
|
22
|
+
*/
|
|
23
|
+
export const selectOnSpace = (node) => {
|
|
24
|
+
const onkeydown = (e) => {
|
|
25
|
+
if (e.key !== ' ' || e.defaultPrevented)
|
|
26
|
+
return;
|
|
27
|
+
if (e.target.closest(SPACE_CONSUMERS))
|
|
28
|
+
return;
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
node.click();
|
|
31
|
+
};
|
|
32
|
+
return on(node, 'keydown', onkeydown);
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Resolves which column a cell sits in. A cell is always a direct child of its row, so its position
|
|
36
|
+
* among its siblings is the column — nothing else in the markup knows it.
|
|
37
|
+
*
|
|
38
|
+
* Tabspot reads the attribute to find a cell again once a virtualized row has remounted, and the
|
|
39
|
+
* reported index is what lets a cell look its own width up.
|
|
40
|
+
*/
|
|
41
|
+
export const colIndex = (report) => (node) => {
|
|
42
|
+
const parent = node.parentElement;
|
|
43
|
+
if (!parent)
|
|
44
|
+
return;
|
|
45
|
+
const index = [...parent.children].indexOf(node);
|
|
46
|
+
node.dataset.colindex = String(index);
|
|
47
|
+
report?.(index);
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* `focusMode: 'group'`. The focusable content of a cell is held out of the tab order until the user
|
|
51
|
+
* asks for it: `Enter` steps in, `Escape` steps back out to the cell itself.
|
|
52
|
+
*
|
|
53
|
+
* Without it every button in every row is its own tab stop, which is exactly what the single tab
|
|
54
|
+
* stop of a grid exists to avoid. Tabspot cannot express this on its own — its grouper is driven by
|
|
55
|
+
* a direction, and a grid has already spent all four arrows — so the two keys are handled here.
|
|
56
|
+
*/
|
|
57
|
+
export const focusGroup = (node) => {
|
|
58
|
+
/**
|
|
59
|
+
* The tabindex a held element carried before is parked on the element itself, so releasing
|
|
60
|
+
* restores it rather than guessing, and a row that remounts takes its own bookkeeping with it.
|
|
61
|
+
*/
|
|
62
|
+
const hold = () => {
|
|
63
|
+
for (const el of node.querySelectorAll(FOCUSABLE)) {
|
|
64
|
+
if (el.dataset.fsHeldTabindex !== undefined)
|
|
65
|
+
continue;
|
|
66
|
+
el.dataset.fsHeldTabindex = el.getAttribute('tabindex') ?? '';
|
|
67
|
+
el.setAttribute('tabindex', '-1');
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const release = () => {
|
|
71
|
+
for (const el of node.querySelectorAll('[data-fs-held-tabindex]')) {
|
|
72
|
+
const previous = el.dataset.fsHeldTabindex;
|
|
73
|
+
if (previous)
|
|
74
|
+
el.setAttribute('tabindex', previous);
|
|
75
|
+
else
|
|
76
|
+
el.removeAttribute('tabindex');
|
|
77
|
+
delete el.dataset.fsHeldTabindex;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const onkeydown = (e) => {
|
|
81
|
+
if (e.key === 'Enter' && e.target === node) {
|
|
82
|
+
release();
|
|
83
|
+
const first = node.querySelector(FOCUSABLE);
|
|
84
|
+
if (!first)
|
|
85
|
+
return hold();
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
first.focus();
|
|
88
|
+
}
|
|
89
|
+
else if (e.key === 'Escape' && e.target !== node) {
|
|
90
|
+
// The root would otherwise read the same Escape as "leave the table".
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
e.stopPropagation();
|
|
93
|
+
hold();
|
|
94
|
+
node.focus();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
/** Focus moving out of the cell ends the visit, however it left. */
|
|
98
|
+
const onfocusout = (e) => {
|
|
99
|
+
if (!node.contains(e.relatedTarget))
|
|
100
|
+
hold();
|
|
101
|
+
};
|
|
102
|
+
hold();
|
|
103
|
+
// Content that arrives later — a menu opening, a row re-rendering — has to be held too.
|
|
104
|
+
const observer = new MutationObserver(() => {
|
|
105
|
+
// The node's own document, so a table inside an iframe reads the focus that belongs to it.
|
|
106
|
+
if (!node.contains(node.ownerDocument.activeElement))
|
|
107
|
+
hold();
|
|
108
|
+
});
|
|
109
|
+
observer.observe(node, { childList: true, subtree: true });
|
|
110
|
+
const offKeydown = on(node, 'keydown', onkeydown);
|
|
111
|
+
const offFocusout = on(node, 'focusout', onfocusout);
|
|
112
|
+
return () => {
|
|
113
|
+
observer.disconnect();
|
|
114
|
+
offKeydown();
|
|
115
|
+
offFocusout();
|
|
116
|
+
release();
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* The element a table part renders. An explicit `as` wins, otherwise the part falls back to its
|
|
121
|
+
* semantic tag, or to a `div` when the table opted out of native elements.
|
|
122
|
+
*
|
|
123
|
+
* Every part accepts exactly its semantic tag or a `div`, so the invariant is checked here rather
|
|
124
|
+
* than against a list each caller would have to carry.
|
|
125
|
+
*/
|
|
126
|
+
export const getTag = (as, native, noNativeElements) => {
|
|
127
|
+
const tag = as ?? (noNativeElements ? 'div' : native);
|
|
128
|
+
if (tag !== native && tag !== 'div')
|
|
129
|
+
throw new Error(`Invalid tag: ${tag}. Must be one of ${native}, div`);
|
|
130
|
+
return tag;
|
|
131
|
+
};
|
|
132
|
+
/** Reads the table context, or fails loudly when a part is rendered outside a `Table`. */
|
|
133
|
+
export const requireTableContext = () => {
|
|
134
|
+
const context = getTableContext();
|
|
135
|
+
if (!context)
|
|
136
|
+
throw new Error(`No TableContext found for ${COMPONENT_NAME}.`);
|
|
137
|
+
return context;
|
|
138
|
+
};
|