twico-ui 1.31.1 → 1.32.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.cjs CHANGED
@@ -8453,7 +8453,13 @@ var DT_CSS = `
8453
8453
  /* #244: batch editor = pick columns (searchable), then set a value for each. */
8454
8454
  .twc-dt__be-add { padding: 6px 6px 8px; border-bottom: var(--border-thin) solid var(--color-divider); }
8455
8455
  .twc-dt__be-row { display: flex; align-items: center; gap: 8px; padding: 5px 6px; }
8456
- .twc-dt__be-name { width: 108px; flex: none; font-size: var(--text-sm); color: var(--color-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
8456
+ /* #341: cap width at min(260, row \u2212 160px) so the value control keeps ~120px (+ gaps + remove) and never
8457
+ collapses \u2014 panel-width-aware in pure CSS, so it also grows if the (resizable) panel is widened. */
8458
+ .twc-dt__be-name { position: relative; flex: none; width: clamp(88px, var(--twc-dt-be-name-usr, 108px), min(260px, 100% - 160px)); font-size: var(--text-sm); color: var(--color-text); }
8459
+ /* #341: ellipsis lives on the inner text span so the outer field can host the (edge-overhanging) resize handle without clipping it. */
8460
+ .twc-dt__be-name-txt { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
8461
+ /* #341: reveal the batch-name resize handle (reused .twc-dt__f-rz) on row hover, like the filter fields. */
8462
+ .twc-dt__be-row:hover .twc-dt__f-rz { opacity: 1; }
8457
8463
  .twc-dt__be-ctl { flex: 1; min-width: 0; }
8458
8464
  .twc-dt__be-x { flex: none; display: inline-grid; place-items: center; width: 24px; height: 24px; padding: 0; border: none;
8459
8465
  background: transparent; color: var(--color-text-subtle); cursor: pointer; border-radius: var(--radius-sm);
@@ -8518,6 +8524,19 @@ var DT_CSS = `
8518
8524
  clipping to one line. height stays a per-row minimum, so single-line cells are unchanged. */
8519
8525
  .twc-dt__td[data-wrap="true"] { white-space: normal; overflow: hidden; text-overflow: clip;
8520
8526
  word-break: break-word; overflow-wrap: anywhere; vertical-align: top; padding-block: 10px; line-height: 1.45; }
8527
+ /* #338: "combined" column cell \u2014 one column showing several source columns' values. */
8528
+ .twc-dt__combine--stack { display: flex; flex-direction: column; gap: 1px; }
8529
+ .twc-dt__combine-sep { color: var(--color-text-subtle); padding-inline: 3px; }
8530
+ .twc-dt__combine-label { color: var(--color-text-muted); font-weight: var(--font-medium); }
8531
+ /* #339: the runtime combine-columns editor panel. */
8532
+ .twc-dt__combine-hint { padding: 2px 12px 8px; font-size: var(--text-xs); color: var(--color-text-muted); }
8533
+ .twc-dt__combine-opts { padding: 8px 12px; border-top: var(--border-thin) solid var(--color-divider); display: flex; flex-direction: column; gap: 8px; }
8534
+ .twc-dt__combine-opt { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: var(--text-sm); color: var(--color-text); }
8535
+ .twc-dt__combine-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 12px; border-top: var(--border-thin) solid var(--color-divider); }
8536
+ .twc-dt__combine-apply { border: none; background: var(--color-primary); color: var(--color-primary-fg); font-family: inherit; font-size: var(--text-sm); font-weight: var(--font-semibold); padding: 6px 14px; border-radius: var(--radius-md); cursor: pointer; }
8537
+ .twc-dt__combine-apply:hover { opacity: 0.9; }
8538
+ .twc-dt__combine-apply:focus-visible { outline: none; box-shadow: var(--ring); }
8539
+ .twc-dt__col-combined { color: var(--color-text-subtle); font-size: var(--text-xs); font-style: italic; }
8521
8540
  .twc-dt__td[data-num="true"] { text-align: end; font-variant-numeric: tabular-nums; }
8522
8541
  /* Auto row-number gutter (rowNumbers) \u2014 a sticky-left ordinal column. */
