@svgrid/grid 1.1.0 → 1.1.1

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 (117) hide show
  1. package/dist/GridMenus.svelte +205 -127
  2. package/dist/SvGrid.controller.svelte.d.ts +44 -1
  3. package/dist/SvGrid.controller.svelte.js +223 -38
  4. package/dist/SvGrid.css +2012 -1709
  5. package/dist/SvGrid.helpers.test.d.ts +1 -0
  6. package/dist/SvGrid.helpers.test.js +298 -0
  7. package/dist/SvGrid.svelte +2346 -2043
  8. package/dist/SvGrid.types.d.ts +91 -4
  9. package/dist/aligned-grids.d.ts +6 -0
  10. package/dist/aligned-grids.js +84 -0
  11. package/dist/aligned-grids.test.d.ts +1 -0
  12. package/dist/aligned-grids.test.js +75 -0
  13. package/dist/build-api.coverage.test.d.ts +20 -0
  14. package/dist/build-api.coverage.test.js +505 -0
  15. package/dist/build-api.js +59 -29
  16. package/dist/cell-render.test.d.ts +1 -0
  17. package/dist/cell-render.test.js +338 -0
  18. package/dist/chart-export.test.d.ts +1 -0
  19. package/dist/chart-export.test.js +302 -0
  20. package/dist/chart.coverage.test.d.ts +1 -0
  21. package/dist/chart.coverage.test.js +748 -0
  22. package/dist/clipboard.js +88 -24
  23. package/dist/clipboard.test.d.ts +1 -0
  24. package/dist/clipboard.test.js +700 -0
  25. package/dist/collaboration.coverage.test.d.ts +1 -0
  26. package/dist/collaboration.coverage.test.js +200 -0
  27. package/dist/column-groups.d.ts +19 -0
  28. package/dist/column-groups.js +62 -0
  29. package/dist/column-groups.test.d.ts +1 -0
  30. package/dist/column-groups.test.js +56 -0
  31. package/dist/column-types.d.ts +10 -0
  32. package/dist/column-types.js +63 -0
  33. package/dist/column-types.test.d.ts +1 -0
  34. package/dist/column-types.test.js +62 -0
  35. package/dist/columns.test.d.ts +1 -0
  36. package/dist/columns.test.js +625 -0
  37. package/dist/core.d.ts +85 -0
  38. package/dist/editing.d.ts +7 -0
  39. package/dist/editing.js +191 -5
  40. package/dist/editing.test.d.ts +1 -0
  41. package/dist/editing.test.js +732 -0
  42. package/dist/editors/cell-editors.coverage.test.d.ts +1 -0
  43. package/dist/editors/cell-editors.coverage.test.js +139 -0
  44. package/dist/facet-buckets.test.d.ts +1 -0
  45. package/dist/facet-buckets.test.js +296 -0
  46. package/dist/filter-operators.test.d.ts +1 -0
  47. package/dist/filter-operators.test.js +135 -0
  48. package/dist/hyperformula-adapter.test.d.ts +1 -0
  49. package/dist/hyperformula-adapter.test.js +205 -0
  50. package/dist/index.d.ts +5 -2
  51. package/dist/index.js +4 -1
  52. package/dist/keyboard-handlers.coverage.test.d.ts +1 -0
  53. package/dist/keyboard-handlers.coverage.test.js +495 -0
  54. package/dist/keyboard-handlers.js +7 -2
  55. package/dist/menus.js +1 -0
  56. package/dist/menus.test.d.ts +1 -0
  57. package/dist/menus.test.js +560 -0
  58. package/dist/named-views.coverage.test.d.ts +1 -0
  59. package/dist/named-views.coverage.test.js +180 -0
  60. package/dist/row-drag.d.ts +49 -0
  61. package/dist/row-drag.js +221 -0
  62. package/dist/row-drag.test.d.ts +1 -0
  63. package/dist/row-drag.test.js +142 -0
  64. package/dist/row-resize.test.d.ts +1 -0
  65. package/dist/row-resize.test.js +329 -0
  66. package/dist/scroll-sync.js +3 -0
  67. package/dist/scroll-sync.test.d.ts +1 -0
  68. package/dist/scroll-sync.test.js +290 -0
  69. package/dist/selection.d.ts +7 -1
  70. package/dist/selection.js +76 -36
  71. package/dist/selection.multi-range.test.d.ts +1 -0
  72. package/dist/selection.multi-range.test.js +55 -0
  73. package/dist/selection.test.d.ts +1 -0
  74. package/dist/selection.test.js +647 -0
  75. package/dist/server-data-source.coverage.test.d.ts +1 -0
  76. package/dist/server-data-source.coverage.test.js +154 -0
  77. package/dist/spreadsheet.d.ts +30 -0
  78. package/dist/spreadsheet.js +48 -0
  79. package/dist/spreadsheet.test.d.ts +1 -0
  80. package/dist/spreadsheet.test.js +446 -0
  81. package/dist/sv-grid-scrollbar.js +13 -1
  82. package/dist/svgrid-wrapper.types.d.ts +19 -0
  83. package/dist/svgrid.behavior.test.js +20 -0
  84. package/dist/svgrid.interaction.test.js +31 -0
  85. package/dist/svgrid.new-features.wrapper.test.js +34 -2
  86. package/dist/test-setup.js +9 -3
  87. package/dist/virtualization/scroll-scaling.d.ts +17 -0
  88. package/dist/virtualization/scroll-scaling.js +35 -0
  89. package/dist/virtualization/scroll-scaling.test.js +42 -1
  90. package/package.json +2 -1
  91. package/src/GridMenus.svelte +205 -127
  92. package/src/SvGrid.controller.svelte.ts +2352 -2195
  93. package/src/SvGrid.css +2012 -1747
  94. package/src/SvGrid.svelte +2346 -2047
  95. package/src/SvGrid.types.ts +537 -456
  96. package/src/aligned-grids.test.ts +80 -0
  97. package/src/aligned-grids.ts +87 -0
  98. package/src/build-api.ts +683 -663
  99. package/src/clipboard.test.ts +49 -0
  100. package/src/clipboard.ts +51 -23
  101. package/src/column-groups.test.ts +59 -0
  102. package/src/column-groups.ts +80 -0
  103. package/src/column-types.test.ts +68 -0
  104. package/src/column-types.ts +82 -0
  105. package/src/core.ts +78 -0
  106. package/src/editing.ts +669 -513
  107. package/src/index.ts +12 -0
  108. package/src/menus.ts +1 -0
  109. package/src/row-drag.test.ts +168 -0
  110. package/src/row-drag.ts +255 -0
  111. package/src/scroll-sync.ts +2 -0
  112. package/src/selection.multi-range.test.ts +61 -0
  113. package/src/selection.ts +71 -37
  114. package/src/spreadsheet.test.ts +489 -445
  115. package/src/spreadsheet.ts +304 -246
  116. package/src/svgrid-wrapper.types.ts +19 -0
  117. package/src/svgrid.new-features.wrapper.test.ts +2 -2
