@svgrid/grid 1.2.3 → 1.2.4

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.
@@ -109,6 +109,36 @@ function getMaxDomScrollHeight() {
109
109
  }
110
110
  return detectedMaxDomHeight;
111
111
  }
112
+ /**
113
+ * Observe an element's size, but run the callback on the next animation frame
114
+ * and coalesce bursts into a single call. This is what keeps the benign but
115
+ * noisy "ResizeObserver loop completed with undelivered notifications" warning
116
+ * out of the console: the browser emits it when an observer callback
117
+ * synchronously mutates layout in a way that would require another notification
118
+ * within the same delivery cycle - which our callbacks do (they bump reactive
119
+ * versions / remeasure, driving a re-layout of the observed element). Deferring
120
+ * the work to the next frame lets the current delivery finish cleanly, so the
121
+ * loop never spans a single cycle. This is especially visible when swapping the
122
+ * whole grid (e.g. switching demos), which remounts everything at once.
123
+ * Returns a disconnect function suitable for an $effect cleanup.
124
+ */
125
+ function observeSizeRaf(el, cb) {
126
+ let frame = 0;
127
+ const observer = new ResizeObserver(() => {
128
+ if (frame)
129
+ return;
130
+ frame = requestAnimationFrame(() => {
131
+ frame = 0;
132
+ cb();
133
+ });
134
+ });
135
+ observer.observe(el);
136
+ return () => {
137
+ if (frame)
138
+ cancelAnimationFrame(frame);
139
+ observer.disconnect();
140
+ };
141
+ }
112
142
  export function createSvGridController(props) {
113
143
  // Resolved capability gates. Capabilities are OFF by default - a bare
114
144
  // grid is a plain read-only table, and each power feature is opted into
@@ -770,6 +800,17 @@ export function createSvGridController(props) {
770
800
  const start = pageIndex * pageSize;
771
801
  return rows.slice(start, start + pageSize);
772
802
  });