8523
8542
  .twc-dt__rownum { text-align: end; font-variant-numeric: tabular-nums; color: var(--color-text-subtle);
@@ -8852,6 +8871,34 @@ function mergeColOrder(saved, propFields) {
8852
8871
  return result;
8853
8872
  }
8854
8873
  var getColVal = (col, row) => col && col.valueGetter ? col.valueGetter(row) : row == null ? void 0 : row[col && col.field];
8874
+ function combineCfg(combine) {
8875
+ if (!combine) return null;
8876
+ const cfg = Array.isArray(combine) ? { fields: combine } : combine;
8877
+ const fields = Array.isArray(cfg.fields) ? cfg.fields.filter((f) => f != null) : [];
8878
+ if (!fields.length) return null;
8879
+ return { fields, separator: cfg.separator != null ? cfg.separator : " \xB7 ", labels: !!cfg.labels, layout: cfg.layout === "stack" ? "stack" : "inline" };
8880
+ }
8881
+ var combineSrcCols = (cfg, byField) => cfg.fields.map((f) => byField && byField[f] || { field: f, headerName: String(f) });
8882
+ var combineValueGetter = (srcCols, cfg) => (row) => srcCols.map((sc) => {
8883
+ const v = getColVal(sc, row);
8884
+ const s = v == null ? "" : String(v);
8885
+ return cfg.labels && s ? `${sc.headerName ?? sc.field}: ${s}` : s;
8886
+ }).filter(Boolean).join(cfg.separator);
8887
+ function renderCombined(srcCols, row, cfg) {
8888
+ const nodes = [];
8889
+ let count = 0;
8890
+ srcCols.forEach((sc, idx) => {
8891
+ const v = getColVal(sc, row);
8892
+ if (v == null || v === "") return;
8893
+ const disp = sc.valueFormatter ? sc.valueFormatter(v, row) : v;
8894
+ const label = cfg.labels ? React51__namespace.default.createElement("span", { className: "twc-dt__combine-label" }, (sc.headerName ?? sc.field) + ": ") : null;
8895
+ if (count > 0 && cfg.layout !== "stack") nodes.push(React51__namespace.default.createElement("span", { key: "sep" + idx, className: "twc-dt__combine-sep", "aria-hidden": "true" }, cfg.separator));
8896
+ nodes.push(React51__namespace.default.createElement("span", { key: idx, className: "twc-dt__combine-item" }, label, disp));
8897
+ count++;
8898
+ });
8899
+ if (!nodes.length) return "";
8900
+ return React51__namespace.default.createElement("span", { className: cfg.layout === "stack" ? "twc-dt__combine twc-dt__combine--stack" : "twc-dt__combine" }, nodes);
8901
+ }
8855
8902
  var diffValueNode = (col, v, row) => col.renderCell ? col.renderCell(v, row) : col.valueFormatter ? col.valueFormatter(v, row) : v == null ? "" : v;
8856
8903
  function renderDiffCell(col, val, row) {
8857
8904
  const meta = row && row.__diffMeta;
@@ -9232,6 +9279,7 @@ function Datatable({
9232
9279
  aggregationValues = null,
9233
9280
  disableColumnReorder = false,
9234
9281
  disableColumnResize = false,
9282
+ columnCombining = false,
9235
9283
  emptyMessage,
9236
9284
  renderEmpty,
9237
9285
  editMode = false,
@@ -9298,9 +9346,19 @@ function Datatable({
9298
9346
  if (diffData && onClassifiedRef.current) onClassifiedRef.current(diffData.counts);
9299
9347
  }, [diffData]);
9300
9348
  const diffRowKeyFn = React51__namespace.default.useCallback((r2) => r2.__diffKey, []);
9349
+ const [userCombine, setUserCombine] = React51__namespace.default.useState({});
9301
9350
  const columns = React51__namespace.default.useMemo(
9302
- () => isDiff ? [DIFF_OP_COLUMN, ...columnsProp.map((c) => ({ ...c, renderCell: (val, row) => renderDiffCell(c, val, row) }))] : columnsProp,
9303
- [isDiff, columnsProp]
9351
+ () => {
9352
+ if (!isDiff) return columnsProp;
9353
+ const byField = {};
9354
+ for (const c of columnsProp) if (c && c.field != null) byField[c.field] = c;
9355
+ return [DIFF_OP_COLUMN, ...columnsProp.map((c) => {
9356
+ const cfg = combineCfg(userCombine[c.field] || c.combine);
9357
+ const base = cfg && !c.valueGetter ? { ...c, valueGetter: combineValueGetter(combineSrcCols(cfg, byField), cfg) } : c;
9358
+ return { ...base, renderCell: (val, row) => renderDiffCell(base, val, row) };
9359
+ })];
9360
+ },
9361
+ [isDiff, columnsProp, userCombine]
9304
9362
  );
9305
9363
  const rows = React51__namespace.default.useMemo(
9306
9364
  () => isDiff ? diffData.rows.filter((r2) => !diffOnly || r2.op !== "unchanged").map((r2) => ({ ...r2.to || r2.from, __diffop: r2.op, __diffMeta: r2, __diffKey: r2.key })) : rowsProp,
@@ -9340,8 +9398,26 @@ function Datatable({
9340
9398
  getActions: () => []
9341
9399
  });
9342
9400
  }
9401
+ const byField = {};
9402
+ for (const c of out) if (c.field != null) byField[c.field] = c;
9403
+ for (let i = 0; i < out.length; i++) {
9404
+ const c = out[i];
9405
+ const cfg = combineCfg(userCombine[c.field] || c.combine);
9406
+ if (!cfg) continue;
9407
+ const srcCols = combineSrcCols(cfg, byField);
9408
+ out[i] = {
9409
+ ...c,
9410
+ editable: false,
9411
+ // the combined value is synthetic — inline editing would write to a phantom field
9412
+ valueGetter: c.valueGetter || combineValueGetter(srcCols, cfg),
9413
+ // Consumer renderCell wins. If they overrode only valueGetter, fall back to the DEFAULT cell render
9414
+ // (renderCell: undefined) so the cell shows THEIR value — keeping display in sync with what
9415
+ // sort/filter/search/export use — rather than the auto source-join. Otherwise render the combined cell.
9416
+ renderCell: c.renderCell || (c.valueGetter ? void 0 : ((value, row) => renderCombined(srcCols, row, cfg)))
9417
+ };
9418
+ }
9343
9419
  return out;
9344
- }, [columns, rowPinning]);
9420
+ }, [columns, rowPinning, userCombine]);
9345
9421
  const _autoKey = React51__namespace.default.useRef();
9346
9422
  if (!_autoKey.current) _autoKey.current = { map: /* @__PURE__ */ new WeakMap(), n: 0 };
