twico-ui 1.33.1 → 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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
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.34.0](https://github.com/EncryptionL/twico-ui/compare/v1.33.1...v1.34.0) (2026-08-14)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **slider:** harden editable inputs (a11y label, mobile minus, form Enter) ([8acc6aa](https://github.com/EncryptionL/twico-ui/commit/8acc6aae0d805fa57da3622595423b5658b61710)), closes [#351](https://github.com/EncryptionL/twico-ui/issues/351)
11
+
12
+
13
+ ### Features
14
+
15
+ * **datatable:** expandable/collapsible rows via renderRowDetail (first pass) ([54a4954](https://github.com/EncryptionL/twico-ui/commit/54a495485f3430a37c4ce4191a96653a286d8307))
16
+ * **slider:** editable numeric inputs — type an exact value, synced with thumbs ([b804c65](https://github.com/EncryptionL/twico-ui/commit/b804c650e416a01992a1fc13d67bed2ec5e45250))
17
+
5
18
  ## [1.33.1](https://github.com/EncryptionL/twico-ui/compare/v1.33.0...v1.33.1) (2026-08-11)
6
19
 
7
20
 
package/dist/index.cjs CHANGED
@@ -8383,6 +8383,16 @@ var DT_CSS = `
8383
8383
  .twc-dt__group-sub span { font-variant-numeric: tabular-nums; }
8384
8384
  .twc-dt__group-sub span b { color: var(--color-text); }
8385
8385
 
8386
+ /* #350: expandable row detail (renderRowDetail) \u2014 leading chevron column + full-width detail row */
8387
+ .twc-dt__expand-cell { text-align: center; padding-inline: 0; }
8388
+ .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); }
8389
+ .twc-dt__expand-btn:hover { color: var(--color-text); background: color-mix(in srgb, currentColor 12%, transparent); }
8390
+ .twc-dt__expand-btn:focus-visible { outline: none; box-shadow: var(--ring); }
8391
+ .twc-dt__expand-chev { display: inline-flex; transform: rotate(-90deg); transition: transform var(--duration-base) var(--ease-spring); }
8392
+ .twc-dt__expand-chev[data-open] { transform: rotate(0deg); color: var(--color-primary); }
8393
+ .twc-dt__expand-chev svg { width: 16px; height: 16px; }
8394
+ .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; }
8395
+
8386
8396
  /* Pivot view */
8387
8397
  .twc-dt__pivot { table-layout: auto; width: 100%; }
8388
8398
  .twc-dt__pivot th, .twc-dt__pivot td { white-space: nowrap; }
@@ -9330,6 +9340,13 @@ function Datatable({
9330
9340
  virtualized = false,
9331
9341
  overscan = 8,
9332
9342
  rowHeight,
9343
+ // #350 (first pass): expandable/collapsible rows. `renderRowDetail(row)` returns the detail panel for a
9344
+ // row (return null/undefined ⇒ that row isn't expandable). Presence of the fn enables a leading chevron
9345
+ // column + a full-width detail <tr> under each expanded row. Expansion is uncontrolled unless
9346
+ // `expandedRowIds` is supplied; `onExpandedRowsChange` fires the next expanded-key array on every toggle.
9347
+ renderRowDetail,
9348
+ expandedRowIds,
9349
+ onExpandedRowsChange,
9333
9350
  className = "",
9334
9351
  ...rest
9335
9352
  }) {
@@ -9536,6 +9553,12 @@ function Datatable({
9536
9553
  const gridRef = React51__namespace.default.useRef(null);
9537
9554
  const [groupBy, setGroupBy] = React51__namespace.default.useState(rowGrouping || []);
9538
9555
  const [collapsed, setCollapsed] = React51__namespace.default.useState(() => /* @__PURE__ */ new Set());
9556
+ const [internalExpanded, setInternalExpanded] = React51__namespace.default.useState(() => /* @__PURE__ */ new Set());
9557
+ const expandControlled = expandedRowIds !== void 0;
9558
+ const expandedSet = React51__namespace.default.useMemo(
9559
+ () => new Set(expandControlled ? expandedRowIds : internalExpanded),
9560
+ [expandControlled, expandedRowIds, internalExpanded]
9561
+ );
9539
9562
  const [pinnedRows, setPinnedRows] = React51__namespace.default.useState({ top: [], bottom: [] });
9540
9563
  const [headH, setHeadH] = React51__namespace.default.useState(41);
9541
9564
  const theadRef = React51__namespace.default.useRef(null);
@@ -9937,7 +9960,9 @@ function Datatable({
9937
9960
  }, [pins, effectiveHidden, visibleCols, colByField, order]);
9938
9961
  const NUM_W = 56;
9939
9962
  const CHK_W = checkboxSelection ? rowReorder ? 68 : 44 : 0;
9940
- const numLeft = CHK_W;
9963
+ const hasRowDetail = typeof renderRowDetail === "function";
9964
+ const EXP_W = hasRowDetail ? 44 : 0;
9965
+ const numLeft = EXP_W + CHK_W;
9941
9966
  const leadW = numLeft + (showRowNum ? NUM_W : 0);
9942
9967
  const visLeft = React51__namespace.default.useMemo(() => pins.left.filter((f) => colByField[f] && !effectiveHidden.has(f)), [pins.left, colByField, effectiveHidden]);
9943
9968
  const visRight = React51__namespace.default.useMemo(() => pins.right.filter((f) => colByField[f] && !effectiveHidden.has(f)), [pins.right, colByField, effectiveHidden]);
@@ -10532,6 +10557,12 @@ function Datatable({
10532
10557
  return n;
10533
10558
  });
10534
10559
  }
10560
+ function toggleRowDetail(k) {
10561
+ const n = new Set(expandedSet);
10562
+ n.has(k) ? n.delete(k) : n.add(k);
10563
+ onExpandedRowsChange?.([...n]);
10564
+ if (!expandControlled) setInternalExpanded(n);
10565
+ }
10535
10566
  function handleRowClick(e, k, row) {
10536
10567
  if (selectionMode === "none") return;
10537
10568
  if (e.target.closest("button, a, input, select, .twc-dt__check, .twc-dt__editor-wrap")) return;
@@ -11378,7 +11409,7 @@ function Datatable({
11378
11409
  const manageableCols = cols.filter((c) => c.field !== "__pinactions__");
11379
11410
  const visibleColCount = manageableCols.filter((c) => !effectiveHidden.has(c.field)).length;
11380
11411
  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) }));
11381
- const totalCols = ordered.length + (checkboxSelection ? 1 : 0) + (showRowNum ? 1 : 0);
11412
+ const totalCols = ordered.length + (checkboxSelection ? 1 : 0) + (showRowNum ? 1 : 0) + (hasRowDetail ? 1 : 0);
11382
11413
  function renderGroupRow(item) {
11383
11414
  const subs = aggOn ? subtotalText(item.rows) : [];
11384
11415
  return /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "twc-dt__group-row", role: "row", children: /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "twc-dt__group-cell", role: "gridcell", colSpan: totalCols, style: { maxWidth: "none" }, children: [
@@ -11432,6 +11463,9 @@ function Datatable({
11432
11463
  function renderLeaf(row, ri, pinSide, midIdx) {
11433
11464
  const k = keyOf(row, ri);
11434
11465
  const sel = selected.has(k);
11466
+ const detail = hasRowDetail && !pinSide ? renderRowDetail(row) : null;
11467
+ const expandable = detail != null;
11468
+ const open = expandable && expandedSet.has(k);
11435
11469
  const rowActive = selectionMode === "row" && activeRowVal === k;
11436
11470
  const stickyStyle = pinSide === "top" ? { position: "sticky", top: headH, zIndex: 5 } : pinSide === "bottom" ? { position: "sticky", bottom: 0, zIndex: 5 } : void 0;
11437
11471
  const h = rowHeights[k];
@@ -11439,159 +11473,165 @@ function Datatable({
11439
11473
  const reorderable = canReorderRows && !pinSide;
11440
11474
  const grabbed = rowGrab && rowGrab.key === k;
11441
11475
  const grabTarget = rowGrab && !grabbed && midIdx != null && rowGrab.index === midIdx ? rowGrab.index >= keyIndexMid.get(rowGrab.key) ? "after" : "before" : void 0;
11442
- return /* @__PURE__ */ jsxRuntime.jsxs(
11443
- "tr",
11444
- {
11445
- className: "twc-dt__row",
11446
- role: "row",
11447
- "aria-rowindex": (paginated && !serverMode ? pageVal * sizeVal : 0) + ri + 2,
11448
- "aria-selected": (checkboxSelection ? sel : rowActive) || void 0,
11449
- "data-op": row.__diffop || void 0,
11450
- "data-selected": sel || void 0,
11451
- "data-active": rowActive || void 0,
11452
- "data-pinned-row": pinSide || void 0,
11453
- "data-reorderable": reorderable || void 0,
11454
- "data-row-dragging": rowDrag.from === k || void 0,
11455
- "data-row-grabbed": grabbed || void 0,
11456
- "data-row-grabtarget": grabTarget,
11457
- "data-row-dropbefore": reorderable && rowDrag.over === k && !rowDrag.after || void 0,
11458
- "data-row-dropafter": reorderable && rowDrag.over === k && rowDrag.after || void 0,
11459
- "data-vrow": virtualizing && midIdx != null ? String(k) : void 0,
11460
- "data-selectable": selectionMode !== "none" || void 0,
11461
- style: Object.keys(rowStyle).length ? rowStyle : void 0,
11462
- draggable: reorderable || void 0,
11463
- onDragStart: reorderable ? (e) => {
11464
- setRowDrag({ from: k, over: null, after: false });
11465
- e.dataTransfer.effectAllowed = "move";
11466
- e.dataTransfer.setData("text/plain", String(k));
11467
- } : void 0,
11468
- onDragOver: reorderable && rowDrag.from != null ? (e) => {
11469
- e.preventDefault();
11470
- const r2 = e.currentTarget.getBoundingClientRect();
11471
- const after = e.clientY > r2.top + r2.height / 2;
11472
- setRowDrag((d) => d.over === k && d.after === after ? d : { ...d, over: k, after });
11473
- } : void 0,
11474
- onDrop: reorderable && rowDrag.from != null ? (e) => {
11475
- e.preventDefault();
11476
- onRowDrop(k);
11477
- } : void 0,
11478
- onDragEnd: reorderable ? () => setRowDrag({ from: null, over: null, after: false }) : void 0,
11479
- onClick: (e) => handleRowClick(e, k, row),
11480
- children: [
11481
- checkboxSelection ? /* @__PURE__ */ jsxRuntime.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: [
11482
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
11483
- reorderable ? rowHandle(k, midIdx, row) : null,
11484
- /* @__PURE__ */ jsxRuntime.jsx(
11485
- "span",
11476
+ return /* @__PURE__ */ jsxRuntime.jsxs(React51__namespace.default.Fragment, { children: [
11477
+ /* @__PURE__ */ jsxRuntime.jsxs(
11478
+ "tr",
11479
+ {
11480
+ className: "twc-dt__row",
11481
+ role: "row",
11482
+ "aria-rowindex": (paginated && !serverMode ? pageVal * sizeVal : 0) + ri + 2,
11483
+ "aria-selected": (checkboxSelection ? sel : rowActive) || void 0,
11484
+ "data-op": row.__diffop || void 0,
11485
+ "data-selected": sel || void 0,
11486
+ "data-active": rowActive || void 0,
11487
+ "data-pinned-row": pinSide || void 0,
11488
+ "data-reorderable": reorderable || void 0,
11489
+ "data-row-dragging": rowDrag.from === k || void 0,
11490
+ "data-row-grabbed": grabbed || void 0,
11491
+ "data-row-grabtarget": grabTarget,
11492
+ "data-row-dropbefore": reorderable && rowDrag.over === k && !rowDrag.after || void 0,
11493
+ "data-row-dropafter": reorderable && rowDrag.over === k && rowDrag.after || void 0,
11494
+ "data-vrow": virtualizing && midIdx != null ? String(k) : void 0,
11495
+ "data-selectable": selectionMode !== "none" || void 0,
11496
+ style: Object.keys(rowStyle).length ? rowStyle : void 0,
11497
+ draggable: reorderable || void 0,
11498
+ onDragStart: reorderable ? (e) => {
11499
+ setRowDrag({ from: k, over: null, after: false });
11500
+ e.dataTransfer.effectAllowed = "move";
11501
+ e.dataTransfer.setData("text/plain", String(k));
11502
+ } : void 0,
11503
+ onDragOver: reorderable && rowDrag.from != null ? (e) => {
11504
+ e.preventDefault();
11505
+ const r2 = e.currentTarget.getBoundingClientRect();
11506
+ const after = e.clientY > r2.top + r2.height / 2;
11507
+ setRowDrag((d) => d.over === k && d.after === after ? d : { ...d, over: k, after });
11508
+ } : void 0,
11509
+ onDrop: reorderable && rowDrag.from != null ? (e) => {
11510
+ e.preventDefault();
11511
+ onRowDrop(k);
11512
+ } : void 0,
11513
+ onDragEnd: reorderable ? () => setRowDrag({ from: null, over: null, after: false }) : void 0,
11514
+ onClick: (e) => handleRowClick(e, k, row),
11515
+ children: [
11516
+ hasRowDetail ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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) => {
11517
+ e.stopPropagation();
11518
+ toggleRowDetail(k);
11519
+ }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__expand-chev", "data-open": open || void 0, children: /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.chevDown }) }) }) : null }) : null,
11520
+ checkboxSelection ? /* @__PURE__ */ jsxRuntime.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: [
11521
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
11522
+ reorderable ? rowHandle(k, midIdx, row) : null,
11523
+ /* @__PURE__ */ jsxRuntime.jsx(
11524
+ "span",
11525
+ {
11526
+ className: "twc-dt__check",
11527
+ "data-checked": sel || void 0,
11528
+ onClick: () => toggleRow(k),
11529
+ role: "checkbox",
11530
+ "aria-checked": sel,
11531
+ "aria-label": "Select row",
11532
+ tabIndex: 0,
11533
+ onKeyDown: (e) => {
11534
+ if (e.key === " " || e.key === "Enter") {
11535
+ e.preventDefault();
11536
+ toggleRow(k);
11537
+ }
11538
+ },
11539
+ children: /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.check })
11540
+ }
11541
+ )
11542
+ ] }),
11543
+ rowResize && !pinSide ? /* @__PURE__ */ jsxRuntime.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
11544
+ ] }) : null,
11545
+ showRowNum ? /* @__PURE__ */ jsxRuntime.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,
11546
+ ordered.map((c, ci) => {
11547
+ const st = stickyOf(c.field);
11548
+ const val = getColVal(c, row);
11549
+ const isActions = c.type === "actions";
11550
+ const editable = isColEditable(c);
11551
+ const isEditing = editing && editing.key === k && editing.field === c.field;
11552
+ const cellActive = selectionMode === "cell" && activeCell && activeCell.key === k && activeCell.field === c.field;
11553
+ const cellSelected = selectionMode === "cell" && cellRect && ri >= cellRect.r0 && ri <= cellRect.r1 && ci >= cellRect.c0 && ci <= cellRect.c1;
11554
+ const cellFlash = clipFx && clipFx.rect && ri >= clipFx.rect.r0 && ri <= clipFx.rect.r1 && ci >= clipFx.rect.c0 && ci <= clipFx.rect.c1;
11555
+ const cellId = selectionMode === "cell" ? `${gridId}-${ri}-${ci}` : void 0;
11556
+ const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
11557
+ const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
11558
+ const cellClass = c.cellClassName ? c.cellClassName(val, row) : void 0;
11559
+ const cellSty = c.cellStyle ? c.cellStyle(val, row) : void 0;
11560
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11561
+ "td",
11486
11562
  {
11487
- className: "twc-dt__check",
11488
- "data-checked": sel || void 0,
11489
- onClick: () => toggleRow(k),
11490
- role: "checkbox",
11491
- "aria-checked": sel,
11492
- "aria-label": "Select row",
11493
- tabIndex: 0,
11494
- onKeyDown: (e) => {
11495
- if (e.key === " " || e.key === "Enter") {
11496
- e.preventDefault();
11497
- toggleRow(k);
11498
- }
11499
- },
11500
- children: /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.check })
11501
- }
11502
- )
11503
- ] }),
11504
- rowResize && !pinSide ? /* @__PURE__ */ jsxRuntime.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
11505
- ] }) : null,
11506
- showRowNum ? /* @__PURE__ */ jsxRuntime.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,
11507
- ordered.map((c, ci) => {
11508
- const st = stickyOf(c.field);
11509
- const val = getColVal(c, row);
11510
- const isActions = c.type === "actions";
11511
- const editable = isColEditable(c);
11512
- const isEditing = editing && editing.key === k && editing.field === c.field;
11513
- const cellActive = selectionMode === "cell" && activeCell && activeCell.key === k && activeCell.field === c.field;
11514
- const cellSelected = selectionMode === "cell" && cellRect && ri >= cellRect.r0 && ri <= cellRect.r1 && ci >= cellRect.c0 && ci <= cellRect.c1;
11515
- const cellFlash = clipFx && clipFx.rect && ri >= clipFx.rect.r0 && ri <= clipFx.rect.r1 && ci >= clipFx.rect.c0 && ci <= clipFx.rect.c1;
11516
- const cellId = selectionMode === "cell" ? `${gridId}-${ri}-${ci}` : void 0;
11517
- const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
11518
- const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
11519
- const cellClass = c.cellClassName ? c.cellClassName(val, row) : void 0;
11520
- const cellSty = c.cellStyle ? c.cellStyle(val, row) : void 0;
11521
- return /* @__PURE__ */ jsxRuntime.jsxs(
11522
- "td",
11523
- {
11524
- id: cellId,
11525
- className: cellClass ? `twc-dt__td ${cellClass}` : "twc-dt__td",
11526
- role: "gridcell",
11527
- "data-r": ri,
11528
- "data-c": ci,
11529
- "aria-colindex": ci + 1 + (checkboxSelection ? 1 : 0),
11530
- tabIndex: focus.r === ri && focus.c === ci ? 0 : -1,
11531
- "aria-selected": cellSelected || void 0,
11532
- "data-num": c.type === "number" || void 0,
11533
- "data-actions": isActions || void 0,
11534
- "data-editable": editable && !isEditing || void 0,
11535
- "data-editing": isEditing || void 0,
11536
- "data-cell-active": cellActive || void 0,
11537
- "data-cell-selected": cellSelected || void 0,
11538
- "data-copied": cellFlash || void 0,
11539
- "data-wrap": effectiveWrapped.has(c.field) || void 0,
11540
- "data-pin": st.pin,
11541
- "data-pin-edge": st.edge,
11542
- style: { width: widthOf(c), ...cellSty, ...st.style, ...st.pin ? { position: "sticky", zIndex: 2 } : null },
11543
- "data-ovtext": cellTitle,
11544
- onClick: selectionMode === "cell" ? (e) => handleCellClick(e, k, row, c) : void 0,
11545
- onFocus: () => setFocus((f) => f.r === ri && f.c === ci ? f : { r: ri, c: ci }),
11546
- onDoubleClick: editable ? () => beginEdit(k, c, row) : void 0,
11547
- children: [
11548
- !checkboxSelection && reorderable && ci === 0 && !isEditing ? /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 6, maxWidth: "100%" }, children: [
11549
- rowHandle(k, midIdx, row),
11550
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis" }, children: isActions ? renderActions(c, row) : c.renderCell ? c.renderCell(val, row) : display })
11551
- ] }) : isEditing ? c.renderEditCell ? (
11552
- // #236: full escape hatch — the host drops in its own editor (searchable/creatable/
11553
- // async Combobox, …) and drives the lifecycle via commit/cancel. Wrapped in
11554
- // .twc-dt__editor-wrap so inline clicks (and any .twc-pop portal dropdown) don't
11555
- // trip the outside-click auto-cancel.
11556
- // #273: the wrapper handles Escape → cancel so it works for every custom editor without
11557
- // each having to wire a keydown (they only get commit/cancel). A custom control that
11558
- // wants Escape itself (e.g. to close its own open dropdown) stops propagation first.
11559
- /* @__PURE__ */ jsxRuntime.jsx(
11560
- "div",
11563
+ id: cellId,
11564
+ className: cellClass ? `twc-dt__td ${cellClass}` : "twc-dt__td",
11565
+ role: "gridcell",
11566
+ "data-r": ri,
11567
+ "data-c": ci,
11568
+ "aria-colindex": ci + 1 + (checkboxSelection ? 1 : 0) + (hasRowDetail ? 1 : 0),
11569
+ tabIndex: focus.r === ri && focus.c === ci ? 0 : -1,
11570
+ "aria-selected": cellSelected || void 0,
11571
+ "data-num": c.type === "number" || void 0,
11572
+ "data-actions": isActions || void 0,
11573
+ "data-editable": editable && !isEditing || void 0,
11574
+ "data-editing": isEditing || void 0,
11575
+ "data-cell-active": cellActive || void 0,
11576
+ "data-cell-selected": cellSelected || void 0,
11577
+ "data-copied": cellFlash || void 0,
11578
+ "data-wrap": effectiveWrapped.has(c.field) || void 0,
11579
+ "data-pin": st.pin,
11580
+ "data-pin-edge": st.edge,
11581
+ style: { width: widthOf(c), ...cellSty, ...st.style, ...st.pin ? { position: "sticky", zIndex: 2 } : null },
11582
+ "data-ovtext": cellTitle,
11583
+ onClick: selectionMode === "cell" ? (e) => handleCellClick(e, k, row, c) : void 0,
11584
+ onFocus: () => setFocus((f) => f.r === ri && f.c === ci ? f : { r: ri, c: ci }),
11585
+ onDoubleClick: editable ? () => beginEdit(k, c, row) : void 0,
11586
+ children: [
11587
+ !checkboxSelection && reorderable && ci === 0 && !isEditing ? /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 6, maxWidth: "100%" }, children: [
11588
+ rowHandle(k, midIdx, row),
11589
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis" }, children: isActions ? renderActions(c, row) : c.renderCell ? c.renderCell(val, row) : display })
11590
+ ] }) : isEditing ? c.renderEditCell ? (
11591
+ // #236: full escape hatch — the host drops in its own editor (searchable/creatable/
11592
+ // async Combobox, …) and drives the lifecycle via commit/cancel. Wrapped in
11593
+ // .twc-dt__editor-wrap so inline clicks (and any .twc-pop portal dropdown) don't
11594
+ // trip the outside-click auto-cancel.
11595
+ // #273: the wrapper handles Escape → cancel so it works for every custom editor without
11596
+ // each having to wire a keydown (they only get commit/cancel). A custom control that
11597
+ // wants Escape itself (e.g. to close its own open dropdown) stops propagation first.
11598
+ /* @__PURE__ */ jsxRuntime.jsx(
11599
+ "div",
11600
+ {
11601
+ className: "twc-dt__editor-wrap",
11602
+ onKeyDown: (e) => {
11603
+ if (e.key === "Escape") {
11604
+ e.stopPropagation();
11605
+ cancelEdit();
11606
+ }
11607
+ },
11608
+ children: c.renderEditCell({ value: editing.value, row, field: c.field, commit: (v) => commitEdit(v), cancel: cancelEdit })
11609
+ }
11610
+ )
11611
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
11612
+ EditCell,
11561
11613
  {
11562
- className: "twc-dt__editor-wrap",
11563
- onKeyDown: (e) => {
11564
- if (e.key === "Escape") {
11565
- e.stopPropagation();
11566
- cancelEdit();
11567
- }
11568
- },
11569
- children: c.renderEditCell({ value: editing.value, row, field: c.field, commit: (v) => commitEdit(v), cancel: cancelEdit })
11614
+ col: c,
11615
+ value: editing.value,
11616
+ options: c.valueOptions ? optionsForField(c.field) : null,
11617
+ onChange: (v) => setEditing((e) => ({ ...e, value: v })),
11618
+ onCommit: () => commitEdit(),
11619
+ onCommitValue: (v) => commitEdit(v),
11620
+ onCancel: cancelEdit,
11621
+ onKeyDown: onEditKey
11570
11622
  }
11571
- )
11572
- ) : /* @__PURE__ */ jsxRuntime.jsx(
11573
- EditCell,
11574
- {
11575
- col: c,
11576
- value: editing.value,
11577
- options: c.valueOptions ? optionsForField(c.field) : null,
11578
- onChange: (v) => setEditing((e) => ({ ...e, value: v })),
11579
- onCommit: () => commitEdit(),
11580
- onCommitValue: (v) => commitEdit(v),
11581
- onCancel: cancelEdit,
11582
- onKeyDown: onEditKey
11583
- }
11584
- ) : isActions ? renderActions(c, row) : c.renderCell ? c.renderCell(val, row) : display,
11585
- editable && !isEditing ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__edit-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.pencil }) }) : null
11586
- ]
11587
- },
11588
- c.field
11589
- );
11590
- })
11591
- ]
11592
- },
11593
- (pinSide ? "p-" : "") + k
11594
- );
11623
+ ) : isActions ? renderActions(c, row) : c.renderCell ? c.renderCell(val, row) : display,
11624
+ editable && !isEditing ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__edit-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.pencil }) }) : null
11625
+ ]
11626
+ },
11627
+ c.field
11628
+ );
11629
+ })
11630
+ ]
11631
+ }
11632
+ ),
11633
+ open ? /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "twc-dt__detail-row", role: "row", children: /* @__PURE__ */ jsxRuntime.jsx("td", { id: `${gridId}-detail-${ri}`, className: "twc-dt__detail-cell", role: "gridcell", colSpan: totalCols, style: { maxWidth: "none" }, children: detail }) }) : null
11634
+ ] }, (pinSide ? "p-" : "") + k);
11595
11635
  }
11596
11636
  function renderPivot() {
11597
11637
  const rFields = pivotConfig.rows, cFields = pivotConfig.columns || [], values = pivotConfig.values;
@@ -11928,14 +11968,15 @@ function Datatable({
11928
11968
  role: "grid",
11929
11969
  "aria-label": ariaLabelAttr || ariaLabel,
11930
11970
  "aria-rowcount": totalRows + 1,
11931
- "aria-colcount": ordered.length + (checkboxSelection ? 1 : 0),
11971
+ "aria-colcount": ordered.length + (checkboxSelection ? 1 : 0) + (hasRowDetail ? 1 : 0),
11932
11972
  "aria-multiselectable": selectionMode === "cell" || void 0,
11933
11973
  "aria-activedescendant": activeCellId,
11934
11974
  "aria-busy": loading || void 0,
11935
11975
  onKeyDown: onGridKeyDown,
11936
11976
  children: [
11937
11977
  /* @__PURE__ */ jsxRuntime.jsx("thead", { ref: theadRef, children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
11938
- checkboxSelection ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("div", { className: "twc-dt__th-inner", style: { justifyContent: "center", padding: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(
11978
+ hasRowDetail ? /* @__PURE__ */ jsxRuntime.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,
11979
+ checkboxSelection ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("div", { className: "twc-dt__th-inner", style: { justifyContent: "center", padding: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(
11939
11980
  "span",
11940
11981
  {
11941
11982
  className: "twc-dt__check",
@@ -12111,7 +12152,8 @@ function Datatable({
12111
12152
  })
12112
12153
  ] }) }),
12113
12154
  /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: loading ? Array.from({ length: paginated ? Math.min(sizeVal, 8) : 8 }).map((_, ri) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "twc-dt__row", role: "row", children: [
12114
- checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", style: { insetInlineStart: 0, width: CHK_W }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "18px", height: 18, borderRadius: 4 } }) }) : null,
12155
+ hasRowDetail ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: "twc-dt__td twc-dt__expand-cell", role: "gridcell", "data-pin": "left", style: { insetInlineStart: 0, width: EXP_W } }) : null,
12156
+ checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", style: { insetInlineStart: EXP_W, width: CHK_W }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "18px", height: 18, borderRadius: 4 } }) }) : null,
12115
12157
  showRowNum ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "16px", height: 14, borderRadius: 4 } }) }) : null,
12116
12158
  ordered.map((c, ci) => {
12117
12159
  const st = stickyOf(c.field);
@@ -12133,7 +12175,8 @@ function Datatable({
12133
12175
  pinnedBottomRows.map((row) => renderLeaf(row, keyIndex.get(keyOf(row)), "bottom"))
12134
12176
  ] }) }),
12135
12177
  hasAggregation && aggOn && !loading && paged.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("tfoot", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { role: "row", children: [
12136
- checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsx("td", { role: "gridcell", "data-pin": "left", "data-pin-edge": visLeft.length || showRowNum ? void 0 : "left", style: { insetInlineStart: 0, width: CHK_W } }) : null,
12178
+ hasRowDetail ? /* @__PURE__ */ jsxRuntime.jsx("td", { role: "gridcell", "data-pin": "left", "data-pin-edge": checkboxSelection || showRowNum || visLeft.length ? void 0 : "left", style: { insetInlineStart: 0, width: EXP_W } }) : null,
12179
+ checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsx("td", { role: "gridcell", "data-pin": "left", "data-pin-edge": visLeft.length || showRowNum ? void 0 : "left", style: { insetInlineStart: EXP_W, width: CHK_W } }) : null,
12137
12180
  showRowNum ? /* @__PURE__ */ jsxRuntime.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,
12138
12181
  ordered.map((c) => {
12139
12182
  const st = stickyOf(c.field);
@@ -17168,6 +17211,18 @@ var SLIDER_CSS = `
17168
17211
  transition: opacity var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-spring);
17169
17212
  }
17170
17213
  .twc-slider__track[data-show-bubble="true"] .twc-slider__bubble { opacity: 1; transform: translateX(-50%) scale(1); }
17214
+ /* #351: editable numeric inputs (manual entry, synced with the thumbs) */
17215
+ .twc-slider__inputs { display: inline-flex; align-items: center; gap: var(--space-1-5); }
17216
+ .twc-slider__input-sep { color: var(--color-text-muted); font-weight: var(--font-semibold); }
17217
+ .twc-slider__input {
17218
+ min-width: 0; text-align: end; font-family: inherit; font-size: var(--text-sm); font-weight: var(--font-semibold);
17219
+ color: var(--_accent); font-variant-numeric: tabular-nums;
17220
+ padding: 2px 6px; border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-sm); background: var(--color-surface);
17221
+ transition: border-color var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard);
17222
+ }
17223
+ .twc-slider__input:hover:not(:disabled) { border-color: var(--color-border-strong); }
17224
+ .twc-slider__input:focus-visible { outline: none; border-color: var(--_accent); box-shadow: var(--ring); }
17225
+ .twc-slider[data-disabled="true"] .twc-slider__input { cursor: not-allowed; }
17171
17226
  `;
17172
17227
  function Slider({
17173
17228
  label,
@@ -17186,6 +17241,7 @@ function Slider({
17186
17241
  range: range2 = false,
17187
17242
  showValue = true,
17188
17243
  showTicks = false,
17244
+ editable = false,
17189
17245
  formatValue,
17190
17246
  getAriaValueText,
17191
17247
  name,
@@ -17224,6 +17280,7 @@ function Slider({
17224
17280
  const indices = isRange ? [0, 1] : [0];
17225
17281
  const trackRef = React51__namespace.default.useRef(null);
17226
17282
  const [dragging, setDragging] = React51__namespace.default.useState(false);
17283
+ const [typed, setTyped] = React51__namespace.default.useState({});
17227
17284
  const fmt = (v) => formatValue ? formatValue(v) : v.toFixed(decimals);
17228
17285
  const valueTextFor = (v) => {
17229
17286
  if (getAriaValueText) return getAriaValueText(v);
@@ -17247,6 +17304,27 @@ function Slider({
17247
17304
  }
17248
17305
  commit(next);
17249
17306
  };
17307
+ const commitInput = (i) => {
17308
+ const s = typed[i];
17309
+ setTyped((t) => {
17310
+ const n = { ...t };
17311
+ delete n[i];
17312
+ return n;
17313
+ });
17314
+ if (s == null || String(s).trim() === "") return;
17315
+ const num = parseFloat(s);
17316
+ if (Number.isFinite(num)) setThumb(i, num);
17317
+ };
17318
+ const stepInput = (i, dir) => {
17319
+ const base = typed[i] != null && Number.isFinite(parseFloat(typed[i])) ? parseFloat(typed[i]) : clampedVals[i];
17320
+ setTyped((t) => {
17321
+ const n = { ...t };
17322
+ delete n[i];
17323
+ return n;
17324
+ });
17325
+ setThumb(i, base + dir * step);
17326
+ };
17327
+ const inputSize = Math.max(String(min).length, String(max).length, decimals > 0 ? decimals + 2 : 1) + 1;
17250
17328
  const fromClientX = (clientX) => {
17251
17329
  const node = trackRef.current;
17252
17330
  if (!node) return clampedVals[0];
@@ -17302,9 +17380,40 @@ function Slider({
17302
17380
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `twc-slider ${className}`, "data-tone": tone, "data-size": size, "data-disabled": disabled || void 0, ...rest, children: [
17303
17381
  __twcStyles,
17304
17382
  name ? clampedVals.map((v, i) => /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: v, disabled: disabled || void 0 }, i)) : null,
17305
- label || showValue ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-slider__head", children: [
17383
+ label || showValue || editable ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-slider__head", children: [
17306
17384
  label ? /* @__PURE__ */ jsxRuntime.jsx("label", { className: "twc-slider__label", id: labelId, children: label }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
17307
- showValue ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-slider__value", children: headValue }) : null
17385
+ editable ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-slider__inputs", children: indices.map((i) => /* @__PURE__ */ jsxRuntime.jsxs(React51__namespace.default.Fragment, { children: [
17386
+ i > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-slider__input-sep", "aria-hidden": "true", children: "\u2013" }) : null,
17387
+ /* @__PURE__ */ jsxRuntime.jsx(
17388
+ "input",
17389
+ {
17390
+ className: "twc-slider__input",
17391
+ type: "text",
17392
+ inputMode: min < 0 ? "text" : "decimal",
17393
+ size: inputSize,
17394
+ value: typed[i] != null ? typed[i] : String(clampedVals[i]),
17395
+ disabled: disabled || void 0,
17396
+ "aria-labelledby": isRange ? void 0 : labelId,
17397
+ "aria-label": isRange ? i === 0 ? "Minimum value" : "Maximum value" : labelId ? void 0 : "Value",
17398
+ onChange: (e) => setTyped((t) => ({ ...t, [i]: e.target.value })),
17399
+ onBlur: () => commitInput(i),
17400
+ onKeyDown: (e) => {
17401
+ if (e.key === "Enter") {
17402
+ if (typed[i] != null) {
17403
+ e.preventDefault();
17404
+ commitInput(i);
17405
+ }
17406
+ } else if (e.key === "ArrowUp") {
17407
+ e.preventDefault();
17408
+ stepInput(i, 1);
17409
+ } else if (e.key === "ArrowDown") {
17410
+ e.preventDefault();
17411
+ stepInput(i, -1);
17412
+ }
17413
+ }
17414
+ }
17415
+ )
17416
+ ] }, i)) }) : showValue ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-slider__value", children: headValue }) : null
17308
17417
  ] }) : null,
17309
17418
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-slider__track", ref: trackRef, "data-show-bubble": dragging || void 0, onPointerDown, children: [
17310
17419
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-slider__rail" }),