twico-ui 1.33.0 → 1.34.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/dist/index.mjs CHANGED
@@ -7942,6 +7942,9 @@ var TOOLTIP_CSS = `
7942
7942
  .twc-tooltip-wrap { display: inline-flex; }
7943
7943
  .twc-tooltip {
7944
7944
  position: fixed; z-index: var(--z-tooltip);
7945
+ /* #348: purely presentational \u2014 the bubble must NEVER intercept the pointer, so neither a hidden
7946
+ (opacity:0) nor a shown bubble can block neighbouring cells/buttons (matches MUI/Radix tooltips). */
7947
+ pointer-events: none;
7945
7948
  padding: 6px 10px; border-radius: var(--radius-md);
7946
7949
  background: var(--color-text); color: var(--color-surface);
7947
7950
  font-family: var(--font-sans); font-size: var(--text-xs); font-weight: var(--font-medium);
@@ -7953,7 +7956,6 @@ var TOOLTIP_CSS = `
7953
7956
  width: max-content; max-width: var(--_tw-maxw, 320px); box-shadow: var(--shadow-md);
7954
7957
  opacity: 0; transform: scale(0.9); transition: opacity var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-spring);
7955
7958
  }
7956
- .twc-tooltip[data-show="true"] { pointer-events: auto; }
7957
7959
  .twc-tooltip[data-show="true"] { opacity: 1; transform: scale(1); }
7958
7960
  .twc-tooltip[data-place="top"] { transform-origin: bottom center; translate: -50% 0; }
7959
7961
  .twc-tooltip[data-place="bottom"] { transform-origin: top center; translate: -50% 0; }