@@ -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
 
@@ -25,6 +25,10 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
25
25
  operator: FilterOperator;
26
26
  value: string;
27
27
  valueTo?: string;
28
+ operator2?: FilterOperator;
29
+ value2?: string;
30
+ valueTo2?: string;
31
+ join?: "AND" | "OR";
28
32
  }>;
29
33
  verticalScrollbarEl: HTMLElement | null;
30
34
  horizontalScrollbarEl: HTMLElement | null;
@@ -35,6 +39,7 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
35
39
  pendingScrollLeft: number;
36
40
  scrollSyncRaf: number | null;
37
41
  selectionRange: SelectionRange;
42
+ selectionRanges: SelectionRange[];
38
43
  isDraggingSelection: boolean;
39
44
  fillDrag: {
40
45
  sourceMinRow: number;
@@ -49,6 +54,10 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
49
54
  colIndex: number;
50
55
  } | null;
51
56
  editingCell: CellEditState;
57
+ fullRowEdit: {
58
+ rowId: string;
59
+ draft: Record<string, unknown>;
60
+ } | null;
52
61
  editedCellValues: Record<string, unknown>;
53
62
  readonly UNDO_LIMIT: number;
54
63
  history: {
@@ -100,6 +109,7 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
100
109
  readonly DATE_OPERATORS: FilterOperator[];
101
110
  readonly CHECKBOX_OPERATORS: FilterOperator[];
102
111
  columnMenuFor: string | null;
112
+ columnMenuTab: "filter" | "columns" | "general";
103
113
  columnMenuPos: MenuPosition;
104
114
  columnMenuSearch: string;
105
115
  filterMenuFor: string | null;
@@ -140,6 +150,8 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
140
150
  internalData: readonly TData[];
141
151
  internalColumns: ColumnDef<TFeatures, TData>[];
142
152
  hiddenColumns: Record<string, boolean>;
153
+ readonly toggleColumnGroup: (groupId: string) => void;
154
+ readonly isColumnGroupCollapsed: (groupId: string) => boolean;
143
155
  readonly externalSortEnabled: boolean;
144
156
  readonly externalFilterEnabled: boolean;
145
157
  readonly passthroughSortedRowModel: ({ rows }: {
@@ -165,6 +177,10 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
165
177
  * multi-level value tree). They render as empty cells so the
166
178
  * layout stays aligned without showing duplicate labels. */
167
179
  isPlaceholder: boolean;
180
+ /** Set when this group cell has a collapse toggle. */
181
+ groupId?: string;
182
+ collapsible: boolean;
183
+ collapsed: boolean;
168
184
  }[];
169
185
  }[];
