twico-ui 1.25.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,13 @@
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
+
5
12
  # [1.25.0](https://github.com/EncryptionL/twico-ui/compare/v1.24.0...v1.25.0) (2026-08-03)
6
13
 
7
14
 
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,
@@ -9299,6 +9306,8 @@ function Datatable({
9299
9306
  const [selected, setSelected] = React51__namespace.default.useState(() => /* @__PURE__ */ new Set());
9300
9307
  const [activeRow, setActiveRow] = React51__namespace.default.useState(null);
9301
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();
9302
9311
  const [internalPage, setInternalPage] = React51__namespace.default.useState(0);
9303
9312
  const [internalRpp, setInternalRpp] = React51__namespace.default.useState(pageSize > 0 ? pageSize : 10);
9304
9313
  const pageControlled = page !== void 0;
@@ -9398,6 +9407,7 @@ function Datatable({
9398
9407
  for (const [id, sz] of Object.entries(s.popoverSizes)) {
9399
9408
  if (!sz || typeof sz !== "object" || typeof sz.w !== "number" || typeof sz.h !== "number") continue;
9400
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());
9401
9411
  }
9402
9412
  if (Object.keys(next).length) setPopSizes(next);
9403
9413
  }
@@ -9849,6 +9859,38 @@ function Datatable({
9849
9859
  middleRows.forEach((r2, i) => m.set(keyOf(r2, keyIndex.get(keyOf(r2)) ?? i), i));
9850
9860
  return m;
9851
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]);
9852
9894
  const canReorderRows = rowReorder && !activeGroupBy.length && !sort;
9853
9895
  function onRowDrop(targetKey) {
9854
9896
  const fromKey = rowDrag.from;
@@ -10261,6 +10303,7 @@ function Datatable({
10261
10303
  const next = { key: k, field: col.field };
10262
10304
  setActiveCell(next);
10263
10305
  setActiveRow(k);
10306
+ if (!(e.shiftKey && anchorCell)) setAnchorCell(next);
10264
10307
  onCellClick?.(row[col.field], row, col.field);
10265
10308
  onActiveCellChange?.(next);
10266
10309
  }
@@ -10272,6 +10315,24 @@ function Datatable({
10272
10315
  if (editing) return;
10273
10316
  const td = e.target.closest(".twc-dt__td[data-r]");
10274
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
+ }
10275
10336
  let r2 = +td.getAttribute("data-r"), c = +td.getAttribute("data-c");
10276
10337
  const maxR = leafRows.length - 1, maxC = ordered.length - 1;
10277
10338
  let handled = true;
@@ -10324,6 +10385,16 @@ function Datatable({
10324
10385
  e.preventDefault();
10325
10386
  setFocus({ r: r2, c });
10326
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
+ }
10327
10398
  }
10328
10399
  }
10329
10400
  React51__namespace.default.useEffect(() => {
@@ -10440,6 +10511,142 @@ function Datatable({
10440
10511
  setBatchEdit(null);
10441
10512
  closeBatchEdit();
10442
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
+ };
10443
10650
  React51__namespace.default.useEffect(() => {
10444
10651
  if (!editing) return;
10445
10652
  const onDown = (e) => {
@@ -10696,6 +10903,7 @@ function Datatable({
10696
10903
  if (node) {
10697
10904
  node.style.removeProperty("--twc-dt-pop-w");
10698
10905
  node.style.removeProperty("--twc-dt-pop-h");
10906
+ node.style.removeProperty("left");
10699
10907
  node.removeAttribute("data-pop-sized");
10700
10908
  }
10701
10909
  setPopSizes((s) => {
@@ -10705,7 +10913,7 @@ function Datatable({
10705
10913
  return n;
10706
10914
  });
10707
10915
  };
10708
- const startPopResize = (id) => (e) => {
10916
+ const startPopResize = (id, side) => (e) => {
10709
10917
  if (e.button != null && e.button !== 0) return;
10710
10918
  e.preventDefault();
10711
10919
  e.stopPropagation();
@@ -10714,21 +10922,27 @@ function Datatable({
10714
10922
  const grip = e.currentTarget;
10715
10923
  const startX = e.clientX, startY = e.clientY;
10716
10924
  const r2 = pop.getBoundingClientRect();
10717
- const startW = r2.width, startH = r2.height;
10925
+ const startW = r2.width, startH = r2.height, startLeft = r2.left, startRight = r2.right;
10718
10926
  const dir = typeof getComputedStyle !== "undefined" && getComputedStyle(pop).direction === "rtl" ? -1 : 1;
10927
+ const toStart = side === "start";
10719
10928
  pop.style.setProperty("--twc-dt-pop-w", `${startW}px`);
10720
10929
  pop.style.setProperty("--twc-dt-pop-h", `${startH}px`);
10721
10930
  grip.setAttribute("data-active", "true");
10722
10931
  pop.setAttribute("data-pop-sized", "");
10723
- let lw = startW, lh = startH;
10932
+ let lw = startW, lh = startH, ll = startLeft;
10724
10933
  const onMove = (ev) => {
10725
- 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());
10726
10936
  lh = clampNum(startH + (ev.clientY - startY), 120, fMaxH());
10727
10937
  pop.style.setProperty("--twc-dt-pop-w", `${lw}px`);
10728
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
+ }
10729
10943
  };
10730
10944
  const onUp = () => {
10731
- setPopSizes((s) => ({ ...s, [id]: { w: lw, h: lh } }));
10945
+ setPopSizes((s) => ({ ...s, [id]: { ...s[id] || {}, w: lw, h: lh, ...toStart ? { left: ll } : {} } }));
10732
10946
  grip.removeAttribute("data-active");
10733
10947
  window.removeEventListener("pointermove", onMove);
10734
10948
  window.removeEventListener("pointerup", onUp);
@@ -10736,7 +10950,7 @@ function Datatable({
10736
10950
  window.addEventListener("pointermove", onMove);
10737
10951
  window.addEventListener("pointerup", onUp);
10738
10952
  };
10739
- const onPopGripKey = (id) => (e) => {
10953
+ const onPopGripKey = (id, side) => (e) => {
10740
10954
  if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
10741
10955
  e.preventDefault();
10742
10956
  clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
@@ -10761,25 +10975,54 @@ function Datatable({
10761
10975
  w = fMaxW();
10762
10976
  h = fMaxH();
10763
10977
  }
10764
- 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 } : {} } }));
10765
10982
  };
10766
- const popStyle = (id) => popSizes[id] ? { "--twc-dt-pop-w": `${popSizes[id].w}px`, "--twc-dt-pop-h": `${popSizes[id].h}px` } : null;
10767
- const popGrip = (id) => resizablePopovers ? /* @__PURE__ */ jsxRuntime.jsx(
10768
- "span",
10769
- {
10770
- className: "twc-dt__pop-grip",
10771
- role: "slider",
10772
- tabIndex: 0,
10773
- "aria-label": "Resize panel (Enter or double-click to reset)",
10774
- "aria-valuetext": popSizes[id] ? `Width ${Math.round(popSizes[id].w)} pixels, height ${Math.round(popSizes[id].h)} pixels` : "Default size",
10775
- onPointerDown: startPopResize(id),
10776
- onKeyDown: onPopGripKey(id),
10777
- onDoubleClick: (e) => {
10778
- e.preventDefault();
10779
- 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
+ }
10780
11006
  }
10781
- }
10782
- ) : null;
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;
10783
11026
  const fieldHandle = (field, rowIdx) => resizableFilters && rowIdx === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
10784
11027
  "span",
10785
11028
  {
@@ -10935,22 +11178,27 @@ function Datatable({
10935
11178
  const editable = isColEditable(c);
10936
11179
  const isEditing = editing && editing.key === k && editing.field === c.field;
10937
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;
10938
11183
  const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
10939
11184
  const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
10940
11185
  return /* @__PURE__ */ jsxRuntime.jsxs(
10941
11186
  "td",
10942
11187
  {
11188
+ id: cellId,
10943
11189
  className: "twc-dt__td",
10944
11190
  role: "gridcell",
10945
11191
  "data-r": ri,
10946
11192
  "data-c": ci,
10947
11193
  "aria-colindex": ci + 1 + (checkboxSelection ? 1 : 0),
10948
11194
  tabIndex: focus.r === ri && focus.c === ci ? 0 : -1,
11195
+ "aria-selected": cellSelected || void 0,
10949
11196
  "data-num": c.type === "number" || void 0,
10950
11197
  "data-actions": isActions || void 0,
10951
11198
  "data-editable": editable && !isEditing || void 0,
10952
11199
  "data-editing": isEditing || void 0,
10953
11200
  "data-cell-active": cellActive || void 0,
11201
+ "data-cell-selected": cellSelected || void 0,
10954
11202
  "data-wrap": wrapped.has(c.field) || void 0,
10955
11203
  "data-pin": st.pin,
10956
11204
  "data-pin-edge": st.edge,
@@ -11312,6 +11560,7 @@ function Datatable({
11312
11560
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__groupbar-clear", onClick: () => setGroupBy([]), children: "Clear all" })
11313
11561
  ] }) : null,
11314
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 }),
11315
11564
  pivotActive ? renderPivot() : /* @__PURE__ */ jsxRuntime.jsx(
11316
11565
  "div",
11317
11566
  {
@@ -11333,6 +11582,8 @@ function Datatable({
11333
11582
  "aria-label": ariaLabelAttr || ariaLabel,
11334
11583
  "aria-rowcount": totalRows + 1,
11335
11584
  "aria-colcount": ordered.length + (checkboxSelection ? 1 : 0),
11585
+ "aria-multiselectable": selectionMode === "cell" || void 0,
11586
+ "aria-activedescendant": activeCellId,
11336
11587
  "aria-busy": loading || void 0,
11337
11588
  onKeyDown: onGridKeyDown,
11338
11589
  children: [