twico-ui 1.24.0 → 1.26.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to Twico UI are documented here (newest first). This file is generated automatically on each release by [semantic-release](https://semantic-release.gitbook.io/) from [Conventional Commits](https://www.conventionalcommits.org/) — do not edit it by hand. The same notes appear on each [GitHub Release](https://github.com/EncryptionL/twico-ui/releases).
4
4
 
5
+ # [1.26.0](https://github.com/EncryptionL/twico-ui/compare/v1.25.0...v1.26.0) (2026-08-03)
6
+
7
+
8
+ ### Features
9
+
10
+ * **datatable:** bottom-left popover grip, cell range selection, keyboard clipboard ([#316](https://github.com/EncryptionL/twico-ui/issues/316), [#317](https://github.com/EncryptionL/twico-ui/issues/317), [#318](https://github.com/EncryptionL/twico-ui/issues/318)) ([0c1a583](https://github.com/EncryptionL/twico-ui/commit/0c1a583ef8b983410547f7f1fb92f8660ec926c3))
11
+
12
+ # [1.25.0](https://github.com/EncryptionL/twico-ui/compare/v1.24.0...v1.25.0) (2026-08-03)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **datatable:** commit the default cell editor value on click-away instead of discarding ([#313](https://github.com/EncryptionL/twico-ui/issues/313)) ([08c0050](https://github.com/EncryptionL/twico-ui/commit/08c00501fc20a4deb5e0bf35be0ab9aa46a7c477))
18
+
19
+
20
+ ### Features
21
+
22
+ * **datatable:** default resizablePopovers to true; decouple it from resizableFilters ([#314](https://github.com/EncryptionL/twico-ui/issues/314)) ([c509956](https://github.com/EncryptionL/twico-ui/commit/c509956b11648e6d138777843514ff9a5ec09e85)), closes [#304](https://github.com/EncryptionL/twico-ui/issues/304) [#292](https://github.com/EncryptionL/twico-ui/issues/292)
23
+
5
24
  # [1.24.0](https://github.com/EncryptionL/twico-ui/compare/v1.23.0...v1.24.0) (2026-07-31)
6
25
 
7
26
 
package/dist/index.cjs CHANGED
@@ -8461,6 +8461,8 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
8461
8461
  .twc-dt__row[data-selectable="true"] { cursor: pointer; }
8462
8462
  .twc-dt__row[data-active="true"] .twc-dt__td { background: var(--color-primary-subtle); box-shadow: inset 0 0 0 9999px transparent; }
8463
8463
  .twc-dt__row[data-active="true"] .twc-dt__td[data-pin] { background: var(--color-primary-subtle); }
8464
+ /* #317: data-cell-selected fills the range rectangle; data-cell-active keeps the ring on the moving corner. */
8465
+ .twc-dt__td[data-cell-selected="true"] { background: var(--color-primary-subtle); }
8464
8466
  .twc-dt__td[data-cell-active="true"] { box-shadow: inset 0 0 0 2px var(--color-primary); background: var(--color-primary-subtle); }
8465
8467
  .twc-dt__row:last-child .twc-dt__td { border-bottom: none; }
8466
8468
 
@@ -8615,6 +8617,9 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
8615
8617
  .twc-dt__pop-grip:hover, .twc-dt__pop-grip:focus-visible, .twc-dt__pop-grip[data-active="true"] { opacity: 1; }
8616
8618
  .twc-dt__pop-grip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
8617
8619
  [dir="rtl"] .twc-dt__pop-grip { cursor: nesw-resize; }
8620
+ /* #316: a second grip in the bottom-inline-start corner grows a right-anchored panel toward the empty side (right edge pinned). */
8621
+ .twc-dt__pop-grip[data-side="start"] { inset-inline-start: 2px; inset-inline-end: auto; cursor: nesw-resize; background: linear-gradient(225deg, transparent 0 55%, var(--color-border-strong) 55% 64%, transparent 64% 73%, var(--color-border-strong) 73% 82%, transparent 82%); }
8622
+ [dir="rtl"] .twc-dt__pop-grip[data-side="start"] { cursor: nwse-resize; }
8618
8623
  @media (pointer: coarse) {
8619
8624
  .twc-dt__f-rz { width: 16px; inset-inline-end: -6px; opacity: 0.4; }
8620
8625
  .twc-dt__pop-grip { width: 20px; height: 20px; opacity: 0.6; }
@@ -9169,6 +9174,8 @@ function Datatable({
9169
9174
  onRowClick,
9170
9175
  onCellClick,
9171
9176
  onActiveCellChange,
9177
+ onCellSelectionChange,
9178
+ enableClipboard = false,
9172
9179
  showAggregation = false,
9173
9180
  ariaLabel = "Data table",
9174
9181
  "aria-label": ariaLabelAttr,
@@ -9180,7 +9187,8 @@ function Datatable({
9180
9187
  onQuickFilterChange,
9181
9188
  toolbarActions,
9182
9189
  resizableFilters = true,
9183
- resizablePopovers = false,
9190
+ resizablePopovers = true,
9191
+ // #314: default-on (matches resizableFilters) — set false to opt a grid out
9184
9192
  filterFieldMaxWidth,
9185
9193
  rowPinning = false,
9186
9194
  rowReorder = false,
@@ -9298,6 +9306,8 @@ function Datatable({
9298
9306
  const [selected, setSelected] = React51__namespace.default.useState(() => /* @__PURE__ */ new Set());
9299
9307
  const [activeRow, setActiveRow] = React51__namespace.default.useState(null);
9300
9308
  const [activeCell, setActiveCell] = React51__namespace.default.useState(null);
9309
+ const [anchorCell, setAnchorCell] = React51__namespace.default.useState(null);
9310
+ const gridId = React51__namespace.default.useId();
9301
9311
  const [internalPage, setInternalPage] = React51__namespace.default.useState(0);
9302
9312
  const [internalRpp, setInternalRpp] = React51__namespace.default.useState(pageSize > 0 ? pageSize : 10);
9303
9313
  const pageControlled = page !== void 0;
@@ -9397,6 +9407,7 @@ function Datatable({
9397
9407
  for (const [id, sz] of Object.entries(s.popoverSizes)) {
9398
9408
  if (!sz || typeof sz !== "object" || typeof sz.w !== "number" || typeof sz.h !== "number") continue;
9399
9409
  next[id] = { w: clampNum(sz.w, popMinW(id), fMaxW()), h: clampNum(sz.h, 120, fMaxH()) };
9410
+ if (typeof sz.left === "number") next[id].left = clampNum(sz.left, 8, fMaxW());
9400
9411
  }
9401
9412
  if (Object.keys(next).length) setPopSizes(next);
9402
9413
  }
@@ -9848,6 +9859,38 @@ function Datatable({
9848
9859
  middleRows.forEach((r2, i) => m.set(keyOf(r2, keyIndex.get(keyOf(r2)) ?? i), i));
9849
9860
  return m;
9850
9861
  }, [middleRows, keyIndex]);
9862
+ const cellColIndex = React51__namespace.default.useCallback((field) => ordered.findIndex((c) => c.field === field), [ordered]);
9863
+ const cellRect = React51__namespace.default.useMemo(() => {
9864
+ if (selectionMode !== "cell" || !activeCell) return null;
9865
+ const ar = keyIndex.get(activeCell.key), ac = cellColIndex(activeCell.field);
9866
+ if (ar == null || ac < 0) return null;
9867
+ const anchor = anchorCell || activeCell;
9868
+ const nr = keyIndex.get(anchor.key), nc = cellColIndex(anchor.field);
9869
+ const r0 = nr == null ? ar : Math.min(ar, nr), r1 = nr == null ? ar : Math.max(ar, nr);
9870
+ const c0 = nc < 0 ? ac : Math.min(ac, nc), c1 = nc < 0 ? ac : Math.max(ac, nc);
9871
+ return { r0, r1, c0, c1 };
9872
+ }, [selectionMode, activeCell, anchorCell, keyIndex, cellColIndex]);
9873
+ const activeCellId = React51__namespace.default.useMemo(() => {
9874
+ if (selectionMode !== "cell" || !activeCell) return void 0;
9875
+ const ar = keyIndex.get(activeCell.key), ac = cellColIndex(activeCell.field);
9876
+ return ar == null || ac < 0 ? void 0 : `${gridId}-${ar}-${ac}`;
9877
+ }, [selectionMode, activeCell, keyIndex, cellColIndex, gridId]);
9878
+ const onCellSelRef = React51__namespace.default.useRef(onCellSelectionChange);
9879
+ onCellSelRef.current = onCellSelectionChange;
9880
+ const prevSelSigRef = React51__namespace.default.useRef("");
9881
+ React51__namespace.default.useEffect(() => {
9882
+ const cb = onCellSelRef.current;
9883
+ if (selectionMode !== "cell" || !cb) return;
9884
+ const cells = [];
9885
+ if (cellRect) for (let r2 = cellRect.r0; r2 <= cellRect.r1; r2++) for (let c = cellRect.c0; c <= cellRect.c1; c++) {
9886
+ const row = leafRows[r2], col = ordered[c];
9887
+ if (row && col) cells.push({ key: keyOf(row, r2), field: col.field });
9888
+ }
9889
+ const sig = cells.map((x) => `${x.key}|${x.field}`).join(",");
9890
+ if (sig === prevSelSigRef.current) return;
9891
+ prevSelSigRef.current = sig;
9892
+ cb(cells);
9893
+ }, [cellRect, selectionMode, leafRows, ordered]);
9851
9894
  const canReorderRows = rowReorder && !activeGroupBy.length && !sort;
9852
9895
  function onRowDrop(targetKey) {
9853
9896
  const fromKey = rowDrag.from;
@@ -10260,6 +10303,7 @@ function Datatable({
10260
10303
  const next = { key: k, field: col.field };
10261
10304
  setActiveCell(next);
10262
10305
  setActiveRow(k);
10306
+ if (!(e.shiftKey && anchorCell)) setAnchorCell(next);
10263
10307
  onCellClick?.(row[col.field], row, col.field);
10264
10308
  onActiveCellChange?.(next);
10265
10309
  }
@@ -10271,6 +10315,24 @@ function Datatable({
10271
10315
  if (editing) return;
10272
10316
  const td = e.target.closest(".twc-dt__td[data-r]");
10273
10317
  if (!td) return;
10318
+ if (enableClipboard && selectionMode === "cell" && (e.ctrlKey || e.metaKey)) {
10319
+ const k = e.key.toLowerCase();
10320
+ if (k === "c") {
10321
+ e.preventDefault();
10322
+ copySelection(false);
10323
+ return;
10324
+ }
10325
+ if (k === "x") {
10326
+ e.preventDefault();
10327
+ copySelection(true);
10328
+ return;
10329
+ }
10330
+ if (k === "v") {
10331
+ e.preventDefault();
10332
+ pasteSelection();
10333
+ return;
10334
+ }
10335
+ }
10274
10336
  let r2 = +td.getAttribute("data-r"), c = +td.getAttribute("data-c");
10275
10337
  const maxR = leafRows.length - 1, maxC = ordered.length - 1;
10276
10338
  let handled = true;
@@ -10323,6 +10385,16 @@ function Datatable({
10323
10385
  e.preventDefault();
10324
10386
  setFocus({ r: r2, c });
10325
10387
  focusCell(r2, c);
10388
+ if (selectionMode === "cell") {
10389
+ const nk = keyOf(leafRows[r2], r2), col = ordered[c];
10390
+ if (col) {
10391
+ const nc = { key: nk, field: col.field };
10392
+ setActiveCell(nc);
10393
+ setActiveRow(nk);
10394
+ if (!e.shiftKey) setAnchorCell(nc);
10395
+ onActiveCellChange?.(nc);
10396
+ }
10397
+ }
10326
10398
  }
10327
10399
  }
10328
10400
  React51__namespace.default.useEffect(() => {
@@ -10439,11 +10511,149 @@ function Datatable({
10439
10511
  setBatchEdit(null);
10440
10512
  closeBatchEdit();
10441
10513
  }
10514
+ const [clipboardMsg, setClipboardMsg] = React51__namespace.default.useState("");
10515
+ const clipboardRef = React51__namespace.default.useRef(null);
10516
+ const copyTypeOf = (col) => col && col.copyType != null ? col.copyType : col && col.type === "number" ? "number" : "text";
10517
+ const writeClipboard = (text) => {
10518
+ const execFallback = () => {
10519
+ try {
10520
+ if (typeof document !== "undefined") {
10521
+ const ta = document.createElement("textarea");
10522
+ ta.value = text;
10523
+ ta.style.position = "fixed";
10524
+ ta.style.opacity = "0";
10525
+ document.body.appendChild(ta);
10526
+ ta.focus();
10527
+ ta.select();
10528
+ document.execCommand("copy");
10529
+ document.body.removeChild(ta);
10530
+ }
10531
+ } catch {
10532
+ }
10533
+ };
10534
+ try {
10535
+ if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text).catch(execFallback);
10536
+ } catch {
10537
+ }
10538
+ execFallback();
10539
+ return Promise.resolve();
10540
+ };
10541
+ const writeCellPatches = (patchByKey) => {
10542
+ if (!patchByKey.size) return 0;
10543
+ const changed = [];
10544
+ const nextAll = rows.map((r2, i) => {
10545
+ const k = keyOf(r2, i);
10546
+ const patch = patchByKey.get(k);
10547
+ if (!patch) return r2;
10548
+ const updated = { ...r2, ...patch };
10549
+ changed.push([updated, r2, patch]);
10550
+ return updated;
10551
+ });
10552
+ changed.forEach(([updated, orig, patch]) => {
10553
+ for (const f of Object.keys(patch)) onRowUpdate?.(updated, orig, f);
10554
+ });
10555
+ if (onRowsChange && changed.length) onRowsChange(nextAll);
10556
+ return changed.length;
10557
+ };
10558
+ const rectCells = () => {
10559
+ if (!cellRect) return null;
10560
+ const out = [];
10561
+ for (let r2 = cellRect.r0; r2 <= cellRect.r1; r2++) {
10562
+ const row = leafRows[r2];
10563
+ if (!row) continue;
10564
+ const line = [];
10565
+ for (let c = cellRect.c0; c <= cellRect.c1; c++) {
10566
+ const col = ordered[c];
10567
+ if (col) line.push({ key: keyOf(row, r2), field: col.field, col, value: getColVal(col, row) });
10568
+ }
10569
+ out.push(line);
10570
+ }
10571
+ return out.length ? out : null;
10572
+ };
10573
+ const copySelection = (cut) => {
10574
+ const grid = rectCells();
10575
+ if (!grid) return;
10576
+ const tsv = grid.map((line) => line.map((cell) => cell.value == null ? "" : String(cell.value)).join(" ")).join("\n");
10577
+ clipboardRef.current = { tsv, colTypes: grid[0].map((cell) => copyTypeOf(cell.col)), matrix: grid.map((line) => line.map((cell) => cell.value)) };
10578
+ writeClipboard(tsv);
10579
+ const n = grid.reduce((a, line) => a + line.length, 0);
10580
+ if (cut) {
10581
+ const patchByKey = /* @__PURE__ */ new Map();
10582
+ let clearable = 0;
10583
+ for (const line of grid) for (const cell of line) {
10584
+ if (!isColEditable(cell.col)) continue;
10585
+ clearable++;
10586
+ const p = patchByKey.get(cell.key) || {};
10587
+ p[cell.field] = cell.col.type === "number" ? null : "";
10588
+ patchByKey.set(cell.key, p);
10589
+ }
10590
+ writeCellPatches(patchByKey);
10591
+ setClipboardMsg(`Cut ${clearable} cell${clearable === 1 ? "" : "s"}${clearable < n ? `, ${n - clearable} read-only kept` : ""}`);
10592
+ } else {
10593
+ setClipboardMsg(`Copied ${n} cell${n === 1 ? "" : "s"}`);
10594
+ }
10595
+ };
10596
+ const pasteSelection = async () => {
10597
+ if (!cellRect) return;
10598
+ let text = null;
10599
+ try {
10600
+ if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.readText) text = await navigator.clipboard.readText();
10601
+ } catch {
10602
+ setClipboardMsg("Paste unavailable \u2014 clipboard permission denied");
10603
+ return;
10604
+ }
10605
+ if (text == null) {
10606
+ setClipboardMsg("Nothing to paste");
10607
+ return;
10608
+ }
10609
+ const inApp = clipboardRef.current && clipboardRef.current.tsv === text ? clipboardRef.current : null;
10610
+ let matrix;
10611
+ if (inApp) matrix = inApp.matrix;
10612
+ else {
10613
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
10614
+ if (lines.length && lines[lines.length - 1] === "") lines.pop();
10615
+ matrix = lines.map((line) => line.split(" "));
10616
+ }
10617
+ const patchByKey = /* @__PURE__ */ new Map();
10618
+ let written = 0, skipped = 0;
10619
+ for (let i = 0; i < matrix.length; i++) {
10620
+ const rr = cellRect.r0 + i;
10621
+ const row = leafRows[rr];
10622
+ if (!row) break;
10623
+ for (let j = 0; j < matrix[i].length; j++) {
10624
+ const cc = cellRect.c0 + j;
10625
+ const col = ordered[cc];
10626
+ if (!col) break;
10627
+ if (!isColEditable(col)) {
10628
+ skipped++;
10629
+ continue;
10630
+ }
10631
+ if (inApp && inApp.colTypes[j] != null && inApp.colTypes[j] !== copyTypeOf(col)) {
10632
+ skipped++;
10633
+ continue;
10634
+ }
10635
+ let v = matrix[i][j];
10636
+ if (col.type === "number") {
10637
+ v = v === "" ? null : Number(v);
10638
+ if (Number.isNaN(v)) v = null;
10639
+ }
10640
+ const k = keyOf(row, rr);
10641
+ const p = patchByKey.get(k) || {};
10642
+ p[col.field] = v;
10643
+ patchByKey.set(k, p);
10644
+ written++;
10645
+ }
10646
+ }
10647
+ writeCellPatches(patchByKey);
10648
+ setClipboardMsg(`Pasted ${written} cell${written === 1 ? "" : "s"}${skipped ? `, ${skipped} skipped (incompatible column)` : ""}`);
10649
+ };
10442
10650
  React51__namespace.default.useEffect(() => {
10443
10651
  if (!editing) return;
10444
10652
  const onDown = (e) => {
10445
10653
  if (e.target.closest(".twc-dt__editor-wrap") || e.target.closest(".twc-pop")) return;
10446
- setEditing(null);
10654
+ const col = colByField[editing.field];
10655
+ if (col && col.renderEditCell) setEditing(null);
10656
+ else commitEdit();
10447
10657
  };
10448
10658
  document.addEventListener("mousedown", onDown, true);
10449
10659
  return () => document.removeEventListener("mousedown", onDown, true);
@@ -10693,6 +10903,7 @@ function Datatable({
10693
10903
  if (node) {
10694
10904
  node.style.removeProperty("--twc-dt-pop-w");
10695
10905
  node.style.removeProperty("--twc-dt-pop-h");
10906
+ node.style.removeProperty("left");
10696
10907
  node.removeAttribute("data-pop-sized");
10697
10908
  }
10698
10909
  setPopSizes((s) => {
@@ -10702,7 +10913,7 @@ function Datatable({
10702
10913
  return n;
10703
10914
  });
10704
10915
  };
10705
- const startPopResize = (id) => (e) => {
10916
+ const startPopResize = (id, side) => (e) => {
10706
10917
  if (e.button != null && e.button !== 0) return;
10707
10918
  e.preventDefault();
10708
10919
  e.stopPropagation();
@@ -10711,21 +10922,27 @@ function Datatable({
10711
10922
  const grip = e.currentTarget;
10712
10923
  const startX = e.clientX, startY = e.clientY;
10713
10924
  const r2 = pop.getBoundingClientRect();
10714
- const startW = r2.width, startH = r2.height;
10925
+ const startW = r2.width, startH = r2.height, startLeft = r2.left, startRight = r2.right;
10715
10926
  const dir = typeof getComputedStyle !== "undefined" && getComputedStyle(pop).direction === "rtl" ? -1 : 1;
10927
+ const toStart = side === "start";
10716
10928
  pop.style.setProperty("--twc-dt-pop-w", `${startW}px`);
10717
10929
  pop.style.setProperty("--twc-dt-pop-h", `${startH}px`);
10718
10930
  grip.setAttribute("data-active", "true");
10719
10931
  pop.setAttribute("data-pop-sized", "");
10720
- let lw = startW, lh = startH;
10932
+ let lw = startW, lh = startH, ll = startLeft;
10721
10933
  const onMove = (ev) => {
10722
- lw = clampNum(startW + (ev.clientX - startX) * dir, popMinW(id), fMaxW());
10934
+ const dw = toStart ? (startX - ev.clientX) * dir : (ev.clientX - startX) * dir;
10935
+ lw = clampNum(startW + dw, popMinW(id), fMaxW());
10723
10936
  lh = clampNum(startH + (ev.clientY - startY), 120, fMaxH());
10724
10937
  pop.style.setProperty("--twc-dt-pop-w", `${lw}px`);
10725
10938
  pop.style.setProperty("--twc-dt-pop-h", `${lh}px`);
10939
+ if (toStart) {
10940
+ ll = Math.max(8, startRight - lw);
10941
+ pop.style.left = `${ll}px`;
10942
+ }
10726
10943
  };
10727
10944
  const onUp = () => {
10728
- setPopSizes((s) => ({ ...s, [id]: { w: lw, h: lh } }));
10945
+ setPopSizes((s) => ({ ...s, [id]: { ...s[id] || {}, w: lw, h: lh, ...toStart ? { left: ll } : {} } }));
10729
10946
  grip.removeAttribute("data-active");
10730
10947
  window.removeEventListener("pointermove", onMove);
10731
10948
  window.removeEventListener("pointerup", onUp);
@@ -10733,7 +10950,7 @@ function Datatable({
10733
10950
  window.addEventListener("pointermove", onMove);
10734
10951
  window.addEventListener("pointerup", onUp);
10735
10952
  };
10736
- const onPopGripKey = (id) => (e) => {
10953
+ const onPopGripKey = (id, side) => (e) => {
10737
10954
  if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
10738
10955
  e.preventDefault();
10739
10956
  clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
@@ -10758,26 +10975,55 @@ function Datatable({
10758
10975
  w = fMaxW();
10759
10976
  h = fMaxH();
10760
10977
  }
10761
- setPopSizes((s) => ({ ...s, [id]: { w: clampNum(w, popMinW(id), fMaxW()), h: clampNum(h, 120, fMaxH()) } }));
10978
+ w = clampNum(w, popMinW(id), fMaxW());
10979
+ h = clampNum(h, 120, fMaxH());
10980
+ const left = side === "start" ? Math.max(8, r2.right - w) : void 0;
10981
+ setPopSizes((s) => ({ ...s, [id]: { ...s[id] || {}, w, h, ...left != null ? { left } : {} } }));
10762
10982
  };
10763
- const popStyle = (id) => popSizes[id] ? { "--twc-dt-pop-w": `${popSizes[id].w}px`, "--twc-dt-pop-h": `${popSizes[id].h}px` } : null;
10764
- const popGrip = (id) => resizablePopovers ? /* @__PURE__ */ jsxRuntime.jsx(
10765
- "span",
10766
- {
10767
- className: "twc-dt__pop-grip",
10768
- role: "slider",
10769
- tabIndex: 0,
10770
- "aria-label": "Resize panel (Enter or double-click to reset)",
10771
- "aria-valuetext": popSizes[id] ? `Width ${Math.round(popSizes[id].w)} pixels, height ${Math.round(popSizes[id].h)} pixels` : "Default size",
10772
- onPointerDown: startPopResize(id),
10773
- onKeyDown: onPopGripKey(id),
10774
- onDoubleClick: (e) => {
10775
- e.preventDefault();
10776
- clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
10983
+ const popStyle = (id) => {
10984
+ const s = popSizes[id];
10985
+ if (!s) return null;
10986
+ const st = { "--twc-dt-pop-w": `${s.w}px`, "--twc-dt-pop-h": `${s.h}px` };
10987
+ if (s.left != null) st.left = `${s.left}px`;
10988
+ return st;
10989
+ };
10990
+ const popGrip = (id) => resizablePopovers ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10991
+ /* @__PURE__ */ jsxRuntime.jsx(
10992
+ "span",
10993
+ {
10994
+ className: "twc-dt__pop-grip",
10995
+ "data-side": "end",
10996
+ role: "slider",
10997
+ tabIndex: 0,
10998
+ "aria-label": "Resize panel (Enter or double-click to reset)",
10999
+ "aria-valuetext": popSizes[id] ? `Width ${Math.round(popSizes[id].w)} pixels, height ${Math.round(popSizes[id].h)} pixels` : "Default size",
11000
+ onPointerDown: startPopResize(id, "end"),
11001
+ onKeyDown: onPopGripKey(id, "end"),
11002
+ onDoubleClick: (e) => {
11003
+ e.preventDefault();
11004
+ clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
11005
+ }
10777
11006
  }
10778
- }
10779
- ) : null;
10780
- const fieldHandle = (field, rowIdx) => (resizableFilters || resizablePopovers) && rowIdx === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
11007
+ ),
11008
+ /* @__PURE__ */ jsxRuntime.jsx(
11009
+ "span",
11010
+ {
11011
+ className: "twc-dt__pop-grip",
11012
+ "data-side": "start",
11013
+ role: "slider",
11014
+ tabIndex: 0,
11015
+ "aria-label": "Resize panel leftward (Enter or double-click to reset)",
11016
+ "aria-valuetext": popSizes[id] ? `Width ${Math.round(popSizes[id].w)} pixels, height ${Math.round(popSizes[id].h)} pixels` : "Default size",
11017
+ onPointerDown: startPopResize(id, "start"),
11018
+ onKeyDown: onPopGripKey(id, "start"),
11019
+ onDoubleClick: (e) => {
11020
+ e.preventDefault();
11021
+ clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
11022
+ }
11023
+ }
11024
+ )
11025
+ ] }) : null;
11026
+ const fieldHandle = (field, rowIdx) => resizableFilters && rowIdx === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
10781
11027
  "span",
10782
11028
  {
10783
11029
  className: "twc-dt__f-rz",
@@ -10932,22 +11178,27 @@ function Datatable({
10932
11178
  const editable = isColEditable(c);
10933
11179
  const isEditing = editing && editing.key === k && editing.field === c.field;
10934
11180
  const cellActive = selectionMode === "cell" && activeCell && activeCell.key === k && activeCell.field === c.field;
11181
+ const cellSelected = selectionMode === "cell" && cellRect && ri >= cellRect.r0 && ri <= cellRect.r1 && ci >= cellRect.c0 && ci <= cellRect.c1;
11182
+ const cellId = selectionMode === "cell" ? `${gridId}-${ri}-${ci}` : void 0;
10935
11183
  const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
10936
11184
  const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
10937
11185
  return /* @__PURE__ */ jsxRuntime.jsxs(
10938
11186
  "td",
10939
11187
  {
11188
+ id: cellId,
10940
11189
  className: "twc-dt__td",
10941
11190
  role: "gridcell",
10942
11191
  "data-r": ri,
10943
11192
  "data-c": ci,
10944
11193
  "aria-colindex": ci + 1 + (checkboxSelection ? 1 : 0),
10945
11194
  tabIndex: focus.r === ri && focus.c === ci ? 0 : -1,
11195
+ "aria-selected": cellSelected || void 0,
10946
11196
  "data-num": c.type === "number" || void 0,
10947
11197
  "data-actions": isActions || void 0,
10948
11198
  "data-editable": editable && !isEditing || void 0,
10949
11199
  "data-editing": isEditing || void 0,
10950
11200
  "data-cell-active": cellActive || void 0,
11201
+ "data-cell-selected": cellSelected || void 0,
10951
11202
  "data-wrap": wrapped.has(c.field) || void 0,
10952
11203
  "data-pin": st.pin,
10953
11204
  "data-pin-edge": st.edge,
@@ -11309,6 +11560,7 @@ function Datatable({
11309
11560
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__groupbar-clear", onClick: () => setGroupBy([]), children: "Clear all" })
11310
11561
  ] }) : null,
11311
11562
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__sr", role: "status", "aria-live": "polite", children: reorderMsg }),
11563
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__sr", role: "status", "aria-live": "polite", children: clipboardMsg }),
11312
11564
  pivotActive ? renderPivot() : /* @__PURE__ */ jsxRuntime.jsx(
11313
11565
  "div",
11314
11566
  {
@@ -11330,6 +11582,8 @@ function Datatable({
11330
11582
  "aria-label": ariaLabelAttr || ariaLabel,
11331
11583
  "aria-rowcount": totalRows + 1,
11332
11584
  "aria-colcount": ordered.length + (checkboxSelection ? 1 : 0),
11585
+ "aria-multiselectable": selectionMode === "cell" || void 0,
11586
+ "aria-activedescendant": activeCellId,
11333
11587
  "aria-busy": loading || void 0,
11334
11588
  onKeyDown: onGridKeyDown,
11335
11589
  children: [
@@ -12191,7 +12445,7 @@ function Datatable({
12191
12445
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__flogic-btn", "data-active": filterLogic === "and" || void 0, "aria-pressed": filterLogic === "and", onClick: () => setFilterLogic("and"), children: "AND" }),
12192
12446
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__flogic-btn", "data-active": filterLogic === "or" || void 0, "aria-pressed": filterLogic === "or", onClick: () => setFilterLogic("or"), children: "OR" })
12193
12447
  ] }) : null,
12194
- (resizableFilters || resizablePopovers) && hasFilterSizeOverride ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: resetFilterSizes, children: "Reset sizes" }) : null,
12448
+ resizableFilters && hasFilterSizeOverride ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: resetFilterSizes, children: "Reset sizes" }) : null,
12195
12449
  filters.length ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
12196
12450
  ] })
12197
12451
  ] }),
@@ -12271,7 +12525,7 @@ function Datatable({
12271
12525
  /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.plus, style: { color: "var(--color-primary)" } }),
12272
12526
  " Add filter"
12273
12527
  ] }) }),
12274
- resizableFilters || resizablePopovers ? /* @__PURE__ */ jsxRuntime.jsx(
12528
+ resizableFilters ? /* @__PURE__ */ jsxRuntime.jsx(
12275
12529
  "span",
12276
12530
  {
12277
12531
  className: "twc-dt__pop-grip",