@@ -8000,9 +8002,6 @@ function Tooltip({
8000
8002
  clearTimeout(closeTimer.current);
8001
8003
  closeTimer.current = setTimeout(() => setShowU(false), 120);
8002
8004
  };
8003
- const cancelClose = () => {
8004
- clearTimeout(closeTimer.current);
8005
- };
8006
8005
  React51__default.useEffect(() => () => {
8007
8006
  clearTimeout(timer.current);
8008
8007
  clearTimeout(closeTimer.current);
@@ -8078,8 +8077,6 @@ function Tooltip({
8078
8077
  "data-show": show || void 0,
8079
8078
  role: "tooltip",
8080
8079
  "aria-hidden": show ? void 0 : "true",
8081
- onMouseEnter: anchored ? void 0 : cancelClose,
8082
- onMouseLeave: anchored ? void 0 : close,
8083
8080
  style: {
8084
8081
  position: "fixed",
8085
8082
  left: coords.left,
@@ -8365,6 +8362,16 @@ var DT_CSS = `
8365
8362
  .twc-dt__group-sub span { font-variant-numeric: tabular-nums; }
8366
8363
  .twc-dt__group-sub span b { color: var(--color-text); }
8367
8364
 
8365
+ /* #350: expandable row detail (renderRowDetail) \u2014 leading chevron column + full-width detail row */
8366
+ .twc-dt__expand-cell { text-align: center; padding-inline: 0; }
8367
+ .twc-dt__expand-btn { display: inline-grid; place-items: center; width: 24px; height: 24px; border: none; padding: 0; background: transparent; color: var(--color-text-subtle); cursor: pointer; border-radius: var(--radius-sm); }
8368
+ .twc-dt__expand-btn:hover { color: var(--color-text); background: color-mix(in srgb, currentColor 12%, transparent); }
8369
+ .twc-dt__expand-btn:focus-visible { outline: none; box-shadow: var(--ring); }
8370
+ .twc-dt__expand-chev { display: inline-flex; transform: rotate(-90deg); transition: transform var(--duration-base) var(--ease-spring); }
8371
+ .twc-dt__expand-chev[data-open] { transform: rotate(0deg); color: var(--color-primary); }
8372
+ .twc-dt__expand-chev svg { width: 16px; height: 16px; }
8373
+ .twc-dt__detail-cell { box-sizing: border-box; background: var(--color-surface-sunken); border-bottom: var(--border-thin) solid var(--color-divider); padding: 12px 16px; }
8374
+
8368
8375
  /* Pivot view */
8369
8376
  .twc-dt__pivot { table-layout: auto; width: 100%; }
8370
8377
  .twc-dt__pivot th, .twc-dt__pivot td { white-space: nowrap; }
@@ -9312,6 +9319,13 @@ function Datatable({
9312
9319
  virtualized = false,
9313
9320
  overscan = 8,
9314
9321
  rowHeight,
9322
+ // #350 (first pass): expandable/collapsible rows. `renderRowDetail(row)` returns the detail panel for a
9323
+ // row (return null/undefined ⇒ that row isn't expandable). Presence of the fn enables a leading chevron
9324
+ // column + a full-width detail <tr> under each expanded row. Expansion is uncontrolled unless
9325
+ // `expandedRowIds` is supplied; `onExpandedRowsChange` fires the next expanded-key array on every toggle.
9326
+ renderRowDetail,
9327
+ expandedRowIds,
9328
+ onExpandedRowsChange,
9315
9329
  className = "",
9316
9330
  ...rest
9317
9331
  }) {
@@ -9518,6 +9532,12 @@ function Datatable({
9518
9532
  const gridRef = React51__default.useRef(null);
9519
9533
  const [groupBy, setGroupBy] = React51__default.useState(rowGrouping || []);
9520
9534
  const [collapsed, setCollapsed] = React51__default.useState(() => /* @__PURE__ */ new Set());
9535
+ const [internalExpanded, setInternalExpanded] = React51__default.useState(() => /* @__PURE__ */ new Set());
9536
+ const expandControlled = expandedRowIds !== void 0;
9537
+ const expandedSet = React51__default.useMemo(
9538
+ () => new Set(expandControlled ? expandedRowIds : internalExpanded),
9539
+ [expandControlled, expandedRowIds, internalExpanded]
9540
+ );
9521
9541
  const [pinnedRows, setPinnedRows] = React51__default.useState({ top: [], bottom: [] });
9522
9542
  const [headH, setHeadH] = React51__default.useState(41);
9523
9543
  const theadRef = React51__default.useRef(null);
@@ -9919,7 +9939,9 @@ function Datatable({
9919
9939
  }, [pins, effectiveHidden, visibleCols, colByField, order]);
9920
9940
  const NUM_W = 56;
9921
9941
  const CHK_W = checkboxSelection ? rowReorder ? 68 : 44 : 0;
9922
- const numLeft = CHK_W;
9942
+ const hasRowDetail = typeof renderRowDetail === "function";
9943
+ const EXP_W = hasRowDetail ? 44 : 0;
9944
+ const numLeft = EXP_W + CHK_W;
9923
9945
  const leadW = numLeft + (showRowNum ? NUM_W : 0);
9924
9946
  const visLeft = React51__default.useMemo(() => pins.left.filter((f) => colByField[f] && !effectiveHidden.has(f)), [pins.left, colByField, effectiveHidden]);
9925
9947
  const visRight = React51__default.useMemo(() => pins.right.filter((f) => colByField[f] && !effectiveHidden.has(f)), [pins.right, colByField, effectiveHidden]);
@@ -10514,6 +10536,12 @@ function Datatable({
10514
10536
  return n;
10515
10537
  });
10516
10538
  }
10539
+ function toggleRowDetail(k) {
10540
+ const n = new Set(expandedSet);
10541
+ n.has(k) ? n.delete(k) : n.add(k);
10542
+ onExpandedRowsChange?.([...n]);
10543
+ if (!expandControlled) setInternalExpanded(n);
10544
+ }
10517
10545
  function handleRowClick(e, k, row) {
10518
10546
  if (selectionMode === "none") return;
10519
10547
  if (e.target.closest("button, a, input, select, .twc-dt__check, .twc-dt__editor-wrap")) return;
@@ -11360,7 +11388,7 @@ function Datatable({
11360
11388
  const manageableCols = cols.filter((c) => c.field !== "__pinactions__");
11361
11389
  const visibleColCount = manageableCols.filter((c) => !effectiveHidden.has(c.field)).length;
11362
11390
  const rppOptions = Array.from(new Set([...pageSizeOptions || [], pageSize].filter((n) => n > 0))).sort((a, b) => a - b).map((n) => ({ value: String(n), label: String(n) }));
11363
- const totalCols = ordered.length + (checkboxSelection ? 1 : 0) + (showRowNum ? 1 : 0);
11391
+ const totalCols = ordered.length + (checkboxSelection ? 1 : 0) + (showRowNum ? 1 : 0) + (hasRowDetail ? 1 : 0);
11364
11392
  function renderGroupRow(item) {
11365
11393
  const subs = aggOn ? subtotalText(item.rows) : [];
11366
11394
  return /* @__PURE__ */ jsx("tr", { className: "twc-dt__group-row", role: "row", children: /* @__PURE__ */ jsxs("td", { className: "twc-dt__group-cell", role: "gridcell", colSpan: totalCols, style: { maxWidth: "none" }, children: [
@@ -11414,6 +11442,9 @@ function Datatable({
11414
11442
  function renderLeaf(row, ri, pinSide, midIdx) {
11415
11443
  const k = keyOf(row, ri);
11416
11444
  const sel = selected.has(k);
11445
+ const detail = hasRowDetail && !pinSide ? renderRowDetail(row) : null;
11446
+ const expandable = detail != null;
11447
+ const open = expandable && expandedSet.has(k);
11417
11448
  const rowActive = selectionMode === "row" && activeRowVal === k;
11418
11449
  const stickyStyle = pinSide === "top" ? { position: "sticky", top: headH, zIndex: 5 } : pinSide === "bottom" ? { position: "sticky", bottom: 0, zIndex: 5 } : void 0;
11419
11450
  const h = rowHeights[k];
@@ -11421,159 +11452,165 @@ function Datatable({
11421
11452
  const reorderable = canReorderRows && !pinSide;
11422
11453
  const grabbed = rowGrab && rowGrab.key === k;
11423
11454
  const grabTarget = rowGrab && !grabbed && midIdx != null && rowGrab.index === midIdx ? rowGrab.index >= keyIndexMid.get(rowGrab.key) ? "after" : "before" : void 0;
11424
- return /* @__PURE__ */ jsxs(
11425
- "tr",
11426
- {
11427
- className: "twc-dt__row",
11428
- role: "row",
11429
- "aria-rowindex": (paginated && !serverMode ? pageVal * sizeVal : 0) + ri + 2,
11430
- "aria-selected": (checkboxSelection ? sel : rowActive) || void 0,
11431
- "data-op": row.__diffop || void 0,
11432
- "data-selected": sel || void 0,
11433
- "data-active": rowActive || void 0,
11434
- "data-pinned-row": pinSide || void 0,
11435
- "data-reorderable": reorderable || void 0,
11436
- "data-row-dragging": rowDrag.from === k || void 0,
11437
- "data-row-grabbed": grabbed || void 0,
11438
- "data-row-grabtarget": grabTarget,
11439
- "data-row-dropbefore": reorderable && rowDrag.over === k && !rowDrag.after || void 0,
11440
- "data-row-dropafter": reorderable && rowDrag.over === k && rowDrag.after || void 0,
11441
- "data-vrow": virtualizing && midIdx != null ? String(k) : void 0,
11442
- "data-selectable": selectionMode !== "none" || void 0,
11443
- style: Object.keys(rowStyle).length ? rowStyle : void 0,
11444
- draggable: reorderable || void 0,
11445
- onDragStart: reorderable ? (e) => {
11446
- setRowDrag({ from: k, over: null, after: false });
11447
- e.dataTransfer.effectAllowed = "move";
11448
- e.dataTransfer.setData("text/plain", String(k));
11449
- } : void 0,
11450
- onDragOver: reorderable && rowDrag.from != null ? (e) => {
11451
- e.preventDefault();
11452
- const r2 = e.currentTarget.getBoundingClientRect();
11453
- const after = e.clientY > r2.top + r2.height / 2;
11454
- setRowDrag((d) => d.over === k && d.after === after ? d : { ...d, over: k, after });
11455
- } : void 0,
11456
- onDrop: reorderable && rowDrag.from != null ? (e) => {
11457
- e.preventDefault();
11458
- onRowDrop(k);
11459
- } : void 0,
11460
- onDragEnd: reorderable ? () => setRowDrag({ from: null, over: null, after: false }) : void 0,
11461
- onClick: (e) => handleRowClick(e, k, row),
11462
- children: [
11463
- checkboxSelection ? /* @__PURE__ */ jsxs("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", "data-pin-edge": visLeft.length || showRowNum ? void 0 : "left", style: { insetInlineStart: 0, width: CHK_W }, children: [
11464
- /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
11465
- reorderable ? rowHandle(k, midIdx, row) : null,
11466
- /* @__PURE__ */ jsx(
11467
- "span",
11455
+ return /* @__PURE__ */ jsxs(React51__default.Fragment, { children: [
11456
+ /* @__PURE__ */ jsxs(
11457
+ "tr",
11458
+ {
11459
+ className: "twc-dt__row",
11460
+ role: "row",
11461
+ "aria-rowindex": (paginated && !serverMode ? pageVal * sizeVal : 0) + ri + 2,
11462
+ "aria-selected": (checkboxSelection ? sel : rowActive) || void 0,
11463
+ "data-op": row.__diffop || void 0,
11464
+ "data-selected": sel || void 0,
11465
+ "data-active": rowActive || void 0,
11466
+ "data-pinned-row": pinSide || void 0,
11467
+ "data-reorderable": reorderable || void 0,
11468
+ "data-row-dragging": rowDrag.from === k || void 0,
11469
+ "data-row-grabbed": grabbed || void 0,
11470
+ "data-row-grabtarget": grabTarget,
11471
+ "data-row-dropbefore": reorderable && rowDrag.over === k && !rowDrag.after || void 0,
11472
+ "data-row-dropafter": reorderable && rowDrag.over === k && rowDrag.after || void 0,
11473
+ "data-vrow": virtualizing && midIdx != null ? String(k) : void 0,
11474
+ "data-selectable": selectionMode !== "none" || void 0,
11475
+ style: Object.keys(rowStyle).length ? rowStyle : void 0,
11476
+ draggable: reorderable || void 0,
11477
+ onDragStart: reorderable ? (e) => {
11478
+ setRowDrag({ from: k, over: null, after: false });
11479
+ e.dataTransfer.effectAllowed = "move";
11480
+ e.dataTransfer.setData("text/plain", String(k));
11481
+ } : void 0,
11482
+ onDragOver: reorderable && rowDrag.from != null ? (e) => {
11483
+ e.preventDefault();
11484
+ const r2 = e.currentTarget.getBoundingClientRect();
11485
+ const after = e.clientY > r2.top + r2.height / 2;
11486
+ setRowDrag((d) => d.over === k && d.after === after ? d : { ...d, over: k, after });
11487
+ } : void 0,
11488
+ onDrop: reorderable && rowDrag.from != null ? (e) => {
11489
+ e.preventDefault();
11490
+ onRowDrop(k);
11491
+ } : void 0,
11492
+ onDragEnd: reorderable ? () => setRowDrag({ from: null, over: null, after: false }) : void 0,
11493
+ onClick: (e) => handleRowClick(e, k, row),
11494
+ children: [
11495
+ hasRowDetail ? /* @__PURE__ */ jsx("td", { className: "twc-dt__td twc-dt__expand-cell", role: "gridcell", "data-pin": "left", "data-pin-edge": checkboxSelection || showRowNum || visLeft.length ? void 0 : "left", style: { insetInlineStart: 0, width: EXP_W }, children: expandable ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__expand-btn", "aria-expanded": open, "aria-controls": open ? `${gridId}-detail-${ri}` : void 0, "aria-label": open ? "Collapse row" : "Expand row", onClick: (e) => {
11496
+ e.stopPropagation();
11497
+ toggleRowDetail(k);
11498
+ }, children: /* @__PURE__ */ jsx("span", { className: "twc-dt__expand-chev", "data-open": open || void 0, children: /* @__PURE__ */ jsx(Svg, { d: I.chevDown }) }) }) : null }) : null,
11499
+ checkboxSelection ? /* @__PURE__ */ jsxs("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", "data-pin-edge": visLeft.length || showRowNum ? void 0 : "left", style: { insetInlineStart: EXP_W, width: CHK_W }, children: [
11500
+ /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
11501
+ reorderable ? rowHandle(k, midIdx, row) : null,
11502
+ /* @__PURE__ */ jsx(
11503
+ "span",
11504
+ {
11505
+ className: "twc-dt__check",
11506
+ "data-checked": sel || void 0,
11507
+ onClick: () => toggleRow(k),
11508
+ role: "checkbox",
11509
+ "aria-checked": sel,
11510
+ "aria-label": "Select row",
11511
+ tabIndex: 0,
11512
+ onKeyDown: (e) => {
11513
+ if (e.key === " " || e.key === "Enter") {
11514
+ e.preventDefault();
11515
+ toggleRow(k);
11516
+ }
11517
+ },
11518
+ children: /* @__PURE__ */ jsx(Svg, { d: I.check })
11519
+ }
11520
+ )
11521
+ ] }),
11522
+ rowResize && !pinSide ? /* @__PURE__ */ jsx("span", { className: "twc-dt__row-resizer", title: "Drag to resize row", onPointerDown: (e) => startRowResize(e, k, e.currentTarget.closest("tr")), onClick: (e) => e.stopPropagation() }) : null
11523
+ ] }) : null,
11524
+ showRowNum ? /* @__PURE__ */ jsx("td", { className: "twc-dt__td twc-dt__rownum", role: "gridcell", "aria-hidden": "true", "data-pin": "left", "data-pin-edge": visLeft.length ? void 0 : "left", style: { insetInlineStart: numLeft, width: NUM_W }, children: typeof ri === "number" ? (paginated || serverMode ? pageVal * sizeVal : 0) + ri + 1 : "" }) : null,
11525
+ ordered.map((c, ci) => {
11526
+ const st = stickyOf(c.field);
11527
+ const val = getColVal(c, row);
11528
+ const isActions = c.type === "actions";
11529
+ const editable = isColEditable(c);
11530
+ const isEditing = editing && editing.key === k && editing.field === c.field;
11531
+ const cellActive = selectionMode === "cell" && activeCell && activeCell.key === k && activeCell.field === c.field;
11532
+ const cellSelected = selectionMode === "cell" && cellRect && ri >= cellRect.r0 && ri <= cellRect.r1 && ci >= cellRect.c0 && ci <= cellRect.c1;
11533
+ const cellFlash = clipFx && clipFx.rect && ri >= clipFx.rect.r0 && ri <= clipFx.rect.r1 && ci >= clipFx.rect.c0 && ci <= clipFx.rect.c1;
11534
+ const cellId = selectionMode === "cell" ? `${gridId}-${ri}-${ci}` : void 0;
11535
+ const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
11536
+ const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
11537
+ const cellClass = c.cellClassName ? c.cellClassName(val, row) : void 0;
11538
+ const cellSty = c.cellStyle ? c.cellStyle(val, row) : void 0;
11539
+ return /* @__PURE__ */ jsxs(
11540
+ "td",
11468
11541
  {
11469
- className: "twc-dt__check",
11470
- "data-checked": sel || void 0,
11471
- onClick: () => toggleRow(k),
11472
- role: "checkbox",
11473
- "aria-checked": sel,
11474
- "aria-label": "Select row",
11475
- tabIndex: 0,
11476
- onKeyDown: (e) => {
11477
- if (e.key === " " || e.key === "Enter") {
11478
- e.preventDefault();
11479
- toggleRow(k);
11480
- }
11481
- },
11482
- children: /* @__PURE__ */ jsx(Svg, { d: I.check })
11483
- }
11484
- )
11485
- ] }),
11486
- rowResize && !pinSide ? /* @__PURE__ */ jsx("span", { className: "twc-dt__row-resizer", title: "Drag to resize row", onPointerDown: (e) => startRowResize(e, k, e.currentTarget.closest("tr")), onClick: (e) => e.stopPropagation() }) : null
11487
- ] }) : null,
11488
- showRowNum ? /* @__PURE__ */ jsx("td", { className: "twc-dt__td twc-dt__rownum", role: "gridcell", "aria-hidden": "true", "data-pin": "left", "data-pin-edge": visLeft.length ? void 0 : "left", style: { insetInlineStart: numLeft, width: NUM_W }, children: typeof ri === "number" ? (paginated || serverMode ? pageVal * sizeVal : 0) + ri + 1 : "" }) : null,
11489
- ordered.map((c, ci) => {
11490
- const st = stickyOf(c.field);
11491
- const val = getColVal(c, row);
11492
- const isActions = c.type === "actions";
11493
- const editable = isColEditable(c);
11494
- const isEditing = editing && editing.key === k && editing.field === c.field;
11495
- const cellActive = selectionMode === "cell" && activeCell && activeCell.key === k && activeCell.field === c.field;
11496
- const cellSelected = selectionMode === "cell" && cellRect && ri >= cellRect.r0 && ri <= cellRect.r1 && ci >= cellRect.c0 && ci <= cellRect.c1;
11497
- const cellFlash = clipFx && clipFx.rect && ri >= clipFx.rect.r0 && ri <= clipFx.rect.r1 && ci >= clipFx.rect.c0 && ci <= clipFx.rect.c1;
11498
- const cellId = selectionMode === "cell" ? `${gridId}-${ri}-${ci}` : void 0;
11499
- const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
11500
- const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
11501
- const cellClass = c.cellClassName ? c.cellClassName(val, row) : void 0;
11502
- const cellSty = c.cellStyle ? c.cellStyle(val, row) : void 0;
11503
- return /* @__PURE__ */ jsxs(
11504
- "td",
11505
- {
11506
- id: cellId,
11507
- className: cellClass ? `twc-dt__td ${cellClass}` : "twc-dt__td",
11508
- role: "gridcell",
11509
- "data-r": ri,
11510
- "data-c": ci,
11511
- "aria-colindex": ci + 1 + (checkboxSelection ? 1 : 0),
11512
- tabIndex: focus.r === ri && focus.c === ci ? 0 : -1,
11513
- "aria-selected": cellSelected || void 0,
11514
- "data-num": c.type === "number" || void 0,
11515
- "data-actions": isActions || void 0,
11516
- "data-editable": editable && !isEditing || void 0,
11517
- "data-editing": isEditing || void 0,
11518
- "data-cell-active": cellActive || void 0,
11519
- "data-cell-selected": cellSelected || void 0,
11520
- "data-copied": cellFlash || void 0,
11521
- "data-wrap": effectiveWrapped.has(c.field) || void 0,
11522
- "data-pin": st.pin,
11523
- "data-pin-edge": st.edge,
11524
- style: { width: widthOf(c), ...cellSty, ...st.style, ...st.pin ? { position: "sticky", zIndex: 2 } : null },
11525
- "data-ovtext": cellTitle,
11526
- onClick: selectionMode === "cell" ? (e) => handleCellClick(e, k, row, c) : void 0,
11527
- onFocus: () => setFocus((f) => f.r === ri && f.c === ci ? f : { r: ri, c: ci }),
11528
- onDoubleClick: editable ? () => beginEdit(k, c, row) : void 0,
11529
- children: [
11530
- !checkboxSelection && reorderable && ci === 0 && !isEditing ? /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 6, maxWidth: "100%" }, children: [
11531
- rowHandle(k, midIdx, row),
11532
- /* @__PURE__ */ jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis" }, children: isActions ? renderActions(c, row) : c.renderCell ? c.renderCell(val, row) : display })
11533
- ] }) : isEditing ? c.renderEditCell ? (
11534
- // #236: full escape hatch — the host drops in its own editor (searchable/creatable/
11535
- // async Combobox, …) and drives the lifecycle via commit/cancel. Wrapped in
11536
- // .twc-dt__editor-wrap so inline clicks (and any .twc-pop portal dropdown) don't
11537
- // trip the outside-click auto-cancel.
11538
- // #273: the wrapper handles Escape → cancel so it works for every custom editor without
11539
- // each having to wire a keydown (they only get commit/cancel). A custom control that
11540
- // wants Escape itself (e.g. to close its own open dropdown) stops propagation first.
11541
- /* @__PURE__ */ jsx(
11542
- "div",
11542
+ id: cellId,
11543
+ className: cellClass ? `twc-dt__td ${cellClass}` : "twc-dt__td",
11544
+ role: "gridcell",
11545
+ "data-r": ri,
11546
+ "data-c": ci,
11547
+ "aria-colindex": ci + 1 + (checkboxSelection ? 1 : 0) + (hasRowDetail ? 1 : 0),
11548
+ tabIndex: focus.r === ri && focus.c === ci ? 0 : -1,
11549
+ "aria-selected": cellSelected || void 0,
11550
+ "data-num": c.type === "number" || void 0,
11551
+ "data-actions": isActions || void 0,
11552
+ "data-editable": editable && !isEditing || void 0,
11553
+ "data-editing": isEditing || void 0,
11554
+ "data-cell-active": cellActive || void 0,
11555
+ "data-cell-selected": cellSelected || void 0,
11556
+ "data-copied": cellFlash || void 0,
11557
+ "data-wrap": effectiveWrapped.has(c.field) || void 0,
11558
+ "data-pin": st.pin,
11559
+ "data-pin-edge": st.edge,
11560
+ style: { width: widthOf(c), ...cellSty, ...st.style, ...st.pin ? { position: "sticky", zIndex: 2 } : null },
11561
+ "data-ovtext": cellTitle,
11562
+ onClick: selectionMode === "cell" ? (e) => handleCellClick(e, k, row, c) : void 0,
11563
+ onFocus: () => setFocus((f) => f.r === ri && f.c === ci ? f : { r: ri, c: ci }),
11564
+ onDoubleClick: editable ? () => beginEdit(k, c, row) : void 0,
11565
+ children: [
11566
+ !checkboxSelection && reorderable && ci === 0 && !isEditing ? /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 6, maxWidth: "100%" }, children: [
11567
+ rowHandle(k, midIdx, row),
11568
+ /* @__PURE__ */ jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis" }, children: isActions ? renderActions(c, row) : c.renderCell ? c.renderCell(val, row) : display })
11569
+ ] }) : isEditing ? c.renderEditCell ? (
11570
+ // #236: full escape hatch — the host drops in its own editor (searchable/creatable/
11571
+ // async Combobox, …) and drives the lifecycle via commit/cancel. Wrapped in
11572
+ // .twc-dt__editor-wrap so inline clicks (and any .twc-pop portal dropdown) don't
11573
+ // trip the outside-click auto-cancel.
11574
+ // #273: the wrapper handles Escape → cancel so it works for every custom editor without
11575
+ // each having to wire a keydown (they only get commit/cancel). A custom control that
11576
+ // wants Escape itself (e.g. to close its own open dropdown) stops propagation first.
11577
+ /* @__PURE__ */ jsx(
11578
+ "div",
11579
+ {
11580
+ className: "twc-dt__editor-wrap",
11581
+ onKeyDown: (e) => {
11582
+ if (e.key === "Escape") {
11583
+ e.stopPropagation();
11584
+ cancelEdit();
11585
+ }
11586
+ },
11587
+ children: c.renderEditCell({ value: editing.value, row, field: c.field, commit: (v) => commitEdit(v), cancel: cancelEdit })
11588
+ }
11589
+ )
11590
+ ) : /* @__PURE__ */ jsx(
11591
+ EditCell,
11543
11592
  {
11544
- className: "twc-dt__editor-wrap",
11545
- onKeyDown: (e) => {
11546
- if (e.key === "Escape") {
11547
- e.stopPropagation();
11548
- cancelEdit();
11549
- }
11550
- },
11551
- children: c.renderEditCell({ value: editing.value, row, field: c.field, commit: (v) => commitEdit(v), cancel: cancelEdit })
11593
+ col: c,
11594
+ value: editing.value,
11595
+ options: c.valueOptions ? optionsForField(c.field) : null,
11596
+ onChange: (v) => setEditing((e) => ({ ...e, value: v })),
11597
+ onCommit: () => commitEdit(),
11598
+ onCommitValue: (v) => commitEdit(v),
11599
+ onCancel: cancelEdit,
11600
+ onKeyDown: onEditKey
11552
11601
  }
11553
- )
11554
- ) : /* @__PURE__ */ jsx(
11555
- EditCell,
11556
- {
11557
- col: c,
11558
- value: editing.value,
11559
- options: c.valueOptions ? optionsForField(c.field) : null,
11560
- onChange: (v) => setEditing((e) => ({ ...e, value: v })),
11561
- onCommit: () => commitEdit(),
11562
- onCommitValue: (v) => commitEdit(v),
11563
- onCancel: cancelEdit,
11564
- onKeyDown: onEditKey
11565
- }
11566
- ) : isActions ? renderActions(c, row) : c.renderCell ? c.renderCell(val, row) : display,
11567
- editable && !isEditing ? /* @__PURE__ */ jsx("span", { className: "twc-dt__edit-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Svg, { d: I.pencil }) }) : null
11568
- ]
11569
- },
11570
- c.field
11571
- );
11572
- })
11573
- ]
11574
- },
11575
- (pinSide ? "p-" : "") + k
11576
- );
11602
+ ) : isActions ? renderActions(c, row) : c.renderCell ? c.renderCell(val, row) : display,
11603
+ editable && !isEditing ? /* @__PURE__ */ jsx("span", { className: "twc-dt__edit-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Svg, { d: I.pencil }) }) : null
11604
+ ]
11605
+ },
11606
+ c.field
11607
+ );
11608
+ })
11609
+ ]
11610
+ }
11611
+ ),
11612
+ open ? /* @__PURE__ */ jsx("tr", { className: "twc-dt__detail-row", role: "row", children: /* @__PURE__ */ jsx("td", { id: `${gridId}-detail-${ri}`, className: "twc-dt__detail-cell", role: "gridcell", colSpan: totalCols, style: { maxWidth: "none" }, children: detail }) }) : null
11613
+ ] }, (pinSide ? "p-" : "") + k);
11577
11614
  }
11578
11615
  function renderPivot() {
11579
11616
  const rFields = pivotConfig.rows, cFields = pivotConfig.columns || [], values = pivotConfig.values;
@@ -11910,14 +11947,15 @@ function Datatable({
11910
11947
  role: "grid",
11911
11948
  "aria-label": ariaLabelAttr || ariaLabel,
11912
11949
  "aria-rowcount": totalRows + 1,
11913
- "aria-colcount": ordered.length + (checkboxSelection ? 1 : 0),
11950
+ "aria-colcount": ordered.length + (checkboxSelection ? 1 : 0) + (hasRowDetail ? 1 : 0),
11914
11951
  "aria-multiselectable": selectionMode === "cell" || void 0,
11915
11952
  "aria-activedescendant": activeCellId,
11916
11953
  "aria-busy": loading || void 0,
11917
11954
  onKeyDown: onGridKeyDown,
11918
11955
  children: [
11919
11956
  /* @__PURE__ */ jsx("thead", { ref: theadRef, children: /* @__PURE__ */ jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
11920
- checkboxSelection ? /* @__PURE__ */ jsx("th", { className: "twc-dt__th", role: "columnheader", "aria-label": "Select", "data-pin": "left", "data-pin-edge": visLeft.length || showRowNum ? void 0 : "left", style: { insetInlineStart: 0, width: CHK_W, minWidth: CHK_W }, children: /* @__PURE__ */ jsx("div", { className: "twc-dt__th-inner", style: { justifyContent: "center", padding: 0 }, children: /* @__PURE__ */ jsx(
11957
+ hasRowDetail ? /* @__PURE__ */ jsx("th", { className: "twc-dt__th twc-dt__expand-cell", role: "columnheader", "aria-label": "Expand", "data-pin": "left", "data-pin-edge": checkboxSelection || showRowNum || visLeft.length ? void 0 : "left", style: { insetInlineStart: 0, width: EXP_W, minWidth: EXP_W } }) : null,
11958
+ checkboxSelection ? /* @__PURE__ */ jsx("th", { className: "twc-dt__th", role: "columnheader", "aria-label": "Select", "data-pin": "left", "data-pin-edge": visLeft.length || showRowNum ? void 0 : "left", style: { insetInlineStart: EXP_W, width: CHK_W, minWidth: CHK_W }, children: /* @__PURE__ */ jsx("div", { className: "twc-dt__th-inner", style: { justifyContent: "center", padding: 0 }, children: /* @__PURE__ */ jsx(
11921
11959
  "span",
11922
11960
  {
11923
11961
  className: "twc-dt__check",
@@ -12093,7 +12131,8 @@ function Datatable({
12093
12131
  })
12094
12132
  ] }) }),
12095
12133
  /* @__PURE__ */ jsx("tbody", { children: loading ? Array.from({ length: paginated ? Math.min(sizeVal, 8) : 8 }).map((_, ri) => /* @__PURE__ */ jsxs("tr", { className: "twc-dt__row", role: "row", children: [
12096
- checkboxSelection ? /* @__PURE__ */ jsx("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", style: { insetInlineStart: 0, width: CHK_W }, children: /* @__PURE__ */ jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "18px", height: 18, borderRadius: 4 } }) }) : null,
12134
+ hasRowDetail ? /* @__PURE__ */ jsx("td", { className: "twc-dt__td twc-dt__expand-cell", role: "gridcell", "data-pin": "left", style: { insetInlineStart: 0, width: EXP_W } }) : null,
12135
+ checkboxSelection ? /* @__PURE__ */ jsx("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", style: { insetInlineStart: EXP_W, width: CHK_W }, children: /* @__PURE__ */ jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "18px", height: 18, borderRadius: 4 } }) }) : null,
12097
12136
  showRowNum ? /* @__PURE__ */ jsx("td", { className: "twc-dt__td twc-dt__rownum", role: "gridcell", "aria-hidden": "true", "data-pin": "left", style: { insetInlineStart: numLeft, width: NUM_W }, children: /* @__PURE__ */ jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "16px", height: 14, borderRadius: 4 } }) }) : null,
12098
12137
  ordered.map((c, ci) => {
12099
12138
  const st = stickyOf(c.field);
@@ -12115,7 +12154,8 @@ function Datatable({
12115
12154
  pinnedBottomRows.map((row) => renderLeaf(row, keyIndex.get(keyOf(row)), "bottom"))
12116
12155
  ] }) }),
12117
12156
  hasAggregation && aggOn && !loading && paged.length > 0 ? /* @__PURE__ */ jsx("tfoot", { children: /* @__PURE__ */ jsxs("tr", { role: "row", children: [
12118
- checkboxSelection ? /* @__PURE__ */ jsx("td", { role: "gridcell", "data-pin": "left", "data-pin-edge": visLeft.length || showRowNum ? void 0 : "left", style: { insetInlineStart: 0, width: CHK_W } }) : null,
12157
+ hasRowDetail ? /* @__PURE__ */ jsx("td", { role: "gridcell", "data-pin": "left", "data-pin-edge": checkboxSelection || showRowNum || visLeft.length ? void 0 : "left", style: { insetInlineStart: 0, width: EXP_W } }) : null,
12158
+ checkboxSelection ? /* @__PURE__ */ jsx("td", { role: "gridcell", "data-pin": "left", "data-pin-edge": visLeft.length || showRowNum ? void 0 : "left", style: { insetInlineStart: EXP_W, width: CHK_W } }) : null,
12119
12159
  showRowNum ? /* @__PURE__ */ jsx("td", { className: "twc-dt__rownum", role: "gridcell", "aria-hidden": "true", "data-pin": "left", "data-pin-edge": visLeft.length ? void 0 : "left", style: { insetInlineStart: numLeft, width: NUM_W } }) : null,
12120
12160
  ordered.map((c) => {
12121
12161
  const st = stickyOf(c.field);
@@ -17150,6 +17190,18 @@ var SLIDER_CSS = `
17150
17190
  transition: opacity var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-spring);
17151
17191
  }
17152
17192
  .twc-slider__track[data-show-bubble="true"] .twc-slider__bubble { opacity: 1; transform: translateX(-50%) scale(1); }
17193
+ /* #351: editable numeric inputs (manual entry, synced with the thumbs) */
17194
+ .twc-slider__inputs { display: inline-flex; align-items: center; gap: var(--space-1-5); }
17195
+ .twc-slider__input-sep { color: var(--color-text-muted); font-weight: var(--font-semibold); }
17196
+ .twc-slider__input {
17197
+ min-width: 0; text-align: end; font-family: inherit; font-size: var(--text-sm); font-weight: var(--font-semibold);
17198
+ color: var(--_accent); font-variant-numeric: tabular-nums;
17199
+ padding: 2px 6px; border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-sm); background: var(--color-surface);
17200
+ transition: border-color var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard);
17201
+ }
17202
+ .twc-slider__input:hover:not(:disabled) { border-color: var(--color-border-strong); }
17203
+ .twc-slider__input:focus-visible { outline: none; border-color: var(--_accent); box-shadow: var(--ring); }
17204
+ .twc-slider[data-disabled="true"] .twc-slider__input { cursor: not-allowed; }
17153
17205
  `;
17154
17206
  function Slider({
17155
17207
  label,
@@ -17168,6 +17220,7 @@ function Slider({
17168
17220
  range: range2 = false,
17169
17221
  showValue = true,
17170
17222
  showTicks = false,
17223
+ editable = false,
17171
17224
  formatValue,
17172
17225
  getAriaValueText,
17173
17226
  name,
@@ -17206,6 +17259,7 @@ function Slider({
17206
17259
  const indices = isRange ? [0, 1] : [0];
17207
17260
  const trackRef = React51__default.useRef(null);
17208
17261
  const [dragging, setDragging] = React51__default.useState(false);
17262
+ const [typed, setTyped] = React51__default.useState({});
17209
17263
  const fmt = (v) => formatValue ? formatValue(v) : v.toFixed(decimals);
17210
17264
  const valueTextFor = (v) => {
17211
17265
  if (getAriaValueText) return getAriaValueText(v);
@@ -17229,6 +17283,27 @@ function Slider({
17229
17283
  }
17230
17284
  commit(next);
17231
17285
  };
17286
+ const commitInput = (i) => {
17287
+ const s = typed[i];
17288
+ setTyped((t) => {
17289
+ const n = { ...t };
17290
+ delete n[i];
17291
+ return n;
17292
+ });
17293
+ if (s == null || String(s).trim() === "") return;
17294
+ const num = parseFloat(s);
17295
+ if (Number.isFinite(num)) setThumb(i, num);
17296
+ };
17297
+ const stepInput = (i, dir) => {
17298
+ const base = typed[i] != null && Number.isFinite(parseFloat(typed[i])) ? parseFloat(typed[i]) : clampedVals[i];
17299
+ setTyped((t) => {
17300
+ const n = { ...t };
17301
+ delete n[i];
17302
+ return n;
17303
+ });
17304
+ setThumb(i, base + dir * step);
17305
+ };
17306
+ const inputSize = Math.max(String(min).length, String(max).length, decimals > 0 ? decimals + 2 : 1) + 1;
17232
17307
  const fromClientX = (clientX) => {
17233
17308
  const node = trackRef.current;
17234
17309
  if (!node) return clampedVals[0];
@@ -17284,9 +17359,40 @@ function Slider({
17284
17359
  return /* @__PURE__ */ jsxs("div", { className: `twc-slider ${className}`, "data-tone": tone, "data-size": size, "data-disabled": disabled || void 0, ...rest, children: [
17285
17360
  __twcStyles,
17286
17361
  name ? clampedVals.map((v, i) => /* @__PURE__ */ jsx("input", { type: "hidden", name, value: v, disabled: disabled || void 0 }, i)) : null,
17287
- label || showValue ? /* @__PURE__ */ jsxs("div", { className: "twc-slider__head", children: [
17362
+ label || showValue || editable ? /* @__PURE__ */ jsxs("div", { className: "twc-slider__head", children: [
17288
17363
  label ? /* @__PURE__ */ jsx("label", { className: "twc-slider__label", id: labelId, children: label }) : /* @__PURE__ */ jsx("span", {}),
17289
- showValue ? /* @__PURE__ */ jsx("span", { className: "twc-slider__value", children: headValue }) : null
17364
+ editable ? /* @__PURE__ */ jsx("span", { className: "twc-slider__inputs", children: indices.map((i) => /* @__PURE__ */ jsxs(React51__default.Fragment, { children: [
17365
+ i > 0 ? /* @__PURE__ */ jsx("span", { className: "twc-slider__input-sep", "aria-hidden": "true", children: "\u2013" }) : null,
17366
+ /* @__PURE__ */ jsx(
17367
+ "input",
17368
+ {
17369
+ className: "twc-slider__input",
17370
+ type: "text",
17371
+ inputMode: min < 0 ? "text" : "decimal",
17372
+ size: inputSize,
17373
+ value: typed[i] != null ? typed[i] : String(clampedVals[i]),
17374
+ disabled: disabled || void 0,
17375
+ "aria-labelledby": isRange ? void 0 : labelId,
17376
+ "aria-label": isRange ? i === 0 ? "Minimum value" : "Maximum value" : labelId ? void 0 : "Value",
17377
+ onChange: (e) => setTyped((t) => ({ ...t, [i]: e.target.value })),
17378
+ onBlur: () => commitInput(i),
17379
+ onKeyDown: (e) => {
17380
+ if (e.key === "Enter") {
17381
+ if (typed[i] != null) {
17382
+ e.preventDefault();
17383
+ commitInput(i);
17384
+ }
17385
+ } else if (e.key === "ArrowUp") {
17386
+ e.preventDefault();
17387
+ stepInput(i, 1);
17388
+ } else if (e.key === "ArrowDown") {
17389
+ e.preventDefault();
17390
+ stepInput(i, -1);
17391
+ }
17392
+ }
17393
+ }
17394
+ )
17395
+ ] }, i)) }) : showValue ? /* @__PURE__ */ jsx("span", { className: "twc-slider__value", children: headValue }) : null
17290
17396
  ] }) : null,
17291
17397
  /* @__PURE__ */ jsxs("div", { className: "twc-slider__track", ref: trackRef, "data-show-bubble": dragging || void 0, onPointerDown, children: [
17292
17398
  /* @__PURE__ */ jsx("div", { className: "twc-slider__rail" }),