9347
9423
  const keyOf = rowKey || ((r2, i) => {
@@ -9375,6 +9451,42 @@ function Datatable({
9375
9451
  n.has(field) ? n.delete(field) : n.add(field);
9376
9452
  return n;
9377
9453
  });
9454
+ const openCombineEditor = (field) => {
9455
+ const existing = userCombine[field];
9456
+ setCombineTarget(field);
9457
+ setCombineDraft({
9458
+ selected: existing ? existing.fields.filter((f) => f !== field) : [],
9459
+ layout: existing && existing.layout === "stack" ? "stack" : "inline",
9460
+ labels: !!(existing && existing.labels)
9461
+ });
9462
+ };
9463
+ const closeCombineEditor = () => {
9464
+ setPanel(null);
9465
+ closePanel();
9466
+ setCombineTarget(null);
9467
+ setCombineDraft(null);
9468
+ restoreTriggerFocus();
9469
+ };
9470
+ const applyCombine = () => {
9471
+ if (!combineTarget || !combineDraft) return;
9472
+ const field = combineTarget;
9473
+ const sources = combineDraft.selected.filter((f) => f !== field);
9474
+ setUserCombine((m) => {
9475
+ const next = { ...m };
9476
+ if (sources.length) next[field] = { fields: [field, ...sources], layout: combineDraft.layout === "stack" ? "stack" : "inline", separator: " \xB7 ", labels: !!combineDraft.labels };
9477
+ else delete next[field];
9478
+ return next;
9479
+ });
9480
+ closeCombineEditor();
9481
+ };
9482
+ const uncombine = (field) => {
9483
+ setUserCombine((m) => {
9484
+ const next = { ...m };
9485
+ delete next[field];
9486
+ return next;
9487
+ });
9488
+ closeCombineEditor();
9489
+ };
9378
9490
  const [pins, setPins] = React51__namespace.default.useState(() => {
9379
9491
  const left = [], right = [];
9380
9492
  columns.forEach((c) => {
@@ -9436,6 +9548,7 @@ function Datatable({
9436
9548
  const rowFocusRef = React51__namespace.default.useRef(null);
9437
9549
  const [fWidths, setFWidths] = React51__namespace.default.useState({});
9438
9550
  const [fPanelSize, setFPanelSize] = React51__namespace.default.useState(null);
9551
+ const [batchNameW, setBatchNameW] = React51__namespace.default.useState(null);
9439
9552
  const [popSizes, setPopSizes] = React51__namespace.default.useState({});
9440
9553
  const serializeState = () => ({
9441
9554
  filters: filters.map(({ id, ...f }) => f),
@@ -9456,7 +9569,11 @@ function Datatable({
9456
9569
  filterPanelSize: fPanelSize || void 0,
9457
9570
  filterFieldWidths: fWidths.col != null || fWidths.op != null || fWidths.val != null || fWidths.logic != null ? fWidths : void 0,
9458
9571
  // #304: additive, optional — user-resized sizes of the other toolbar popovers, keyed by popover id.
9459
- popoverSizes: Object.keys(popSizes).length ? popSizes : void 0
9572
+ popoverSizes: Object.keys(popSizes).length ? popSizes : void 0,
9573
+ // #339: additive, optional — runtime user-defined combined columns, keyed by target field.
9574
+ columnCombine: Object.keys(userCombine).length ? userCombine : void 0,
9575
+ // #341: additive, optional — user-resized batch-editor column-name field width (px).
9576
+ batchNameWidth: batchNameW != null ? batchNameW : void 0
9460
9577
  });
9461
9578
  const applyState = (s) => {
9462
9579
  if (!s || typeof s !== "object") return;
@@ -9501,6 +9618,16 @@ function Datatable({
9501
9618
  }
9502
9619
  if (Object.keys(next).length) setPopSizes(next);
9503
9620
  }
9621
+ if (s.columnCombine && typeof s.columnCombine === "object") {
9622
+ const uc = {};
9623
+ for (const [f, cfg] of Object.entries(s.columnCombine)) {
9624
+ if (!known.has(f) || !cfg || !Array.isArray(cfg.fields)) continue;
9625
+ const rest2 = cfg.fields.filter((x) => known.has(x) && x !== f);
9626
+ if (rest2.length) uc[f] = { fields: [f, ...rest2], layout: cfg.layout === "stack" ? "stack" : "inline", separator: typeof cfg.separator === "string" ? cfg.separator : " \xB7 ", labels: !!cfg.labels };
9627
+ }
9628
+ if (Object.keys(uc).length) setUserCombine(uc);
9629
+ }
9630
+ if (typeof s.batchNameWidth === "number") setBatchNameW(clampNum(s.batchNameWidth, 88, 260));
9504
9631
  };
9505
9632
  const stateReadyRef = React51__namespace.default.useRef(false);
9506
9633
  const stateRestoredRef = React51__namespace.default.useRef(false);
@@ -9518,7 +9645,7 @@ function Datatable({
9518
9645
  }
9519
9646
  }
9520
9647
  return void 0;
9521
- }, [filters, filterLogic, sort, quick, pageVal, sizeVal, order, widths, hidden, pins, density, fWidths, fPanelSize, popSizes, stateKey]);
9648
+ }, [filters, filterLogic, sort, quick, pageVal, sizeVal, order, widths, hidden, pins, density, fWidths, fPanelSize, popSizes, userCombine, batchNameW, stateKey]);
9522
9649
  const restoreState = () => {
9523
9650
  let saved = null;
9524
9651
  if (stateKey) {
@@ -9659,6 +9786,15 @@ function Datatable({
9659
9786
  const [menuPos, openMenu, closeMenu] = useFloating();
9660
9787
  const [panel, setPanel] = React51__namespace.default.useState(null);
9661
9788
  const [panelPos, openPanel, closePanel] = useFloating();
9789
+ const [combineTarget, setCombineTarget] = React51__namespace.default.useState(null);
9790
+ const [combineDraft, setCombineDraft] = React51__namespace.default.useState(null);
9791
+ const combinePanelRef = React51__namespace.default.useRef(null);
9792
+ React51__namespace.default.useEffect(() => {
9793
+ if (panel === "combine" && combinePanelRef.current) {
9794
+ const el = combinePanelRef.current.querySelector('[role="switch"], button, [tabindex]');
9795
+ (el || combinePanelRef.current).focus?.();
9796
+ }
9797
+ }, [panel]);
9662
9798
  const [rowMenu, setRowMenu] = React51__namespace.default.useState(null);
9663
9799
  const [rowMenuPos, openRowMenu, closeRowMenu] = useFloating();
9664
9800
  const [exportOpen, setExportOpen] = React51__namespace.default.useState(false);
@@ -9759,7 +9895,21 @@ function Datatable({
9759
9895
  };
9760
9896
  }, [exportOpen]);
9761
9897
  const colByField = React51__namespace.default.useMemo(() => Object.fromEntries(cols.map((c) => [c.field, c])), [cols]);
9762
- const visibleCols = cols.filter((c) => !hidden.has(c.field));
9898
+ const combineSources = React51__namespace.default.useMemo(() => {
9899
+ const s = /* @__PURE__ */ new Set();
9900
+ for (const cfg of Object.values(userCombine)) (cfg && cfg.fields ? cfg.fields : []).slice(1).forEach((f) => s.add(f));
9901
+ return s;
9902
+ }, [userCombine]);
9903
+ const stackCombineTargets = React51__namespace.default.useMemo(
9904
+ () => new Set(cols.filter((c) => {
9905
+ const cfg = combineCfg(userCombine[c.field] || c.combine);
9906
+ return cfg && cfg.layout === "stack";
9907
+ }).map((c) => c.field)),
9908
+ [cols, userCombine]
9909
+ );
9910
+ const effectiveHidden = React51__namespace.default.useMemo(() => combineSources.size ? /* @__PURE__ */ new Set([...hidden, ...combineSources]) : hidden, [hidden, combineSources]);
9911
+ const effectiveWrapped = React51__namespace.default.useMemo(() => stackCombineTargets.size ? /* @__PURE__ */ new Set([...wrapped, ...stackCombineTargets]) : wrapped, [wrapped, stackCombineTargets]);
9912
+ const visibleCols = cols.filter((c) => !effectiveHidden.has(c.field));
9763
9913
  const colFieldsKey = JSON.stringify(
9764
9914
  cols.filter((c) => typeof c.field === "string" && c.type !== "actions").map((c) => c.field)
9765
9915
  );
@@ -9778,17 +9928,17 @@ function Datatable({
9778
9928
  const i = order.indexOf(f);
9779
9929
  return i === -1 ? 9999 : i;
9780
9930
  };
9781
- const L = pins.left.map((f) => colByField[f]).filter((c) => c && !hidden.has(c.field));
9782
- const R = pins.right.map((f) => colByField[f]).filter((c) => c && !hidden.has(c.field));
9931
+ const L = pins.left.map((f) => colByField[f]).filter((c) => c && !effectiveHidden.has(c.field));
9932
+ const R = pins.right.map((f) => colByField[f]).filter((c) => c && !effectiveHidden.has(c.field));
9783
9933
  const mid = visibleCols.filter((c) => !pins.left.includes(c.field) && !pins.right.includes(c.field)).sort((a, b) => orderIdx(a.field) - orderIdx(b.field));
9784
9934
  return [...L, ...mid, ...R];
9785
- }, [pins, hidden, visibleCols, colByField, order]);
9935
+ }, [pins, effectiveHidden, visibleCols, colByField, order]);
9786
9936
  const NUM_W = 56;
9787
9937
  const CHK_W = checkboxSelection ? rowReorder ? 68 : 44 : 0;
9788
9938
  const numLeft = CHK_W;
9789
9939
  const leadW = numLeft + (showRowNum ? NUM_W : 0);
9790
- const visLeft = React51__namespace.default.useMemo(() => pins.left.filter((f) => colByField[f] && !hidden.has(f)), [pins.left, colByField, hidden]);
9791
- const visRight = React51__namespace.default.useMemo(() => pins.right.filter((f) => colByField[f] && !hidden.has(f)), [pins.right, colByField, hidden]);
9940
+ const visLeft = React51__namespace.default.useMemo(() => pins.left.filter((f) => colByField[f] && !effectiveHidden.has(f)), [pins.left, colByField, effectiveHidden]);
9941
+ const visRight = React51__namespace.default.useMemo(() => pins.right.filter((f) => colByField[f] && !effectiveHidden.has(f)), [pins.right, colByField, effectiveHidden]);
9792
9942
  const stickyOf = (field) => {
9793
9943
  if (visLeft.includes(field)) {
9794
9944
  let off = leadW;
@@ -10953,6 +11103,54 @@ function Datatable({
10953
11103
  if (field === "val") p.removeAttribute("data-val-fixed");
10954
11104
  }
10955
11105
  };
11106
+ const BE_NAME_MIN = 88, BE_NAME_MAX = 260;
11107
+ const startBeNameResize = (e) => {
11108
+ if (e.button != null && e.button !== 0) return;
11109
+ e.preventDefault();
11110
+ e.stopPropagation();
11111
+ const handle = e.currentTarget;
11112
+ const panel2 = handle.closest(".twc-dt__cfg"), nameEl = handle.closest(".twc-dt__be-name");
11113
+ if (!panel2 || !nameEl) return;
11114
+ const startX = e.clientX, startW = nameEl.getBoundingClientRect().width;
11115
+ const dir = getComputedStyle(panel2).direction === "rtl" ? -1 : 1;
11116
+ handle.setAttribute("data-active", "true");
11117
+ let last = startW;
11118
+ const onMove = (ev) => {
11119
+ last = clampNum(startW + (ev.clientX - startX) * dir, BE_NAME_MIN, BE_NAME_MAX);
11120
+ panel2.style.setProperty("--twc-dt-be-name-usr", `${last}px`);
11121
+ };
11122
+ const onUp = () => {
11123
+ setBatchNameW(last);
11124
+ handle.removeAttribute("data-active");
11125
+ window.removeEventListener("pointermove", onMove);
11126
+ window.removeEventListener("pointerup", onUp);
11127
+ };
11128
+ window.addEventListener("pointermove", onMove);
11129
+ window.addEventListener("pointerup", onUp);
11130
+ };
11131
+ const onBeNameResizeKey = (e) => {
11132
+ const panel2 = e.currentTarget.closest(".twc-dt__cfg");
11133
+ if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
11134
+ e.preventDefault();
11135
+ e.stopPropagation();
11136
+ setBatchNameW(null);
11137
+ if (panel2) panel2.style.removeProperty("--twc-dt-be-name-usr");
11138
+ return;
11139
+ }
11140
+ if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) return;
11141
+ e.preventDefault();
11142
+ e.stopPropagation();
11143
+ const nameEl = e.currentTarget.closest(".twc-dt__be-name");
11144
+ const cur = nameEl ? nameEl.getBoundingClientRect().width : batchNameW ?? 108;
11145
+ const dir = panel2 && getComputedStyle(panel2).direction === "rtl" ? -1 : 1;
11146
+ const step = e.shiftKey ? 2 : 10;
11147
+ let next = cur;
11148
+ if (e.key === "ArrowRight") next = cur + step * dir;
11149
+ else if (e.key === "ArrowLeft") next = cur - step * dir;
11150
+ else if (e.key === "Home") next = BE_NAME_MIN;
11151
+ else if (e.key === "End") next = BE_NAME_MAX;
11152
+ setBatchNameW(clampNum(next, BE_NAME_MIN, BE_NAME_MAX));
11153
+ };
10956
11154
  const startPanelResize = (e) => {
10957
11155
  if (e.button != null && e.button !== 0) return;
10958
11156
  e.preventDefault();
@@ -11175,7 +11373,7 @@ function Datatable({
11175
11373
  const shownColRows = cols.filter((c) => c.field !== "__pinactions__" && c.headerName.toLowerCase().includes(colQuery.trim().toLowerCase())).sort((a, b) => orderIdxOf(a.field) - orderIdxOf(b.field));
11176
11374
  const rowNumMatch = rowNumbers && "row number".includes(colQuery.trim().toLowerCase());
11177
11375
  const manageableCols = cols.filter((c) => c.field !== "__pinactions__");
11178
- const visibleColCount = manageableCols.filter((c) => !hidden.has(c.field)).length;
11376
+ const visibleColCount = manageableCols.filter((c) => !effectiveHidden.has(c.field)).length;
11179
11377
  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) }));
11180
11378
  const totalCols = ordered.length + (checkboxSelection ? 1 : 0) + (showRowNum ? 1 : 0);
11181
11379
  function renderGroupRow(item) {
@@ -11333,7 +11531,7 @@ function Datatable({
11333
11531
  "data-cell-active": cellActive || void 0,
11334
11532
  "data-cell-selected": cellSelected || void 0,
11335
11533
  "data-copied": cellFlash || void 0,
11336
- "data-wrap": wrapped.has(c.field) || void 0,
11534
+ "data-wrap": effectiveWrapped.has(c.field) || void 0,
11337
11535
  "data-pin": st.pin,
11338
11536
  "data-pin-edge": st.edge,
11339
11537
  style: { width: widthOf(c), ...st.style },
@@ -12030,7 +12228,7 @@ function Datatable({
12030
12228
  delete values[f];
12031
12229
  return { ...b, fields, values };
12032
12230
  });
12033
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__pop twc-dt__cfg", style: { top: batchEditPos.top, left: batchEditPos.left, width: 320, ...popStyle("batchedit") }, "data-pop-sized": popSizes.batchedit ? "" : void 0, children: [
12231
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__pop twc-dt__cfg", style: { top: batchEditPos.top, left: batchEditPos.left, width: 320, "--twc-dt-be-name-usr": batchNameW != null ? `${batchNameW}px` : void 0, ...popStyle("batchedit") }, "data-pop-sized": popSizes.batchedit ? "" : void 0, children: [
12034
12232
  /* @__PURE__ */ jsxRuntime.jsx(Caret, { pos: batchEditPos }),
12035
12233
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__cfg-head", children: [
12036
12234
  "Edit ",
@@ -12051,10 +12249,25 @@ function Datatable({
12051
12249
  onChange: addField
12052
12250
  }
12053
12251
  ) }) : null,
12054
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__cfg-list", children: pickedCols.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__be-empty", children: "Pick a column to set its value on all selected rows." }) : pickedCols.map((c) => {
12252
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__cfg-list", children: pickedCols.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__be-empty", children: "Pick a column to set its value on all selected rows." }) : pickedCols.map((c, i) => {
12055
12253
  const opts = c.valueOptions ? c.valueOptions.map((o) => typeof o === "string" ? { value: o, label: o } : o) : null;
12056
12254
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__be-row", children: [
12057
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__be-name", title: String(c.headerName ?? c.field), children: c.headerName }),
12255
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__be-name", title: String(c.headerName ?? c.field), children: [
12256
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__be-name-txt", children: c.headerName }),
12257
+ resizableFilters && i === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
12258
+ "span",
12259
+ {
12260
+ className: "twc-dt__f-rz",
12261
+ role: "separator",
12262
+ "aria-orientation": "vertical",
12263
+ tabIndex: 0,
12264
+ "aria-label": "Resize column name field (Enter to reset)",
12265
+ onPointerDown: startBeNameResize,
12266
+ onClick: (e) => e.stopPropagation(),
12267
+ onKeyDown: onBeNameResizeKey
12268
+ }
12269
+ ) : null
12270
+ ] }),
12058
12271
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__be-ctl", children: c.renderBatchEditCell ? c.renderBatchEditCell({
12059
12272
  value: batchEdit.values[c.field],
12060
12273
  field: c.field,
@@ -12169,6 +12382,18 @@ function Datatable({
12169
12382
  " ",
12170
12383
  groupBy.includes(c.field) ? "Stop grouping" : "Group by this column"
12171
12384
  ] }) : null,
12385
+ columnCombining && c.type !== "actions" ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", role: "menuitem", className: "twc-dt__mi", "data-active": !!userCombine[c.field] || void 0, onClick: () => {
12386
+ const anchor = menuTriggerRef.current;
12387
+ setColMenu(null);
12388
+ closeMenu();
12389
+ openCombineEditor(c.field);
12390
+ setPanel("combine");
12391
+ openPanel(anchor, "right", 300);
12392
+ }, children: [
12393
+ /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.columns }),
12394
+ " ",
12395
+ userCombine[c.field] ? "Edit combined column\u2026" : "Combine columns\u2026"
12396
+ ] }) : null,
12172
12397
  !disableColumnReorder && c.reorderable !== false && c.type !== "actions" && !pins.left.includes(c.field) && !pins.right.includes(c.field) && movableMidFields.length > 1 ? (() => {
12173
12398
  const midIdx = movableMidFields.indexOf(c.field);
12174
12399
  const atFirst = midIdx === 0;
@@ -12336,10 +12561,13 @@ function Datatable({
12336
12561
  e.preventDefault();
12337
12562
  onColDrop(c.field);
12338
12563
  } : void 0,
12339
- onClick: () => c.hideable && toggleHiddenField(c.field),
12564
+ onClick: () => c.hideable && !combineSources.has(c.field) && toggleHiddenField(c.field),
12340
12565
  children: [
12341
12566
  canDrag ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__col-grip", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.grip }) }) : null,
12342
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__col-name", children: c.headerName }),
12567
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__col-name", children: [
12568
+ c.headerName,
12569
+ combineSources.has(c.field) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__col-combined", children: " \xB7 combined" }) : null
12570
+ ] }),
12343
12571
  c.pinnable && c.type !== "actions" ? (
12344
12572
  // Pin from here too, so columns scrolled out of view (no reachable header ⋮) can still be pinned.
12345
12573
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__col-pins", draggable: false, onClick: (e) => e.stopPropagation(), children: [
@@ -12375,19 +12603,19 @@ function Datatable({
12375
12603
  "span",
12376
12604
  {
12377
12605
  className: "twc-dt__sw",
12378
- "data-on": !hidden.has(c.field) || void 0,
12606
+ "data-on": !effectiveHidden.has(c.field) || void 0,
12379
12607
  role: "switch",
12380
- "aria-checked": !hidden.has(c.field),
12608
+ "aria-checked": !effectiveHidden.has(c.field),
12381
12609
  "aria-label": c.headerName,
12382
- "aria-disabled": !c.hideable || void 0,
12383
- tabIndex: c.hideable ? 0 : -1,
12610
+ "aria-disabled": !c.hideable || combineSources.has(c.field) || void 0,
12611
+ tabIndex: c.hideable && !combineSources.has(c.field) ? 0 : -1,
12384
12612
  onKeyDown: (e) => {
12385
- if ((e.key === "Enter" || e.key === " ") && c.hideable) {
12613
+ if ((e.key === "Enter" || e.key === " ") && c.hideable && !combineSources.has(c.field)) {
12386
12614
  e.preventDefault();
12387
12615
  toggleHiddenField(c.field);
12388
12616
  }
12389
12617
  },
12390
- style: c.hideable ? void 0 : { opacity: 0.4 }
12618
+ style: c.hideable && !combineSources.has(c.field) ? void 0 : { opacity: 0.4 }
12391
12619
  }
12392
12620
  )
12393
12621
  ]
@@ -12398,6 +12626,82 @@ function Datatable({
12398
12626
  ] }),
12399
12627
  popGrip("columns")
12400
12628
  ] }) : null,