803
+ // When a filter reduces the dataset, the stored pageIndex can point beyond
804
+ // the last valid page. Reset to page 0 so the grid never shows a blank body.
805
+ $effect(() => {
806
+ if (!paginationEnabled)
807
+ return;
808
+ const { pageIndex, pageSize } = paginationState;
809
+ const pageCount = Math.ceil(allRowsBeforePagination.length / pageSize);
810
+ if (pageCount > 0 && pageIndex >= pageCount) {
811
+ grid.setPagination({ pageIndex: 0, pageSize });
812
+ }
813
+ });
773
814
  const rowSelectionState = $derived.by(() => {
774
815
  gridStateVersion;
775
816
  return grid.getState().rowSelection ?? {};
@@ -1045,15 +1086,46 @@ export function createSvGridController(props) {
1045
1086
  return columnVirtualizer.getTotalSize();
1046
1087
  });
1047
1088
  const renderedColumnItems = $derived.by(() => {
1048
- if (columnVirtualizationEnabled)
1049
- return virtualColumns;
1050
- let start = 0;
1051
- return allColumns.map((column, index) => {
1052
- const size = getColumnWidth(column.id);
1053
- const item = { index, key: index, size, start, end: start + size };
1054
- start += size;
1055
- return item;
1056
- });
1089
+ if (!columnVirtualizationEnabled) {
1090
+ let start = 0;
1091
+ return allColumns.map((column, index) => {
1092
+ const size = getColumnWidth(column.id);
1093
+ const item = { index, key: index, size, start, end: start + size };
1094
+ start += size;
1095
+ return item;
1096
+ });
1097
+ }
1098
+ // Pinned columns are position:sticky, so they only stay pinned while their
1099
+ // cell is in the DOM. Plain column virtualization drops them once they leave
1100
+ // the scroll window, and the pinned column vanishes. Because allColumns is
1101
+ // ordered [pinnedLeft, unpinned, pinnedRight], we keep the rendered window
1102
+ // CONTIGUOUS from the pinned-left prefix (index 0) through the pinned-right
1103
+ // suffix (last index) whenever those exist. The pinned cells are then always
1104
+ // rendered - the existing single-spacer layout positions everything, so no
1105
+ // markup changes are needed. (Cost: with a pinned side, the columns between
1106
+ // that edge and the window are also rendered; negligible for typical grids,
1107
+ // and correctness beats shaving a few off-screen cells.)
1108
+ const window = virtualColumns;
1109
+ const hasLeft = columnPinning.left.length > 0;
1110
+ const hasRight = columnPinning.right.length > 0;
1111
+ if ((!hasLeft && !hasRight) || window.length === 0)
1112
+ return window;
1113
+ const firstIdx = window[0].index;
1114
+ const lastIdx = window[window.length - 1].index;
1115
+ const startIndex = hasLeft ? 0 : firstIdx;
1116
+ const endIndex = hasRight ? allColumns.length - 1 : lastIdx;
1117
+ if (startIndex === firstIdx && endIndex === lastIdx)
1118
+ return window;
1119
+ const items = [];
1120
+ let offset = 0;
1121
+ for (let i = 0; i < startIndex; i += 1)
1122
+ offset += getColumnWidth(allColumns[i].id);
1123
+ for (let i = startIndex; i <= endIndex; i += 1) {
1124
+ const size = getColumnWidth(allColumns[i].id);
1125
+ items.push({ index: i, key: i, size, start: offset, end: offset + size });
1126
+ offset += size;
1127
+ }
1128
+ return items;
1057
1129
  });
1058
1130
  const renderedColumns = $derived.by(() => renderedColumnItems
1059
1131
  .map((item) => ({ item, column: allColumns[item.index] }))
@@ -1137,11 +1209,9 @@ export function createSvGridController(props) {
1137
1209
  if (!theadEl)
1138
1210
  return;
1139
1211
  headerHeight = theadEl.offsetHeight;
1140
- const observer = new ResizeObserver(() => {
1212
+ return observeSizeRaf(theadEl, () => {
1141
1213
  headerHeight = theadEl?.offsetHeight ?? 0;
1142
1214
  });
1143
- observer.observe(theadEl);
1144
- return () => observer.disconnect();
1145
1215
  });
1146
1216
  // Bump scrollVersion when the table's layout size changes so scrollbar
1147
1217
  // visibility (and the thumb math that depends on scroll metrics) updates
@@ -1149,11 +1219,9 @@ export function createSvGridController(props) {
1149
1219
  $effect(() => {
1150
1220
  if (!gridRootEl)
1151
1221
  return;
1152
- const observer = new ResizeObserver(() => {
1222
+ return observeSizeRaf(gridRootEl, () => {
1153
1223
  scrollVersion += 1;
1154
1224
  });
1155
- observer.observe(gridRootEl);
1156
- return () => observer.disconnect();
1157
1225
  });
1158
1226
  $effect(() => {
1159
1227
  if (!allRows.length || !allColumns.length)
@@ -1270,13 +1338,11 @@ export function createSvGridController(props) {
1270
1338
  $effect(() => {
1271
1339
  if (!scrollContainer)
1272
1340
  return;
1273
- const observer = new ResizeObserver(() => {
1341
+ return observeSizeRaf(scrollContainer, () => {
1274
1342
  viewportVersion += 1;
1275
1343
  if (!hasMeasured)
1276
1344
  hasMeasured = true;
1277
1345
  });
1278
- observer.observe(scrollContainer);
1279
- return () => observer.disconnect();
1280
1346
  });
1281
1347
  $effect(() => {
1282
1348
  // Reading columnWidths here registers it as a reactive dependency so
package/dist/a11y.d.ts CHANGED
@@ -29,7 +29,7 @@ export declare function getGridRowA11yProps(rowIndex?: number): {
29
29
  readonly role: "row";
30
30
  };
31
31
  export declare function getGridCellA11yProps(input?: GridCellA11yInput): {
32
- readonly 'aria-selected'?: boolean | undefined;
32
+ readonly 'aria-selected': boolean;
33
33
  readonly 'aria-rowindex'?: number | undefined;
34
34
  readonly 'aria-colindex'?: number | undefined;
35
35
  readonly id?: string | undefined;
package/dist/a11y.js CHANGED
@@ -29,7 +29,7 @@ export function getGridCellA11yProps(input = {}) {
29
29
  ...(id ? { id } : {}),
30
30
  ...(colIndex !== undefined ? { 'aria-colindex': colIndex } : {}),
31
31
  ...(rowIndex !== undefined ? { 'aria-rowindex': rowIndex } : {}),
32
- ...(selected ? { 'aria-selected': true } : {}),
32
+ 'aria-selected': selected,
33
33
  };
34
34
  }
35
35
  export function getGridCellDomId(baseId, rowIndex, colIndex) {