@svgrid/grid 2.6.22 → 2.6.23

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 (115) hide show
  1. package/dist/GridMenus.svelte +184 -6
  2. package/dist/SvAutoComplete.svelte +4 -0
  3. package/dist/SvComboBox.svelte +5 -1
  4. package/dist/SvDateRangeInput.svelte +25 -3
  5. package/dist/SvDropDownList.svelte +4 -0
  6. package/dist/SvForm.svelte +8 -8
  7. package/dist/SvGrid.controller.svelte.d.ts +48 -1
  8. package/dist/SvGrid.controller.svelte.js +214 -52
  9. package/dist/SvGrid.css +221 -0
  10. package/dist/SvGrid.helpers.d.ts +55 -0
  11. package/dist/SvGrid.helpers.js +83 -0
  12. package/dist/SvGrid.svelte +145 -0
  13. package/dist/SvGrid.types.d.ts +105 -0
  14. package/dist/SvGridCellEditor.svelte +99 -17
  15. package/dist/SvGridSelect.svelte +6 -2
  16. package/dist/SvGridSelect.svelte.d.ts +1 -1
  17. package/dist/SvListBox.svelte +8 -0
  18. package/dist/SvMultiSelect.svelte +6 -2
  19. package/dist/SvMultiSelect.svelte.d.ts +1 -1
  20. package/dist/SvNumberInput.svelte +4 -0
  21. package/dist/SvRichCell.svelte +125 -0
  22. package/dist/SvRichCell.svelte.d.ts +15 -0
  23. package/dist/SvTextArea.svelte +5 -1
  24. package/dist/SvTreeSelect.svelte +6 -2
  25. package/dist/SvTreeSelect.svelte.d.ts +1 -1
  26. package/dist/builtin-editors.d.ts +2 -2
  27. package/dist/builtin-editors.js +19 -3
  28. package/dist/cdn/GridMenus-BzWXQjv3.js +600 -0
  29. package/dist/cdn/GridMenus-xr_rHx8F.js +601 -0
  30. package/dist/cdn/SvDateRangeInput-CaOuMs8O.js +198 -0
  31. package/dist/cdn/SvDateRangeInput-DMLKmGEc.js +199 -0
  32. package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +816 -0
  33. package/dist/cdn/SvDateTimePicker-sonaH0oh.js +812 -0
  34. package/dist/cdn/SvGridCellEditor-BAPSC7EL.js +550 -0
  35. package/dist/cdn/SvGridCellEditor-BwzmUWtL.js +549 -0
  36. package/dist/cdn/date-format-BNii4zeD.js +1393 -0
  37. package/dist/cdn/date-format-BnnHlqGw.js +1395 -0
  38. package/dist/cdn/{editor-registry-CiI0xlKg.js → editor-registry-BhgE3S84.js} +51 -25
  39. package/dist/cdn/{src-D1lXwq1l.js → src-DGo7IHug.js} +5730 -5198
  40. package/dist/cdn/{src-C9Hihx1W.js → src-skQN5eqh.js} +7216 -6684
  41. package/dist/cdn/svgrid.js +14 -12
  42. package/dist/cdn/svgrid.svelte-external.js +14 -12
  43. package/dist/cell-values.d.ts +16 -0
  44. package/dist/cell-values.js +28 -0
  45. package/dist/clipboard.d.ts +22 -0
  46. package/dist/clipboard.js +405 -27
  47. package/dist/editing.js +12 -7
  48. package/dist/editors/cell-editors.d.ts +11 -0
  49. package/dist/editors/cell-editors.js +18 -1
  50. package/dist/grid-messages.d.ts +15 -0
  51. package/dist/grid-messages.js +15 -0
  52. package/dist/index.d.ts +4 -0
  53. package/dist/index.js +4 -0
  54. package/dist/list-option.d.ts +6 -0
  55. package/dist/menus.js +40 -7
  56. package/dist/sanitize-html.d.ts +37 -0
  57. package/dist/sanitize-html.js +234 -0
  58. package/dist/selection-bar-view.svelte.d.ts +25 -0
  59. package/dist/selection-bar-view.svelte.js +13 -0
  60. package/dist/selection.d.ts +2 -1
  61. package/dist/selection.js +129 -13
  62. package/package.json +1 -1
  63. package/src/GridMenus.svelte +184 -6
  64. package/src/SvAutoComplete.svelte +4 -0
  65. package/src/SvComboBox.svelte +5 -1
  66. package/src/SvDateRangeInput.svelte +25 -3
  67. package/src/SvDropDownList.svelte +4 -0
  68. package/src/SvForm.svelte +8 -8
  69. package/src/SvGrid.controller.svelte.ts +251 -61
  70. package/src/SvGrid.css +221 -0
  71. package/src/SvGrid.helpers.ts +87 -0
  72. package/src/SvGrid.svelte +145 -0
  73. package/src/SvGrid.types.ts +112 -0
  74. package/src/SvGridCellEditor.svelte +99 -17
  75. package/src/SvGridSelect.svelte +6 -2
  76. package/src/SvListBox.svelte +8 -0
  77. package/src/SvMultiSelect.svelte +6 -2
  78. package/src/SvNumberInput.svelte +4 -0
  79. package/src/SvRichCell.svelte +125 -0
  80. package/src/SvTextArea.svelte +5 -1
  81. package/src/SvTreeSelect.svelte +6 -2
  82. package/src/builtin-editors.test.ts +18 -1
  83. package/src/builtin-editors.ts +19 -3
  84. package/src/cell-values.ts +30 -0
  85. package/src/clipboard.test.ts +93 -20
  86. package/src/clipboard.ts +433 -33
  87. package/src/date-string-column.test.ts +87 -0
  88. package/src/editing.test.ts +25 -0
  89. package/src/editing.ts +12 -9
  90. package/src/editor-block-prop.test.ts +132 -0
  91. package/src/editors/cell-editors.ts +27 -1
  92. package/src/grid-messages.ts +34 -0
  93. package/src/index.ts +13 -0
  94. package/src/list-option.ts +6 -0
  95. package/src/menus.test.ts +43 -0
  96. package/src/menus.ts +37 -7
  97. package/src/move-cells.test.ts +850 -0
  98. package/src/number-editor-literal.test.ts +84 -0
  99. package/src/number-editor.grid.test.ts +160 -0
  100. package/src/sanitize-html.test.ts +182 -0
  101. package/src/sanitize-html.ts +235 -0
  102. package/src/selection-bar-view.svelte.ts +36 -0
  103. package/src/selection.test.ts +157 -0
  104. package/src/selection.ts +131 -13
  105. package/src/svgrid.filter-menu-listbox.svelte.test.ts +56 -10
  106. package/src/svgrid.in-suggest-snapshot.svelte.test.ts +91 -0
  107. package/src/svgrid.menu-scroll-close.test.ts +204 -0
  108. package/src/svgrid.selection-bar-seam.test.ts +185 -0
  109. package/src/svgrid.upsell-license.test.ts +117 -0
  110. package/dist/cdn/GridMenus-BuoBPqxx.js +0 -493
  111. package/dist/cdn/GridMenus-n4llxoOI.js +0 -494
  112. package/dist/cdn/SvDateTimePicker-CHnUkWcH.js +0 -2206
  113. package/dist/cdn/SvDateTimePicker-CRQH_U7E.js +0 -2201
  114. package/dist/cdn/SvGridCellEditor-BGUCzuPB.js +0 -523
  115. package/dist/cdn/SvGridCellEditor-G8IMWIws.js +0 -522
