@ui-organized/svelte-table 0.0.0-stage → 6.0.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 (208) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +99 -2
  3. package/dist/DataTable/DataTable.svelte +62 -0
  4. package/dist/DataTable/DataTable.svelte.d.ts +29 -0
  5. package/dist/DataTable/DataTable.svelte.d.ts.map +1 -0
  6. package/dist/DataTable/DataTable.test.svelte.d.ts +2 -0
  7. package/dist/DataTable/DataTable.test.svelte.d.ts.map +1 -0
  8. package/dist/DataTable/DataTable.test.svelte.js +360 -0
  9. package/dist/DataTable/index.d.ts +2 -0
  10. package/dist/DataTable/index.d.ts.map +1 -0
  11. package/dist/DataTable/index.js +1 -0
  12. package/dist/components/Table/CellEditor.svelte +29 -0
  13. package/dist/components/Table/CellEditor.svelte.d.ts +8 -0
  14. package/dist/components/Table/CellEditor.svelte.d.ts.map +1 -0
  15. package/dist/components/Table/MoveButtons.svelte +60 -0
  16. package/dist/components/Table/MoveButtons.svelte.d.ts +8 -0
  17. package/dist/components/Table/MoveButtons.svelte.d.ts.map +1 -0
  18. package/dist/components/Table/ResizeHandle.svelte +82 -0
  19. package/dist/components/Table/ResizeHandle.svelte.d.ts +28 -0
  20. package/dist/components/Table/ResizeHandle.svelte.d.ts.map +1 -0
  21. package/dist/components/Table/Table.svelte +17 -0
  22. package/dist/components/Table/Table.svelte.d.ts +9 -0
  23. package/dist/components/Table/Table.svelte.d.ts.map +1 -0
  24. package/dist/components/Table/TableBody.svelte +78 -0
  25. package/dist/components/Table/TableBody.svelte.d.ts +30 -0
  26. package/dist/components/Table/TableBody.svelte.d.ts.map +1 -0
  27. package/dist/components/Table/TableCell.svelte +106 -0
  28. package/dist/components/Table/TableCell.svelte.d.ts +34 -0
  29. package/dist/components/Table/TableCell.svelte.d.ts.map +1 -0
  30. package/dist/components/Table/TableFooter.svelte +34 -0
  31. package/dist/components/Table/TableFooter.svelte.d.ts +9 -0
  32. package/dist/components/Table/TableFooter.svelte.d.ts.map +1 -0
  33. package/dist/components/Table/TableHeadCell.svelte +157 -0
  34. package/dist/components/Table/TableHeadCell.svelte.d.ts +29 -0
  35. package/dist/components/Table/TableHeadCell.svelte.d.ts.map +1 -0
  36. package/dist/components/Table/TableHeader.svelte +22 -0
  37. package/dist/components/Table/TableHeader.svelte.d.ts +7 -0
  38. package/dist/components/Table/TableHeader.svelte.d.ts.map +1 -0
  39. package/dist/components/Table/TableRow.svelte +84 -0
  40. package/dist/components/Table/TableRow.svelte.d.ts +32 -0
  41. package/dist/components/Table/TableRow.svelte.d.ts.map +1 -0
  42. package/dist/components/Table/TableViewport.svelte +69 -0
  43. package/dist/components/Table/TableViewport.svelte.d.ts +10 -0
  44. package/dist/components/Table/TableViewport.svelte.d.ts.map +1 -0
  45. package/dist/components/Table/index.d.ts +23 -0
  46. package/dist/components/Table/index.d.ts.map +1 -0
  47. package/dist/components/Table/index.js +22 -0
  48. package/dist/components/TableCards/TableCard.svelte +96 -0
  49. package/dist/components/TableCards/TableCard.svelte.d.ts +31 -0
  50. package/dist/components/TableCards/TableCard.svelte.d.ts.map +1 -0
  51. package/dist/components/TableCards/TableCards.svelte +61 -0
  52. package/dist/components/TableCards/TableCards.svelte.d.ts +7 -0
  53. package/dist/components/TableCards/TableCards.svelte.d.ts.map +1 -0
  54. package/dist/components/TableCards/index.d.ts +3 -0
  55. package/dist/components/TableCards/index.d.ts.map +1 -0
  56. package/dist/components/TableCards/index.js +2 -0
  57. package/dist/components/TableDetailSheet/TableDetailSheet.svelte +130 -0
  58. package/dist/components/TableDetailSheet/TableDetailSheet.svelte.d.ts +8 -0
  59. package/dist/components/TableDetailSheet/TableDetailSheet.svelte.d.ts.map +1 -0
  60. package/dist/components/TableDetailSheet/index.d.ts +2 -0
  61. package/dist/components/TableDetailSheet/index.d.ts.map +1 -0
  62. package/dist/components/TableDetailSheet/index.js +1 -0
  63. package/dist/components/TableFilters/TableFilterAdd.svelte +89 -0
  64. package/dist/components/TableFilters/TableFilterAdd.svelte.d.ts +15 -0
  65. package/dist/components/TableFilters/TableFilterAdd.svelte.d.ts.map +1 -0
  66. package/dist/components/TableFilters/TableFilterChip.svelte +164 -0
  67. package/dist/components/TableFilters/TableFilterChip.svelte.d.ts +9 -0
  68. package/dist/components/TableFilters/TableFilterChip.svelte.d.ts.map +1 -0
  69. package/dist/components/TableFilters/TableFilterEditor.svelte +40 -0
  70. package/dist/components/TableFilters/TableFilterEditor.svelte.d.ts +9 -0
  71. package/dist/components/TableFilters/TableFilterEditor.svelte.d.ts.map +1 -0
  72. package/dist/components/TableFilters/TableFilterValueEditor.svelte +209 -0
  73. package/dist/components/TableFilters/TableFilterValueEditor.svelte.d.ts +10 -0
  74. package/dist/components/TableFilters/TableFilterValueEditor.svelte.d.ts.map +1 -0
  75. package/dist/components/TableFilters/TableFilters.svelte +51 -0
  76. package/dist/components/TableFilters/TableFilters.svelte.d.ts +7 -0
  77. package/dist/components/TableFilters/TableFilters.svelte.d.ts.map +1 -0
  78. package/dist/components/TableFilters/index.d.ts +7 -0
  79. package/dist/components/TableFilters/index.d.ts.map +1 -0
  80. package/dist/components/TableFilters/index.js +6 -0
  81. package/dist/components/TableFilters/useAutoFocusField.d.ts +30 -0
  82. package/dist/components/TableFilters/useAutoFocusField.d.ts.map +1 -0
  83. package/dist/components/TableFilters/useAutoFocusField.js +59 -0
  84. package/dist/components/TablePagination/TablePagination.svelte +61 -0
  85. package/dist/components/TablePagination/TablePagination.svelte.d.ts +9 -0
  86. package/dist/components/TablePagination/TablePagination.svelte.d.ts.map +1 -0
  87. package/dist/components/TablePagination/index.d.ts +2 -0
  88. package/dist/components/TablePagination/index.d.ts.map +1 -0
  89. package/dist/components/TablePagination/index.js +1 -0
  90. package/dist/components/TableRowActions/TableRowActions.svelte +68 -0
  91. package/dist/components/TableRowActions/TableRowActions.svelte.d.ts +37 -0
  92. package/dist/components/TableRowActions/TableRowActions.svelte.d.ts.map +1 -0
  93. package/dist/components/TableRowActions/index.d.ts +2 -0
  94. package/dist/components/TableRowActions/index.d.ts.map +1 -0
  95. package/dist/components/TableRowActions/index.js +1 -0
  96. package/dist/components/TableSelectionBar/TableSelectionBar.svelte +88 -0
  97. package/dist/components/TableSelectionBar/TableSelectionBar.svelte.d.ts +30 -0
  98. package/dist/components/TableSelectionBar/TableSelectionBar.svelte.d.ts.map +1 -0
  99. package/dist/components/TableSelectionBar/index.d.ts +2 -0
  100. package/dist/components/TableSelectionBar/index.d.ts.map +1 -0
  101. package/dist/components/TableSelectionBar/index.js +1 -0
  102. package/dist/components/TableSelectionFooter/TableSelectionFooter.svelte +38 -0
  103. package/dist/components/TableSelectionFooter/TableSelectionFooter.svelte.d.ts +7 -0
  104. package/dist/components/TableSelectionFooter/TableSelectionFooter.svelte.d.ts.map +1 -0
  105. package/dist/components/TableSelectionFooter/index.d.ts +2 -0
  106. package/dist/components/TableSelectionFooter/index.d.ts.map +1 -0
  107. package/dist/components/TableSelectionFooter/index.js +1 -0
  108. package/dist/components/TableSheets/TableFilterSheet.svelte +150 -0
  109. package/dist/components/TableSheets/TableFilterSheet.svelte.d.ts +8 -0
  110. package/dist/components/TableSheets/TableFilterSheet.svelte.d.ts.map +1 -0
  111. package/dist/components/TableSheets/TableFilterSheetCondition.svelte +53 -0
  112. package/dist/components/TableSheets/TableFilterSheetCondition.svelte.d.ts +9 -0
  113. package/dist/components/TableSheets/TableFilterSheetCondition.svelte.d.ts.map +1 -0
  114. package/dist/components/TableSheets/TableSheetButton.svelte +44 -0
  115. package/dist/components/TableSheets/TableSheetButton.svelte.d.ts +15 -0
  116. package/dist/components/TableSheets/TableSheetButton.svelte.d.ts.map +1 -0
  117. package/dist/components/TableSheets/TableSortSheet.svelte +100 -0
  118. package/dist/components/TableSheets/TableSortSheet.svelte.d.ts +8 -0
  119. package/dist/components/TableSheets/TableSortSheet.svelte.d.ts.map +1 -0
  120. package/dist/components/TableSheets/index.d.ts +4 -0
  121. package/dist/components/TableSheets/index.d.ts.map +1 -0
  122. package/dist/components/TableSheets/index.js +3 -0
  123. package/dist/components/TableStates/TableEmpty.svelte +35 -0
  124. package/dist/components/TableStates/TableEmpty.svelte.d.ts +11 -0
  125. package/dist/components/TableStates/TableEmpty.svelte.d.ts.map +1 -0
  126. package/dist/components/TableStates/TableError.svelte +26 -0
  127. package/dist/components/TableStates/TableError.svelte.d.ts +10 -0
  128. package/dist/components/TableStates/TableError.svelte.d.ts.map +1 -0
  129. package/dist/components/TableStates/TableLoading.svelte +33 -0
  130. package/dist/components/TableStates/TableLoading.svelte.d.ts +8 -0
  131. package/dist/components/TableStates/TableLoading.svelte.d.ts.map +1 -0
  132. package/dist/components/TableStates/index.d.ts +4 -0
  133. package/dist/components/TableStates/index.d.ts.map +1 -0
  134. package/dist/components/TableStates/index.js +3 -0
  135. package/dist/components/TableSubBar/TableSubBar.svelte +34 -0
  136. package/dist/components/TableSubBar/TableSubBar.svelte.d.ts +7 -0
  137. package/dist/components/TableSubBar/TableSubBar.svelte.d.ts.map +1 -0
  138. package/dist/components/TableSubBar/index.d.ts +2 -0
  139. package/dist/components/TableSubBar/index.d.ts.map +1 -0
  140. package/dist/components/TableSubBar/index.js +1 -0
  141. package/dist/components/TableToolbar/TableActions.svelte +40 -0
  142. package/dist/components/TableToolbar/TableActions.svelte.d.ts +9 -0
  143. package/dist/components/TableToolbar/TableActions.svelte.d.ts.map +1 -0
  144. package/dist/components/TableToolbar/TableExportMenu.svelte +60 -0
  145. package/dist/components/TableToolbar/TableExportMenu.svelte.d.ts +8 -0
  146. package/dist/components/TableToolbar/TableExportMenu.svelte.d.ts.map +1 -0
  147. package/dist/components/TableToolbar/TableReorderToggle.svelte +27 -0
  148. package/dist/components/TableToolbar/TableReorderToggle.svelte.d.ts +7 -0
  149. package/dist/components/TableToolbar/TableReorderToggle.svelte.d.ts.map +1 -0
  150. package/dist/components/TableToolbar/TableScrollButtons.svelte +54 -0
  151. package/dist/components/TableToolbar/TableScrollButtons.svelte.d.ts +9 -0
  152. package/dist/components/TableToolbar/TableScrollButtons.svelte.d.ts.map +1 -0
  153. package/dist/components/TableToolbar/TableSearch.svelte +24 -0
  154. package/dist/components/TableToolbar/TableSearch.svelte.d.ts +9 -0
  155. package/dist/components/TableToolbar/TableSearch.svelte.d.ts.map +1 -0
  156. package/dist/components/TableToolbar/TableSortMenu.svelte +97 -0
  157. package/dist/components/TableToolbar/TableSortMenu.svelte.d.ts +8 -0
  158. package/dist/components/TableToolbar/TableSortMenu.svelte.d.ts.map +1 -0
  159. package/dist/components/TableToolbar/TableToolbar.svelte +113 -0
  160. package/dist/components/TableToolbar/TableToolbar.svelte.d.ts +9 -0
  161. package/dist/components/TableToolbar/TableToolbar.svelte.d.ts.map +1 -0
  162. package/dist/components/TableToolbar/TableViewOptions.svelte +69 -0
  163. package/dist/components/TableToolbar/TableViewOptions.svelte.d.ts +8 -0
  164. package/dist/components/TableToolbar/TableViewOptions.svelte.d.ts.map +1 -0
  165. package/dist/components/TableToolbar/index.d.ts +9 -0
  166. package/dist/components/TableToolbar/index.d.ts.map +1 -0
  167. package/dist/components/TableToolbar/index.js +8 -0
  168. package/dist/core/ActionsHeader.svelte +8 -0
  169. package/dist/core/ActionsHeader.svelte.d.ts +27 -0
  170. package/dist/core/ActionsHeader.svelte.d.ts.map +1 -0
  171. package/dist/core/SelectAllCell.svelte +21 -0
  172. package/dist/core/SelectAllCell.svelte.d.ts +19 -0
  173. package/dist/core/SelectAllCell.svelte.d.ts.map +1 -0
  174. package/dist/core/SelectCell.svelte +37 -0
  175. package/dist/core/SelectCell.svelte.d.ts +8 -0
  176. package/dist/core/SelectCell.svelte.d.ts.map +1 -0
  177. package/dist/core/createDataTable.svelte.d.ts +4 -0
  178. package/dist/core/createDataTable.svelte.d.ts.map +1 -0
  179. package/dist/core/createDataTable.svelte.js +1152 -0
  180. package/dist/core/createDataTable.test.svelte.d.ts +2 -0
  181. package/dist/core/createDataTable.test.svelte.d.ts.map +1 -0
  182. package/dist/core/createDataTable.test.svelte.js +156 -0
  183. package/dist/core/createDeferred.svelte.d.ts +38 -0
  184. package/dist/core/createDeferred.svelte.d.ts.map +1 -0
  185. package/dist/core/createDeferred.svelte.js +81 -0
  186. package/dist/core/elementRef.d.ts +17 -0
  187. package/dist/core/elementRef.d.ts.map +1 -0
  188. package/dist/core/elementRef.js +20 -0
  189. package/dist/core/props.d.ts +3 -0
  190. package/dist/core/props.d.ts.map +1 -0
  191. package/dist/core/props.js +46 -0
  192. package/dist/core/systemColumns.d.ts +16 -0
  193. package/dist/core/systemColumns.d.ts.map +1 -0
  194. package/dist/core/systemColumns.js +53 -0
  195. package/dist/core/tableContext.d.ts +7 -0
  196. package/dist/core/tableContext.d.ts.map +1 -0
  197. package/dist/core/tableContext.js +30 -0
  198. package/dist/core/trackStore.svelte.d.ts +24 -0
  199. package/dist/core/trackStore.svelte.d.ts.map +1 -0
  200. package/dist/core/trackStore.svelte.js +28 -0
  201. package/dist/core/types.d.ts +397 -0
  202. package/dist/core/types.d.ts.map +1 -0
  203. package/dist/core/types.js +1 -0
  204. package/dist/index.d.ts +31 -0
  205. package/dist/index.d.ts.map +1 -0
  206. package/dist/index.js +31 -0
  207. package/dist/styles.css +895 -0
  208. package/package.json +77 -3
