@svgrid/grid 1.1.0 → 1.1.2

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 (148) hide show
  1. package/dist/FlexRender.svelte +96 -96
  2. package/dist/GridMenus.svelte +205 -127
  3. package/dist/SvGrid.controller.svelte.d.ts +44 -1
  4. package/dist/SvGrid.controller.svelte.js +240 -44
  5. package/dist/SvGrid.css +325 -22
  6. package/dist/SvGrid.helpers.test.d.ts +1 -0
  7. package/dist/SvGrid.helpers.test.js +298 -0
  8. package/dist/SvGrid.svelte +729 -203
  9. package/dist/SvGrid.types.d.ts +91 -4
  10. package/dist/aligned-grids.d.ts +6 -0
  11. package/dist/aligned-grids.js +84 -0
  12. package/dist/aligned-grids.test.d.ts +1 -0
  13. package/dist/aligned-grids.test.js +75 -0
  14. package/dist/build-api.coverage.test.d.ts +20 -0
  15. package/dist/build-api.coverage.test.js +505 -0
  16. package/dist/build-api.js +61 -31
  17. package/dist/cell-render.test.d.ts +1 -0
  18. package/dist/cell-render.test.js +338 -0
  19. package/dist/cell-values.d.ts +1 -1
  20. package/dist/cell-values.js +7 -7
  21. package/dist/chart-export.test.d.ts +1 -0
  22. package/dist/chart-export.test.js +302 -0
  23. package/dist/chart.coverage.test.d.ts +1 -0
  24. package/dist/chart.coverage.test.js +748 -0
  25. package/dist/clipboard.js +88 -24
  26. package/dist/clipboard.test.d.ts +1 -0
  27. package/dist/clipboard.test.js +700 -0
  28. package/dist/collaboration.coverage.test.d.ts +1 -0
  29. package/dist/collaboration.coverage.test.js +200 -0
  30. package/dist/column-groups.d.ts +19 -0
  31. package/dist/column-groups.js +62 -0
  32. package/dist/column-groups.test.d.ts +1 -0
  33. package/dist/column-groups.test.js +56 -0
  34. package/dist/column-types.d.ts +10 -0
  35. package/dist/column-types.js +63 -0
  36. package/dist/column-types.test.d.ts +1 -0
  37. package/dist/column-types.test.js +62 -0
  38. package/dist/columns.test.d.ts +1 -0
  39. package/dist/columns.test.js +625 -0
  40. package/dist/core.d.ts +86 -1
  41. package/dist/core.js +2 -2
  42. package/dist/editing.d.ts +7 -0
  43. package/dist/editing.js +191 -5
  44. package/dist/editing.test.d.ts +1 -0
  45. package/dist/editing.test.js +732 -0
  46. package/dist/editors/cell-editors.coverage.test.d.ts +1 -0
  47. package/dist/editors/cell-editors.coverage.test.js +139 -0
  48. package/dist/facet-buckets.test.d.ts +1 -0
  49. package/dist/facet-buckets.test.js +296 -0
  50. package/dist/filter-operators.test.d.ts +1 -0
  51. package/dist/filter-operators.test.js +135 -0
  52. package/dist/hyperformula-adapter.test.d.ts +1 -0
  53. package/dist/hyperformula-adapter.test.js +205 -0
  54. package/dist/index.d.ts +5 -2
  55. package/dist/index.js +4 -1
  56. package/dist/keyboard-handlers.coverage.test.d.ts +1 -0
  57. package/dist/keyboard-handlers.coverage.test.js +495 -0
  58. package/dist/keyboard-handlers.js +7 -2
  59. package/dist/menus.js +1 -0
  60. package/dist/menus.test.d.ts +1 -0
  61. package/dist/menus.test.js +560 -0
  62. package/dist/named-views.coverage.test.d.ts +1 -0
  63. package/dist/named-views.coverage.test.js +180 -0
  64. package/dist/row-drag.d.ts +49 -0
  65. package/dist/row-drag.js +221 -0
  66. package/dist/row-drag.test.d.ts +1 -0
  67. package/dist/row-drag.test.js +142 -0
  68. package/dist/row-resize.test.d.ts +1 -0
  69. package/dist/row-resize.test.js +329 -0
  70. package/dist/scroll-sync.js +3 -0
  71. package/dist/scroll-sync.test.d.ts +1 -0
  72. package/dist/scroll-sync.test.js +290 -0
  73. package/dist/selection.d.ts +7 -1
  74. package/dist/selection.js +76 -36
  75. package/dist/selection.multi-range.test.d.ts +1 -0
  76. package/dist/selection.multi-range.test.js +55 -0
  77. package/dist/selection.test.d.ts +1 -0
  78. package/dist/selection.test.js +647 -0
  79. package/dist/server-data-source.coverage.test.d.ts +1 -0
  80. package/dist/server-data-source.coverage.test.js +154 -0
  81. package/dist/spreadsheet.d.ts +30 -0
  82. package/dist/spreadsheet.js +48 -0
  83. package/dist/spreadsheet.test.d.ts +1 -0
  84. package/dist/spreadsheet.test.js +446 -0
  85. package/dist/summaries.js +4 -4
  86. package/dist/sv-grid-scrollbar.js +13 -1
  87. package/dist/svgrid-wrapper.types.d.ts +19 -0
  88. package/dist/svgrid.behavior.test.js +20 -0
  89. package/dist/svgrid.interaction.test.js +31 -0
  90. package/dist/svgrid.new-features.wrapper.test.js +34 -2
  91. package/dist/test-setup.js +9 -3
  92. package/dist/virtualization/scroll-scaling.d.ts +17 -0
  93. package/dist/virtualization/scroll-scaling.js +35 -0
  94. package/dist/virtualization/scroll-scaling.test.js +42 -1
  95. package/package.json +2 -1
  96. package/src/FlexRender.svelte +96 -96
  97. package/src/GridMenus.svelte +205 -127
  98. package/src/SvGrid.controller.svelte.ts +204 -36
  99. package/src/SvGrid.css +277 -12
  100. package/src/SvGrid.svelte +727 -205
  101. package/src/SvGrid.types.ts +85 -4
  102. package/src/a11y.contract.test.ts +49 -49
  103. package/src/a11y.test.ts +59 -59
  104. package/src/a11y.ts +59 -59
  105. package/src/aligned-grids.test.ts +80 -0
  106. package/src/aligned-grids.ts +87 -0
  107. package/src/build-api.ts +43 -23
  108. package/src/cell-formatting.ts +169 -169
  109. package/src/cell-values.ts +4 -4
  110. package/src/clipboard.test.ts +49 -0
  111. package/src/clipboard.ts +51 -23
  112. package/src/column-groups.test.ts +59 -0
  113. package/src/column-groups.ts +80 -0
  114. package/src/column-types.test.ts +68 -0
  115. package/src/column-types.ts +82 -0
  116. package/src/core.performance.test.ts +30 -30
  117. package/src/core.ts +1077 -999
  118. package/src/createGrid.svelte.ts +42 -42
  119. package/src/createGrid.test.ts +10 -10
  120. package/src/createGridState.svelte.ts +17 -17
  121. package/src/editing.ts +161 -5
  122. package/src/flex-render.ts +3 -3
  123. package/src/index.ts +208 -196
  124. package/src/keyboard.test.ts +59 -59
  125. package/src/keyboard.ts +97 -97
  126. package/src/menus.ts +1 -0
  127. package/src/merge-objects.ts +48 -48
  128. package/src/render-component.ts +28 -28
  129. package/src/row-drag.test.ts +168 -0
  130. package/src/row-drag.ts +255 -0
  131. package/src/scroll-sync.ts +2 -0
  132. package/src/selection.multi-range.test.ts +61 -0
  133. package/src/selection.ts +71 -37
  134. package/src/spreadsheet.test.ts +46 -2
  135. package/src/spreadsheet.ts +58 -0
  136. package/src/static-functions.ts +11 -11
  137. package/src/subscribe.ts +38 -38
  138. package/src/summaries.ts +4 -4
  139. package/src/svgrid-wrapper.types.ts +412 -393
  140. package/src/svgrid.features.test.ts +157 -157
  141. package/src/svgrid.new-features.wrapper.test.ts +2 -2
  142. package/src/svgrid.wrapper.test.ts +40 -40
  143. package/src/virtualization/column-virtualizer.test.ts +27 -27
  144. package/src/virtualization/column-virtualizer.ts +30 -30
  145. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  146. package/src/virtualization/types.ts +30 -30
  147. package/src/virtualization/virtualizer.test.ts +47 -47
  148. package/src/virtualization/virtualizer.ts +296 -296