@@ -16,6 +16,9 @@
16
16
  } from "./SvGrid.types";
17
17
  import {
18
18
  getEditorInputType,
19
+ usesRichDateFilter,
20
+ toIsoDateLocal,
21
+ fromIsoDateLocal,
19
22
  } from "./SvGrid.helpers";
20
23
 
21
24
  import type { SvGridController } from "./SvGrid.controller.svelte";
@@ -72,11 +75,45 @@
72
75
  // anything OUTSIDE them scrolls, or on resize. Scrolling the menu's own body
73
76
  // or its facet list has to keep it open - hence onScrollOutside rather than a
74
77
  // bare capture-phase listener.
78
+ //
79
+ // The cell context menu and the comment editor were missing from this gate.
80
+ // Both are positioned from a one-time cursor measurement and rendered
81
+ // `position: fixed`, so after a scroll they float over unrelated cells - the
82
+ // exact defect the effect exists to prevent. Their backdrop swallows a wheel
83
+ // over the grid, but it does not stop the PAGE scrolling underneath them, and
84
+ // it does not stop a programmatic scroll.
85
+ //
86
+ // The comment editor is dismissed by SAVING rather than discarding. Clicking
87
+ // the backdrop or pressing Escape throws the draft away, and that is fine -
88
+ // both are the user saying "away with this". A scroll is not a dismissal
89
+ // gesture, so it must not destroy text the user has typed.
75
90
  $effect(() => {
76
- if (!(columnMenuFor || filterMenuFor || operatorMenuFor || chooseColumnsPos)) return;
77
- const close = () => closeMenus();
91
+ if (
92
+ !(
93
+ columnMenuFor ||
94
+ filterMenuFor ||
95
+ operatorMenuFor ||
96
+ chooseColumnsPos ||
97
+ contextMenuFor ||
98
+ commentEditFor
99
+ )
100
+ )
101
+ return;
102
+ const close = () => {
103
+ closeMenus();
104
+ closeContextMenu();
105
+ if (ctrl.commentEditFor) saveComment();
106
+ };
78
107
  const offScroll = onScrollOutside(
79
- () => Array.from(document.querySelectorAll<HTMLElement>('.sv-grid-menu')),
108
+ // Scoped to THIS grid's menus. A bare document query treats a second
109
+ // grid's open menu as "inside", so scrolling in that other menu would
110
+ // leave this one stranded at its stale coordinates.
111
+ () =>
112
+ Array.from(
113
+ document.querySelectorAll<HTMLElement>(
114
+ `.sv-grid-menu[data-svgrid-menu="${ctrl.gridDomId}"]`,
115
+ ),
116
+ ),
80
117
  close,
81
118
  );
82
119
  window.addEventListener('resize', close);
@@ -96,6 +133,71 @@
96
133
  const clearGroupingFromMenu = $derived(ctrl.clearGroupingFromMenu);
97
134
  const columnMenuFacetValues = $derived(ctrl.columnMenuFacetValues);
98
135
  const columnMenuVisibleFacets = $derived(ctrl.columnMenuVisibleFacets);
136
+
137
+ /**
138
+ * How many value rows the checklist shows. It was pinned at 7 regardless of
139
+ * the window, so a tall screen left most of the space under the menu empty
140
+ * while the list stayed a 7-row porthole over a few hundred values.
141
+ *
142
+ * Read once per open rather than on resize: the popover is transient, and
143
+ * the menu is already capped by its own `max-height`.
144
+ */
145
+ const FACET_ROW_HEIGHT = 24;
146
+ const facetRows = $derived.by(function facetRows_d() {
147
+ if (typeof window === "undefined") return 7;
148
+ const top = (filterMenuFor ? filterMenuPos.y : columnMenuPos.y) || 0;
149
+ // What is left under the menu's top edge once the condition block, the
150
+ // search row and the action bar have taken their share.
151
+ const room = window.innerHeight - top - 320;
152
+ return Math.max(7, Math.min(16, Math.floor(room / FACET_ROW_HEIGHT)));
153
+ });
154
+
155
+ /**
156
+ * The same date controls the filter ROW uses. Without them a date column
157
+ * offered two different date UIs depending on which surface you touched:
158
+ * a rich picker under the header, a bare `<input type="date">` in the menu.
159
+ *
160
+ * Lazy even though GridMenus is itself a lazy chunk - the picker drags in
161
+ * the date-format engine, and most grids that open a column menu have no
162
+ * date column at all.
163
+ */
164
+ let MenuDatePicker = $state<typeof import("./SvDateTimePicker.svelte").default | null>(null);
165
+ let MenuRangePicker = $state<typeof import("./SvDateRangeInput.svelte").default | null>(null);
166
+ $effect(() => {
167
+ const colId = filterMenuFor ?? columnMenuFor;
168
+ if (!colId) return;
169
+ const column = allColumns.find((c) => c.id === colId);
170
+ if (!usesRichDateFilter(column?.columnDef.editorType)) return;
171
+ if (!MenuDatePicker)
172
+ import("./SvDateTimePicker.svelte").then((m) => (MenuDatePicker = m.default));
173
+ if (!MenuRangePicker)
174
+ import("./SvDateRangeInput.svelte").then((m) => (MenuRangePicker = m.default));
175
+ });
176
+
177
+ /**
178
+ * Opening the filter popover used to leave focus on the funnel button
179
+ * outside it, so a keyboard user landed nowhere and had to tab in past the
180
+ * whole menu. Focus the first control instead - the condition value if the
181
+ * column has one, else the value search.
182
+ */
183
+ function focusFilterPanel(node: HTMLElement) {
184
+ requestAnimationFrame(() => {
185
+ const first = node.querySelector<HTMLElement>(
186
+ ".sv-grid-menu-condition-value, .sv-dtp__input, .sv-dri__input, .sv-grid-menu-search",
187
+ );
188
+ first?.focus({ preventScroll: true });
189
+ });
190
+ }
191
+
192
+ /** Enter anywhere in the panel means "done" - filtering is already live. */
193
+ function onFilterPanelKeydown(event: KeyboardEvent) {
194
+ if (event.key !== "Enter" || event.shiftKey) return;
195
+ const target = event.target as HTMLElement | null;
196
+ // Let the listbox and real buttons keep Enter for their own activation.
197
+ if (target?.closest(".sv-listbox") || target?.tagName === "BUTTON") return;
198
+ event.preventDefault();
199
+ closeMenus();
200
+ }
99
201
  const columnMenuFacetOptions = $derived(ctrl.columnMenuFacetOptions);
100
202
  const columnMenuSelectedFacets = $derived(ctrl.columnMenuSelectedFacets);
101
203
  const setFacetSelection = $derived(ctrl.setFacetSelection);
@@ -187,6 +289,21 @@
187
289
  {@const menuOperatorOptions = operatorsForColumn(menuColumn)}
188
290
  {@const menuActiveOperator = filterMenuValues[colId]?.operator ?? defaultOperatorFor(menuColumn)}
189
291
  {@const menuInputType = getEditorInputType((menuColumn?.columnDef.editorType ?? "text") as CellEditorType)}
292
+ {@const richDate =
293
+ usesRichDateFilter(menuColumn?.columnDef.editorType) &&
294
+ menuActiveOperator !== "isBlank" &&
295
+ menuActiveOperator !== "isNotBlank" &&
296
+ menuActiveOperator !== "regex" &&
297
+ menuActiveOperator !== "in" &&
298
+ menuActiveOperator !== "notIn"}
299
+ {@const searching =
300
+ columnMenuVisibleFacets.length !== columnMenuFacetValues.length}
301
+ {@const conditionActive =
302
+ menuActiveOperator === "isBlank" ||
303
+ menuActiveOperator === "isNotBlank" ||
304
+ (filterMenuValues[colId]?.value ?? "").trim().length > 0}
305
+ {@const pickedCount = columnMenuSelectedFacets.size}
306
+ {@const totalCount = columnMenuFacetValues.length}
190
307
  <div class="sv-grid-menu-filter">
191
308
  <div class="sv-grid-menu-filter-head">
192
309
  <span class="sv-grid-header-icon">{@render icon("filter")}</span> Filter
@@ -204,6 +321,40 @@
204
321
  {/each}
205
322
  </select>
206
323
  {#if menuActiveOperator !== "isBlank" && menuActiveOperator !== "isNotBlank"}
324
+ {#if richDate}
325
+ <!-- Same controls the filter row uses, so a date column behaves the
326
+ same wherever you touch it: a picker, or a range field for
327
+ `between`. `regex` and the set operators keep the text box - a
328
+ pattern and a token list are not dates. -->
329
+ {#if menuActiveOperator === "between"}
330
+ {#if MenuRangePicker}
331
+ {@const RangePicker = MenuRangePicker}
332
+ {@const from = fromIsoDateLocal(filterMenuValues[colId]?.value)}
333
+ {@const to = fromIsoDateLocal(filterMenuValues[colId]?.valueTo)}
334
+ <RangePicker
335
+ value={from && to ? [from, to] : null}
336
+ formatString="yyyy-MM-dd"
337
+ block
338
+ placeholder="Pick a range..."
339
+ onChange={(range) => {
340
+ updateFilterMenuValue(colId, range ? toIsoDateLocal(range[0]) : "");
341
+ updateFilterMenuValueTo(colId, range ? toIsoDateLocal(range[1]) : "");
342
+ }}
343
+ />
344
+ {/if}
345
+ {:else if MenuDatePicker}
346
+ {@const DatePicker = MenuDatePicker}
347
+ <DatePicker
348
+ value={(filterMenuValues[colId]?.value ?? "") || null}
349
+ formatString="yyyy-MM-dd"
350
+ dropDownDisplayMode="calendar"
351
+ block
352
+ nullable
353
+ placeholder="Filter value..."
354
+ onChange={(d) => updateFilterMenuValue(colId, toIsoDateLocal(d))}
355
+ />
356
+ {/if}
357
+ {:else}
207
358
  {@const isSetOp =
208
359
  menuActiveOperator === "in" || menuActiveOperator === "notIn"}
209
360
  <!-- in/notIn drives the shared value-suggestions dropdown (a windowed
@@ -239,6 +390,9 @@
239
390
  oninput={(event) => updateFilterMenuValueTo(colId, (event.currentTarget as HTMLInputElement).value)}
240
391
  />
241
392
  {/if}
393
+ {/if}
394
+ <!-- A second condition is offered for every column type, date
395
+ included, so this sits outside the value-control branch. -->
242
396
  {@const mf = filterMenuValues[colId]}
243
397
  {#if mf?.operator2}
244
398
  {@const op2 = mf.operator2 ?? defaultOperatorFor(menuColumn)}
@@ -288,7 +442,16 @@
288
442
  {/if}
289
443
  {/if}
290
444
  <div class="sv-grid-menu-sep"></div>
291
- <div class="sv-grid-menu-filter-head">Values</div>
445
+ <div class="sv-grid-menu-filter-head">
446
+ Values
447
+ <!-- The condition above and this checklist are ANDed. Nothing said so,
448
+ so a row could vanish for a reason neither half showed on its own. -->
449
+ {#if conditionActive}
450
+ <span class="sv-grid-menu-head-note">and the condition above</span>
451
+ {:else if pickedCount < totalCount}
452
+ <span class="sv-grid-menu-head-note">{pickedCount} of {totalCount}</span>
453
+ {/if}
454
+ </div>
292
455
  <input
293
456
  class="sv-grid-menu-search"
294
457
  placeholder="Search values..."
@@ -296,7 +459,11 @@
296
459
  />
297
460
  <label class="sv-grid-facet sv-grid-facet-all">
298
461
  <input type="checkbox" checked={isAllFacetsChecked(colId)} onchange={() => toggleAllFacets(colId)} />
299
- <span class="sv-grid-facet-label">(Select all)</span>
462
+ <!-- Says what it will actually do: with a query typed it acts on the
463
+ matches only, leaving the rest of the selection untouched. -->
464
+ <span class="sv-grid-facet-label"
465
+ >{searching ? "(Select all results)" : "(Select all)"}</span
466
+ >
300
467
  </label>
301
468
  <!-- Windowed checklist. A high-cardinality column (10k distinct symbols
302
469
  on a live feed) would otherwise mount a label + checkbox per value -
@@ -308,7 +475,7 @@
308
475
  checkbox
309
476
  multiple
310
477
  virtual={columnMenuFacetOptions.length > FACET_VIRTUAL_THRESHOLD}
311
- rows={7}
478
+ rows={facetRows}
312
479
  rowHeight={24}
313
480
  size="sm"
314
481
  ariaLabel="Filter values"
@@ -348,6 +515,7 @@
348
515
  {@const menuTab = ctrl.columnMenuTab}
349
516
  <div
350
517
  class="sv-grid-menu sv-grid-column-menu"
518
+ data-svgrid-menu={ctrl.gridDomId}
351
519
  role="menu"
352
520
  style={`left: ${columnMenuPos.x}px; top: ${columnMenuPos.y}px; max-height: calc(100vh - ${columnMenuPos.y}px - 12px);`}
353
521
  >
@@ -535,10 +703,15 @@
535
703
  {/if}
536
704
 
537
705
  {#if filterMenuFor && showColumnFiltersEffective}
706
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
538
707
  <div
539
708
  class="sv-grid-menu sv-grid-filter-menu"
709
+ data-svgrid-menu={ctrl.gridDomId}
540
710
  role="menu"
541
711
  style={`left: ${filterMenuPos.x}px; top: ${filterMenuPos.y}px; max-height: calc(100vh - ${filterMenuPos.y}px - 12px);`}
712
+ tabindex="-1"
713
+ use:focusFilterPanel
714
+ onkeydown={onFilterPanelKeydown}
542
715
  >
543
716
  {@render filterPanelBody(filterMenuFor)}
544
717
  </div>
@@ -548,6 +721,7 @@
548
721
  {@const everyColumn = grid.getAllColumns()}
549
722
  <div
550
723
  class="sv-grid-menu sv-grid-choose-columns-menu"
724
+ data-svgrid-menu={ctrl.gridDomId}
551
725
  role="menu"
552
726
  style={`left: ${chooseColumnsPos.x}px; top: ${chooseColumnsPos.y}px;`}
553
727
  >
@@ -576,6 +750,7 @@
576
750
  filterMenuValues[opColumnId]?.operator ?? defaultOperatorFor(opColumn)}
577
751
  <div
578
752
  class="sv-grid-menu sv-grid-operator-menu"
753
+ data-svgrid-menu={ctrl.gridDomId}
579
754
  role="menu"
580
755
  style={`left: ${operatorMenuPos.x}px; top: ${operatorMenuPos.y}px;`}
581
756
  >
@@ -613,6 +788,7 @@
613
788
  <!-- svelte-ignore a11y_no_static_element_interactions -->
614
789
  <div
615
790
  class="sv-grid-menu sv-grid-in-suggest"
791
+ data-svgrid-menu={ctrl.gridDomId}
616
792
  style={`left: ${inSuggestPos.x}px; top: ${inSuggestPos.y}px;`}
617
793
  onmousedown={(event) => event.preventDefault()}
618
794
  >
@@ -656,6 +832,7 @@
656
832
  ></div>
657
833
  <div
658
834
  class="sv-grid-menu sv-grid-context-menu"
835
+ data-svgrid-menu={ctrl.gridDomId}
659
836
  role="menu"
660
837
  style={`left: ${contextMenuPos.x}px; top: ${contextMenuPos.y}px;`}
661
838
  >
@@ -683,6 +860,7 @@
683
860
  ></div>
684
861
  <div
685
862
  class="sv-grid-menu sv-grid-comment-editor"
863
+ data-svgrid-menu={ctrl.gridDomId}
686
864
  role="dialog"
687
865
  aria-label="Edit comment"
688
866
  style={`left: ${commentEditFor.x}px; top: ${commentEditFor.y}px;`}
@@ -39,6 +39,7 @@
39
39
  size = 'md',
40
40
  ariaLabel,
41
41
  invalid = false,
42
+ block = false,
42
43
  required = false,
43
44
  error,
44
45
  label,
@@ -111,6 +112,7 @@
111
112
  <input
112
113
  bind:this={fieldEl}
113
114
  class="sv-ac sv-ac--{size}"
115
+ class:is-block={block}
114
116
  class:is-invalid={invalid}
115
117
  type="text"
116
118
  {placeholder}
@@ -162,4 +164,6 @@
162
164
  background-size: 6px 4px; background-position: center; background-repeat: repeat-x;
163
165
  }
164
166
  :global(.sv-ddl__grip:hover .sv-ddl__grip-dots) { opacity: 1; }
167
+ /* Fill the container - see `block` in SvEditorProps. */
168
+ .sv-ac.is-block { width: 100%; max-width: 100%; }
165
169
  </style>
@@ -63,6 +63,7 @@
63
63
  size = 'md',
64
64
  ariaLabel,
65
65
  invalid = false,
66
+ block = false,
66
67
  required = false,
67
68
  error,
68
69
  label,
@@ -182,7 +183,8 @@
182
183
  </script>
183
184
 
184
185
  <SvField id={uid} {label} {hint} {error} {required} {dir} loading={loadingProp || loading}>
185
- <div bind:this={fieldEl} class="sv-combo sv-combo--{size}" class:is-open={combo.open} class:is-disabled={disabled} class:is-invalid={invalid}>
186
+ <div bind:this={fieldEl} class="sv-combo sv-combo--{size}"
187
+ class:is-block={block} class:is-open={combo.open} class:is-disabled={disabled} class:is-invalid={invalid}>
186
188
  <input
187
189
  bind:this={inputEl}
188
190
  class="sv-combo__input"
@@ -272,4 +274,6 @@
272
274
  background-size: 6px 4px; background-position: center; background-repeat: repeat-x;
273
275
  }
274
276
  :global(.sv-ddl__grip:hover .sv-ddl__grip-dots) { opacity: 1; }
277
+ /* Fill the container - see `block` in SvEditorProps. */
278
+ .sv-combo.is-block { width: 100%; max-width: 100%; }
275
279
  </style>
@@ -74,6 +74,7 @@
74
74
  size = 'md',
75
75
  ariaLabel,
76
76
  invalid = false,
77
+ block = false,
77
78
  required = false,
78
79
  error,
79
80
  label,
@@ -117,6 +118,7 @@
117
118
 
118
119
  // --- Portalled popover positioning (DOM-bound; stays in the component) -------
119
120
  let triggerEl = $state<HTMLDivElement | null>(null)
121
+ let inputEl = $state<HTMLInputElement | null>(null)
120
122
  let panelEl = $state<HTMLDivElement | null>(null)
121
123
  let panelRect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false, maxHeight: 0, availHeight: 0 })
122
124
 
@@ -157,8 +159,26 @@
157
159
  </script>
158
160
 
159
161
  <SvField id={uid} {label} {hint} {error} {required} {dir}>
160
- <div class="sv-dri" class:sv-dri--disabled={disabled}>
161
- <div class="sv-dri__field sv-dri__field--{size}" class:is-invalid={invalid} bind:this={triggerEl} dir={resolvedDir}>
162
+ <div class="sv-dri" class:is-block={block} class:sv-dri--disabled={disabled}>
163
+ <!-- The whole field is the trigger, not just the <input>. The input is
164
+ readonly and carries the dialog semantics, but it is the flex child that
165
+ shrinks: squeezed into a narrow container (a grid filter cell, say) it
166
+ collapses to a couple of pixels and the icon, padding and range text
167
+ around it were all dead to a click. The input keeps the a11y role and
168
+ focus; this only widens the hit area. -->
169
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
170
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
171
+ <div
172
+ class="sv-dri__field sv-dri__field--{size}"
173
+ class:is-invalid={invalid}
174
+ bind:this={triggerEl}
175
+ dir={resolvedDir}
176
+ onclick={() => {
177
+ if (!isInteractive) return
178
+ inputEl?.focus()
179
+ toggle()
180
+ }}
181
+ >
162
182
  <svg class="sv-dri__icon" viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2" /><path d="M16 2v4M8 2v4M3 10h18" /></svg>
163
183
  <input
164
184
  id={uid}
@@ -171,8 +191,8 @@
171
191
  {...editorAria({ id: uid, invalid, required, error, hint, ariaLabel })}
172
192
  aria-haspopup="dialog"
173
193
  aria-expanded={open}
174
- onclick={() => isInteractive && toggle()}
175
194
  onkeydown={onFieldKeydown}
195
+ bind:this={inputEl}
176
196
  use:focusOpen
177
197
  />
178
198
  {#if value && isInteractive}
@@ -253,4 +273,6 @@
253
273
  background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e2e8f0);
254
274
  border-radius: 12px; box-shadow: 0 16px 48px -12px rgba(15, 23, 42, 0.35);
255
275
  }
276
+ /* Fill the container - see `block` in SvEditorProps. */
277
+ .sv-dri.is-block, .sv-dri.is-block .sv-dri__field { width: 100%; max-width: 100%; }
256
278
  </style>
@@ -57,6 +57,7 @@
57
57
  size = 'md',
58
58
  ariaLabel,
59
59
  invalid = false,
60
+ block = false,
60
61
  required = false,
61
62
  error,
62
63
  label,
@@ -231,6 +232,7 @@
231
232
  <button
232
233
  bind:this={triggerEl}
233
234
  class="sv-ddl sv-ddl--{size}"
235
+ class:is-block={block}
234
236
  class:is-open={ddl.open}
235
237
  class:is-disabled={disabled}
236
238
  class:is-invalid={invalid}
@@ -371,4 +373,6 @@
371
373
  :global(.sv-ddl__empty) { padding: 12px 12px; font-size: 13px; color: var(--sg-muted, #64748b); text-align: center; }
372
374
  :global(.sv-ddl__header) { padding: 8px 10px; border-bottom: 1px solid var(--sg-border, #e2e8f0); font-size: 12.5px; color: var(--sg-muted, #64748b); }
373
375
  :global(.sv-ddl__footer) { padding: 8px 10px; border-top: 1px solid var(--sg-border, #e2e8f0); font-size: 12.5px; }
376
+ /* Fill the container - see `block` in SvEditorProps. */
377
+ .sv-ddl.is-block { width: 100%; max-width: 100%; }
374
378
  </style>
@@ -177,7 +177,7 @@
177
177
  {#if o.type === 'textarea'}
178
178
  <textarea id={o.id} class="sv-form__control sv-form__textarea" rows="3" disabled={o.disabled} readonly={o.readonly} placeholder={o.placeholder} value={o.value ?? ''} aria-invalid={o.invalid ? 'true' : undefined} aria-describedby={o.describedby} oninput={(e) => o.onChange(e.currentTarget.value)} onblur={o.onBlur}></textarea>
179
179
  {:else if o.type === 'number'}
180
- <SvNumberInput id={o.id} value={o.value ?? null} disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} block min={o.field?.min ?? -Infinity} max={o.field?.max ?? Infinity} step={o.field?.step ?? 1} precision={o.field?.precision} prefix={o.field?.prefix ?? ''} suffix={o.field?.suffix ?? ''} onChange={o.onChange} />
180
+ <SvNumberInput id={o.id} value={o.value ?? null} disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} block placeholder={o.placeholder} min={o.field?.min ?? -Infinity} max={o.field?.max ?? Infinity} step={o.field?.step ?? 1} precision={o.field?.precision} prefix={o.field?.prefix ?? ''} suffix={o.field?.suffix ?? ''} onChange={o.onChange} />
181
181
  {:else if o.type === 'password'}
182
182
  <SvPasswordInput id={o.id} value={o.value ?? ''} disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} block onChange={o.onChange} />
183
183
  {:else if o.type === 'mask'}
@@ -185,17 +185,17 @@
185
185
  {:else if o.type === 'phone'}
186
186
  <SvPhoneInput id={o.id} value={o.value ?? ''} disabled={o.disabled} invalid={o.invalid} block placeholder={o.placeholder} onChange={(v) => o.onChange(v)} />
187
187
  {:else if o.type === 'country'}
188
- <SvCountryInput id={o.id} value={o.value ?? null} disabled={o.disabled} invalid={o.invalid} onChange={o.onChange} />
188
+ <SvCountryInput id={o.id} value={o.value ?? null} disabled={o.disabled} invalid={o.invalid} block onChange={o.onChange} />
189
189
  {:else if o.type === 'select'}
190
- <SvDropDownList id={o.id} options={o.options ?? []} value={o.value ?? null} disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} placeholder={o.placeholder} onChange={o.onChange} />
190
+ <SvDropDownList id={o.id} options={o.options ?? []} value={o.value ?? null} disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} block placeholder={o.placeholder} onChange={o.onChange} />
191
191
  {:else if o.type === 'combobox'}
192
- <SvComboBox id={o.id} options={o.options ?? []} value={o.value ?? null} disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} clearable placeholder={o.placeholder} loadOptions={o.field?.loadOptions ? (q) => o.field!.loadOptions!(q, form.values) : undefined} onChange={o.onChange} />
192
+ <SvComboBox id={o.id} options={o.options ?? []} value={o.value ?? null} disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} block clearable placeholder={o.placeholder} loadOptions={o.field?.loadOptions ? (q) => o.field!.loadOptions!(q, form.values) : undefined} onChange={o.onChange} />
193
193
  {:else if o.type === 'multiselect'}
194
- <SvMultiSelect id={o.id} options={o.options ?? []} value={Array.isArray(o.value) ? o.value : []} disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} placeholder={o.placeholder} onChange={o.onChange} />
194
+ <SvMultiSelect id={o.id} options={o.options ?? []} value={Array.isArray(o.value) ? o.value : []} disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} block placeholder={o.placeholder} onChange={o.onChange} />
195
195
  {:else if o.type === 'radio'}
196
196
  <SvRadioGroup id={o.id} options={o.options ?? []} value={o.value ?? null} disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} orientation="horizontal" onChange={o.onChange} />
197
197
  {:else if o.type === 'tags'}
198
- <SvTagsInput id={o.id} value={Array.isArray(o.value) ? o.value : []} disabled={o.disabled} invalid={o.invalid} placeholder={o.placeholder} onChange={o.onChange} />
198
+ <SvTagsInput id={o.id} value={Array.isArray(o.value) ? o.value : []} disabled={o.disabled} invalid={o.invalid} block placeholder={o.placeholder} onChange={o.onChange} />
199
199
  {:else if o.type === 'slider'}
200
200
  <SvSlider id={o.id} value={o.value ?? o.field?.min ?? 0} min={o.field?.min ?? 0} max={o.field?.max ?? 100} step={o.field?.step ?? 1} disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} showValue onChange={o.onChange} />