170
186
  readonly pinnedOffsets: {
@@ -176,6 +192,17 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
176
192
  colDragId: string | null;
177
193
  colDropOnId: string | null;
178
194
  colDropSide: "before" | "after" | null;
195
+ rowDragActive: boolean;
196
+ rowDropIndex: number | null;
197
+ rowDropSide: "before" | "after" | null;
198
+ readonly onRowDragStart: (e: DragEvent, rowIndex: number) => void;
199
+ readonly onRowDragOver: (e: DragEvent, rowIndex: number) => void;
200
+ readonly onRowDragLeave: (rowIndex: number) => void;
201
+ readonly onRowDrop: (e: DragEvent, rowIndex: number) => void;
202
+ readonly onRowsContainerDragOver: (e: DragEvent) => void;
203
+ readonly onRowsContainerDrop: (e: DragEvent) => void;
204
+ readonly onRowDragEnd: () => void;
205
+ readonly broadcastAlignedScroll: (left: number) => void;
179
206
  readonly getCurrentColumnOrder: () => string[];
180
207
  readonly emitColumnOrder: () => void;
181
208
  readonly setColumnOrderInternal: (ids: ReadonlyArray<string>) => void;
@@ -215,6 +242,7 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
215
242
  max: number;
216
243
  } | null;
217
244
  toolPanelOpen: boolean;
245
+ toolPanelTab: "columns" | "filters";
218
246
  readonly toolPanelEnabled: boolean;
219
247
  readonly toolPanelColumns: Column<TData>[];
220
248
  readonly toolPanelHeaderLabel: typeof toolPanelHeaderLabel;
@@ -324,7 +352,7 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
324
352
  readonly startColumnResize: (event: PointerEvent, columnId: string) => void;
325
353
  readonly onColumnResizeMove: (event: PointerEvent) => void;
326
354
  readonly endColumnResize: () => void;
327
- readonly setSelection: (rowIndex: number, colIndex: number) => void;
355
+ readonly setSelection: (rowIndex: number, colIndex: number, additive?: boolean) => void;
328
356
  readonly extendSelection: (rowIndex: number, colIndex: number) => void;
329
357
  readonly isCellInSelectedRange: (rowIndex: number, colIndex: number) => boolean;
330
358
  readonly getCellRangeEdges: (rowIndex: number, colIndex: number) => {
@@ -333,6 +361,12 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
333
361
  left: boolean;
334
362
  right: boolean;
335
363
  } | null;
364
+ readonly getSelectionRects: () => {
365
+ minRow: number;
366
+ maxRow: number;
367
+ minCol: number;
368
+ maxCol: number;
369
+ }[];
336
370
  readonly fillHandleCell: {
337
371
  rowIndex: any;
338
372
  colIndex: any;
@@ -354,9 +388,18 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
354
388
  readonly onCellClick: (rowIndex: number, colIndex: number) => void;
355
389
  readonly emitCellDoubleClick: (rowIndex: number, colIndex: number) => void;
356
390
  readonly copySelectionToClipboard: () => void;
391
+ readonly cutSelectionToClipboard: () => Promise<void>;
392
+ readonly pasteFromClipboard: () => Promise<void>;
393
+ readonly onGridPaste: (event: ClipboardEvent) => void;
357
394
  readonly clearSelectedCells: () => boolean;
358
395
  readonly onCellDoubleClick: (rowIndex: number, colIndex: number) => void;
359
396
  readonly startEditingWithChar: (rowIndex: number, colIndex: number, char: string) => boolean;
397
+ readonly startEditing: (rowIndex: number, columnId: string) => boolean;
398
+ readonly stopEditing: (cancel?: boolean) => boolean;
399
+ readonly startFullRowEdit: (rowIndex: number) => boolean;
400
+ readonly setFullRowDraft: (columnId: string, value: unknown) => void;
401
+ readonly commitFullRowEdit: () => void;
402
+ readonly cancelFullRowEdit: () => void;
360
403
  readonly saveEditingCell: () => void;
361
404
  readonly applyHistoryStep: (step: {
362
405
  rowId: string;