12629
+ panel === "combine" && panelPos && combineTarget && combineDraft ? (() => {
12630
+ const target = colByField[combineTarget];
12631
+ const candidates = cols.filter((c) => c.field !== combineTarget && c.type !== "actions" && c.field !== "__pinactions__" && c.hideable !== false && !userCombine[c.field]);
12632
+ const toggle = (f) => setCombineDraft((d) => ({ ...d, selected: d.selected.includes(f) ? d.selected.filter((x) => x !== f) : [...d.selected, f] }));
12633
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: combinePanelRef, tabIndex: -1, className: "twc-dt__pop twc-dt__cols", style: { top: panelPos.top, left: panelPos.left, ...popStyle("combine") }, "data-pop-sized": popSizes.combine ? "" : void 0, role: "dialog", "aria-label": `Combine columns into ${target?.headerName || combineTarget}`, children: [
12634
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__panel-head", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__panel-title", children: [
12635
+ "Combine into \u201C",
12636
+ target?.headerName || combineTarget,
12637
+ "\u201D"
12638
+ ] }) }),
12639
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__combine-hint", children: "Pick columns to merge into this one \u2014 their data shows in this cell and their own columns hide." }),
12640
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__col-list", children: candidates.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__empty", style: { padding: "18px 12px" }, children: "No other columns" }) : candidates.map((c) => {
12641
+ const on = combineDraft.selected.includes(c.field);
12642
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__col-row", onClick: () => toggle(c.field), children: [
12643
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__col-name", children: c.headerName }),
12644
+ /* @__PURE__ */ jsxRuntime.jsx(
12645
+ "span",
12646
+ {
12647
+ className: "twc-dt__sw",
12648
+ "data-on": on || void 0,
12649
+ role: "switch",
12650
+ "aria-checked": on,
12651
+ "aria-label": c.headerName,
12652
+ tabIndex: 0,
12653
+ onKeyDown: (e) => {
12654
+ if (e.key === "Enter" || e.key === " ") {
12655
+ e.preventDefault();
12656
+ toggle(c.field);
12657
+ }
12658
+ }
12659
+ }
12660
+ )
12661
+ ] }, c.field);
12662
+ }) }),
12663
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__combine-opts", children: [
12664
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "twc-dt__combine-opt", children: [
12665
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Layout" }),
12666
+ /* @__PURE__ */ jsxRuntime.jsx(
12667
+ Select,
12668
+ {
12669
+ size: "sm",
12670
+ portal: true,
12671
+ matchTriggerWidth: false,
12672
+ value: combineDraft.layout,
12673
+ options: [{ value: "inline", label: "Inline" }, { value: "stack", label: "Stacked" }],
12674
+ onChange: (v) => setCombineDraft((d) => ({ ...d, layout: v }))
12675
+ }
12676
+ )
12677
+ ] }),
12678
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__combine-opt", onClick: () => setCombineDraft((d) => ({ ...d, labels: !d.labels })), style: { cursor: "pointer" }, children: [
12679
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Show labels" }),
12680
+ /* @__PURE__ */ jsxRuntime.jsx(
12681
+ "span",
12682
+ {
12683
+ className: "twc-dt__sw",
12684
+ "data-on": combineDraft.labels || void 0,
12685
+ role: "switch",
12686
+ "aria-checked": combineDraft.labels,
12687
+ "aria-label": "Show labels",
12688
+ tabIndex: 0,
12689
+ onKeyDown: (e) => {
12690
+ if (e.key === "Enter" || e.key === " ") {
12691
+ e.preventDefault();
12692
+ setCombineDraft((d) => ({ ...d, labels: !d.labels }));
12693
+ }
12694
+ }
12695
+ }
12696
+ )
12697
+ ] })
12698
+ ] }),
12699
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__combine-foot", children: [
12700
+ userCombine[combineTarget] ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: () => uncombine(combineTarget), children: "Uncombine" }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
12701
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__combine-apply", onClick: applyCombine, children: "Apply" })
12702
+ ] })
12703
+ ] });
12704
+ })() : null,
12401
12705
  panel === "agg" && panelPos ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__pop twc-dt__cfg", style: { top: panelPos.top, left: panelPos.left, width: 300, ...popStyle("agg") }, "data-pop-sized": popSizes.agg ? "" : void 0, role: "dialog", "aria-label": "Aggregation settings", children: [