201
201
  {:else if o.type === 'checkbox'}
@@ -203,9 +203,9 @@
203
203
  {:else if o.type === 'switch'}
204
204
  <div class="sv-form__inline"><SvSwitchButton checked={!!o.value} disabled={o.disabled} readonly={o.readonly} onChange={o.onChange} ariaLabel={o.label} /><span>{o.label}</span></div>
205
205
  {:else if o.type === 'date'}
206
- <SvDateTimePicker value={dateVal(o.value)} dropDownDisplayMode="calendar" formatString="yyyy-MM-dd" disabled={o.disabled} onChange={o.onChange} />
206
+ <SvDateTimePicker id={o.id} value={dateVal(o.value)} dropDownDisplayMode="calendar" formatString="yyyy-MM-dd" disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} block placeholder={o.placeholder ?? 'Select date'} onChange={o.onChange} />
207
207
  {:else if o.type === 'datetime'}
208
- <SvDateTimePicker value={dateVal(o.value)} dropDownDisplayMode="both" disabled={o.disabled} onChange={o.onChange} />
208
+ <SvDateTimePicker id={o.id} value={dateVal(o.value)} dropDownDisplayMode="both" disabled={o.disabled} readonly={o.readonly} invalid={o.invalid} block placeholder={o.placeholder} onChange={o.onChange} />
209
209
  {:else if o.type === 'color'}