@@ -0,0 +1,164 @@
1
+ <!--
2
+ One condition, as a segmented chip (Figma 2462:2636): "Name | is greater than
3
+ | 30". Each segment opens the control for the one parameter it shows — the
4
+ field menu, the operator menu, the value editor — so changing a filter is one
5
+ click on the part you mean rather than a trip through a form.
6
+
7
+ Removal lives in the field menu and at the foot of the value editor, not in a
8
+ dismiss button: a fourth 24px target in a row of segments is a coin-flip on
9
+ touch, and an operator that takes no value ("is empty") has no value editor to
10
+ put it in.
11
+ -->
12
+ <script lang="ts">
13
+ import { clsx } from "clsx";
14
+ import {
15
+ Chip,
16
+ ChipSegment,
17
+ Menu,
18
+ MenuContent,
19
+ MenuItem,
20
+ MenuRadioGroup,
21
+ MenuRadioItem,
22
+ MenuSeparator,
23
+ MenuTrigger,
24
+ Popover,
25
+ PopoverContent,
26
+ PopoverTitle,
27
+ PopoverTrigger,
28
+ } from "@ui-organized/svelte";
29
+ import { afterRowEntrance, type TableFilterCondition } from "@ui-organized/table-core";
30
+ import { getTable } from "../../core/tableContext.js";
31
+ import { autoFocusField, focusAfterRemoval } from "./useAutoFocusField.js";
32
+ import TableFilterEditor from "./TableFilterEditor.svelte";
33
+ import { internalGlyph as glyph } from "@ui-organized/svelte";
34
+
35
+ let { condition, class: className }: { condition: TableFilterCondition; class?: string } =
36
+ $props();
37
+ const table = getTable();
38
+
39
+ // Which chip's editor is open is filter state, not chip state: the chip that
40
+ // opens is often the one the *header* just created, and removing an open
41
+ // condition has to close its editor rather than leave a dangling id behind.
42
+ const editing = $derived(table.filters.editing === condition.id);
43
+ const setOpen = (next: boolean) => table.filters.setEditing(next ? condition.id : null);
44
+ // The first chip arrives inside a row that is still animating open, so the
45
+ // editor waits for the row to settle rather than open against a moving
46
+ // anchor and jump. An existing chip settles at once.
47
+ let settled = $state(false);
48
+ $effect(() => {
49
+ if (!editing) {
50
+ settled = false;
51
+ return;
52
+ }
53
+ const chip = document.querySelector(`[data-filter-id="${CSS.escape(condition.id)}"]`);
54
+ return afterRowEntrance(chip, () => {
55
+ settled = true;
56
+ });
57
+ });
58
+ const open = $derived(editing && settled);
59
+ // The two menus are this chip's alone, so they are local.
60
+ let menu = $state<"field" | "operator" | null>(null);
61
+ const setMenu = (name: "field" | "operator") => (next: boolean) => {
62
+ menu = next ? name : null;
63
+ };
64
+
65
+ // The condition as it was when the editor opened, so Escape can revert it.
66
+ let snapshot = $state<TableFilterCondition | null>(null);
67
+ let editorEl = $state<HTMLElement | null>(null);
68
+
69
+ $effect(() => autoFocusField(editorEl, open));
70
+
71
+ $effect(() => {
72
+ // Only when the editor opens — capturing on every change would defeat it.
73
+ if (open) snapshot = condition;
74
+ });
75
+
76
+ const description = $derived(table.filters.describe(condition));
77
+ const takesValue = $derived(table.filters.arityOf(condition) !== "none");
78
+
79
+ const remove = () => {
80
+ table.filters.remove(condition.id);
81
+ focusAfterRemoval(condition.id);
82
+ };
83
+
84
+ const onKeyDown = (event: KeyboardEvent) => {
85
+ if (event.key !== "Escape" || !snapshot) return;
86
+ // Ark closes on Escape anyway; this puts the value back first, so Escape
87
+ // abandons the edit rather than committing it.
88
+ table.filters.restore(snapshot);
89
+ };
90
+ </script>
91
+
92
+ <!--
93
+ The group's name is the whole sentence, so a screen reader moving into any one
94
+ segment hears what the filter *is* before what the button does.
95
+ -->
96
+ <Chip
97
+ class={clsx("data-table__filter-chip", className)}
98
+ aria-label={`Filter: ${description.text}`}
99
+ selected={editing || menu !== null}
100
+ incomplete={!description.complete}
101
+ data-column-id={condition.columnId}
102
+ data-filter-id={condition.id}
103
+ >
104
+ <Menu open={menu === "field"} onOpenChange={setMenu("field")}>
105
+ <MenuTrigger>
106
+ {#snippet asChild(props)}
107
+ <ChipSegment {...(props() as Record<string, never>)} part="label">{description.field}</ChipSegment>
108
+ {/snippet}
109
+ </MenuTrigger>
110
+ <MenuContent class="data-table__filter-picker">
111
+ <MenuRadioGroup
112
+ value={condition.columnId}
113
+ onValueChange={(columnId) => table.filters.changeField(condition.id, columnId)}
114
+ >
115
+ {#each table.filters.fields as field (field.columnId)}
116
+ <MenuRadioItem value={field.columnId}>{field.label}</MenuRadioItem>
117
+ {/each}
118
+ </MenuRadioGroup>
119
+ <MenuSeparator />
120
+ <MenuItem value="remove" icon={glyph("trash")} onSelect={remove}>Remove filter</MenuItem>
121
+ </MenuContent>
122
+ </Menu>
123
+
124
+ <Menu open={menu === "operator"} onOpenChange={setMenu("operator")}>
125
+ <MenuTrigger>
126
+ {#snippet asChild(props)}
127
+ <ChipSegment {...(props() as Record<string, never>)} part="operator">{description.relative}</ChipSegment>
128
+ {/snippet}
129
+ </MenuTrigger>
130
+ <MenuContent class="data-table__filter-picker">
131
+ <MenuRadioGroup
132
+ value={condition.operator}
133
+ onValueChange={(operator) => table.filters.update(condition.id, { operator })}
134
+ >
135
+ {#each table.filters.operatorsFor(condition) as operator (operator.id)}
136
+ <MenuRadioItem value={operator.id}>{operator.label}</MenuRadioItem>
137
+ {/each}
138
+ </MenuRadioGroup>
139
+ </MenuContent>
140
+ </Menu>
141
+
142
+ {#if takesValue}
143
+ <Popover {open} onOpenChange={setOpen}>
144
+ <PopoverTrigger>
145
+ {#snippet asChild(props)}
146
+ <ChipSegment {...(props() as Record<string, never>)} part="value">{description.value}</ChipSegment>
147
+ {/snippet}
148
+ </PopoverTrigger>
149
+ <PopoverContent
150
+ class="data-table__filter-editor-popup"
151
+ side="bottom"
152
+ align="start"
153
+ onkeydown={onKeyDown}
154
+ >
155
+ <div bind:this={editorEl} class="data-table__filter-editor">
156
+ <PopoverTitle class="data-table__filter-editor-title"
157
+ >{description.field} {description.relative}</PopoverTitle
158
+ >
159
+ <TableFilterEditor {condition} portalContainer={editorEl} />
160
+ </div>
161
+ </PopoverContent>
162
+ </Popover>
163
+ {/if}
164
+ </Chip>
@@ -0,0 +1,9 @@
1
+ import { type TableFilterCondition } from "@ui-organized/table-core";
2
+ type $$ComponentProps = {
3
+ condition: TableFilterCondition;
4
+ class?: string;
5
+ };
6
+ declare const TableFilterChip: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type TableFilterChip = ReturnType<typeof TableFilterChip>;
8
+ export default TableFilterChip;
9
+ //# sourceMappingURL=TableFilterChip.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableFilterChip.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/TableFilters/TableFilterChip.svelte.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAoB,KAAK,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAMtF,KAAK,gBAAgB,GAAI;IAAE,SAAS,EAAE,oBAAoB,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAyH9E,QAAA,MAAM,eAAe,sDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -0,0 +1,40 @@
1
+ <!--
2
+ One condition's value editor (Figma 2298:726): the value, then a rule and a
3
+ remove. The field and the operator have segments of their own on the chip, so
4
+ the editor carries only what the value segment opens.
5
+ -->
6
+ <script lang="ts">
7
+ import { Button, Divider } from "@ui-organized/svelte";
8
+ import type { TableFilterCondition } from "@ui-organized/table-core";
9
+ import { getTable } from "../../core/tableContext.js";
10
+ import { focusAfterRemoval } from "./useAutoFocusField.js";
11
+ import TableFilterValueEditor from "./TableFilterValueEditor.svelte";
12
+ import { internalGlyph as glyph } from "@ui-organized/svelte";
13
+
14
+ let {
15
+ condition,
16
+ portalContainer,
17
+ }: { condition: TableFilterCondition; portalContainer?: HTMLElement | null } = $props();
18
+
19
+ const table = getTable();
20
+ const arity = $derived(table.filters.arityOf(condition));
21
+
22
+ const remove = () => {
23
+ table.filters.remove(condition.id);
24
+ focusAfterRemoval(condition.id);
25
+ };
26
+ </script>
27
+
28
+ <TableFilterValueEditor {condition} {arity} {portalContainer} />
29
+
30
+ <Divider class="data-table__filter-editor-rule" />
31
+
32
+ <Button
33
+ intent="ghost"
34
+ size={table.size}
35
+ icon={glyph("trash")}
36
+ class="data-table__filter-editor-remove"
37
+ onclick={remove}
38
+ >
39
+ Remove
40
+ </Button>
@@ -0,0 +1,9 @@
1
+ import type { TableFilterCondition } from "@ui-organized/table-core";
2
+ type $$ComponentProps = {
3
+ condition: TableFilterCondition;
4
+ portalContainer?: HTMLElement | null;
5
+ };
6
+ declare const TableFilterEditor: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type TableFilterEditor = ReturnType<typeof TableFilterEditor>;
8
+ export default TableFilterEditor;
9
+ //# sourceMappingURL=TableFilterEditor.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableFilterEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/TableFilters/TableFilterEditor.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAMpE,KAAK,gBAAgB,GAAI;IAAE,SAAS,EAAE,oBAAoB,CAAC;IAAC,eAAe,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,CAAC;AAkCpG,QAAA,MAAM,iBAAiB,sDAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,209 @@
1
+ <!--
2
+ The value half of a condition editor.
3
+
4
+ Which control appears follows from **type and operator together**, never from
5
+ the type alone — that is the whole point of separating the two. A date column
6
+ shows one date field for "is before", two for "is between", and a number plus a
7
+ unit for "is in the last".
8
+ -->
9
+ <script lang="ts">
10
+ import { clsx } from "clsx";
11
+ import {
12
+ DateInput,
13
+ Input,
14
+ Listbox,
15
+ NumberField,
16
+ SearchInput,
17
+ SegmentedControl,
18
+ Select,
19
+ } from "@ui-organized/svelte";
20
+ import {
21
+ searchOptions,
22
+ type FilterOperatorArity,
23
+ type TableFilterCondition,
24
+ type TableFilterValue,
25
+ } from "@ui-organized/table-core";
26
+ import { getTable } from "../../core/tableContext.js";
27
+
28
+ /** Options above this many get a search box — the article's "search in the panel". */
29
+ const SEARCHABLE_ABOVE = 8;
30
+
31
+ let {
32
+ condition,
33
+ arity,
34
+ portalContainer,
35
+ }: {
36
+ condition: TableFilterCondition;
37
+ arity: FilterOperatorArity;
38
+ portalContainer?: HTMLElement | null;
39
+ } = $props();
40
+
41
+ const table = getTable();
42
+ let query = $state("");
43
+ const type = $derived(table.filters.typeOf(condition));
44
+ const size = $derived(table.size);
45
+
46
+ const set = (values: TableFilterValue[]) => table.filters.update(condition.id, { values });
47
+ const setAt = (index: number, value: TableFilterValue) => {
48
+ const values = [...condition.values];
49
+ while (values.length <= index) values.push(null);
50
+ values[index] = value;
51
+ set(values);
52
+ };
53
+
54
+ const booleanValue = $derived.by(() => {
55
+ const first = condition.values[0];
56
+ return first === null || first === undefined ? undefined : String(first);
57
+ });
58
+
59
+ const options = $derived(table.filters.optionsFor(condition));
60
+ const searchable = $derived(options.length > SEARCHABLE_ABOVE);
61
+ const visible = $derived(searchOptions(options, query));
62
+ const selected = $derived(condition.values.map(String));
63
+
64
+ const numberAt = (index: number) =>
65
+ typeof condition.values[index] === "number" ? (condition.values[index] as number) : null;
66
+ const stringAt = (index: number) => String(condition.values[index] ?? "");
67
+
68
+ const UNITS = [
69
+ { value: "day", label: "days" },
70
+ { value: "week", label: "weeks" },
71
+ { value: "month", label: "months" },
72
+ { value: "year", label: "years" },
73
+ ];
74
+ const relative = $derived(
75
+ condition.operator === "in-last" || condition.operator === "in-next",
76
+ );
77
+ </script>
78
+
79
+ <!-- `is empty` and `is today` take nothing, so there is nothing to render. -->
80
+ {#if arity === "none"}
81
+ <!-- nothing -->
82
+ {:else if type === "boolean"}
83
+ <SegmentedControl
84
+ {size}
85
+ aria-label="Value"
86
+ items={[
87
+ { value: "true", label: "Yes" },
88
+ { value: "false", label: "No" },
89
+ ]}
90
+ value={booleanValue}
91
+ onValueChange={(value) => set([value === "true"])}
92
+ />
93
+ {:else if type === "enum"}
94
+ {#if searchable}
95
+ <SearchInput
96
+ {size}
97
+ aria-label="Search values"
98
+ value={query}
99
+ oninput={(event) => (query = event.currentTarget.value)}
100
+ onClear={() => (query = "")}
101
+ />
102
+ {/if}
103
+ <!--
104
+ `label`, not `aria-label`: Listbox has a closed prop API, so an `aria-label`
105
+ is silently dropped and the listbox ships unnamed — an
106
+ `aria-input-field-name` violation that only shows up with the popover open,
107
+ which the story-level axe gate never sees.
108
+
109
+ The count rides in the label because `ListboxOption.label` is a string —
110
+ which is also exactly what a screen reader reads out. A zero-count option
111
+ would filter to nothing, and is never disabled while selected or the user
112
+ could not untick it.
113
+ -->
114
+ <Listbox
115
+ {size}
116
+ class="data-table__filter-field data-table__filter-field--unlabelled"
117
+ selectionMode={arity === "many" ? "multiple" : "single"}
118
+ label="Value"
119
+ options={visible.map((option) => ({
120
+ value: option.value,
121
+ label: option.count > 0 ? `${option.label} · ${option.count}` : option.label,
122
+ disabled: option.disabled,
123
+ }))}
124
+ value={selected}
125
+ emptyMessage="No matching values"
126
+ onValueChange={(values) => set(arity === "many" ? values : values.slice(0, 1))}
127
+ />
128
+ {:else if type === "date"}
129
+ <!-- "in the last 7 days" — a count and a unit, not a date. -->
130
+ {#if relative}
131
+ <div class="data-table__filter-editor-value">
132
+ <NumberField
133
+ {size}
134
+ label="Amount"
135
+ min={1}
136
+ value={numberAt(0)}
137
+ onValueChange={(value) => setAt(0, value)}
138
+ />
139
+ <Select
140
+ {size}
141
+ label="Unit"
142
+ {portalContainer}
143
+ value={condition.unit ?? "day"}
144
+ options={UNITS}
145
+ onValueChange={(unit) =>
146
+ table.filters.update(condition.id, {
147
+ unit: unit as "day" | "week" | "month" | "year",
148
+ })}
149
+ />
150
+ </div>
151
+ {:else}
152
+ <div class="data-table__filter-editor-value">
153
+ <!-- Into the editor, like the Select above: the calendar portalled to
154
+ <body> sits on the same popover tier as this editor, and the editor,
155
+ mounted later, painted over it. -->
156
+ <DateInput
157
+ {size}
158
+ label={arity === "two" ? "From" : "Date"}
159
+ {portalContainer}
160
+ class={clsx(
161
+ "data-table__filter-field",
162
+ arity !== "two" && "data-table__filter-field--unlabelled",
163
+ )}
164
+ value={stringAt(0)}
165
+ oninput={(event) => setAt(0, event.currentTarget.value || null)}
166
+ />
167
+ {#if arity === "two"}
168
+ <DateInput
169
+ {size}
170
+ label="To"
171
+ {portalContainer}
172
+ value={stringAt(1)}
173
+ oninput={(event) => setAt(1, event.currentTarget.value || null)}
174
+ />
175
+ {/if}
176
+ </div>
177
+ {/if}
178
+ {:else if type === "number"}
179
+ <div class="data-table__filter-editor-value">
180
+ <NumberField
181
+ {size}
182
+ label={arity === "two" ? "Minimum" : "Value"}
183
+ class={clsx(
184
+ "data-table__filter-field",
185
+ arity !== "two" && "data-table__filter-field--unlabelled",
186
+ )}
187
+ placeholder={arity === "two" ? undefined : "Data"}
188
+ value={numberAt(0)}
189
+ onValueChange={(value) => setAt(0, value)}
190
+ />
191
+ {#if arity === "two"}
192
+ <NumberField
193
+ {size}
194
+ label="Maximum"
195
+ value={numberAt(1)}
196
+ onValueChange={(value) => setAt(1, value)}
197
+ />
198
+ {/if}
199
+ </div>
200
+ {:else}
201
+ <Input
202
+ {size}
203
+ label="Value"
204
+ class="data-table__filter-field data-table__filter-field--unlabelled"
205
+ placeholder="Data"
206
+ value={stringAt(0)}
207
+ oninput={(event) => setAt(0, event.currentTarget.value)}
208
+ />
209
+ {/if}
@@ -0,0 +1,10 @@
1
+ import { type FilterOperatorArity, type TableFilterCondition } from "@ui-organized/table-core";
2
+ type $$ComponentProps = {
3
+ condition: TableFilterCondition;
4
+ arity: FilterOperatorArity;
5
+ portalContainer?: HTMLElement | null;
6
+ };
7
+ declare const TableFilterValueEditor: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type TableFilterValueEditor = ReturnType<typeof TableFilterValueEditor>;
9
+ export default TableFilterValueEditor;
10
+ //# sourceMappingURL=TableFilterValueEditor.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableFilterValueEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/TableFilters/TableFilterValueEditor.svelte.ts"],"names":[],"mappings":"AAaA,OAAO,EAEH,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAE1B,MAAM,0BAA0B,CAAC;AAGnC,KAAK,gBAAgB,GAAI;IACtB,SAAS,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE,mBAAmB,CAAC;IAC3B,eAAe,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACtC,CAAC;AA+GJ,QAAA,MAAM,sBAAsB,sDAAwC,CAAC;AACrE,KAAK,sBAAsB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACxE,eAAe,sBAAsB,CAAC"}
@@ -0,0 +1,51 @@
1
+ <!--
2
+ The applied filters, on their own line under the header (Figma 2298:362).
3
+
4
+ A *summary*, not a control panel: the filter button lives in the header above,
5
+ and this line does not exist until the user has actually filtered something. A
6
+ bar that is always present and usually empty is chrome that teaches people to
7
+ stop looking at it.
8
+ -->
9
+ <script lang="ts">
10
+ import { clsx } from "clsx";
11
+ import { Button } from "@ui-organized/svelte";
12
+ import { getTable } from "../../core/tableContext.js";
13
+ import TableFilterAdd from "./TableFilterAdd.svelte";
14
+ import TableFilterChip from "./TableFilterChip.svelte";
15
+ import { internalGlyph as glyph } from "@ui-organized/svelte";
16
+
17
+ let { class: className }: { class?: string } = $props();
18
+ const table = getTable();
19
+ const conditions = $derived(table.filters.conditions);
20
+ const render = $derived(table.options.filterable !== false && conditions.length > 0);
21
+ </script>
22
+
23
+ <!--
24
+ `role="group"`, not `role="toolbar"`: toolbar promises arrow-key navigation
25
+ between its controls, and until that lands the promise would be a lie to
26
+ assistive tech.
27
+ -->
28
+ {#if render}
29
+ <div class={clsx("data-table__filters", className)} role="group" aria-label="Filters">
30
+ <span class="data-table__filters-label">Filters</span>
31
+
32
+ {#each conditions as condition (condition.id)}
33
+ <TableFilterChip {condition} />
34
+ {/each}
35
+
36
+ <TableFilterAdd label="Add" />
37
+
38
+ <Button intent="ghost" size="sm" icon={glyph("rotate-ccw")} onclick={() => table.filters.clear()}>
39
+ Reset
40
+ </Button>
41
+
42
+ <!--
43
+ One polite region for add / remove / change / clear. Deliberately not a
44
+ visible count: that changes on every keystroke elsewhere, and a live region
45
+ that re-announces per keystroke is a firehose.
46
+ -->
47
+ <span class="data-table__sr-only" role="status" aria-live="polite">
48
+ {table.filters.announcement}
49
+ </span>
50
+ </div>
51
+ {/if}
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ class?: string;
3
+ };
4
+ declare const TableFilters: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type TableFilters = ReturnType<typeof TableFilters>;
6
+ export default TableFilters;
7
+ //# sourceMappingURL=TableFilters.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableFilters.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/TableFilters/TableFilters.svelte.ts"],"names":[],"mappings":"AAUC,KAAK,gBAAgB,GAAI;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAwC7C,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { default as TableFilters } from "./TableFilters.svelte";
2
+ export { default as TableFilterAdd } from "./TableFilterAdd.svelte";
3
+ export { default as TableFilterChip } from "./TableFilterChip.svelte";
4
+ export { default as TableFilterEditor } from "./TableFilterEditor.svelte";
5
+ export { default as TableFilterValueEditor } from "./TableFilterValueEditor.svelte";
6
+ export { autoFocusField, focusAfterRemoval } from "./useAutoFocusField.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/TableFilters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { default as TableFilters } from "./TableFilters.svelte";
2
+ export { default as TableFilterAdd } from "./TableFilterAdd.svelte";
3
+ export { default as TableFilterChip } from "./TableFilterChip.svelte";
4
+ export { default as TableFilterEditor } from "./TableFilterEditor.svelte";
5
+ export { default as TableFilterValueEditor } from "./TableFilterValueEditor.svelte";
6
+ export { autoFocusField, focusAfterRemoval } from "./useAutoFocusField.js";
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Put focus in the first typeable field of a popover once it opens.
3
+ *
4
+ * The pattern analysis is explicit about this — "enable autofocus … it's
5
+ * possible for the user to start typing right away" — and the obvious spelling,
6
+ * an `autofocus` attribute, does not work here: Ark moves focus into the popover
7
+ * content *after* mount, so the attribute is overwritten and focus lands on a
8
+ * button.
9
+ *
10
+ * **Two** frames, not one. A single frame is not enough: a focus trace in the
11
+ * React adapter showed this landing correctly and then Ark's `Select` trigger
12
+ * taking focus one millisecond later, because the operator picker is the first
13
+ * focusable child. Waiting a second frame puts this last, which is the only
14
+ * thing that sticks — and it is Ark's behaviour, not React's, so it is the same
15
+ * here.
16
+ *
17
+ * Deliberately only an `<input>`: a text, number, date or search field is
18
+ * something you type into immediately. An enum list without a search box has
19
+ * nothing to type into, so its focus is left exactly where Ark put it.
20
+ *
21
+ * A plain function rather than a rune-bearing helper, so the caller decides
22
+ * which `$effect` it runs in — the chip owns both the element and the open flag.
23
+ */
24
+ export declare function autoFocusField(container: HTMLElement | null, open: boolean): (() => void) | void;
25
+ /**
26
+ * Focus has to land somewhere deliberate when a chip disappears, or it falls to
27
+ * `<body>` and a keyboard user loses their place entirely.
28
+ */
29
+ export declare function focusAfterRemoval(removedId: string): void;
30
+ //# sourceMappingURL=useAutoFocusField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAutoFocusField.d.ts","sourceRoot":"","sources":["../../../src/lib/components/TableFilters/useAutoFocusField.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,EAAE,IAAI,EAAE,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAmBhG;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CASzD"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Put focus in the first typeable field of a popover once it opens.
3
+ *
4
+ * The pattern analysis is explicit about this — "enable autofocus … it's
5
+ * possible for the user to start typing right away" — and the obvious spelling,
6
+ * an `autofocus` attribute, does not work here: Ark moves focus into the popover
7
+ * content *after* mount, so the attribute is overwritten and focus lands on a
8
+ * button.
9
+ *
10
+ * **Two** frames, not one. A single frame is not enough: a focus trace in the
11
+ * React adapter showed this landing correctly and then Ark's `Select` trigger
12
+ * taking focus one millisecond later, because the operator picker is the first
13
+ * focusable child. Waiting a second frame puts this last, which is the only
14
+ * thing that sticks — and it is Ark's behaviour, not React's, so it is the same
15
+ * here.
16
+ *
17
+ * Deliberately only an `<input>`: a text, number, date or search field is
18
+ * something you type into immediately. An enum list without a search box has
19
+ * nothing to type into, so its focus is left exactly where Ark put it.
20
+ *
21
+ * A plain function rather than a rune-bearing helper, so the caller decides
22
+ * which `$effect` it runs in — the chip owns both the element and the open flag.
23
+ */
24
+ export function autoFocusField(container, open) {
25
+ if (!open || !container || typeof requestAnimationFrame !== "function")
26
+ return;
27
+ let inner = 0;
28
+ const outer = requestAnimationFrame(() => {
29
+ inner = requestAnimationFrame(() => {
30
+ const field = container.querySelector('input:not([type="hidden"]):not([disabled])');
31
+ // Already there — Ark's own initial focus can land on the field, and
32
+ // the user may be typing by now. Selecting would eat what they typed.
33
+ if (!field || field === document.activeElement)
34
+ return;
35
+ field.focus();
36
+ field.select();
37
+ });
38
+ });
39
+ return () => {
40
+ cancelAnimationFrame(outer);
41
+ cancelAnimationFrame(inner);
42
+ };
43
+ }
44
+ /**
45
+ * Focus has to land somewhere deliberate when a chip disappears, or it falls to
46
+ * `<body>` and a keyboard user loses their place entirely.
47
+ */
48
+ export function focusAfterRemoval(removedId) {
49
+ if (typeof requestAnimationFrame !== "function")
50
+ return;
51
+ requestAnimationFrame(() => {
52
+ const bar = document.querySelector(".data-table__filters");
53
+ if (!bar)
54
+ return;
55
+ const chips = [...bar.querySelectorAll("[data-filter-id]")];
56
+ const next = chips.find((element) => element.dataset.filterId !== removedId);
57
+ (next?.querySelector("button") ?? bar.querySelector("button"))?.focus();
58
+ });
59
+ }