12402
12706
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__panel-head", children: [
12403
12707
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__panel-title", children: "Aggregation" }),
@@ -15550,6 +15854,8 @@ var ICONBTN_CSS = `
15550
15854
  .twc-iconbtn:focus-visible { outline: none; box-shadow: var(--ring); }
15551
15855
  .twc-iconbtn:active:not(:disabled) { transform: scale(var(--press-scale)); }
15552
15856
  .twc-iconbtn:disabled { opacity: 0.5; cursor: not-allowed; }
15857
+ /* #342: disabled state for the anchor form (as="a") \u2014 :disabled doesn't apply to <a>. */
15858
+ .twc-iconbtn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
15553
15859
  .twc-iconbtn svg { width: 1.25em; height: 1.25em; }
15554
15860
 
15555
15861
  /* tone supplies the accent set; variant decides how it's applied \u2014 mirrors Button. Default tone
@@ -15568,6 +15874,11 @@ var ICONBTN_CSS = `
15568
15874
  .twc-iconbtn[data-variant="outline"]:hover:not(:disabled) { border-color: var(--_accent); color: var(--_accent); background: var(--color-surface-sunken); }
15569
15875
  .twc-iconbtn[data-variant="ghost"]:hover:not(:disabled) { background: var(--color-surface-sunken); color: var(--_accent); }
