@ornery/ui-grid-vanilla 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/demo/index.html CHANGED
@@ -179,7 +179,7 @@
179
179
  <p>
180
180
  Familiar <code>gridOptions</code> and <code>onRegisterApi</code>, rebuilt as a
181
181
  framework-neutral custom element with the Rust-backed pipeline, grouping, sorting,
182
- filtering, and virtualization.
182
+ filtering, Excel-style column resizing, and virtualization.
183
183
  </p>
184
184
  </div>
185
185
  <div class="grid-demo-actions">
@@ -210,8 +210,8 @@
210
210
  <div class="grid-demo-toolbar">
211
211
  <div><strong id="visible-rows-inline">0</strong> of <span id="total-rows-value">0</span> rows</div>
212
212
  <p>
213
- <code>gridOptions</code> compatibility layer: sorting, filtering, grouping, templating,
214
- and virtualized rendering.
213
+ <code>gridOptions</code> compatibility layer: sorting, filtering, grouping, column resizing,
214
+ templating, and virtualized rendering.
215
215
  </p>
216
216
  </div>
217
217
 
package/demo/main.ts CHANGED
@@ -28,6 +28,7 @@ const options: GridOptions = {
28
28
  enableSorting: true,
29
29
  enableFiltering: true,
30
30
  enableGrouping: true,
31
+ enableColumnResizing: true,
31
32
  enableVirtualization: true,
32
33
  virtualizationThreshold: 25,
33
34
  grouping: { groupBy: ['status'] },
package/dist/index.d.mts CHANGED
@@ -41,6 +41,7 @@ declare class VanillaGridController {
41
41
  private expandedTreeRows;
42
42
  private pinnedColumns;
43
43
  private columnOrder;
44
+ private columnWidthOverrides;
44
45
  private currentPage;
45
46
  private pageSize;
46
47
  private editingCell;
@@ -84,6 +85,11 @@ declare class VanillaGridController {
84
85
  updateEditingValue(value: string): void;
85
86
  commitCellEdit(): void;
86
87
  cancelCellEdit(): void;
88
+ canResizeColumns(): boolean;
89
+ setColumnWidthOverride(columnName: string, widthPx: number): void;
90
+ /** Returns the grid-template-columns string as if `columnName` had the given width,
91
+ * without triggering a full refresh. Used for smooth drag-resize DOM updates. */
92
+ buildTemplateColumnsWithOverride(columnName: string, widthPx: number): string;
87
93
  isGroupingEnabled(): boolean;
88
94
  isSortingEnabled(): boolean;
89
95
  isFilteringEnabled(): boolean;
@@ -236,6 +242,8 @@ declare class UiGridStandaloneElement extends HTMLElement {
236
242
  set enablePinning(value: boolean);
237
243
  get enableColumnMoving(): boolean;
238
244
  set enableColumnMoving(value: boolean);
245
+ get enableColumnResizing(): boolean;
246
+ set enableColumnResizing(value: boolean);
239
247
  get enableCellEdit(): boolean;
240
248
  set enableCellEdit(value: boolean);
241
249
  get enableCellEditOnFocus(): boolean;
@@ -296,6 +304,7 @@ declare class UiGridStandaloneElement extends HTMLElement {
296
304
  private render;
297
305
  private renderHeaderCell;
298
306
  private renderFilterCell;
307
+ private measureAutoColumnWidth;
299
308
  private renderDisplayItem;
300
309
  private renderBodyCell;
301
310
  private renderPagination;
package/dist/index.d.ts CHANGED
@@ -41,6 +41,7 @@ declare class VanillaGridController {
41
41
  private expandedTreeRows;
42
42
  private pinnedColumns;
43
43
  private columnOrder;
44
+ private columnWidthOverrides;
44
45
  private currentPage;
45
46
  private pageSize;
46
47
  private editingCell;
@@ -84,6 +85,11 @@ declare class VanillaGridController {
84
85
  updateEditingValue(value: string): void;
85
86
  commitCellEdit(): void;
86
87
  cancelCellEdit(): void;
88
+ canResizeColumns(): boolean;
89
+ setColumnWidthOverride(columnName: string, widthPx: number): void;
90
+ /** Returns the grid-template-columns string as if `columnName` had the given width,
91
+ * without triggering a full refresh. Used for smooth drag-resize DOM updates. */
92
+ buildTemplateColumnsWithOverride(columnName: string, widthPx: number): string;
87
93
  isGroupingEnabled(): boolean;
88
94
  isSortingEnabled(): boolean;
89
95
  isFilteringEnabled(): boolean;
@@ -236,6 +242,8 @@ declare class UiGridStandaloneElement extends HTMLElement {
236
242
  set enablePinning(value: boolean);
237
243
  get enableColumnMoving(): boolean;
238
244
  set enableColumnMoving(value: boolean);
245
+ get enableColumnResizing(): boolean;
246
+ set enableColumnResizing(value: boolean);
239
247
  get enableCellEdit(): boolean;
240
248
  set enableCellEdit(value: boolean);
241
249
  get enableCellEditOnFocus(): boolean;
@@ -296,6 +304,7 @@ declare class UiGridStandaloneElement extends HTMLElement {
296
304
  private render;
297
305
  private renderHeaderCell;
298
306
  private renderFilterCell;
307
+ private measureAutoColumnWidth;
299
308
  private renderDisplayItem;
300
309
  private renderBodyCell;
301
310
  private renderPagination;
package/dist/index.js CHANGED
@@ -55,6 +55,7 @@ var VanillaGridController = class {
55
55
  expandedTreeRows = {};
56
56
  pinnedColumns = {};
57
57
  columnOrder = [];
58
+ columnWidthOverrides = {};
58
59
  currentPage = 1;
59
60
  pageSize = 0;
60
61
  editingCell = null;
@@ -463,6 +464,24 @@ var VanillaGridController = class {
463
464
  });
464
465
  this.refresh();
465
466
  }
467
+ canResizeColumns() {
468
+ return this.options.enableColumnResizing !== false;
469
+ }
470
+ setColumnWidthOverride(columnName, widthPx) {
471
+ const nextWidth = `${Math.max(88, Math.round(widthPx))}px`;
472
+ this.columnWidthOverrides = { ...this.columnWidthOverrides, [columnName]: nextWidth };
473
+ this.refresh();
474
+ }
475
+ /** Returns the grid-template-columns string as if `columnName` had the given width,
476
+ * without triggering a full refresh. Used for smooth drag-resize DOM updates. */
477
+ buildTemplateColumnsWithOverride(columnName, widthPx) {
478
+ const widthStr = `${Math.max(88, Math.round(widthPx))}px`;
479
+ return buildGridTemplateColumns(
480
+ this.visibleColumns.map(
481
+ (c) => c.name === columnName ? { ...c, width: widthStr } : c
482
+ )
483
+ );
484
+ }
466
485
  isGroupingEnabled() {
467
486
  return (0, import_ui_grid_core.isGridGroupingEnabled)(this.options);
468
487
  }
@@ -581,9 +600,13 @@ var VanillaGridController = class {
581
600
  }
582
601
  refresh() {
583
602
  const orderedColumns = orderVisibleColumns(this.options.columnDefs, this.columnOrder);
603
+ const applyWidthOverrides = (columns) => columns.map((col) => {
604
+ const override = this.columnWidthOverrides[col.name];
605
+ return override == null ? col : { ...col, width: override };
606
+ });
584
607
  const pinnedEntries = Object.entries(this.pinnedColumns);
585
608
  if (pinnedEntries.length === 0) {
586
- this.visibleColumns = orderedColumns;
609
+ this.visibleColumns = applyWidthOverrides(orderedColumns);
587
610
  } else {
588
611
  const byName = new Map(orderedColumns.map((column) => [column.name, column]));
589
612
  const pinnedLeft = pinnedEntries.filter((entry) => entry[1] === "left").map((entry) => byName.get(entry[0])).filter((column) => column !== void 0);
@@ -591,7 +614,7 @@ var VanillaGridController = class {
591
614
  const middleColumns = orderedColumns.filter(
592
615
  (column) => this.pinnedColumns[column.name] === void 0
593
616
  );
594
- this.visibleColumns = [...pinnedLeft, ...middleColumns, ...pinnedRight];
617
+ this.visibleColumns = applyWidthOverrides([...pinnedLeft, ...middleColumns, ...pinnedRight]);
595
618
  }
596
619
  this.pipeline = import_ui_grid_core.defaultGridEngine.buildPipeline({
597
620
  options: this.options,
@@ -915,6 +938,9 @@ var UiGridStandaloneElement = class extends HTMLElement {
915
938
  const enableColumnMoving = this.parseBooleanAttribute("enable-column-moving");
916
939
  if (enableColumnMoving !== void 0)
917
940
  this.attributeOptions.enableColumnMoving = enableColumnMoving;
941
+ const enableColumnResizing = this.parseBooleanAttribute("enable-column-resizing");
942
+ if (enableColumnResizing !== void 0)
943
+ this.attributeOptions.enableColumnResizing = enableColumnResizing;
918
944
  const enableCellEdit = this.parseBooleanAttribute("enable-cell-edit");
919
945
  if (enableCellEdit !== void 0) this.attributeOptions.enableCellEdit = enableCellEdit;
920
946
  const enableCellEditOnFocus = this.parseBooleanAttribute("enable-cell-edit-on-focus");
@@ -1163,6 +1189,13 @@ var UiGridStandaloneElement = class extends HTMLElement {
1163
1189
  this.activeOptions = { ...this.activeOptions, enableColumnMoving: value };
1164
1190
  this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1165
1191
  }
1192
+ get enableColumnResizing() {
1193
+ return this.options.enableColumnResizing ?? true;
1194
+ }
1195
+ set enableColumnResizing(value) {
1196
+ this.activeOptions = { ...this.activeOptions, enableColumnResizing: value };
1197
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1198
+ }
1166
1199
  get enableCellEdit() {
1167
1200
  return this.options.enableCellEdit ?? false;
1168
1201
  }
@@ -1583,11 +1616,51 @@ var UiGridStandaloneElement = class extends HTMLElement {
1583
1616
  this.controller.setPageSize(value);
1584
1617
  }
1585
1618
  });
1619
+ root.addEventListener("mousedown", (event) => {
1620
+ const target = event.target;
1621
+ if (!target || !this.controller) return;
1622
+ const resizer = target.closest(".column-resizer[data-column]");
1623
+ if (!resizer || !this.controller.canResizeColumns()) return;
1624
+ const columnName = resizer.dataset["column"];
1625
+ if (!columnName) return;
1626
+ const headerCell = resizer.closest(".header-cell");
1627
+ if (!headerCell) return;
1628
+ event.preventDefault();
1629
+ event.stopPropagation();
1630
+ const startX = event.clientX;
1631
+ const startWidth = headerCell.getBoundingClientRect().width;
1632
+ let lastWidth = startWidth;
1633
+ const handleMove = (moveEvent) => {
1634
+ lastWidth = Math.max(88, startWidth + (moveEvent.clientX - startX));
1635
+ const newTemplate = this.controller.buildTemplateColumnsWithOverride(columnName, lastWidth);
1636
+ const root2 = this.shadowRoot ?? this;
1637
+ root2.querySelectorAll(".header-grid, .filter-grid, .body-grid").forEach((el) => {
1638
+ el.style.gridTemplateColumns = newTemplate;
1639
+ });
1640
+ };
1641
+ const handleUp = () => {
1642
+ window.removeEventListener("mousemove", handleMove);
1643
+ window.removeEventListener("mouseup", handleUp);
1644
+ this.controller.setColumnWidthOverride(columnName, lastWidth);
1645
+ };
1646
+ window.addEventListener("mousemove", handleMove);
1647
+ window.addEventListener("mouseup", handleUp);
1648
+ });
1586
1649
  root.addEventListener("dblclick", (event) => {
1587
1650
  const target = event.target;
1588
1651
  if (!target || !this.controller) {
1589
1652
  return;
1590
1653
  }
1654
+ const resizer = target.closest(".column-resizer[data-column]");
1655
+ if (resizer && this.controller.canResizeColumns()) {
1656
+ const columnName2 = resizer.dataset["column"];
1657
+ if (columnName2) {
1658
+ event.preventDefault();
1659
+ event.stopPropagation();
1660
+ this.controller.setColumnWidthOverride(columnName2, this.measureAutoColumnWidth(columnName2));
1661
+ return;
1662
+ }
1663
+ }
1591
1664
  const cell = target.closest(".body-cell");
1592
1665
  if (!cell) {
1593
1666
  return;
@@ -1913,8 +1986,10 @@ var UiGridStandaloneElement = class extends HTMLElement {
1913
1986
  const sortIconKey = sortDirection === import_ui_grid_core2.SORT_DIRECTIONS.asc ? "sortAsc" : sortDirection === import_ui_grid_core2.SORT_DIRECTIONS.desc ? "sortDesc" : "sortNone";
1914
1987
  const draggable = (0, import_ui_grid_core2.canGridMoveColumns)(options) ? 'draggable="true"' : "";
1915
1988
  const pinLabel = isPinned ? this.snapshot?.labels.unpin ?? "Unpin" : this.snapshot?.labels.pinColumn ?? "Pin";
1989
+ const canResize = controller.canResizeColumns();
1916
1990
  const headerValue = escapeHtml((0, import_ui_grid_core2.formatGridHeaderDisplayValue)((0, import_ui_grid_core2.buildGridHeaderContext)(column)));
1917
- return `<div class="${classes}" data-column="${escapeHtml(column.name)}" ${draggable} style="${stickyStyle}"><span class="header-label">${headerValue}</span><span class="header-actions">${sortEnabled ? `<button type="button" class="header-action" data-action="sort" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(sortLabel)}" ${canSort ? "" : "disabled"}>${this.renderControlIcon(sortIconKey)}<span class="sr-only">${escapeHtml(sortLabel)}</span></button>` : ""}${groupingEnabled ? `<button type="button" class="chip-action${controller.isColumnGrouped(column) ? " chip-action-active" : ""}" data-action="group" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(groupingLabel)}" ${canGroup ? "" : "disabled"}>${this.renderControlIcon("group")}<span class="sr-only">${escapeHtml(groupingLabel)}</span></button>` : ""}${canPin ? `<div class="pin-control${this.openPinMenuColumn === column.name ? " pin-control-open" : ""}"><button type="button" class="chip-action pin-trigger${isPinned ? " chip-action-active" : ""}" data-action="pin-trigger" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(pinLabel)}">${this.renderControlIcon("pin")}<span class="sr-only">${escapeHtml(pinLabel)}</span></button><div class="pin-menu" role="menu" aria-label="${escapeHtml(pinLabel)}"><button type="button" class="pin-menu-action" data-action="pin-left" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}">${this.renderIconWithClass("control-icon", "pinLeft")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}</span></button><button type="button" class="pin-menu-action" data-action="pin-right" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}">${this.renderIconWithClass("control-icon", "pinRight")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}</span></button></div></div>` : ""}</span></div>`;
1991
+ const resizerHtml = canResize ? `<button type="button" class="column-resizer" data-action="resize" data-column="${escapeHtml(column.name)}" aria-label="Resize ${escapeHtml(headerValue)} column" title="Drag to resize, double-click to auto fit"></button>` : "";
1992
+ return `<div class="${classes}" data-column="${escapeHtml(column.name)}" ${draggable} style="${stickyStyle}"><span class="header-label">${headerValue}</span><span class="header-actions">${sortEnabled ? `<button type="button" class="header-action" data-action="sort" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(sortLabel)}" ${canSort ? "" : "disabled"}>${this.renderControlIcon(sortIconKey)}<span class="sr-only">${escapeHtml(sortLabel)}</span></button>` : ""}${groupingEnabled ? `<button type="button" class="chip-action${controller.isColumnGrouped(column) ? " chip-action-active" : ""}" data-action="group" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(groupingLabel)}" ${canGroup ? "" : "disabled"}>${this.renderControlIcon("group")}<span class="sr-only">${escapeHtml(groupingLabel)}</span></button>` : ""}${canPin ? `<div class="pin-control${this.openPinMenuColumn === column.name ? " pin-control-open" : ""}"><button type="button" class="chip-action pin-trigger${isPinned ? " chip-action-active" : ""}" data-action="pin-trigger" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(pinLabel)}">${this.renderControlIcon("pin")}<span class="sr-only">${escapeHtml(pinLabel)}</span></button><div class="pin-menu" role="menu" aria-label="${escapeHtml(pinLabel)}"><button type="button" class="pin-menu-action" data-action="pin-left" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}">${this.renderIconWithClass("control-icon", "pinLeft")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}</span></button><button type="button" class="pin-menu-action" data-action="pin-right" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}">${this.renderIconWithClass("control-icon", "pinRight")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}</span></button></div></div>` : ""}</span>${resizerHtml}</div>`;
1918
1993
  }
1919
1994
  renderFilterCell(column) {
1920
1995
  const value = this.snapshot?.activeFilters[column.name] ?? "";
@@ -1930,6 +2005,23 @@ var UiGridStandaloneElement = class extends HTMLElement {
1930
2005
  const stickyStyle = pinOffset ? `${pinOffset.side}:${pinOffset.offset};` : "";
1931
2006
  return `<label class="${classes}" style="${stickyStyle}"><input class="ui-grid-filter-input" data-role="filter" data-column="${escapeHtml(column.name)}" placeholder="${escapeHtml(controller.filterPlaceholder(column))}" value="${escapeHtml(value)}" ${canFilter ? "" : "disabled"}></label>`;
1932
2007
  }
2008
+ measureAutoColumnWidth(columnName) {
2009
+ const root = this.shadowRoot;
2010
+ if (root == null) return 176;
2011
+ const escaped = CSS.escape ? CSS.escape(columnName) : columnName.replace(/([\\".#:[\](){}+~> ])/g, "\\$1");
2012
+ const selectors = [
2013
+ `.header-cell[data-column="${escaped}"]`,
2014
+ `.filter-cell[data-column="${escaped}"]`,
2015
+ `.body-cell[data-column="${escaped}"] .cell-shell`
2016
+ ];
2017
+ let maxWidth = 0;
2018
+ for (const selector of selectors) {
2019
+ for (const el of root.querySelectorAll(selector)) {
2020
+ maxWidth = Math.max(maxWidth, el.scrollWidth);
2021
+ }
2022
+ }
2023
+ return maxWidth + 12;
2024
+ }
1933
2025
  renderDisplayItem(item, displayIndex) {
1934
2026
  if (!this.snapshot || !this.controller) {
1935
2027
  return "";
package/dist/index.mjs CHANGED
@@ -97,6 +97,7 @@ var VanillaGridController = class {
97
97
  expandedTreeRows = {};
98
98
  pinnedColumns = {};
99
99
  columnOrder = [];
100
+ columnWidthOverrides = {};
100
101
  currentPage = 1;
101
102
  pageSize = 0;
102
103
  editingCell = null;
@@ -505,6 +506,24 @@ var VanillaGridController = class {
505
506
  });
506
507
  this.refresh();
507
508
  }
509
+ canResizeColumns() {
510
+ return this.options.enableColumnResizing !== false;
511
+ }
512
+ setColumnWidthOverride(columnName, widthPx) {
513
+ const nextWidth = `${Math.max(88, Math.round(widthPx))}px`;
514
+ this.columnWidthOverrides = { ...this.columnWidthOverrides, [columnName]: nextWidth };
515
+ this.refresh();
516
+ }
517
+ /** Returns the grid-template-columns string as if `columnName` had the given width,
518
+ * without triggering a full refresh. Used for smooth drag-resize DOM updates. */
519
+ buildTemplateColumnsWithOverride(columnName, widthPx) {
520
+ const widthStr = `${Math.max(88, Math.round(widthPx))}px`;
521
+ return buildGridTemplateColumns(
522
+ this.visibleColumns.map(
523
+ (c) => c.name === columnName ? { ...c, width: widthStr } : c
524
+ )
525
+ );
526
+ }
508
527
  isGroupingEnabled() {
509
528
  return isGridGroupingEnabled(this.options);
510
529
  }
@@ -623,9 +642,13 @@ var VanillaGridController = class {
623
642
  }
624
643
  refresh() {
625
644
  const orderedColumns = orderVisibleColumns(this.options.columnDefs, this.columnOrder);
645
+ const applyWidthOverrides = (columns) => columns.map((col) => {
646
+ const override = this.columnWidthOverrides[col.name];
647
+ return override == null ? col : { ...col, width: override };
648
+ });
626
649
  const pinnedEntries = Object.entries(this.pinnedColumns);
627
650
  if (pinnedEntries.length === 0) {
628
- this.visibleColumns = orderedColumns;
651
+ this.visibleColumns = applyWidthOverrides(orderedColumns);
629
652
  } else {
630
653
  const byName = new Map(orderedColumns.map((column) => [column.name, column]));
631
654
  const pinnedLeft = pinnedEntries.filter((entry) => entry[1] === "left").map((entry) => byName.get(entry[0])).filter((column) => column !== void 0);
@@ -633,7 +656,7 @@ var VanillaGridController = class {
633
656
  const middleColumns = orderedColumns.filter(
634
657
  (column) => this.pinnedColumns[column.name] === void 0
635
658
  );
636
- this.visibleColumns = [...pinnedLeft, ...middleColumns, ...pinnedRight];
659
+ this.visibleColumns = applyWidthOverrides([...pinnedLeft, ...middleColumns, ...pinnedRight]);
637
660
  }
638
661
  this.pipeline = defaultGridEngine.buildPipeline({
639
662
  options: this.options,
@@ -957,6 +980,9 @@ var UiGridStandaloneElement = class extends HTMLElement {
957
980
  const enableColumnMoving = this.parseBooleanAttribute("enable-column-moving");
958
981
  if (enableColumnMoving !== void 0)
959
982
  this.attributeOptions.enableColumnMoving = enableColumnMoving;
983
+ const enableColumnResizing = this.parseBooleanAttribute("enable-column-resizing");
984
+ if (enableColumnResizing !== void 0)
985
+ this.attributeOptions.enableColumnResizing = enableColumnResizing;
960
986
  const enableCellEdit = this.parseBooleanAttribute("enable-cell-edit");
961
987
  if (enableCellEdit !== void 0) this.attributeOptions.enableCellEdit = enableCellEdit;
962
988
  const enableCellEditOnFocus = this.parseBooleanAttribute("enable-cell-edit-on-focus");
@@ -1205,6 +1231,13 @@ var UiGridStandaloneElement = class extends HTMLElement {
1205
1231
  this.activeOptions = { ...this.activeOptions, enableColumnMoving: value };
1206
1232
  this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1207
1233
  }
1234
+ get enableColumnResizing() {
1235
+ return this.options.enableColumnResizing ?? true;
1236
+ }
1237
+ set enableColumnResizing(value) {
1238
+ this.activeOptions = { ...this.activeOptions, enableColumnResizing: value };
1239
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1240
+ }
1208
1241
  get enableCellEdit() {
1209
1242
  return this.options.enableCellEdit ?? false;
1210
1243
  }
@@ -1625,11 +1658,51 @@ var UiGridStandaloneElement = class extends HTMLElement {
1625
1658
  this.controller.setPageSize(value);
1626
1659
  }
1627
1660
  });
1661
+ root.addEventListener("mousedown", (event) => {
1662
+ const target = event.target;
1663
+ if (!target || !this.controller) return;
1664
+ const resizer = target.closest(".column-resizer[data-column]");
1665
+ if (!resizer || !this.controller.canResizeColumns()) return;
1666
+ const columnName = resizer.dataset["column"];
1667
+ if (!columnName) return;
1668
+ const headerCell = resizer.closest(".header-cell");
1669
+ if (!headerCell) return;
1670
+ event.preventDefault();
1671
+ event.stopPropagation();
1672
+ const startX = event.clientX;
1673
+ const startWidth = headerCell.getBoundingClientRect().width;
1674
+ let lastWidth = startWidth;
1675
+ const handleMove = (moveEvent) => {
1676
+ lastWidth = Math.max(88, startWidth + (moveEvent.clientX - startX));
1677
+ const newTemplate = this.controller.buildTemplateColumnsWithOverride(columnName, lastWidth);
1678
+ const root2 = this.shadowRoot ?? this;
1679
+ root2.querySelectorAll(".header-grid, .filter-grid, .body-grid").forEach((el) => {
1680
+ el.style.gridTemplateColumns = newTemplate;
1681
+ });
1682
+ };
1683
+ const handleUp = () => {
1684
+ window.removeEventListener("mousemove", handleMove);
1685
+ window.removeEventListener("mouseup", handleUp);
1686
+ this.controller.setColumnWidthOverride(columnName, lastWidth);
1687
+ };
1688
+ window.addEventListener("mousemove", handleMove);
1689
+ window.addEventListener("mouseup", handleUp);
1690
+ });
1628
1691
  root.addEventListener("dblclick", (event) => {
1629
1692
  const target = event.target;
1630
1693
  if (!target || !this.controller) {
1631
1694
  return;
1632
1695
  }
1696
+ const resizer = target.closest(".column-resizer[data-column]");
1697
+ if (resizer && this.controller.canResizeColumns()) {
1698
+ const columnName2 = resizer.dataset["column"];
1699
+ if (columnName2) {
1700
+ event.preventDefault();
1701
+ event.stopPropagation();
1702
+ this.controller.setColumnWidthOverride(columnName2, this.measureAutoColumnWidth(columnName2));
1703
+ return;
1704
+ }
1705
+ }
1633
1706
  const cell = target.closest(".body-cell");
1634
1707
  if (!cell) {
1635
1708
  return;
@@ -1955,8 +2028,10 @@ var UiGridStandaloneElement = class extends HTMLElement {
1955
2028
  const sortIconKey = sortDirection === SORT_DIRECTIONS2.asc ? "sortAsc" : sortDirection === SORT_DIRECTIONS2.desc ? "sortDesc" : "sortNone";
1956
2029
  const draggable = canGridMoveColumns2(options) ? 'draggable="true"' : "";
1957
2030
  const pinLabel = isPinned ? this.snapshot?.labels.unpin ?? "Unpin" : this.snapshot?.labels.pinColumn ?? "Pin";
2031
+ const canResize = controller.canResizeColumns();
1958
2032
  const headerValue = escapeHtml(formatGridHeaderDisplayValue(buildGridHeaderContext(column)));
1959
- return `<div class="${classes}" data-column="${escapeHtml(column.name)}" ${draggable} style="${stickyStyle}"><span class="header-label">${headerValue}</span><span class="header-actions">${sortEnabled ? `<button type="button" class="header-action" data-action="sort" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(sortLabel)}" ${canSort ? "" : "disabled"}>${this.renderControlIcon(sortIconKey)}<span class="sr-only">${escapeHtml(sortLabel)}</span></button>` : ""}${groupingEnabled ? `<button type="button" class="chip-action${controller.isColumnGrouped(column) ? " chip-action-active" : ""}" data-action="group" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(groupingLabel)}" ${canGroup ? "" : "disabled"}>${this.renderControlIcon("group")}<span class="sr-only">${escapeHtml(groupingLabel)}</span></button>` : ""}${canPin ? `<div class="pin-control${this.openPinMenuColumn === column.name ? " pin-control-open" : ""}"><button type="button" class="chip-action pin-trigger${isPinned ? " chip-action-active" : ""}" data-action="pin-trigger" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(pinLabel)}">${this.renderControlIcon("pin")}<span class="sr-only">${escapeHtml(pinLabel)}</span></button><div class="pin-menu" role="menu" aria-label="${escapeHtml(pinLabel)}"><button type="button" class="pin-menu-action" data-action="pin-left" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}">${this.renderIconWithClass("control-icon", "pinLeft")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}</span></button><button type="button" class="pin-menu-action" data-action="pin-right" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}">${this.renderIconWithClass("control-icon", "pinRight")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}</span></button></div></div>` : ""}</span></div>`;
2033
+ const resizerHtml = canResize ? `<button type="button" class="column-resizer" data-action="resize" data-column="${escapeHtml(column.name)}" aria-label="Resize ${escapeHtml(headerValue)} column" title="Drag to resize, double-click to auto fit"></button>` : "";
2034
+ return `<div class="${classes}" data-column="${escapeHtml(column.name)}" ${draggable} style="${stickyStyle}"><span class="header-label">${headerValue}</span><span class="header-actions">${sortEnabled ? `<button type="button" class="header-action" data-action="sort" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(sortLabel)}" ${canSort ? "" : "disabled"}>${this.renderControlIcon(sortIconKey)}<span class="sr-only">${escapeHtml(sortLabel)}</span></button>` : ""}${groupingEnabled ? `<button type="button" class="chip-action${controller.isColumnGrouped(column) ? " chip-action-active" : ""}" data-action="group" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(groupingLabel)}" ${canGroup ? "" : "disabled"}>${this.renderControlIcon("group")}<span class="sr-only">${escapeHtml(groupingLabel)}</span></button>` : ""}${canPin ? `<div class="pin-control${this.openPinMenuColumn === column.name ? " pin-control-open" : ""}"><button type="button" class="chip-action pin-trigger${isPinned ? " chip-action-active" : ""}" data-action="pin-trigger" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(pinLabel)}">${this.renderControlIcon("pin")}<span class="sr-only">${escapeHtml(pinLabel)}</span></button><div class="pin-menu" role="menu" aria-label="${escapeHtml(pinLabel)}"><button type="button" class="pin-menu-action" data-action="pin-left" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}">${this.renderIconWithClass("control-icon", "pinLeft")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}</span></button><button type="button" class="pin-menu-action" data-action="pin-right" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}">${this.renderIconWithClass("control-icon", "pinRight")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}</span></button></div></div>` : ""}</span>${resizerHtml}</div>`;
1960
2035
  }
1961
2036
  renderFilterCell(column) {
1962
2037
  const value = this.snapshot?.activeFilters[column.name] ?? "";
@@ -1972,6 +2047,23 @@ var UiGridStandaloneElement = class extends HTMLElement {
1972
2047
  const stickyStyle = pinOffset ? `${pinOffset.side}:${pinOffset.offset};` : "";
1973
2048
  return `<label class="${classes}" style="${stickyStyle}"><input class="ui-grid-filter-input" data-role="filter" data-column="${escapeHtml(column.name)}" placeholder="${escapeHtml(controller.filterPlaceholder(column))}" value="${escapeHtml(value)}" ${canFilter ? "" : "disabled"}></label>`;
1974
2049
  }
2050
+ measureAutoColumnWidth(columnName) {
2051
+ const root = this.shadowRoot;
2052
+ if (root == null) return 176;
2053
+ const escaped = CSS.escape ? CSS.escape(columnName) : columnName.replace(/([\\".#:[\](){}+~> ])/g, "\\$1");
2054
+ const selectors = [
2055
+ `.header-cell[data-column="${escaped}"]`,
2056
+ `.filter-cell[data-column="${escaped}"]`,
2057
+ `.body-cell[data-column="${escaped}"] .cell-shell`
2058
+ ];
2059
+ let maxWidth = 0;
2060
+ for (const selector of selectors) {
2061
+ for (const el of root.querySelectorAll(selector)) {
2062
+ maxWidth = Math.max(maxWidth, el.scrollWidth);
2063
+ }
2064
+ }
2065
+ return maxWidth + 12;
2066
+ }
1975
2067
  renderDisplayItem(item, displayIndex) {
1976
2068
  if (!this.snapshot || !this.controller) {
1977
2069
  return "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornery/ui-grid-vanilla",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Vanilla DOM wrapper for the Rust/WASM-backed @ornery/ui-grid-core custom element",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -16,7 +16,7 @@
16
16
  }
17
17
  },
18
18
  "dependencies": {
19
- "@ornery/ui-grid-core": "0.1.9"
19
+ "@ornery/ui-grid-core": "0.1.10"
20
20
  },
21
21
  "devDependencies": {
22
22
  "typescript": "~5.8.0",
@@ -134,6 +134,7 @@ export class VanillaGridController {
134
134
  private expandedTreeRows: Record<string, boolean> = {};
135
135
  private pinnedColumns: PinnedColumnState = {};
136
136
  private columnOrder: string[] = [];
137
+ private columnWidthOverrides: Record<string, string> = {};
137
138
  private currentPage = 1;
138
139
  private pageSize = 0;
139
140
  private editingCell: GridCellPosition | null = null;
@@ -590,6 +591,27 @@ export class VanillaGridController {
590
591
  this.refresh();
591
592
  }
592
593
 
594
+ canResizeColumns(): boolean {
595
+ return this.options.enableColumnResizing !== false;
596
+ }
597
+
598
+ setColumnWidthOverride(columnName: string, widthPx: number): void {
599
+ const nextWidth = `${Math.max(88, Math.round(widthPx))}px`;
600
+ this.columnWidthOverrides = { ...this.columnWidthOverrides, [columnName]: nextWidth };
601
+ this.refresh();
602
+ }
603
+
604
+ /** Returns the grid-template-columns string as if `columnName` had the given width,
605
+ * without triggering a full refresh. Used for smooth drag-resize DOM updates. */
606
+ buildTemplateColumnsWithOverride(columnName: string, widthPx: number): string {
607
+ const widthStr = `${Math.max(88, Math.round(widthPx))}px`;
608
+ return buildGridTemplateColumns(
609
+ this.visibleColumns.map((c) =>
610
+ c.name === columnName ? { ...c, width: widthStr } : c,
611
+ ),
612
+ );
613
+ }
614
+
593
615
  isGroupingEnabled(): boolean {
594
616
  return isGridGroupingEnabled(this.options);
595
617
  }
@@ -743,10 +765,15 @@ export class VanillaGridController {
743
765
 
744
766
  private refresh(): void {
745
767
  const orderedColumns = orderVisibleColumns(this.options.columnDefs, this.columnOrder);
768
+ const applyWidthOverrides = (columns: GridColumnDef[]): GridColumnDef[] =>
769
+ columns.map((col) => {
770
+ const override = this.columnWidthOverrides[col.name];
771
+ return override == null ? col : { ...col, width: override };
772
+ });
746
773
  const pinnedEntries = Object.entries(this.pinnedColumns);
747
774
 
748
775
  if (pinnedEntries.length === 0) {
749
- this.visibleColumns = orderedColumns;
776
+ this.visibleColumns = applyWidthOverrides(orderedColumns);
750
777
  } else {
751
778
  const byName = new Map(orderedColumns.map((column) => [column.name, column]));
752
779
  const pinnedLeft = pinnedEntries
@@ -760,7 +787,7 @@ export class VanillaGridController {
760
787
  const middleColumns = orderedColumns.filter(
761
788
  (column) => this.pinnedColumns[column.name] === undefined,
762
789
  );
763
- this.visibleColumns = [...pinnedLeft, ...middleColumns, ...pinnedRight];
790
+ this.visibleColumns = applyWidthOverrides([...pinnedLeft, ...middleColumns, ...pinnedRight]);
764
791
  }
765
792
 
766
793
  this.pipeline = defaultGridEngine.buildPipeline({
@@ -5,8 +5,11 @@ import {
5
5
  canGridMoveColumns,
6
6
  downloadGridCsvFile,
7
7
  exportCsvRows,
8
+ findNextGridCell,
8
9
  formatGridHeaderDisplayValue,
9
10
  getCellValue,
11
+ isGridNavigationKey,
12
+ isPrintableGridKey,
10
13
  sanitizeDownloadFilename,
11
14
  type DisplayItem,
12
15
  type GridColumnDef,
@@ -283,6 +286,10 @@ export class UiGridStandaloneElement extends HTMLElement {
283
286
  if (enableColumnMoving !== undefined)
284
287
  this.attributeOptions.enableColumnMoving = enableColumnMoving;
285
288
 
289
+ const enableColumnResizing = this.parseBooleanAttribute('enable-column-resizing');
290
+ if (enableColumnResizing !== undefined)
291
+ this.attributeOptions.enableColumnResizing = enableColumnResizing;
292
+
286
293
  const enableCellEdit = this.parseBooleanAttribute('enable-cell-edit');
287
294
  if (enableCellEdit !== undefined) this.attributeOptions.enableCellEdit = enableCellEdit;
288
295
 
@@ -580,6 +587,14 @@ export class UiGridStandaloneElement extends HTMLElement {
580
587
  this.ensureController(this.buildEffectiveOptions(this.activeOptions as VanillaGridOptions));
581
588
  }
582
589
 
590
+ get enableColumnResizing(): boolean {
591
+ return (this.options as any).enableColumnResizing ?? true;
592
+ }
593
+ set enableColumnResizing(value: boolean) {
594
+ this.activeOptions = { ...this.activeOptions, enableColumnResizing: value } as any;
595
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions as VanillaGridOptions));
596
+ }
597
+
583
598
  get enableCellEdit(): boolean {
584
599
  return this.options.enableCellEdit ?? false;
585
600
  }
@@ -1084,12 +1099,65 @@ export class UiGridStandaloneElement extends HTMLElement {
1084
1099
  }
1085
1100
  });
1086
1101
 
1102
+ root.addEventListener('mousedown', (event) => {
1103
+ const target = event.target as HTMLElement | null;
1104
+ if (!target || !this.controller) return;
1105
+
1106
+ const resizer = target.closest<HTMLElement>('.column-resizer[data-column]');
1107
+ if (!resizer || !this.controller.canResizeColumns()) return;
1108
+
1109
+ const columnName = resizer.dataset['column'];
1110
+ if (!columnName) return;
1111
+
1112
+ const headerCell = resizer.closest<HTMLElement>('.header-cell');
1113
+ if (!headerCell) return;
1114
+
1115
+ event.preventDefault();
1116
+ event.stopPropagation();
1117
+
1118
+ const startX = (event as MouseEvent).clientX;
1119
+ const startWidth = headerCell.getBoundingClientRect().width;
1120
+ let lastWidth = startWidth;
1121
+
1122
+ const handleMove = (moveEvent: MouseEvent): void => {
1123
+ lastWidth = Math.max(88, startWidth + (moveEvent.clientX - startX));
1124
+
1125
+ // Write directly to DOM — skip full refresh while dragging.
1126
+ const newTemplate = this.controller!.buildTemplateColumnsWithOverride(columnName, lastWidth);
1127
+ const root = this.shadowRoot ?? this;
1128
+ (root as ShadowRoot | HTMLElement).querySelectorAll<HTMLElement>('.header-grid, .filter-grid, .body-grid').forEach((el) => {
1129
+ el.style.gridTemplateColumns = newTemplate;
1130
+ });
1131
+ };
1132
+
1133
+ const handleUp = (): void => {
1134
+ window.removeEventListener('mousemove', handleMove);
1135
+ window.removeEventListener('mouseup', handleUp);
1136
+ // Commit final width once — triggers one full refresh.
1137
+ this.controller!.setColumnWidthOverride(columnName, lastWidth);
1138
+ };
1139
+
1140
+ window.addEventListener('mousemove', handleMove);
1141
+ window.addEventListener('mouseup', handleUp);
1142
+ });
1143
+
1087
1144
  root.addEventListener('dblclick', (event) => {
1088
1145
  const target = event.target as HTMLElement | null;
1089
1146
  if (!target || !this.controller) {
1090
1147
  return;
1091
1148
  }
1092
1149
 
1150
+ const resizer = target.closest<HTMLElement>('.column-resizer[data-column]');
1151
+ if (resizer && this.controller.canResizeColumns()) {
1152
+ const columnName = resizer.dataset['column'];
1153
+ if (columnName) {
1154
+ event.preventDefault();
1155
+ event.stopPropagation();
1156
+ this.controller.setColumnWidthOverride(columnName, this.measureAutoColumnWidth(columnName));
1157
+ return;
1158
+ }
1159
+ }
1160
+
1093
1161
  const cell = target.closest<HTMLElement>('.body-cell');
1094
1162
  if (!cell) {
1095
1163
  return;
@@ -1520,9 +1588,13 @@ export class UiGridStandaloneElement extends HTMLElement {
1520
1588
  const pinLabel = isPinned
1521
1589
  ? (this.snapshot?.labels.unpin ?? 'Unpin')
1522
1590
  : (this.snapshot?.labels.pinColumn ?? 'Pin');
1591
+ const canResize = controller.canResizeColumns();
1523
1592
  const headerValue = escapeHtml(formatGridHeaderDisplayValue(buildGridHeaderContext(column)));
1593
+ const resizerHtml = canResize
1594
+ ? `<button type="button" class="column-resizer" data-action="resize" data-column="${escapeHtml(column.name)}" aria-label="Resize ${escapeHtml(headerValue)} column" title="Drag to resize, double-click to auto fit"></button>`
1595
+ : '';
1524
1596
 
1525
- return `<div class="${classes}" data-column="${escapeHtml(column.name)}" ${draggable} style="${stickyStyle}"><span class="header-label">${headerValue}</span><span class="header-actions">${sortEnabled ? `<button type="button" class="header-action" data-action="sort" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(sortLabel)}" ${canSort ? '' : 'disabled'}>${this.renderControlIcon(sortIconKey)}<span class="sr-only">${escapeHtml(sortLabel)}</span></button>` : ''}${groupingEnabled ? `<button type="button" class="chip-action${controller.isColumnGrouped(column) ? ' chip-action-active' : ''}" data-action="group" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(groupingLabel)}" ${canGroup ? '' : 'disabled'}>${this.renderControlIcon('group')}<span class="sr-only">${escapeHtml(groupingLabel)}</span></button>` : ''}${canPin ? `<div class="pin-control${this.openPinMenuColumn === column.name ? ' pin-control-open' : ''}"><button type="button" class="chip-action pin-trigger${isPinned ? ' chip-action-active' : ''}" data-action="pin-trigger" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(pinLabel)}">${this.renderControlIcon('pin')}<span class="sr-only">${escapeHtml(pinLabel)}</span></button><div class="pin-menu" role="menu" aria-label="${escapeHtml(pinLabel)}"><button type="button" class="pin-menu-action" data-action="pin-left" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinLeft ?? 'Pin left')}">${this.renderIconWithClass('control-icon', 'pinLeft')}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinLeft ?? 'Pin left')}</span></button><button type="button" class="pin-menu-action" data-action="pin-right" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinRight ?? 'Pin right')}">${this.renderIconWithClass('control-icon', 'pinRight')}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinRight ?? 'Pin right')}</span></button></div></div>` : ''}</span></div>`;
1597
+ return `<div class="${classes}" data-column="${escapeHtml(column.name)}" ${draggable} style="${stickyStyle}"><span class="header-label">${headerValue}</span><span class="header-actions">${sortEnabled ? `<button type="button" class="header-action" data-action="sort" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(sortLabel)}" ${canSort ? '' : 'disabled'}>${this.renderControlIcon(sortIconKey)}<span class="sr-only">${escapeHtml(sortLabel)}</span></button>` : ''}${groupingEnabled ? `<button type="button" class="chip-action${controller.isColumnGrouped(column) ? ' chip-action-active' : ''}" data-action="group" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(groupingLabel)}" ${canGroup ? '' : 'disabled'}>${this.renderControlIcon('group')}<span class="sr-only">${escapeHtml(groupingLabel)}</span></button>` : ''}${canPin ? `<div class="pin-control${this.openPinMenuColumn === column.name ? ' pin-control-open' : ''}"><button type="button" class="chip-action pin-trigger${isPinned ? ' chip-action-active' : ''}" data-action="pin-trigger" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(pinLabel)}">${this.renderControlIcon('pin')}<span class="sr-only">${escapeHtml(pinLabel)}</span></button><div class="pin-menu" role="menu" aria-label="${escapeHtml(pinLabel)}"><button type="button" class="pin-menu-action" data-action="pin-left" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinLeft ?? 'Pin left')}">${this.renderIconWithClass('control-icon', 'pinLeft')}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinLeft ?? 'Pin left')}</span></button><button type="button" class="pin-menu-action" data-action="pin-right" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinRight ?? 'Pin right')}">${this.renderIconWithClass('control-icon', 'pinRight')}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinRight ?? 'Pin right')}</span></button></div></div>` : ''}</span>${resizerHtml}</div>`;
1526
1598
  }
1527
1599
 
1528
1600
  private renderFilterCell(column: GridColumnDef): string {
@@ -1542,6 +1614,24 @@ export class UiGridStandaloneElement extends HTMLElement {
1542
1614
  return `<label class="${classes}" style="${stickyStyle}"><input class="ui-grid-filter-input" data-role="filter" data-column="${escapeHtml(column.name)}" placeholder="${escapeHtml(controller.filterPlaceholder(column))}" value="${escapeHtml(value)}" ${canFilter ? '' : 'disabled'}></label>`;
1543
1615
  }
1544
1616
 
1617
+ private measureAutoColumnWidth(columnName: string): number {
1618
+ const root = this.shadowRoot;
1619
+ if (root == null) return 176;
1620
+ const escaped = CSS.escape ? CSS.escape(columnName) : columnName.replace(/([\\".#:[\](){}+~> ])/g, '\\$1');
1621
+ const selectors = [
1622
+ `.header-cell[data-column="${escaped}"]`,
1623
+ `.filter-cell[data-column="${escaped}"]`,
1624
+ `.body-cell[data-column="${escaped}"] .cell-shell`,
1625
+ ];
1626
+ let maxWidth = 0;
1627
+ for (const selector of selectors) {
1628
+ for (const el of root.querySelectorAll<HTMLElement>(selector)) {
1629
+ maxWidth = Math.max(maxWidth, el.scrollWidth);
1630
+ }
1631
+ }
1632
+ return maxWidth + 12;
1633
+ }
1634
+
1545
1635
  private renderDisplayItem(item: DisplayItem, displayIndex: number): string {
1546
1636
  if (!this.snapshot || !this.controller) {
1547
1637
  return '';