210
210
  <SvColorInput id={o.id} value={o.value ?? '#3b82f6'} disabled={o.disabled} invalid={o.invalid} onChange={o.onChange} />
211
211
  {:else if o.type === 'file'}
@@ -51,6 +51,18 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
51
51
  targetRow: number;
52
52
  targetCol: number;
53
53
  } | null;
54
+ moveDrag: {
55
+ sourceMinRow: number;
56
+ sourceMaxRow: number;
57
+ sourceMinCol: number;
58
+ sourceMaxCol: number;
59
+ grabRow: number;
60
+ grabCol: number;
61
+ targetRow: number;
62
+ targetCol: number;
63
+ copy: boolean;
64
+ } | null;
65
+ moveGrabHover: boolean;
54
66
  activeAtPointerDown: {
55
67
  rowIndex: number;
56
68
  colIndex: number;
@@ -127,6 +139,7 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
127
139
  readonly inSuggestOptions: {
128
140
  value: string;
129
141
  label: string;
142
+ hint: string | undefined;
130
143
  }[];
131
144
  readonly facetValuesForColumn: (columnId: string) => Array<string>;
132
145
  chooseColumnsPos: MenuPosition | null;
@@ -159,6 +172,7 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
159
172
  readonly showRowSelectionEffective: boolean;
160
173
  readonly rowSummariesEnabled: boolean;
161
174
  readonly enableCellSelectionEffective: boolean;
175
+ readonly moveCellsEffective: boolean;
162
176
  readonly flushScheduledScrollSync: () => void;
163
177
  readonly scheduleScrollSync: (scrollTop: number, scrollLeft: number) => void;
164
178
  internalData: readonly TData[];
@@ -276,6 +290,15 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
276
290
  readonly allRowsBeforePagination: Row<TData>[];
277
291
  readonly allRows: Row<TData>[];
278
292
  readonly rowSelectionState: any;
293
+ readonly selectionBarOn: boolean;
294
+ readonly selectionBarActions: unknown[];
295
+ readonly selectionBarPosition: "top" | "bottom";
296
+ readonly selectionBarMaxVisible: number;
297
+ readonly selectionBarHideClear: boolean;
298
+ readonly selectionBarTarget: {
299
+ rows: TData[];
300
+ ids: string[];
301
+ };
279
302
  lastSelectionSerialized: string;
280
303
  lastCellRangeSerialized: string;
281
304
  readonly statusBarEnabled: boolean;
@@ -531,6 +554,7 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
531
554
  colIndex: any;
532
555
  } | null;