15570
15876
  `;
15877
+ function safeHref6(url) {
15878
+ if (url == null) return void 0;
15879
+ const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
15880
+ return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
15881
+ }
15571
15882
  function IconButton({
15572
15883
  children,
15573
15884
  icon,
@@ -15576,21 +15887,28 @@ function IconButton({
15576
15887
  size = "md",
15577
15888
  round = false,
15578
15889
  disabled = false,
15890
+ as = "button",
15891
+ href,
15579
15892
  className = "",
15580
15893
  "aria-label": ariaLabel,
15581
15894
  ...rest
15582
15895
  }) {
15583
15896
  const __twcStyles = useScopedStyles("twc-iconbtn-styles", ICONBTN_CSS);
15897
+ const Tag2 = as;
15898
+ const inert = Tag2 === "a" && disabled;
15584
15899
  return /* @__PURE__ */ jsxRuntime.jsxs(
15585
- "button",
15900
+ Tag2,
15586
15901
  {
15587
15902
  className: `twc-iconbtn ${className}`,
15588
15903
  "data-variant": variant,
15589
15904
  "data-tone": tone,
15590
15905
  "data-size": size,
15591
15906
  "data-round": round || void 0,
15592
- disabled,
15593
- type: "button",
15907
+ disabled: Tag2 === "button" ? disabled : void 0,
15908
+ type: Tag2 === "button" ? "button" : void 0,
15909
+ href: Tag2 === "a" && !inert ? safeHref6(href) : void 0,
15910
+ "aria-disabled": inert || void 0,
15911
+ tabIndex: inert ? -1 : void 0,
15594
15912
  "aria-label": ariaLabel,
15595
15913
  ...rest,
15596
15914
  children: [
@@ -15792,7 +16110,7 @@ var LIST_CSS = `
15792
16110
  `;
15793
16111
  function List({ items, plain = false, emptyMessage, className = "", ...rest }) {
15794
16112
  const __twcStyles = useScopedStyles("twc-list-styles", LIST_CSS);
15795
- const safeHref13 = (url) => {
16113
+ const safeHref14 = (url) => {
15796
16114
  if (url == null) return void 0;
15797
16115
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
15798
16116
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -15804,7 +16122,7 @@ function List({ items, plain = false, emptyMessage, className = "", ...rest }) {
15804
16122
  /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: `twc-list ${className}`, role: "list", "data-plain": plain || void 0, ...rest, children: [
15805
16123
  __twcStyles,
15806
16124
  items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("li", { role: "presentation", style: { display: "contents" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-list__empty", children: emptyMessage ?? "Nothing here yet" }) }) : items.map((it, i) => {
15807
- const href = safeHref13(it.href);
16125
+ const href = safeHref14(it.href);
15808
16126
  const interactive = Boolean(it.onClick || href);
15809
16127
  const Tag2 = href ? "a" : interactive ? "button" : "div";
15810
16128
  const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -15860,7 +16178,7 @@ button.twc-navbar__brand, a.twc-navbar__brand { cursor: pointer; }
15860
16178
  .twc-navbar__menu-btn { display: inline-grid; }
15861
16179
  }
15862
16180
  `;
