@bendyline/squisq-grid-react 2.11.20 → 2.11.21
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/dist/index.js +87 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -844,6 +844,8 @@ function DataGrid({
|
|
|
844
844
|
const rootRef = useRef(null);
|
|
845
845
|
const opMenuRef = useRef(null);
|
|
846
846
|
const valueMenuRef = useRef(null);
|
|
847
|
+
const menuTriggerRef = useRef(null);
|
|
848
|
+
const pendingCellFocus = useRef(false);
|
|
847
849
|
const focusRef = useRef(null);
|
|
848
850
|
const wasEditingRef = useRef(false);
|
|
849
851
|
useEffect(() => {
|
|
@@ -895,6 +897,7 @@ function DataGrid({
|
|
|
895
897
|
await restoreRef.current;
|
|
896
898
|
const result = await provider.setView(next);
|
|
897
899
|
cache.current = { version: cache.current.version + 1, pages: /* @__PURE__ */ new Map() };
|
|
900
|
+
selectionCells.current.clear();
|
|
898
901
|
inFlight.current.clear();
|
|
899
902
|
setViewRowCount(result.viewRowCount);
|
|
900
903
|
setStaleView(false);
|
|
@@ -951,7 +954,9 @@ function DataGrid({
|
|
|
951
954
|
if (rowId === void 0 || !cells) return null;
|
|
952
955
|
return { rowId, cells };
|
|
953
956
|
}, []);
|
|
957
|
+
const viewVersion = cache.current.version;
|
|
954
958
|
useEffect(() => {
|
|
959
|
+
selectionCells.current.clear();
|
|
955
960
|
if (!selection || !schema) return;
|
|
956
961
|
const { r0, r1 } = normalizedRange(selection);
|
|
957
962
|
if ((r1 - r0 + 1) * schema.columns.length > COPY_CELL_CAP) return;
|
|
@@ -964,11 +969,13 @@ function DataGrid({
|
|
|
964
969
|
page.cells.forEach((cells, index) => rows.set(page.start + index, cells));
|
|
965
970
|
}
|
|
966
971
|
selectionCells.current = rows;
|
|
967
|
-
})()
|
|
972
|
+
})().catch(() => {
|
|
973
|
+
if (!cancelled) selectionCells.current.clear();
|
|
974
|
+
});
|
|
968
975
|
return () => {
|
|
969
976
|
cancelled = true;
|
|
970
977
|
};
|
|
971
|
-
}, [provider, schema, selection]);
|
|
978
|
+
}, [provider, schema, selection, viewVersion, dirtyTick]);
|
|
972
979
|
const handleCopy = useCallback(
|
|
973
980
|
(event) => {
|
|
974
981
|
if (!selection || !schema || editing) return;
|
|
@@ -981,7 +988,7 @@ function DataGrid({
|
|
|
981
988
|
const lines = [];
|
|
982
989
|
const htmlRows = [];
|
|
983
990
|
for (let row = r0; row <= lastRow; row++) {
|
|
984
|
-
const cells =
|
|
991
|
+
const cells = cellAt(row)?.cells ?? selectionCells.current.get(row);
|
|
985
992
|
const values = [];
|
|
986
993
|
for (let col = c0; col <= c1; col++) values.push(cellDisplay(cells?.[col] ?? null));
|
|
987
994
|
lines.push(values.join(" "));
|
|
@@ -1265,6 +1272,7 @@ function DataGrid({
|
|
|
1265
1272
|
(col, columnName, next) => {
|
|
1266
1273
|
setFilterOps((prev) => ({ ...prev, [col]: next }));
|
|
1267
1274
|
setOpMenuCol(null);
|
|
1275
|
+
menuTriggerRef.current?.focus();
|
|
1268
1276
|
const clause = viewRef.current.filter.find((entry) => entry.column === columnName);
|
|
1269
1277
|
if (next.unary) {
|
|
1270
1278
|
setColumnFilter(columnName, "", next.op, false, true);
|
|
@@ -1285,6 +1293,7 @@ function DataGrid({
|
|
|
1285
1293
|
}));
|
|
1286
1294
|
setOpMenuCol(null);
|
|
1287
1295
|
setColumnFilter(columnName, "", prior.op, prior.caseSensitive);
|
|
1296
|
+
menuTriggerRef.current?.focus();
|
|
1288
1297
|
},
|
|
1289
1298
|
[opStateFor, schema, setColumnFilter]
|
|
1290
1299
|
);
|
|
@@ -1296,6 +1305,7 @@ function DataGrid({
|
|
|
1296
1305
|
}, []);
|
|
1297
1306
|
const toggleOpMenu = useCallback(
|
|
1298
1307
|
(col, anchor) => {
|
|
1308
|
+
menuTriggerRef.current = anchor;
|
|
1299
1309
|
setValueMenuCol(null);
|
|
1300
1310
|
setOpMenuPos(menuAnchorPos(anchor));
|
|
1301
1311
|
setOpMenuCol((open) => open === col ? null : col);
|
|
@@ -1304,6 +1314,7 @@ function DataGrid({
|
|
|
1304
1314
|
);
|
|
1305
1315
|
const openValueMenu = useCallback(
|
|
1306
1316
|
(col, anchor) => {
|
|
1317
|
+
menuTriggerRef.current = anchor;
|
|
1307
1318
|
setOpMenuCol(null);
|
|
1308
1319
|
if (valueMenuCol === col) {
|
|
1309
1320
|
setValueMenuCol(null);
|
|
@@ -1328,17 +1339,38 @@ function DataGrid({
|
|
|
1328
1339
|
menu.style.top = `${Math.max(4, Math.min(parseFloat(menu.style.top) || 0, maxTop))}px`;
|
|
1329
1340
|
}
|
|
1330
1341
|
}, [opMenuCol, valueMenuCol, valueMenuData]);
|
|
1342
|
+
useLayoutEffect(() => {
|
|
1343
|
+
const menu = opMenuRef.current ?? valueMenuRef.current;
|
|
1344
|
+
if (!menu || menu.contains(document.activeElement)) return;
|
|
1345
|
+
(menu.querySelector('[aria-checked="true"]') ?? menu.querySelector('[role^="menuitem"]'))?.focus();
|
|
1346
|
+
}, [opMenuCol, valueMenuCol, valueMenuData]);
|
|
1347
|
+
const handleMenuKeyDown = (event) => {
|
|
1348
|
+
if (event.key === "Tab") {
|
|
1349
|
+
setOpMenuCol(null);
|
|
1350
|
+
setValueMenuCol(null);
|
|
1351
|
+
menuTriggerRef.current?.focus();
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
1355
|
+
event.preventDefault();
|
|
1356
|
+
const items = [...event.currentTarget.querySelectorAll('[role^="menuitem"]')];
|
|
1357
|
+
const current = items.indexOf(document.activeElement);
|
|
1358
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : event.key === "ArrowUp" ? (current - 1 + items.length) % items.length : (current + 1) % items.length;
|
|
1359
|
+
items[next]?.focus();
|
|
1360
|
+
};
|
|
1331
1361
|
const chooseFilterValue = useCallback(
|
|
1332
1362
|
(col, columnName, value) => {
|
|
1333
1363
|
setFilterOps((prev) => ({ ...prev, [col]: { op: "=", caseSensitive: false } }));
|
|
1334
1364
|
setValueMenuCol(null);
|
|
1335
1365
|
setColumnFilter(columnName, value, "=", false);
|
|
1366
|
+
menuTriggerRef.current?.focus();
|
|
1336
1367
|
},
|
|
1337
1368
|
[setColumnFilter]
|
|
1338
1369
|
);
|
|
1339
1370
|
const moveFocus = useCallback(
|
|
1340
1371
|
(rowDelta, colDelta, extend, absolute) => {
|
|
1341
|
-
if (!schema) return;
|
|
1372
|
+
if (!schema || viewRowCount === 0 || schema.columns.length === 0) return;
|
|
1373
|
+
pendingCellFocus.current = true;
|
|
1342
1374
|
setSelection((prev) => {
|
|
1343
1375
|
const from = prev?.focus ?? { row: 0, col: 0 };
|
|
1344
1376
|
const next = {
|
|
@@ -1354,10 +1386,16 @@ function DataGrid({
|
|
|
1354
1386
|
},
|
|
1355
1387
|
[schema, viewRowCount, virtualizer]
|
|
1356
1388
|
);
|
|
1389
|
+
useLayoutEffect(() => {
|
|
1390
|
+
if (!pendingCellFocus.current || editing || !focusRef.current) return;
|
|
1391
|
+
focusRef.current.focus();
|
|
1392
|
+
pendingCellFocus.current = false;
|
|
1393
|
+
});
|
|
1357
1394
|
const handleKeyDown = useCallback(
|
|
1358
1395
|
(event) => {
|
|
1359
|
-
if (!schema) return;
|
|
1396
|
+
if (!schema || event.defaultPrevented) return;
|
|
1360
1397
|
const target = event.target;
|
|
1398
|
+
if (!target?.closest('[role="gridcell"]')) return;
|
|
1361
1399
|
const inTextEntry = target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target !== null && target.isContentEditable;
|
|
1362
1400
|
if (inTextEntry && !editing) return;
|
|
1363
1401
|
const meta = event.metaKey || event.ctrlKey;
|
|
@@ -1374,7 +1412,7 @@ function DataGrid({
|
|
|
1374
1412
|
}
|
|
1375
1413
|
return;
|
|
1376
1414
|
}
|
|
1377
|
-
const pageRows = Math.max(1, Math.floor(height /
|
|
1415
|
+
const pageRows = Math.max(1, Math.floor(height / rowHeight) - 1);
|
|
1378
1416
|
switch (event.key) {
|
|
1379
1417
|
case "ArrowDown":
|
|
1380
1418
|
moveFocus(
|
|
@@ -1454,6 +1492,7 @@ function DataGrid({
|
|
|
1454
1492
|
commitEdit,
|
|
1455
1493
|
editing,
|
|
1456
1494
|
height,
|
|
1495
|
+
rowHeight,
|
|
1457
1496
|
moveFocus,
|
|
1458
1497
|
runJournal,
|
|
1459
1498
|
schema,
|
|
@@ -1483,16 +1522,13 @@ function DataGrid({
|
|
|
1483
1522
|
).join(" ");
|
|
1484
1523
|
}, [colWidths, defaultColumnWidths, schema]);
|
|
1485
1524
|
const range = selection ? normalizedRange(selection) : null;
|
|
1525
|
+
const focusRowRendered = selection && virtualItems.some((item) => item.index === selection.focus.row);
|
|
1486
1526
|
const filterValueFor = (name) => view.filter.find((clause) => clause.column === name)?.value ?? "";
|
|
1487
1527
|
return /* @__PURE__ */ jsxs(
|
|
1488
1528
|
"div",
|
|
1489
1529
|
{
|
|
1490
1530
|
ref: rootRef,
|
|
1491
1531
|
className: `squisq-grid${className ? ` ${className}` : ""}`,
|
|
1492
|
-
role: "grid",
|
|
1493
|
-
"aria-rowcount": viewRowCount + 1,
|
|
1494
|
-
"aria-colcount": schema?.columns.length ?? 0,
|
|
1495
|
-
"aria-multiselectable": "true",
|
|
1496
1532
|
onKeyDown: handleKeyDown,
|
|
1497
1533
|
onCopy: handleCopy,
|
|
1498
1534
|
onPaste: handlePaste,
|
|
@@ -1506,15 +1542,22 @@ function DataGrid({
|
|
|
1506
1542
|
}
|
|
1507
1543
|
},
|
|
1508
1544
|
onKeyDownCapture: (event) => {
|
|
1509
|
-
if (event.key !== "Escape") return;
|
|
1545
|
+
if (event.key !== "Escape" || opMenuCol === null && valueMenuCol === null) return;
|
|
1546
|
+
event.preventDefault();
|
|
1547
|
+
event.stopPropagation();
|
|
1510
1548
|
if (opMenuCol !== null) setOpMenuCol(null);
|
|
1511
1549
|
if (valueMenuCol !== null) setValueMenuCol(null);
|
|
1550
|
+
menuTriggerRef.current?.focus();
|
|
1512
1551
|
},
|
|
1513
1552
|
children: [
|
|
1514
1553
|
/* @__PURE__ */ jsxs(
|
|
1515
1554
|
"div",
|
|
1516
1555
|
{
|
|
1517
1556
|
className: "squisq-grid-scroller",
|
|
1557
|
+
role: "grid",
|
|
1558
|
+
"aria-rowcount": viewRowCount + 1,
|
|
1559
|
+
"aria-colcount": schema?.columns.length ?? 0,
|
|
1560
|
+
"aria-multiselectable": "true",
|
|
1518
1561
|
ref: bodyRef,
|
|
1519
1562
|
style: { height },
|
|
1520
1563
|
onScroll: () => {
|
|
@@ -1561,6 +1604,7 @@ function DataGrid({
|
|
|
1561
1604
|
className: `squisq-grid-opbutton${opState.caseSensitive ? " squisq-grid-opbutton--cs" : ""}`,
|
|
1562
1605
|
"aria-label": `Filter operator for ${column.name}`,
|
|
1563
1606
|
"aria-expanded": opMenuCol === col,
|
|
1607
|
+
"aria-haspopup": "menu",
|
|
1564
1608
|
title: `${opChoicesFor(column.kind).find((c) => choiceMatches(c, opState))?.label ?? opState.op}${opState.caseSensitive ? " (case-sensitive)" : ""}`,
|
|
1565
1609
|
onClick: (event) => toggleOpMenu(col, event.currentTarget),
|
|
1566
1610
|
children: [
|
|
@@ -1592,6 +1636,7 @@ function DataGrid({
|
|
|
1592
1636
|
className: "squisq-grid-valuebutton",
|
|
1593
1637
|
"aria-label": `Filter ${column.name} by value`,
|
|
1594
1638
|
"aria-expanded": valueMenuCol === col,
|
|
1639
|
+
"aria-haspopup": "menu",
|
|
1595
1640
|
title: "Filter by value",
|
|
1596
1641
|
onClick: (event) => openValueMenu(col, event.currentTarget),
|
|
1597
1642
|
children: "\u25BE"
|
|
@@ -1659,8 +1704,14 @@ function DataGrid({
|
|
|
1659
1704
|
...locked && lockedReason ? { title: lockedReason } : formula !== void 0 ? { title: `=${formula}` } : {},
|
|
1660
1705
|
...locked ? { "aria-readonly": "true" } : {},
|
|
1661
1706
|
className: classes,
|
|
1662
|
-
tabIndex: isFocus ? 0 : -1,
|
|
1707
|
+
tabIndex: isFocus || !focusRowRendered && item.index === virtualItems[0]?.index && col === 0 ? 0 : -1,
|
|
1663
1708
|
ref: isFocus ? focusRef : void 0,
|
|
1709
|
+
onFocus: () => {
|
|
1710
|
+
const pos = { row: item.index, col };
|
|
1711
|
+
setSelection(
|
|
1712
|
+
(prev) => prev?.focus.row === pos.row && prev.focus.col === pos.col ? prev : { anchor: pos, focus: pos }
|
|
1713
|
+
);
|
|
1714
|
+
},
|
|
1664
1715
|
onMouseDown: (event) => {
|
|
1665
1716
|
const pos = { row: item.index, col };
|
|
1666
1717
|
setSelection(
|
|
@@ -1673,6 +1724,8 @@ function DataGrid({
|
|
|
1673
1724
|
{
|
|
1674
1725
|
className: `squisq-grid-editor${editing.error ? " squisq-grid-editor--error" : ""}`,
|
|
1675
1726
|
autoFocus: true,
|
|
1727
|
+
"aria-label": `Edit ${column.name}, row ${item.index + 1}`,
|
|
1728
|
+
"aria-invalid": editing.error ? true : void 0,
|
|
1676
1729
|
value: editing.draft,
|
|
1677
1730
|
title: editing.error,
|
|
1678
1731
|
onChange: (event) => setEditing({ ...editing, draft: event.target.value, error: void 0 }),
|
|
@@ -1702,6 +1755,8 @@ function DataGrid({
|
|
|
1702
1755
|
ref: opMenuRef,
|
|
1703
1756
|
className: "squisq-grid-opmenu",
|
|
1704
1757
|
role: "menu",
|
|
1758
|
+
"aria-label": `Filter operator for ${column.name}`,
|
|
1759
|
+
onKeyDown: handleMenuKeyDown,
|
|
1705
1760
|
style: { left: opMenuPos.left, top: opMenuPos.top },
|
|
1706
1761
|
children: [
|
|
1707
1762
|
filterActive && /* @__PURE__ */ jsx(
|
|
@@ -1733,21 +1788,24 @@ function DataGrid({
|
|
|
1733
1788
|
},
|
|
1734
1789
|
`${choice.op}${choice.unary ? "0" : ""}`
|
|
1735
1790
|
)),
|
|
1736
|
-
caseCapable && /* @__PURE__ */ jsxs(
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1791
|
+
caseCapable && /* @__PURE__ */ jsxs(
|
|
1792
|
+
"button",
|
|
1793
|
+
{
|
|
1794
|
+
type: "button",
|
|
1795
|
+
role: "menuitemcheckbox",
|
|
1796
|
+
"aria-checked": opState.caseSensitive,
|
|
1797
|
+
className: "squisq-grid-opoption squisq-grid-opcase",
|
|
1798
|
+
onClick: () => chooseFilterOp(col, column.name, {
|
|
1799
|
+
op: opState.op,
|
|
1800
|
+
caseSensitive: !opState.caseSensitive,
|
|
1801
|
+
...opState.unary ? { unary: true } : {}
|
|
1802
|
+
}),
|
|
1803
|
+
children: [
|
|
1804
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: opState.caseSensitive ? "\u2713" : "\u25A1" }),
|
|
1805
|
+
"Case sensitive"
|
|
1806
|
+
]
|
|
1807
|
+
}
|
|
1808
|
+
)
|
|
1751
1809
|
]
|
|
1752
1810
|
}
|
|
1753
1811
|
);
|
|
@@ -1763,6 +1821,8 @@ function DataGrid({
|
|
|
1763
1821
|
ref: valueMenuRef,
|
|
1764
1822
|
className: "squisq-grid-valuemenu",
|
|
1765
1823
|
role: "menu",
|
|
1824
|
+
"aria-label": `Filter ${column.name} by value`,
|
|
1825
|
+
onKeyDown: handleMenuKeyDown,
|
|
1766
1826
|
style: { left: valueMenuPos.left, top: valueMenuPos.top },
|
|
1767
1827
|
children: valueMenuData?.col === col ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1768
1828
|
filterActive && /* @__PURE__ */ jsx(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-grid-react",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.21",
|
|
4
4
|
"description": "Virtualized data grid for squisq data sidecars: columnar worker store, TableQueryProvider, and a TanStack-Virtual React renderer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|