533
556
  readonly isInFillPreview: (rowIndex: number, colIndex: number) => boolean;
557
+ readonly dragPreviewRect: () => any;
534
558
  readonly fillMarqueeEdges: (rowIndex: number, colIndex: number) => {
535
559
  top: boolean;
536
560
  bottom: boolean;
@@ -552,10 +576,32 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
552
576
  readonly startFillDrag: (event: PointerEvent, rowIndex: number, colIndex: number) => void;
553
577
  readonly onFillPointerMove: (event: PointerEvent) => void;
554
578
  readonly onFillPointerUp: () => void;
579
+ readonly isOnMoveGrabStrip: (cell: HTMLElement, rowIndex: number, colIndex: number, clientX: number, clientY: number) => boolean;
580
+ readonly startMoveDrag: (event: PointerEvent, rowIndex: number, colIndex: number) => boolean;
581
+ readonly onMovePointerMove: (event: PointerEvent) => void;
582
+ readonly onMovePointerUp: (event?: PointerEvent) => void;
583
+ readonly trackEdgeScroll: (event: PointerEvent) => void;
584
+ readonly stopEdgeScroll: () => void;
585
+ readonly applyMoveRange: () => void;
586
+ readonly moveDestRect: (d: {
587
+ sourceMinRow: number;
588
+ sourceMaxRow: number;
589
+ sourceMinCol: number;
590
+ sourceMaxCol: number;
591
+ grabRow: number;
592
+ grabCol: number;
593
+ targetRow: number;
594
+ targetCol: number;
595
+ }) => {
596
+ minRow: number;
597
+ maxRow: number;
598
+ minCol: number;
599
+ maxCol: number;
600
+ } | null;
555
601
  readonly toggleBooleanCell: (rowIndex: number, colIndex: number) => void;
556
602
  readonly onCellPointerDown: (rowIndex: number, colIndex: number, event: PointerEvent) => void;
557
603
  readonly onCellPointerEnter: (rowIndex: number, colIndex: number) => void;
558
- readonly endDragSelection: () => void;
604
+ readonly endDragSelection: (event?: PointerEvent) => void;
559
605
  readonly onWindowPointerMove: (event: PointerEvent) => void;
560
606
  readonly onCellClick: (rowIndex: number, colIndex: number) => void;
561
607
  readonly emitCellDoubleClick: (rowIndex: number, colIndex: number) => void;
@@ -643,6 +689,7 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
643
689
  readonly columnMenuFacetOptions: {
644
690
  value: string;
645
691
  label: string;
692
+ hint: string | undefined;
646
693
  }[];
647
694
  readonly columnMenuSelectedFacets: ReadonlySet<string>;
648
695
  readonly isFacetChecked: (columnId: string, value: string) => any;