15863
- function safeHref6(url) {
16181
+ function safeHref7(url) {
15864
16182
  if (url == null) return void 0;
15865
16183
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
15866
16184
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -15883,9 +16201,9 @@ function Navbar({
15883
16201
  return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: `twc-navbar ${className}`, "data-sticky": sticky || void 0, ...rest, children: [
15884
16202
  __twcStyles,
15885
16203
  onMenuClick ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-navbar__menu-btn", "aria-expanded": menuOpen, "aria-controls": menuControls, "aria-label": menuOpen ? "Close menu" : "Open menu", onClick: onMenuClick, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 6h16M4 12h16M4 18h16" }) }) }) : null,
15886
- brand ? brandHref ? /* @__PURE__ */ jsxRuntime.jsx("a", { className: "twc-navbar__brand", href: safeHref6(brandHref), children: brand }) : onBrandClick ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-navbar__brand", onClick: onBrandClick, children: brand }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-navbar__brand", children: brand }) : null,
16204
+ brand ? brandHref ? /* @__PURE__ */ jsxRuntime.jsx("a", { className: "twc-navbar__brand", href: safeHref7(brandHref), children: brand }) : onBrandClick ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-navbar__brand", onClick: onBrandClick, children: brand }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-navbar__brand", children: brand }) : null,
15887
16205
  /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "twc-navbar__links", "aria-label": navLabel, children: links.map((l, i) => {
15888
- const href = safeHref6(l.href);
16206
+ const href = safeHref7(l.href);
15889
16207
  const inner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
15890
16208
  l.icon,
15891
16209
  l.label
@@ -16672,13 +16990,13 @@ function Sidebar({
16672
16990
  document.addEventListener("keydown", onKey);
16673
16991
  return () => document.removeEventListener("keydown", onKey);
16674
16992
  }, [overlay, open, openProp, onOpenChange]);
16675
- const safeHref13 = (url) => {
16993
+ const safeHref14 = (url) => {
16676
16994
  if (url == null) return void 0;
16677
16995
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
16678
16996
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
16679
16997
  };
16680
16998
  const renderItem = (it, i) => {
16681
- const href = safeHref13(it.href);
16999
+ const href = safeHref14(it.href);
16682
17000
  const labelStr = typeof it.label === "string" ? it.label : void 0;
16683
17001
  const inner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
16684
17002
  it.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-sidebar__ic", "aria-hidden": "true", children: it.icon }) : labelStr != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-sidebar__ic twc-sidebar__ic--initial", "aria-hidden": "true", children: labelStr.charAt(0) }) : null,
@@ -18392,7 +18710,7 @@ function bgValue(bg) {
18392
18710
  if (BG[bg]) return `var(${BG[bg]})`;
18393
18711
  return String(bg).startsWith("--") ? `var(${bg})` : bg;
18394
18712
  }
18395
- function safeHref7(url) {
18713
+ function safeHref8(url) {
18396
18714
  if (url == null) return void 0;
18397
18715
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18398
18716
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18440,7 +18758,7 @@ var Box = React51__namespace.default.forwardRef(function Box2({
18440
18758
  ...style,
18441
18759
  ...flatStyle
18442
18760
  };
18443
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref7(rest.href);
18761
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref8(rest.href);
18444
18762
  return /* @__PURE__ */ jsxRuntime.jsxs(Tag2, { ref, className: `twc-box ${className}`.trim(), style: s, "data-twc-sx": sxAttr, ...rest, children: [
18445
18763
  styleNode,
18446
18764
  children
@@ -18448,7 +18766,7 @@ var Box = React51__namespace.default.forwardRef(function Box2({
18448
18766
  });
18449
18767
  Box.displayName = "Box";
18450
18768
  var SIZES = { sm: 640, md: 768, lg: 1024, xl: 1280, full: "100%" };
18451
- function safeHref8(url) {
18769
+ function safeHref9(url) {
18452
18770
  if (url == null) return void 0;
18453
18771
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18454
18772
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18465,7 +18783,7 @@ var Container = React51__namespace.default.forwardRef(function Container2({
18465
18783
  }, ref) {
18466
18784
  const { flatStyle, styleNode, sxAttr } = useSx(sx);
18467
18785
  const max = SIZES[size] ?? size;
18468
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref8(rest.href);
18786
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref9(rest.href);
18469
18787
  return /* @__PURE__ */ jsxRuntime.jsxs(
18470
18788
  Tag2,
18471
18789
  {
@@ -18496,7 +18814,7 @@ function space2(v) {
18496
18814
  if (typeof v === "number") return `var(--space-${String(v).replace(".", "-")})`;
18497
18815
  return v;
18498
18816
  }
18499
- function safeHref9(url) {
18817
+ function safeHref10(url) {
18500
18818
  if (url == null) return void 0;
18501
18819
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18502
18820
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18524,7 +18842,7 @@ var Stack = React51__namespace.default.forwardRef(function Stack2({
18524
18842
  ...rest
18525
18843
  }, ref) {
18526
18844
  const { flatStyle, styleNode, sxAttr } = useSx(sx);
18527
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref9(rest.href);
18845
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref10(rest.href);
18528
18846
  const kids = divider != null ? React51__namespace.default.Children.toArray(children).flatMap((child, i, arr) => i < arr.length - 1 ? [child, /* @__PURE__ */ jsxRuntime.jsx(React51__namespace.default.Fragment, { children: divider }, `twc-stack-div-${i}`)] : [child]) : children;
18529
18847
  return /* @__PURE__ */ jsxRuntime.jsxs(
18530
18848
  Tag2,
@@ -18579,7 +18897,7 @@ var CODE_CSS = `
18579
18897
  .twc-code__copy:focus-visible { outline: none; box-shadow: var(--ring); }
18580
18898
  .twc-code__copy svg { width: 15px; height: 15px; display: block; }
18581
18899
  `;
18582
- function safeHref10(url) {
18900
+ function safeHref11(url) {
18583
18901
  if (url == null) return void 0;
18584
18902
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18585
18903
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18596,7 +18914,7 @@ function Code({
18596
18914
  }) {
18597
18915
  const __twcStyles = useScopedStyles("twc-code-styles", CODE_CSS);
18598
18916
  const Tag2 = as ?? (block ? "pre" : "code");
18599
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref10(rest.href);
18917
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref11(rest.href);
18600
18918
  const rootRef = React51__namespace.default.useRef(null);
18601
18919
  const [copied, setCopied] = React51__namespace.default.useState(false);
18602
18920
  const timerRef = React51__namespace.default.useRef(null);
@@ -18621,7 +18939,7 @@ function Code({
18621
18939
  ] });
18622
18940
  }
18623
18941
  var LEVEL_SIZE = { 1: "4xl", 2: "2xl", 3: "xl", 4: "lg", 5: "base", 6: "sm" };
18624
- function safeHref11(url) {
18942
+ function safeHref12(url) {
18625
18943
  if (url == null) return void 0;
18626
18944
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18627
18945
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18643,7 +18961,7 @@ var Heading = React51__namespace.default.forwardRef(function Heading2({
18643
18961
  const { flatStyle, styleNode, sxAttr } = useSx(sx);
18644
18962
  const Tag2 = as || `h${level}`;
18645
18963
  const sz = size || (display ? "display" : LEVEL_SIZE[level]) || "2xl";
18646
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref11(rest.href);
18964
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref12(rest.href);
18647
18965
  return /* @__PURE__ */ jsxRuntime.jsxs(
18648
18966
  Tag2,
18649
18967
  {
@@ -18690,7 +19008,7 @@ var TONE = {
18690
19008
  // Adopt the surrounding color (e.g. inside a solid Button/Badge/Alert or a link).
18691
19009
  inherit: "inherit"
18692
19010
  };
18693
- function safeHref12(url) {
19011
+ function safeHref13(url) {
18694
19012
  if (url == null) return void 0;
18695
19013
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18696
19014
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18710,7 +19028,7 @@ var Text = React51__namespace.default.forwardRef(function Text2({
18710
19028
  ...rest
18711
19029
  }, ref) {
18712
19030
  const { flatStyle, styleNode, sxAttr } = useSx(sx);
18713
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref12(rest.href);
19031
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref13(rest.href);
18714
19032
  const toneToken = TONE[tone] || TONE.default;
18715
19033
  return /* @__PURE__ */ jsxRuntime.jsxs(
18716
19034
  Tag2,