@@ -1,96 +1,96 @@
1
- <script
2
- lang="ts"
3
- generics="TData extends RowData"
4
- >
5
- import { isFunction } from './core'
6
- import {
7
- RenderComponentConfig,
8
- RenderSnippetConfig,
9
- } from './render-component'
10
- import type {
11
- Cell,
12
- CellContext,
13
- ColumnDefTemplate,
14
- Header,
15
- HeaderContext,
16
- RowData,
17
- } from './core'
18
-
19
- type Props =
20
- | {
21
- content?: ColumnDefTemplate<
22
- | HeaderContext<TData>
23
- | CellContext<TData>
24
- >
25
- context:
26
- | HeaderContext<TData>
27
- | CellContext<TData>
28
- cell?: never
29
- header?: never
30
- footer?: never
31
- }
32
- | {
33
- cell: Cell<TData>
34
- content?: never
35
- context?: never
36
- header?: never
37
- footer?: never
38
- }
39
- | {
40
- header: Header<TData>
41
- content?: never
42
- context?: never
43
- cell?: never
44
- footer?: never
45
- }
46
- | {
47
- footer: Header<TData>
48
- content?: never
49
- context?: never
50
- cell?: never
51
- header?: never
52
- }
53
-
54
- let props: Props = $props()
55
-
56
- const resolved = $derived.by(() => {
57
- if ('cell' in props && props.cell) {
58
- return {
59
- content: props.cell.column.columnDef.cell,
60
- context: props.cell.getContext(),
61
- }
62
- }
63
- if ('header' in props && props.header) {
64
- return {
65
- content: props.header.column.columnDef.header,
66
- context: props.header.getContext(),
67
- }
68
- }
69
- if ('footer' in props && props.footer) {
70
- return {
71
- content: props.footer.column.columnDef.footer,
72
- context: props.footer.getContext(),
73
- }
74
- }
75
- return {
76
- content: props.content,
77
- context: props.context,
78
- }
79
- })
80
-
81
- const result = $derived(
82
- isFunction(resolved.content)
83
- ? resolved.content(resolved.context as any)
84
- : undefined,
85
- )
86
- </script>
87
-
88
- {#if typeof resolved.content === 'string'}
89
- {resolved.content}
90
- {:else if result instanceof RenderComponentConfig}
91
- <result.component {...result.props} />
92
- {:else if result instanceof RenderSnippetConfig}
93
- {@render result.snippet(result.params)}
94
- {:else if result !== undefined}
95
- {result}
96
- {/if}
1
+ <script
2
+ lang="ts"
3
+ generics="TData extends RowData"
4
+ >
5
+ import { isFunction } from './core'
6
+ import {
7
+ RenderComponentConfig,
8
+ RenderSnippetConfig,
9
+ } from './render-component'
10
+ import type {
11
+ Cell,
12
+ CellContext,
13
+ ColumnDefTemplate,
14
+ Header,
15
+ HeaderContext,
16
+ RowData,
17
+ } from './core'
18
+
19
+ type Props =
20
+ | {
21
+ content?: ColumnDefTemplate<
22
+ | HeaderContext<TData>
23
+ | CellContext<TData>
24
+ >
25
+ context:
26
+ | HeaderContext<TData>
27
+ | CellContext<TData>
28
+ cell?: never
29
+ header?: never
30
+ footer?: never
31
+ }
32
+ | {
33
+ cell: Cell<TData>
34
+ content?: never
35
+ context?: never
36
+ header?: never
37
+ footer?: never
38
+ }
39
+ | {
40
+ header: Header<TData>
41
+ content?: never
42
+ context?: never
43
+ cell?: never
44
+ footer?: never
45
+ }
46
+ | {
47
+ footer: Header<TData>
48
+ content?: never
49
+ context?: never
50
+ cell?: never
51
+ header?: never
52
+ }
53
+
54
+ let props: Props = $props()
55
+
56
+ const resolved = $derived.by(() => {
57
+ if ('cell' in props && props.cell) {
58
+ return {
59
+ content: props.cell.column.columnDef.cell,
60
+ context: props.cell.getContext(),
61
+ }
62
+ }
63
+ if ('header' in props && props.header) {
64
+ return {
65
+ content: props.header.column.columnDef.header,
66
+ context: props.header.getContext(),
67
+ }
68
+ }
69
+ if ('footer' in props && props.footer) {
70
+ return {
71
+ content: props.footer.column.columnDef.footer,
72
+ context: props.footer.getContext(),
73
+ }
74
+ }
75
+ return {
76
+ content: props.content,
77
+ context: props.context,
78
+ }
79
+ })
80
+
81
+ const result = $derived(
82
+ isFunction(resolved.content)
83
+ ? resolved.content(resolved.context as any)
84
+ : undefined,
85
+ )
86
+ </script>
87
+
88
+ {#if typeof resolved.content === 'string'}
89
+ {resolved.content}
90
+ {:else if result instanceof RenderComponentConfig}
91
+ <result.component {...result.props} />
92
+ {:else if result instanceof RenderSnippetConfig}
93
+ {@render result.snippet(result.params)}
94
+ {:else if result !== undefined}
95
+ {result}
96
+ {/if}
@@ -108,6 +108,7 @@
108
108
  const operatorMenuPos = $derived(ctrl.operatorMenuPos);
109
109
  const chooseColumnsPos = $derived(ctrl.chooseColumnsPos);
110
110
  const showColumnFiltersEffective = $derived(ctrl.showColumnFiltersEffective);
111
+ const columnMenuTabsEnabled = $derived(ctrl.props.columnMenuTabs === true);
111
112
  const grid = $derived(ctrl.grid);
112
113
  const allColumns = $derived(ctrl.allColumns);
113
114
  const isColumnPinned = $derived(ctrl.isColumnPinned);
@@ -123,6 +124,15 @@
123
124
  const operatorsForColumn = $derived(ctrl.operatorsForColumn);
124
125
  const defaultOperatorFor = $derived(ctrl.defaultOperatorFor);
125
126
  const operatorLabelFor = $derived(ctrl.operatorLabelFor);
127
+
128
+ // Patch a column's menu filter (used for the optional 2nd condition + join
129
+ // of multi-condition filtering). Merges into the existing entry, creating a
130
+ // base entry from the column's default operator when none exists yet.
131
+ function patchFilter(columnId: string, patch: Record<string, unknown>) {
132
+ const col = allColumns.find((c) => c.id === columnId);
133
+ const cur = ctrl.filterMenuValues[columnId] ?? { operator: defaultOperatorFor(col), value: "" };
134
+ ctrl.filterMenuValues = { ...ctrl.filterMenuValues, [columnId]: { ...cur, ...patch } };
135
+ }
126
136
  const isColumnFiltered = $derived(ctrl.isColumnFiltered);
127
137
  const closeMenus = $derived(ctrl.closeMenus);
128
138
  const autosizeColumn = $derived(ctrl.autosizeColumn);
@@ -169,15 +179,190 @@
169
179
  ></div>
170
180
  {/if}
171
181
 
182
+ <!-- Shared filter UI: rendered both in the funnel popover and in the column
183
+ menu's Filter tab, so the two never diverge. -->
184
+ {#snippet filterPanelBody(colId: string)}
185
+ {@const menuColumn = allColumns.find((c) => c.id === colId)}
186
+ {@const menuOperatorOptions = operatorsForColumn(menuColumn)}
187
+ {@const menuActiveOperator = filterMenuValues[colId]?.operator ?? defaultOperatorFor(menuColumn)}
188
+ {@const menuInputType = getEditorInputType((menuColumn?.columnDef.editorType ?? "text") as CellEditorType)}
189
+ <div class="sv-grid-menu-filter">
190
+ <div class="sv-grid-menu-filter-head">
191
+ <span class="sv-grid-header-icon">{@render icon("filter")}</span> Filter
192
+ condition
193
+ </div>
194
+ <select
195
+ class="sv-grid-menu-operator-select"
196
+ aria-label="Filter condition"
197
+ value={menuActiveOperator}
198
+ onchange={(event) =>
199
+ updateFilterOperator(colId, (event.currentTarget as HTMLSelectElement).value as FilterOperator)}
200
+ >
201
+ {#each menuOperatorOptions as option (option.value)}
202
+ <option value={option.value}>{operatorLabelFor(option, menuColumn)}</option>
203
+ {/each}
204
+ </select>
205
+ {#if menuActiveOperator !== "isBlank"}
206
+ <input
207
+ class="sv-grid-menu-condition-value"
208
+ type={menuInputType}
209
+ value={filterMenuValues[colId]?.value ?? ""}
210
+ placeholder={menuActiveOperator === "between" ? "From" : "Filter value..."}
211
+ oninput={(event) => updateFilterMenuValue(colId, (event.currentTarget as HTMLInputElement).value)}
212
+ />
213
+ {#if menuActiveOperator === "between"}
214
+ <input
215
+ class="sv-grid-menu-condition-value"
216
+ type={menuInputType}
217
+ value={filterMenuValues[colId]?.valueTo ?? ""}
218
+ placeholder="To"
219
+ oninput={(event) => updateFilterMenuValueTo(colId, (event.currentTarget as HTMLInputElement).value)}
220
+ />
221
+ {/if}
222
+ {@const mf = filterMenuValues[colId]}
223
+ {#if mf?.operator2}
224
+ {@const op2 = mf.operator2 ?? defaultOperatorFor(menuColumn)}
225
+ <div class="sv-grid-menu-join" role="radiogroup" aria-label="Combine conditions">
226
+ <button type="button" class:is-on={(mf.join ?? "AND") === "AND"}
227
+ onclick={() => patchFilter(colId, { join: "AND" })}>AND</button>
228
+ <button type="button" class:is-on={mf.join === "OR"}
229
+ onclick={() => patchFilter(colId, { join: "OR" })}>OR</button>
230
+ <button type="button" class="sv-grid-menu-join-x" title="Remove second condition"
231
+ aria-label="Remove second condition"
232
+ onclick={() => patchFilter(colId, { operator2: undefined, value2: undefined, valueTo2: undefined, join: undefined })}>×</button>
233
+ </div>
234
+ <select
235
+ class="sv-grid-menu-operator-select"
236
+ aria-label="Second filter condition"
237
+ value={op2}
238
+ onchange={(event) =>
239
+ patchFilter(colId, { operator2: (event.currentTarget as HTMLSelectElement).value as FilterOperator })}
240
+ >
241
+ {#each menuOperatorOptions as option (option.value)}
242
+ <option value={option.value}>{operatorLabelFor(option, menuColumn)}</option>
243
+ {/each}
244
+ </select>
245
+ {#if op2 !== "isBlank"}
246
+ <input
247
+ class="sv-grid-menu-condition-value"
248
+ type={menuInputType}
249
+ value={mf.value2 ?? ""}
250
+ placeholder={op2 === "between" ? "From" : "Filter value..."}
251
+ oninput={(event) => patchFilter(colId, { value2: (event.currentTarget as HTMLInputElement).value })}
252
+ />
253
+ {#if op2 === "between"}
254
+ <input
255
+ class="sv-grid-menu-condition-value"
256
+ type={menuInputType}
257
+ value={mf.valueTo2 ?? ""}
258
+ placeholder="To"
259
+ oninput={(event) => patchFilter(colId, { valueTo2: (event.currentTarget as HTMLInputElement).value })}
260
+ />
261
+ {/if}
262
+ {/if}
263
+ {:else}
264
+ <button type="button" class="sv-grid-menu-add-cond"
265
+ onclick={() => patchFilter(colId, { operator2: defaultOperatorFor(menuColumn), value2: "", join: "AND" })}>
266
+ + Add condition
267
+ </button>
268
+ {/if}
269
+ {/if}
270
+ <div class="sv-grid-menu-sep"></div>
271
+ <div class="sv-grid-menu-filter-head">Values</div>
272
+ <input
273
+ class="sv-grid-menu-search"
274
+ placeholder="Search values..."
275
+ bind:value={ctrl.columnMenuSearch}
276
+ />
277
+ <label class="sv-grid-facet sv-grid-facet-all">
278
+ <input type="checkbox" checked={isAllFacetsChecked(colId)} onchange={() => toggleAllFacets(colId)} />
279
+ <span class="sv-grid-facet-label">(Select all)</span>
280
+ </label>
281
+ <div class="sv-grid-facet-list">
282
+ {#each columnMenuVisibleFacets as value (value)}
283
+ <label class="sv-grid-facet">
284
+ <input type="checkbox" checked={isFacetChecked(colId, value)} onchange={() => toggleFacetValue(colId, value)} />
285
+ <span class="sv-grid-facet-label">{value === "" ? "(Blanks)" : value}</span>
286
+ </label>
287
+ {:else}
288
+ <div class="sv-grid-facet-empty">No values</div>
289
+ {/each}
290
+ </div>
291
+ {#if columnMenuFacetValues.length > columnMenuVisibleFacets.length}
292
+ <div class="sv-grid-facet-note">
293
+ Showing {columnMenuVisibleFacets.length} of {columnMenuFacetValues.length}
294
+ </div>
295
+ {/if}
296
+ <div class="sv-grid-menu-actions">
297
+ <button
298
+ type="button"
299
+ class="sv-grid-menu-btn"
300
+ disabled={!isColumnFiltered(colId)}
301
+ onclick={() => clearColumnFilter(colId)}
302
+ >
303
+ Clear filter
304
+ </button>
305
+ <button type="button" class="sv-grid-menu-btn sv-grid-menu-btn-primary" onclick={closeMenus}>
306
+ Done
307
+ </button>
308
+ </div>
309
+ </div>
310
+ {/snippet}
311
+
172
312
  {#if columnMenuFor}
173
313
  {@const menuColumnId = columnMenuFor}
174
314
  {@const menuCol = allColumns.find((c) => c.id === menuColumnId)}
175
315
  {@const menuCanSort = menuCol?.getCanSort?.() ?? false}
316
+ {@const menuCanFilter = (menuCol?.columnDef.field != null) && (menuCol?.columnDef.filterable !== false)}
317
+ {@const menuTab = ctrl.columnMenuTab}
176
318
  <div
177
319
  class="sv-grid-menu sv-grid-column-menu"
178
320
  role="menu"
179
- style={`left: ${columnMenuPos.x}px; top: ${columnMenuPos.y}px;`}
321
+ style={`left: ${columnMenuPos.x}px; top: ${columnMenuPos.y}px; max-height: calc(100vh - ${columnMenuPos.y}px - 12px);`}
180
322
  >
323
+ {#if columnMenuTabsEnabled}
324
+ <div class="sv-grid-menu-tabs" role="tablist">
325
+ <button type="button" class="sv-grid-menu-tab" class:is-active={menuTab === "general"}
326
+ role="tab" aria-selected={menuTab === "general"}
327
+ onclick={() => (ctrl.columnMenuTab = "general")}>General</button>
328
+ {#if menuCanFilter && showColumnFiltersEffective}
329
+ <button type="button" class="sv-grid-menu-tab" class:is-active={menuTab === "filter"}
330
+ role="tab" aria-selected={menuTab === "filter"}
331
+ onclick={() => (ctrl.columnMenuTab = "filter")}>Filter</button>
332
+ {/if}
333
+ <button type="button" class="sv-grid-menu-tab" class:is-active={menuTab === "columns"}
334
+ role="tab" aria-selected={menuTab === "columns"}
335
+ onclick={() => (ctrl.columnMenuTab = "columns")}>Columns</button>
336
+ </div>
337
+ {/if}
338
+ {#if columnMenuTabsEnabled && menuTab === "filter" && menuCanFilter && showColumnFiltersEffective}
339
+ {@render filterPanelBody(menuColumnId)}
340
+ {:else if columnMenuTabsEnabled && menuTab === "columns"}
341
+ <div class="sv-grid-menu-filter-head">Visible columns</div>
342
+ <div class="sv-grid-facet-list">
343
+ {#each grid.getAllColumns() as column (column.id)}
344
+ <label class="sv-grid-facet">
345
+ <input
346
+ type="checkbox"
347
+ checked={!ctrl.hiddenColumns[column.id]}
348
+ onchange={(event) => {
349
+ const visible = (event.currentTarget as HTMLInputElement).checked;
350
+ if (visible) {
351
+ const next = { ...ctrl.hiddenColumns };
352
+ delete next[column.id];
353
+ ctrl.hiddenColumns = next;
354
+ } else {
355
+ ctrl.hiddenColumns = { ...ctrl.hiddenColumns, [column.id]: true };
356
+ }
357
+ }}
358
+ />
359
+ <span class="sv-grid-facet-label"
360
+ >{typeof column.columnDef.header === "string" ? column.columnDef.header : column.id}</span
361
+ >
362
+ </label>
363
+ {/each}
364
+ </div>
365
+ {:else}
181
366
  {#if menuCanSort}
182
367
  <button
183
368
  type="button"
@@ -290,21 +475,23 @@
290
475
  </button>
291
476
  {/if}
292
477
  <div class="sv-grid-menu-sep"></div>
293
- <button
294
- type="button"
295
- class="sv-grid-menu-item"
296
- class:is-open={chooseColumnsPos !== null}
297
- role="menuitem"
298
- aria-haspopup="menu"
299
- aria-expanded={chooseColumnsPos !== null}
300
- onclick={(event) => openChooseColumns(event)}
301
- >
302
- <span class="sv-grid-header-icon">{@render icon("columns")}</span>
303
- Choose columns
304
- <span class="sv-grid-header-icon sv-grid-menu-item-chevron"
305
- >{@render icon("chevron-down")}</span
478
+ {#if !columnMenuTabsEnabled}
479
+ <!-- Flat menu: reach the column chooser via a submenu (tabbed mode has
480
+ a Columns tab instead). -->
481
+ <button
482
+ type="button"
483
+ class="sv-grid-menu-item"
484
+ class:is-open={chooseColumnsPos !== null}
485
+ role="menuitem"
486
+ aria-haspopup="menu"
487
+ aria-expanded={chooseColumnsPos !== null}
488
+ onclick={(event) => openChooseColumns(event)}
306
489
  >
307
- </button>
490
+ <span class="sv-grid-header-icon">{@render icon("columns")}</span>
491
+ Choose columns
492
+ <span class="sv-grid-header-icon sv-grid-menu-item-chevron">{@render icon("chevron-down")}</span>
493
+ </button>
494
+ {/if}
308
495
  <button
309
496
  type="button"
310
497
  class="sv-grid-menu-item"
@@ -316,126 +503,17 @@
316
503
  >
317
504
  <span class="sv-grid-header-icon">{@render icon("reset")}</span> Reset columns
318
505
  </button>
506
+ {/if}
319
507
  </div>
320
508
  {/if}
321
509
 
322
510
  {#if filterMenuFor && showColumnFiltersEffective}
323
- {@const menuColumnId = filterMenuFor}
324
- {@const menuColumn = allColumns.find((c) => c.id === menuColumnId)}
325
- {@const menuOperatorOptions = operatorsForColumn(menuColumn)}
326
- {@const menuActiveOperator =
327
- filterMenuValues[menuColumnId]?.operator ??
328
- defaultOperatorFor(menuColumn)}
329
- {@const menuInputType = getEditorInputType(
330
- (menuColumn?.columnDef.editorType ?? "text") as CellEditorType,
331
- )}
332
511
  <div
333
512
  class="sv-grid-menu sv-grid-filter-menu"
334
513
  role="menu"
335
- style={`left: ${filterMenuPos.x}px; top: ${filterMenuPos.y}px;`}
514
+ style={`left: ${filterMenuPos.x}px; top: ${filterMenuPos.y}px; max-height: calc(100vh - ${filterMenuPos.y}px - 12px);`}
336
515
  >
337
- <div class="sv-grid-menu-filter">
338
- <div class="sv-grid-menu-filter-head">
339
- <span class="sv-grid-header-icon">{@render icon("filter")}</span> Filter
340
- condition
341
- </div>
342
- <select
343
- class="sv-grid-menu-operator-select"
344
- aria-label="Filter condition"
345
- value={menuActiveOperator}
346
- onchange={(event) =>
347
- updateFilterOperator(
348
- menuColumnId,
349
- (event.currentTarget as HTMLSelectElement)
350
- .value as FilterOperator,
351
- )}
352
- >
353
- {#each menuOperatorOptions as option (option.value)}
354
- <option value={option.value}
355
- >{operatorLabelFor(option, menuColumn)}</option
356
- >
357
- {/each}
358
- </select>
359
- {#if menuActiveOperator !== "isBlank"}
360
- <input
361
- class="sv-grid-menu-condition-value"
362
- type={menuInputType}
363
- value={filterMenuValues[menuColumnId]?.value ?? ""}
364
- placeholder={menuActiveOperator === "between" ? "From" : "Filter value..."}
365
- oninput={(event) =>
366
- updateFilterMenuValue(
367
- menuColumnId,
368
- (event.currentTarget as HTMLInputElement).value,
369
- )}
370
- />
371
- {#if menuActiveOperator === "between"}
372
- <input
373
- class="sv-grid-menu-condition-value"
374
- type={menuInputType}
375
- value={filterMenuValues[menuColumnId]?.valueTo ?? ""}
376
- placeholder="To"
377
- oninput={(event) =>
378
- updateFilterMenuValueTo(
379
- menuColumnId,
380
- (event.currentTarget as HTMLInputElement).value,
381
- )}
382
- />
383
- {/if}
384
- {/if}
385
- <div class="sv-grid-menu-sep"></div>
386
- <div class="sv-grid-menu-filter-head">Values</div>
387
- <input
388
- class="sv-grid-menu-search"
389
- placeholder="Search values..."
390
- bind:value={ctrl.columnMenuSearch}
391
- />
392
- <label class="sv-grid-facet sv-grid-facet-all">
393
- <input
394
- type="checkbox"
395
- checked={isAllFacetsChecked(menuColumnId)}
396
- onchange={() => toggleAllFacets(menuColumnId)}
397
- />
398
- <span class="sv-grid-facet-label">(Select all)</span>
399
- </label>
400
- <div class="sv-grid-facet-list">
401
- {#each columnMenuVisibleFacets as value (value)}
402
- <label class="sv-grid-facet">
403
- <input
404
- type="checkbox"
405
- checked={isFacetChecked(menuColumnId, value)}
406
- onchange={() => toggleFacetValue(menuColumnId, value)}
407
- />
408
- <span class="sv-grid-facet-label"
409
- >{value === "" ? "(Blanks)" : value}</span
410
- >
411
- </label>
412
- {:else}
413
- <div class="sv-grid-facet-empty">No values</div>
414
- {/each}
415
- </div>
416
- {#if columnMenuFacetValues.length > columnMenuVisibleFacets.length}
417
- <div class="sv-grid-facet-note">
418
- Showing {columnMenuVisibleFacets.length} of {columnMenuFacetValues.length}
419
- </div>
420
- {/if}
421
- <div class="sv-grid-menu-actions">
422
- <button
423
- type="button"
424
- class="sv-grid-menu-btn"
425
- disabled={!isColumnFiltered(menuColumnId)}
426
- onclick={() => clearColumnFilter(menuColumnId)}
427
- >
428
- Clear filter
429
- </button>
430
- <button
431
- type="button"
432
- class="sv-grid-menu-btn sv-grid-menu-btn-primary"
433
- onclick={closeMenus}
434
- >
435
- Done
436
- </button>
437
- </div>
438
- </div>
516
+ {@render filterPanelBody(filterMenuFor)}
439
517
  </div>
440
518
  {/if}
441
519