twico-ui 1.31.1 → 1.32.1

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
@@ -5888,6 +5888,7 @@ function Combobox({
5888
5888
  setQuery("");
5889
5889
  }
5890
5890
  function commit(v) {
5891
+ if (disabled) return;
5891
5892
  const o = flat.find((x) => x.value === v);
5892
5893
  if (o && o.disabled) return;
5893
5894
  if (value === void 0) setInternal(v);
@@ -6064,7 +6065,7 @@ function Combobox({
6064
6065
  ...rest
6065
6066
  }
6066
6067
  ),
6067
- clearable && selected && !open ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-cb__clear", "aria-label": "Clear", onClick: (e) => {
6068
+ clearable && selected && !open && !disabled ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-cb__clear", "aria-label": "Clear", onClick: (e) => {
6068
6069
  e.stopPropagation();
6069
6070
  commit(null);
6070
6071
  }, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null,
@@ -7354,6 +7355,7 @@ var MULTI_CSS = `
7354
7355
  background: var(--_accent-subtle); color: var(--_accent-subtle-fg);
7355
7356
  border-radius: var(--radius-full); font-size: var(--text-xs); font-weight: var(--font-semibold);
7356
7357
  }
7358
+ .twc-ms__control[data-disabled="true"] .twc-ms__chip { padding-inline-end: 9px; } /* #342: no \u2715 \u2192 symmetric pad */
7357
7359
  .twc-ms__chip-x { display: inline-grid; place-items: center; width: 16px; height: 16px; border: none; padding: 0; background: transparent; color: inherit; cursor: pointer; border-radius: var(--radius-full); opacity: 0.7; }
7358
7360
  .twc-ms__chip-x:hover { opacity: 1; background: color-mix(in srgb, currentColor 18%, transparent); }
7359
7361
  .twc-ms__chip-x svg { width: 12px; height: 12px; }
@@ -7596,6 +7598,7 @@ function MultiSelect({
7596
7598
  setScrollTop(next);
7597
7599
  }, [active, open, virtualized, rows, listH]);
7598
7600
  function commit(next) {
7601
+ if (disabled) return;
7599
7602
  if (value === void 0) setInternal(next);
7600
7603
  onChange?.(next);
7601
7604
  }
@@ -7752,10 +7755,10 @@ function MultiSelect({
7752
7755
  children: [
7753
7756
  (maxTagCount != null ? selectedOpts.slice(0, maxTagCount) : selectedOpts).map((o) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-ms__chip", children: [
7754
7757
  o.label,
7755
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", tabIndex: -1, className: "twc-ms__chip-x", "aria-label": `Remove ${o.label}`, onClick: (e) => {
7758
+ !disabled ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", tabIndex: -1, className: "twc-ms__chip-x", "aria-label": `Remove ${o.label}`, onClick: (e) => {
7756
7759
  e.stopPropagation();
7757
7760
  toggle(o.value);
7758
- }, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.6", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) })
7761
+ }, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.6", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
7759
7762
  ] }, o.value)),
7760
7763
  maxTagCount != null && selectedOpts.length > maxTagCount ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-ms__more", title: selectedOpts.slice(maxTagCount).map((o) => o.label).join(", "), children: [
7761
7764
  "+",
@@ -7808,8 +7811,10 @@ function MultiSelect({
7808
7811
  tabIndex: -1,
7809
7812
  onClick: (e) => {
7810
7813
  e.stopPropagation();
7811
- setOpen((o) => !o);
7812
- inputRef.current?.focus();
7814
+ if (!disabled) {
7815
+ setOpen((o) => !o);
7816
+ inputRef.current?.focus();
7817
+ }
7813
7818
  },
7814
7819
  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: "m6 9 6 6 6-6" }) })
7815
7820
  }
@@ -8453,7 +8458,13 @@ var DT_CSS = `
8453
8458
  /* #244: batch editor = pick columns (searchable), then set a value for each. */
8454
8459
  .twc-dt__be-add { padding: 6px 6px 8px; border-bottom: var(--border-thin) solid var(--color-divider); }
8455
8460
  .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; }
8461
+ /* #341: cap width at min(260, row \u2212 160px) so the value control keeps ~120px (+ gaps + remove) and never
8462
+ collapses \u2014 panel-width-aware in pure CSS, so it also grows if the (resizable) panel is widened. */
8463
+ .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); }
8464
+ /* #341: ellipsis lives on the inner text span so the outer field can host the (edge-overhanging) resize handle without clipping it. */
8465
+ .twc-dt__be-name-txt { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
8466
+ /* #341: reveal the batch-name resize handle (reused .twc-dt__f-rz) on row hover, like the filter fields. */
8467
+ .twc-dt__be-row:hover .twc-dt__f-rz { opacity: 1; }
8457
8468
  .twc-dt__be-ctl { flex: 1; min-width: 0; }
8458
8469
  .twc-dt__be-x { flex: none; display: inline-grid; place-items: center; width: 24px; height: 24px; padding: 0; border: none;
8459
8470
  background: transparent; color: var(--color-text-subtle); cursor: pointer; border-radius: var(--radius-sm);
@@ -8518,6 +8529,19 @@ var DT_CSS = `
8518
8529
  clipping to one line. height stays a per-row minimum, so single-line cells are unchanged. */
8519
8530
  .twc-dt__td[data-wrap="true"] { white-space: normal; overflow: hidden; text-overflow: clip;
8520
8531
  word-break: break-word; overflow-wrap: anywhere; vertical-align: top; padding-block: 10px; line-height: 1.45; }
8532
+ /* #338: "combined" column cell \u2014 one column showing several source columns' values. */
8533
+ .twc-dt__combine--stack { display: flex; flex-direction: column; gap: 1px; }
8534
+ .twc-dt__combine-sep { color: var(--color-text-subtle); padding-inline: 3px; }
8535
+ .twc-dt__combine-label { color: var(--color-text-muted); font-weight: var(--font-medium); }
8536
+ /* #339: the runtime combine-columns editor panel. */
8537
+ .twc-dt__combine-hint { padding: 2px 12px 8px; font-size: var(--text-xs); color: var(--color-text-muted); }
8538
+ .twc-dt__combine-opts { padding: 8px 12px; border-top: var(--border-thin) solid var(--color-divider); display: flex; flex-direction: column; gap: 8px; }
8539
+ .twc-dt__combine-opt { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: var(--text-sm); color: var(--color-text); }
8540
+ .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); }
8541
+ .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; }
8542
+ .twc-dt__combine-apply:hover { opacity: 0.9; }
8543
+ .twc-dt__combine-apply:focus-visible { outline: none; box-shadow: var(--ring); }
8544
+ .twc-dt__col-combined { color: var(--color-text-subtle); font-size: var(--text-xs); font-style: italic; }
8521
8545
  .twc-dt__td[data-num="true"] { text-align: end; font-variant-numeric: tabular-nums; }
8522
8546
  /* Auto row-number gutter (rowNumbers) \u2014 a sticky-left ordinal column. */
8523
8547
  .twc-dt__rownum { text-align: end; font-variant-numeric: tabular-nums; color: var(--color-text-subtle);
@@ -8852,6 +8876,34 @@ function mergeColOrder(saved, propFields) {
8852
8876
  return result;
8853
8877
  }
8854
8878
  var getColVal = (col, row) => col && col.valueGetter ? col.valueGetter(row) : row == null ? void 0 : row[col && col.field];
8879
+ function combineCfg(combine) {
8880
+ if (!combine) return null;
8881
+ const cfg = Array.isArray(combine) ? { fields: combine } : combine;
8882
+ const fields = Array.isArray(cfg.fields) ? cfg.fields.filter((f) => f != null) : [];
8883
+ if (!fields.length) return null;
8884
+ return { fields, separator: cfg.separator != null ? cfg.separator : " \xB7 ", labels: !!cfg.labels, layout: cfg.layout === "stack" ? "stack" : "inline" };
8885
+ }
8886
+ var combineSrcCols = (cfg, byField) => cfg.fields.map((f) => byField && byField[f] || { field: f, headerName: String(f) });
8887
+ var combineValueGetter = (srcCols, cfg) => (row) => srcCols.map((sc) => {
8888
+ const v = getColVal(sc, row);
8889
+ const s = v == null ? "" : String(v);
8890
+ return cfg.labels && s ? `${sc.headerName ?? sc.field}: ${s}` : s;
8891
+ }).filter(Boolean).join(cfg.separator);
8892
+ function renderCombined(srcCols, row, cfg) {
8893
+ const nodes = [];
8894
+ let count = 0;
8895
+ srcCols.forEach((sc, idx) => {
8896
+ const v = getColVal(sc, row);
8897
+ if (v == null || v === "") return;
8898
+ const disp = sc.valueFormatter ? sc.valueFormatter(v, row) : v;
8899
+ const label = cfg.labels ? React51__namespace.default.createElement("span", { className: "twc-dt__combine-label" }, (sc.headerName ?? sc.field) + ": ") : null;
8900
+ 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));
8901
+ nodes.push(React51__namespace.default.createElement("span", { key: idx, className: "twc-dt__combine-item" }, label, disp));
8902
+ count++;
8903
+ });
8904
+ if (!nodes.length) return "";
8905
+ return React51__namespace.default.createElement("span", { className: cfg.layout === "stack" ? "twc-dt__combine twc-dt__combine--stack" : "twc-dt__combine" }, nodes);
8906
+ }
8855
8907
  var diffValueNode = (col, v, row) => col.renderCell ? col.renderCell(v, row) : col.valueFormatter ? col.valueFormatter(v, row) : v == null ? "" : v;
8856
8908
  function renderDiffCell(col, val, row) {
8857
8909
  const meta = row && row.__diffMeta;
@@ -9232,6 +9284,7 @@ function Datatable({
9232
9284
  aggregationValues = null,
9233
9285
  disableColumnReorder = false,
9234
9286
  disableColumnResize = false,
9287
+ columnCombining = false,
9235
9288
  emptyMessage,
9236
9289
  renderEmpty,
9237
9290
  editMode = false,
@@ -9298,9 +9351,19 @@ function Datatable({
9298
9351
  if (diffData && onClassifiedRef.current) onClassifiedRef.current(diffData.counts);
9299
9352
  }, [diffData]);
9300
9353
  const diffRowKeyFn = React51__namespace.default.useCallback((r2) => r2.__diffKey, []);
9354
+ const [userCombine, setUserCombine] = React51__namespace.default.useState({});
9301
9355
  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]
9356
+ () => {
9357
+ if (!isDiff) return columnsProp;
9358
+ const byField = {};
9359
+ for (const c of columnsProp) if (c && c.field != null) byField[c.field] = c;
9360
+ return [DIFF_OP_COLUMN, ...columnsProp.map((c) => {
9361
+ const cfg = combineCfg(userCombine[c.field] || c.combine);
9362
+ const base = cfg && !c.valueGetter ? { ...c, valueGetter: combineValueGetter(combineSrcCols(cfg, byField), cfg) } : c;
9363
+ return { ...base, renderCell: (val, row) => renderDiffCell(base, val, row) };
9364
+ })];
9365
+ },
9366
+ [isDiff, columnsProp, userCombine]
9304
9367
  );
9305
9368
  const rows = React51__namespace.default.useMemo(
9306
9369
  () => 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 +9403,26 @@ function Datatable({
9340
9403
  getActions: () => []
9341
9404
  });
9342
9405
  }
9406
+ const byField = {};
9407
+ for (const c of out) if (c.field != null) byField[c.field] = c;
9408
+ for (let i = 0; i < out.length; i++) {
9409
+ const c = out[i];
9410
+ const cfg = combineCfg(userCombine[c.field] || c.combine);
9411
+ if (!cfg) continue;
9412
+ const srcCols = combineSrcCols(cfg, byField);
9413
+ out[i] = {
9414
+ ...c,
9415
+ editable: false,
9416
+ // the combined value is synthetic — inline editing would write to a phantom field
9417
+ valueGetter: c.valueGetter || combineValueGetter(srcCols, cfg),
9418
+ // Consumer renderCell wins. If they overrode only valueGetter, fall back to the DEFAULT cell render
9419
+ // (renderCell: undefined) so the cell shows THEIR value — keeping display in sync with what
9420
+ // sort/filter/search/export use — rather than the auto source-join. Otherwise render the combined cell.
9421
+ renderCell: c.renderCell || (c.valueGetter ? void 0 : ((value, row) => renderCombined(srcCols, row, cfg)))
9422
+ };
9423
+ }
9343
9424
  return out;
9344
- }, [columns, rowPinning]);
9425
+ }, [columns, rowPinning, userCombine]);
9345
9426
  const _autoKey = React51__namespace.default.useRef();
9346
9427
  if (!_autoKey.current) _autoKey.current = { map: /* @__PURE__ */ new WeakMap(), n: 0 };
9347
9428
  const keyOf = rowKey || ((r2, i) => {
@@ -9375,6 +9456,42 @@ function Datatable({
9375
9456
  n.has(field) ? n.delete(field) : n.add(field);
9376
9457
  return n;
9377
9458
  });
9459
+ const openCombineEditor = (field) => {
9460
+ const existing = userCombine[field];
9461
+ setCombineTarget(field);
9462
+ setCombineDraft({
9463
+ selected: existing ? existing.fields.filter((f) => f !== field) : [],
9464
+ layout: existing && existing.layout === "stack" ? "stack" : "inline",
9465
+ labels: !!(existing && existing.labels)
9466
+ });
9467
+ };
9468
+ const closeCombineEditor = () => {
9469
+ setPanel(null);
9470
+ closePanel();
9471
+ setCombineTarget(null);
9472
+ setCombineDraft(null);
9473
+ restoreTriggerFocus();
9474
+ };
9475
+ const applyCombine = () => {
9476
+ if (!combineTarget || !combineDraft) return;
9477
+ const field = combineTarget;
9478
+ const sources = combineDraft.selected.filter((f) => f !== field);
9479
+ setUserCombine((m) => {
9480
+ const next = { ...m };
9481
+ if (sources.length) next[field] = { fields: [field, ...sources], layout: combineDraft.layout === "stack" ? "stack" : "inline", separator: " \xB7 ", labels: !!combineDraft.labels };
9482
+ else delete next[field];
9483
+ return next;
9484
+ });
9485
+ closeCombineEditor();
9486
+ };
9487
+ const uncombine = (field) => {
9488
+ setUserCombine((m) => {
9489
+ const next = { ...m };
9490
+ delete next[field];
9491
+ return next;
9492
+ });
9493
+ closeCombineEditor();
9494
+ };
9378
9495
  const [pins, setPins] = React51__namespace.default.useState(() => {
9379
9496
  const left = [], right = [];
9380
9497
  columns.forEach((c) => {
@@ -9436,6 +9553,7 @@ function Datatable({
9436
9553
  const rowFocusRef = React51__namespace.default.useRef(null);
9437
9554
  const [fWidths, setFWidths] = React51__namespace.default.useState({});
9438
9555
  const [fPanelSize, setFPanelSize] = React51__namespace.default.useState(null);
9556
+ const [batchNameW, setBatchNameW] = React51__namespace.default.useState(null);
9439
9557
  const [popSizes, setPopSizes] = React51__namespace.default.useState({});
9440
9558
  const serializeState = () => ({
9441
9559
  filters: filters.map(({ id, ...f }) => f),
@@ -9456,7 +9574,11 @@ function Datatable({
9456
9574
  filterPanelSize: fPanelSize || void 0,
9457
9575
  filterFieldWidths: fWidths.col != null || fWidths.op != null || fWidths.val != null || fWidths.logic != null ? fWidths : void 0,
9458
9576
  // #304: additive, optional — user-resized sizes of the other toolbar popovers, keyed by popover id.
9459
- popoverSizes: Object.keys(popSizes).length ? popSizes : void 0
9577
+ popoverSizes: Object.keys(popSizes).length ? popSizes : void 0,
9578
+ // #339: additive, optional — runtime user-defined combined columns, keyed by target field.
9579
+ columnCombine: Object.keys(userCombine).length ? userCombine : void 0,
9580
+ // #341: additive, optional — user-resized batch-editor column-name field width (px).
9581
+ batchNameWidth: batchNameW != null ? batchNameW : void 0
9460
9582
  });
9461
9583
  const applyState = (s) => {
9462
9584
  if (!s || typeof s !== "object") return;
@@ -9501,6 +9623,16 @@ function Datatable({
9501
9623
  }
9502
9624
  if (Object.keys(next).length) setPopSizes(next);
9503
9625
  }
9626
+ if (s.columnCombine && typeof s.columnCombine === "object") {
9627
+ const uc = {};
9628
+ for (const [f, cfg] of Object.entries(s.columnCombine)) {
9629
+ if (!known.has(f) || !cfg || !Array.isArray(cfg.fields)) continue;
9630
+ const rest2 = cfg.fields.filter((x) => known.has(x) && x !== f);
9631
+ 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 };
9632
+ }
9633
+ if (Object.keys(uc).length) setUserCombine(uc);
9634
+ }
9635
+ if (typeof s.batchNameWidth === "number") setBatchNameW(clampNum(s.batchNameWidth, 88, 260));
9504
9636
  };
9505
9637
  const stateReadyRef = React51__namespace.default.useRef(false);
9506
9638
  const stateRestoredRef = React51__namespace.default.useRef(false);
@@ -9518,7 +9650,7 @@ function Datatable({
9518
9650
  }
9519
9651
  }
9520
9652
  return void 0;
9521
- }, [filters, filterLogic, sort, quick, pageVal, sizeVal, order, widths, hidden, pins, density, fWidths, fPanelSize, popSizes, stateKey]);
9653
+ }, [filters, filterLogic, sort, quick, pageVal, sizeVal, order, widths, hidden, pins, density, fWidths, fPanelSize, popSizes, userCombine, batchNameW, stateKey]);
9522
9654
  const restoreState = () => {
9523
9655
  let saved = null;
9524
9656
  if (stateKey) {
@@ -9659,6 +9791,15 @@ function Datatable({
9659
9791
  const [menuPos, openMenu, closeMenu] = useFloating();
9660
9792
  const [panel, setPanel] = React51__namespace.default.useState(null);
9661
9793
  const [panelPos, openPanel, closePanel] = useFloating();
9794
+ const [combineTarget, setCombineTarget] = React51__namespace.default.useState(null);
9795
+ const [combineDraft, setCombineDraft] = React51__namespace.default.useState(null);
9796
+ const combinePanelRef = React51__namespace.default.useRef(null);
9797
+ React51__namespace.default.useEffect(() => {
9798
+ if (panel === "combine" && combinePanelRef.current) {
9799
+ const el = combinePanelRef.current.querySelector('[role="switch"], button, [tabindex]');
9800
+ (el || combinePanelRef.current).focus?.();
9801
+ }
9802
+ }, [panel]);
9662
9803
  const [rowMenu, setRowMenu] = React51__namespace.default.useState(null);
9663
9804
  const [rowMenuPos, openRowMenu, closeRowMenu] = useFloating();
9664
9805
  const [exportOpen, setExportOpen] = React51__namespace.default.useState(false);
@@ -9759,7 +9900,21 @@ function Datatable({
9759
9900
  };
9760
9901
  }, [exportOpen]);
9761
9902
  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));
9903
+ const combineSources = React51__namespace.default.useMemo(() => {
9904
+ const s = /* @__PURE__ */ new Set();
9905
+ for (const cfg of Object.values(userCombine)) (cfg && cfg.fields ? cfg.fields : []).slice(1).forEach((f) => s.add(f));
9906
+ return s;
9907
+ }, [userCombine]);
9908
+ const stackCombineTargets = React51__namespace.default.useMemo(
9909
+ () => new Set(cols.filter((c) => {
9910
+ const cfg = combineCfg(userCombine[c.field] || c.combine);
9911
+ return cfg && cfg.layout === "stack";
9912
+ }).map((c) => c.field)),
9913
+ [cols, userCombine]
9914
+ );
9915
+ const effectiveHidden = React51__namespace.default.useMemo(() => combineSources.size ? /* @__PURE__ */ new Set([...hidden, ...combineSources]) : hidden, [hidden, combineSources]);
9916
+ const effectiveWrapped = React51__namespace.default.useMemo(() => stackCombineTargets.size ? /* @__PURE__ */ new Set([...wrapped, ...stackCombineTargets]) : wrapped, [wrapped, stackCombineTargets]);
9917
+ const visibleCols = cols.filter((c) => !effectiveHidden.has(c.field));
9763
9918
  const colFieldsKey = JSON.stringify(
9764
9919
  cols.filter((c) => typeof c.field === "string" && c.type !== "actions").map((c) => c.field)
9765
9920
  );
@@ -9778,17 +9933,17 @@ function Datatable({
9778
9933
  const i = order.indexOf(f);
9779
9934
  return i === -1 ? 9999 : i;
9780
9935
  };
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));
9936
+ const L = pins.left.map((f) => colByField[f]).filter((c) => c && !effectiveHidden.has(c.field));
9937
+ const R = pins.right.map((f) => colByField[f]).filter((c) => c && !effectiveHidden.has(c.field));
9783
9938
  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
9939
  return [...L, ...mid, ...R];
9785
- }, [pins, hidden, visibleCols, colByField, order]);
9940
+ }, [pins, effectiveHidden, visibleCols, colByField, order]);
9786
9941
  const NUM_W = 56;
9787
9942
  const CHK_W = checkboxSelection ? rowReorder ? 68 : 44 : 0;
9788
9943
  const numLeft = CHK_W;
9789
9944
  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]);
9945
+ const visLeft = React51__namespace.default.useMemo(() => pins.left.filter((f) => colByField[f] && !effectiveHidden.has(f)), [pins.left, colByField, effectiveHidden]);
9946
+ const visRight = React51__namespace.default.useMemo(() => pins.right.filter((f) => colByField[f] && !effectiveHidden.has(f)), [pins.right, colByField, effectiveHidden]);
9792
9947
  const stickyOf = (field) => {
9793
9948
  if (visLeft.includes(field)) {
9794
9949
  let off = leadW;
@@ -10404,6 +10559,7 @@ function Datatable({
10404
10559
  if (el) el.focus();
10405
10560
  }
10406
10561
  function onGridKeyDown(e) {
10562
+ if (e.target.closest("input, textarea, select, [contenteditable='true']")) return;
10407
10563
  if (editing) return;
10408
10564
  const td = e.target.closest(".twc-dt__td[data-r]");
10409
10565
  if (!td) return;
@@ -10953,6 +11109,54 @@ function Datatable({
10953
11109
  if (field === "val") p.removeAttribute("data-val-fixed");
10954
11110
  }
10955
11111
  };
11112
+ const BE_NAME_MIN = 88, BE_NAME_MAX = 260;
11113
+ const startBeNameResize = (e) => {
11114
+ if (e.button != null && e.button !== 0) return;
11115
+ e.preventDefault();
11116
+ e.stopPropagation();
11117
+ const handle = e.currentTarget;
11118
+ const panel2 = handle.closest(".twc-dt__cfg"), nameEl = handle.closest(".twc-dt__be-name");
11119
+ if (!panel2 || !nameEl) return;
11120
+ const startX = e.clientX, startW = nameEl.getBoundingClientRect().width;
11121
+ const dir = getComputedStyle(panel2).direction === "rtl" ? -1 : 1;
11122
+ handle.setAttribute("data-active", "true");
11123
+ let last = startW;
11124
+ const onMove = (ev) => {
11125
+ last = clampNum(startW + (ev.clientX - startX) * dir, BE_NAME_MIN, BE_NAME_MAX);
11126
+ panel2.style.setProperty("--twc-dt-be-name-usr", `${last}px`);
11127
+ };
11128
+ const onUp = () => {
11129
+ setBatchNameW(last);
11130
+ handle.removeAttribute("data-active");
11131
+ window.removeEventListener("pointermove", onMove);
11132
+ window.removeEventListener("pointerup", onUp);
11133
+ };
11134
+ window.addEventListener("pointermove", onMove);
11135
+ window.addEventListener("pointerup", onUp);
11136
+ };
11137
+ const onBeNameResizeKey = (e) => {
11138
+ const panel2 = e.currentTarget.closest(".twc-dt__cfg");
11139
+ if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
11140
+ e.preventDefault();
11141
+ e.stopPropagation();
11142
+ setBatchNameW(null);
11143
+ if (panel2) panel2.style.removeProperty("--twc-dt-be-name-usr");
11144
+ return;
11145
+ }
11146
+ if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) return;
11147
+ e.preventDefault();
11148
+ e.stopPropagation();
11149
+ const nameEl = e.currentTarget.closest(".twc-dt__be-name");
11150
+ const cur = nameEl ? nameEl.getBoundingClientRect().width : batchNameW ?? 108;
11151
+ const dir = panel2 && getComputedStyle(panel2).direction === "rtl" ? -1 : 1;
11152
+ const step = e.shiftKey ? 2 : 10;
11153
+ let next = cur;
11154
+ if (e.key === "ArrowRight") next = cur + step * dir;
11155
+ else if (e.key === "ArrowLeft") next = cur - step * dir;
11156
+ else if (e.key === "Home") next = BE_NAME_MIN;
11157
+ else if (e.key === "End") next = BE_NAME_MAX;
11158
+ setBatchNameW(clampNum(next, BE_NAME_MIN, BE_NAME_MAX));
11159
+ };
10956
11160
  const startPanelResize = (e) => {
10957
11161
  if (e.button != null && e.button !== 0) return;
10958
11162
  e.preventDefault();
@@ -11175,7 +11379,7 @@ function Datatable({
11175
11379
  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
11380
  const rowNumMatch = rowNumbers && "row number".includes(colQuery.trim().toLowerCase());
11177
11381
  const manageableCols = cols.filter((c) => c.field !== "__pinactions__");
11178
- const visibleColCount = manageableCols.filter((c) => !hidden.has(c.field)).length;
11382
+ const visibleColCount = manageableCols.filter((c) => !effectiveHidden.has(c.field)).length;
11179
11383
  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
11384
  const totalCols = ordered.length + (checkboxSelection ? 1 : 0) + (showRowNum ? 1 : 0);
11181
11385
  function renderGroupRow(item) {
@@ -11333,7 +11537,7 @@ function Datatable({
11333
11537
  "data-cell-active": cellActive || void 0,
11334
11538
  "data-cell-selected": cellSelected || void 0,
11335
11539
  "data-copied": cellFlash || void 0,
11336
- "data-wrap": wrapped.has(c.field) || void 0,
11540
+ "data-wrap": effectiveWrapped.has(c.field) || void 0,
11337
11541
  "data-pin": st.pin,
11338
11542
  "data-pin-edge": st.edge,
11339
11543
  style: { width: widthOf(c), ...st.style },
@@ -12030,7 +12234,7 @@ function Datatable({
12030
12234
  delete values[f];
12031
12235
  return { ...b, fields, values };
12032
12236
  });
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: [
12237
+ 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
12238
  /* @__PURE__ */ jsxRuntime.jsx(Caret, { pos: batchEditPos }),
12035
12239
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__cfg-head", children: [
12036
12240
  "Edit ",
@@ -12051,10 +12255,25 @@ function Datatable({
12051
12255
  onChange: addField
12052
12256
  }
12053
12257
  ) }) : 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) => {
12258
+ /* @__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
12259
  const opts = c.valueOptions ? c.valueOptions.map((o) => typeof o === "string" ? { value: o, label: o } : o) : null;
12056
12260
  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 }),
12261
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__be-name", title: String(c.headerName ?? c.field), children: [
12262
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__be-name-txt", children: c.headerName }),
12263
+ resizableFilters && i === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
12264
+ "span",
12265
+ {
12266
+ className: "twc-dt__f-rz",
12267
+ role: "separator",
12268
+ "aria-orientation": "vertical",
12269
+ tabIndex: 0,
12270
+ "aria-label": "Resize column name field (Enter to reset)",
12271
+ onPointerDown: startBeNameResize,
12272
+ onClick: (e) => e.stopPropagation(),
12273
+ onKeyDown: onBeNameResizeKey
12274
+ }
12275
+ ) : null
12276
+ ] }),
12058
12277
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__be-ctl", children: c.renderBatchEditCell ? c.renderBatchEditCell({
12059
12278
  value: batchEdit.values[c.field],
12060
12279
  field: c.field,
@@ -12169,6 +12388,18 @@ function Datatable({
12169
12388
  " ",
12170
12389
  groupBy.includes(c.field) ? "Stop grouping" : "Group by this column"
12171
12390
  ] }) : null,
12391
+ columnCombining && c.type !== "actions" ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", role: "menuitem", className: "twc-dt__mi", "data-active": !!userCombine[c.field] || void 0, onClick: () => {
12392
+ const anchor = menuTriggerRef.current;
12393
+ setColMenu(null);
12394
+ closeMenu();
12395
+ openCombineEditor(c.field);
12396
+ setPanel("combine");
12397
+ openPanel(anchor, "right", 300);
12398
+ }, children: [
12399
+ /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.columns }),
12400
+ " ",
12401
+ userCombine[c.field] ? "Edit combined column\u2026" : "Combine columns\u2026"
12402
+ ] }) : null,
12172
12403
  !disableColumnReorder && c.reorderable !== false && c.type !== "actions" && !pins.left.includes(c.field) && !pins.right.includes(c.field) && movableMidFields.length > 1 ? (() => {
12173
12404
  const midIdx = movableMidFields.indexOf(c.field);
12174
12405
  const atFirst = midIdx === 0;
@@ -12336,10 +12567,13 @@ function Datatable({
12336
12567
  e.preventDefault();
12337
12568
  onColDrop(c.field);
12338
12569
  } : void 0,
12339
- onClick: () => c.hideable && toggleHiddenField(c.field),
12570
+ onClick: () => c.hideable && !combineSources.has(c.field) && toggleHiddenField(c.field),
12340
12571
  children: [
12341
12572
  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 }),
12573
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__col-name", children: [
12574
+ c.headerName,
12575
+ combineSources.has(c.field) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__col-combined", children: " \xB7 combined" }) : null
12576
+ ] }),
12343
12577
  c.pinnable && c.type !== "actions" ? (
12344
12578
  // Pin from here too, so columns scrolled out of view (no reachable header ⋮) can still be pinned.
12345
12579
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__col-pins", draggable: false, onClick: (e) => e.stopPropagation(), children: [
@@ -12375,19 +12609,19 @@ function Datatable({
12375
12609
  "span",
12376
12610
  {
12377
12611
  className: "twc-dt__sw",
12378
- "data-on": !hidden.has(c.field) || void 0,
12612
+ "data-on": !effectiveHidden.has(c.field) || void 0,
12379
12613
  role: "switch",
12380
- "aria-checked": !hidden.has(c.field),
12614
+ "aria-checked": !effectiveHidden.has(c.field),
12381
12615
  "aria-label": c.headerName,
12382
- "aria-disabled": !c.hideable || void 0,
12383
- tabIndex: c.hideable ? 0 : -1,
12616
+ "aria-disabled": !c.hideable || combineSources.has(c.field) || void 0,
12617
+ tabIndex: c.hideable && !combineSources.has(c.field) ? 0 : -1,
12384
12618
  onKeyDown: (e) => {
12385
- if ((e.key === "Enter" || e.key === " ") && c.hideable) {
12619
+ if ((e.key === "Enter" || e.key === " ") && c.hideable && !combineSources.has(c.field)) {
12386
12620
  e.preventDefault();
12387
12621
  toggleHiddenField(c.field);
12388
12622
  }
12389
12623
  },
12390
- style: c.hideable ? void 0 : { opacity: 0.4 }
12624
+ style: c.hideable && !combineSources.has(c.field) ? void 0 : { opacity: 0.4 }
12391
12625
  }
12392
12626
  )
12393
12627
  ]
@@ -12398,6 +12632,82 @@ function Datatable({
12398
12632
  ] }),
12399
12633
  popGrip("columns")
12400
12634
  ] }) : null,
12635
+ panel === "combine" && panelPos && combineTarget && combineDraft ? (() => {
12636
+ const target = colByField[combineTarget];
12637
+ const candidates = cols.filter((c) => c.field !== combineTarget && c.type !== "actions" && c.field !== "__pinactions__" && c.hideable !== false && !userCombine[c.field]);
12638
+ const toggle = (f) => setCombineDraft((d) => ({ ...d, selected: d.selected.includes(f) ? d.selected.filter((x) => x !== f) : [...d.selected, f] }));
12639
+ 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: [
12640
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__panel-head", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__panel-title", children: [
12641
+ "Combine into \u201C",
12642
+ target?.headerName || combineTarget,
12643
+ "\u201D"
12644
+ ] }) }),
12645
+ /* @__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." }),
12646
+ /* @__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) => {
12647
+ const on = combineDraft.selected.includes(c.field);
12648
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__col-row", onClick: () => toggle(c.field), children: [
12649
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__col-name", children: c.headerName }),
12650
+ /* @__PURE__ */ jsxRuntime.jsx(
12651
+ "span",
12652
+ {
12653
+ className: "twc-dt__sw",
12654
+ "data-on": on || void 0,
12655
+ role: "switch",
12656
+ "aria-checked": on,
12657
+ "aria-label": c.headerName,
12658
+ tabIndex: 0,
12659
+ onKeyDown: (e) => {
12660
+ if (e.key === "Enter" || e.key === " ") {
12661
+ e.preventDefault();
12662
+ toggle(c.field);
12663
+ }
12664
+ }
12665
+ }
12666
+ )
12667
+ ] }, c.field);
12668
+ }) }),
12669
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__combine-opts", children: [
12670
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "twc-dt__combine-opt", children: [
12671
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Layout" }),
12672
+ /* @__PURE__ */ jsxRuntime.jsx(
12673
+ Select,
12674
+ {
12675
+ size: "sm",
12676
+ portal: true,
12677
+ matchTriggerWidth: false,
12678
+ value: combineDraft.layout,
12679
+ options: [{ value: "inline", label: "Inline" }, { value: "stack", label: "Stacked" }],
12680
+ onChange: (v) => setCombineDraft((d) => ({ ...d, layout: v }))
12681
+ }
12682
+ )
12683
+ ] }),
12684
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__combine-opt", onClick: () => setCombineDraft((d) => ({ ...d, labels: !d.labels })), style: { cursor: "pointer" }, children: [
12685
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Show labels" }),
12686
+ /* @__PURE__ */ jsxRuntime.jsx(
12687
+ "span",
12688
+ {
12689
+ className: "twc-dt__sw",
12690
+ "data-on": combineDraft.labels || void 0,
12691
+ role: "switch",
12692
+ "aria-checked": combineDraft.labels,
12693
+ "aria-label": "Show labels",
12694
+ tabIndex: 0,
12695
+ onKeyDown: (e) => {
12696
+ if (e.key === "Enter" || e.key === " ") {
12697
+ e.preventDefault();
12698
+ setCombineDraft((d) => ({ ...d, labels: !d.labels }));
12699
+ }
12700
+ }
12701
+ }
12702
+ )
12703
+ ] })
12704
+ ] }),
12705
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__combine-foot", children: [
12706
+ userCombine[combineTarget] ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: () => uncombine(combineTarget), children: "Uncombine" }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
12707
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__combine-apply", onClick: applyCombine, children: "Apply" })
12708
+ ] })
12709
+ ] });
12710
+ })() : null,
12401
12711
  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
12712
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__panel-head", children: [
12403
12713
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__panel-title", children: "Aggregation" }),
@@ -15445,6 +15755,7 @@ function FileUpload({
15445
15755
  } else if (accepted.length) set(accepted.slice(0, 1));
15446
15756
  }
15447
15757
  function remove(i) {
15758
+ if (disabled) return;
15448
15759
  set(files.filter((_, idx) => idx !== i));
15449
15760
  }
15450
15761
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `twc-upload ${className}`, "data-size": size, ...rest, children: [
@@ -15515,7 +15826,7 @@ function FileUpload({
15515
15826
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-upload__file-name", children: f.name }),
15516
15827
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-upload__file-size", children: fmtSize(f.size) })
15517
15828
  ] }),
15518
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: "twc-upload__file-x", "aria-label": `Remove ${f.name}`, onClick: () => remove(i), type: "button", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) })
15829
+ !disabled ? /* @__PURE__ */ jsxRuntime.jsx("button", { className: "twc-upload__file-x", "aria-label": `Remove ${f.name}`, onClick: () => remove(i), type: "button", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
15519
15830
  ] }, fileKey(f))) }) : null,
15520
15831
  error ? /* @__PURE__ */ jsxRuntime.jsx("span", { id: descId, className: "twc-field__error", children: error }) : null
15521
15832
  ] });
@@ -15550,6 +15861,8 @@ var ICONBTN_CSS = `
15550
15861
  .twc-iconbtn:focus-visible { outline: none; box-shadow: var(--ring); }
15551
15862
  .twc-iconbtn:active:not(:disabled) { transform: scale(var(--press-scale)); }
15552
15863
  .twc-iconbtn:disabled { opacity: 0.5; cursor: not-allowed; }
15864
+ /* #342: disabled state for the anchor form (as="a") \u2014 :disabled doesn't apply to <a>. */
15865
+ .twc-iconbtn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
15553
15866
  .twc-iconbtn svg { width: 1.25em; height: 1.25em; }
15554
15867
 
15555
15868
  /* tone supplies the accent set; variant decides how it's applied \u2014 mirrors Button. Default tone
@@ -15568,6 +15881,11 @@ var ICONBTN_CSS = `
15568
15881
  .twc-iconbtn[data-variant="outline"]:hover:not(:disabled) { border-color: var(--_accent); color: var(--_accent); background: var(--color-surface-sunken); }
15569
15882
  .twc-iconbtn[data-variant="ghost"]:hover:not(:disabled) { background: var(--color-surface-sunken); color: var(--_accent); }
15570
15883
  `;
15884
+ function safeHref6(url) {
15885
+ if (url == null) return void 0;
15886
+ const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
15887
+ return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
15888
+ }
15571
15889
  function IconButton({
15572
15890
  children,
15573
15891
  icon,
@@ -15576,21 +15894,28 @@ function IconButton({
15576
15894
  size = "md",
15577
15895
  round = false,
15578
15896
  disabled = false,
15897
+ as = "button",
15898
+ href,
15579
15899
  className = "",
15580
15900
  "aria-label": ariaLabel,
15581
15901
  ...rest
15582
15902
  }) {
15583
15903
  const __twcStyles = useScopedStyles("twc-iconbtn-styles", ICONBTN_CSS);
15904
+ const Tag2 = as;
15905
+ const inert = Tag2 === "a" && disabled;
15584
15906
  return /* @__PURE__ */ jsxRuntime.jsxs(
15585
- "button",
15907
+ Tag2,
15586
15908
  {
15587
15909
  className: `twc-iconbtn ${className}`,
15588
15910
  "data-variant": variant,
15589
15911
  "data-tone": tone,
15590
15912
  "data-size": size,
15591
15913
  "data-round": round || void 0,
15592
- disabled,
15593
- type: "button",
15914
+ disabled: Tag2 === "button" ? disabled : void 0,
15915
+ type: Tag2 === "button" ? "button" : void 0,
15916
+ href: Tag2 === "a" && !inert ? safeHref6(href) : void 0,
15917
+ "aria-disabled": inert || void 0,
15918
+ tabIndex: inert ? -1 : void 0,
15594
15919
  "aria-label": ariaLabel,
15595
15920
  ...rest,
15596
15921
  children: [
@@ -15792,7 +16117,7 @@ var LIST_CSS = `
15792
16117
  `;
15793
16118
  function List({ items, plain = false, emptyMessage, className = "", ...rest }) {
15794
16119
  const __twcStyles = useScopedStyles("twc-list-styles", LIST_CSS);
15795
- const safeHref13 = (url) => {
16120
+ const safeHref14 = (url) => {
15796
16121
  if (url == null) return void 0;
15797
16122
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
15798
16123
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -15804,7 +16129,7 @@ function List({ items, plain = false, emptyMessage, className = "", ...rest }) {
15804
16129
  /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: `twc-list ${className}`, role: "list", "data-plain": plain || void 0, ...rest, children: [
15805
16130
  __twcStyles,
15806
16131
  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);
16132
+ const href = safeHref14(it.href);
15808
16133
  const interactive = Boolean(it.onClick || href);
15809
16134
  const Tag2 = href ? "a" : interactive ? "button" : "div";
15810
16135
  const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -15860,7 +16185,7 @@ button.twc-navbar__brand, a.twc-navbar__brand { cursor: pointer; }
15860
16185
  .twc-navbar__menu-btn { display: inline-grid; }
15861
16186
  }
15862
16187
  `;
15863
- function safeHref6(url) {
16188
+ function safeHref7(url) {
15864
16189
  if (url == null) return void 0;
15865
16190
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
15866
16191
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -15883,9 +16208,9 @@ function Navbar({
15883
16208
  return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: `twc-navbar ${className}`, "data-sticky": sticky || void 0, ...rest, children: [
15884
16209
  __twcStyles,
15885
16210
  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,
16211
+ 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
16212
  /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "twc-navbar__links", "aria-label": navLabel, children: links.map((l, i) => {
15888
- const href = safeHref6(l.href);
16213
+ const href = safeHref7(l.href);
15889
16214
  const inner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
15890
16215
  l.icon,
15891
16216
  l.label
@@ -16672,13 +16997,13 @@ function Sidebar({
16672
16997
  document.addEventListener("keydown", onKey);
16673
16998
  return () => document.removeEventListener("keydown", onKey);
16674
16999
  }, [overlay, open, openProp, onOpenChange]);
16675
- const safeHref13 = (url) => {
17000
+ const safeHref14 = (url) => {
16676
17001
  if (url == null) return void 0;
16677
17002
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
16678
17003
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
16679
17004
  };
16680
17005
  const renderItem = (it, i) => {
16681
- const href = safeHref13(it.href);
17006
+ const href = safeHref14(it.href);
16682
17007
  const labelStr = typeof it.label === "string" ? it.label : void 0;
16683
17008
  const inner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
16684
17009
  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 +18717,7 @@ function bgValue(bg) {
18392
18717
  if (BG[bg]) return `var(${BG[bg]})`;
18393
18718
  return String(bg).startsWith("--") ? `var(${bg})` : bg;
18394
18719
  }
18395
- function safeHref7(url) {
18720
+ function safeHref8(url) {
18396
18721
  if (url == null) return void 0;
18397
18722
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18398
18723
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18440,7 +18765,7 @@ var Box = React51__namespace.default.forwardRef(function Box2({
18440
18765
  ...style,
18441
18766
  ...flatStyle
18442
18767
  };
18443
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref7(rest.href);
18768
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref8(rest.href);
18444
18769
  return /* @__PURE__ */ jsxRuntime.jsxs(Tag2, { ref, className: `twc-box ${className}`.trim(), style: s, "data-twc-sx": sxAttr, ...rest, children: [
18445
18770
  styleNode,
18446
18771
  children
@@ -18448,7 +18773,7 @@ var Box = React51__namespace.default.forwardRef(function Box2({
18448
18773
  });
18449
18774
  Box.displayName = "Box";
18450
18775
  var SIZES = { sm: 640, md: 768, lg: 1024, xl: 1280, full: "100%" };
18451
- function safeHref8(url) {
18776
+ function safeHref9(url) {
18452
18777
  if (url == null) return void 0;
18453
18778
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18454
18779
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18465,7 +18790,7 @@ var Container = React51__namespace.default.forwardRef(function Container2({
18465
18790
  }, ref) {
18466
18791
  const { flatStyle, styleNode, sxAttr } = useSx(sx);
18467
18792
  const max = SIZES[size] ?? size;
18468
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref8(rest.href);
18793
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref9(rest.href);
18469
18794
  return /* @__PURE__ */ jsxRuntime.jsxs(
18470
18795
  Tag2,
18471
18796
  {
@@ -18496,7 +18821,7 @@ function space2(v) {
18496
18821
  if (typeof v === "number") return `var(--space-${String(v).replace(".", "-")})`;
18497
18822
  return v;
18498
18823
  }
18499
- function safeHref9(url) {
18824
+ function safeHref10(url) {
18500
18825
  if (url == null) return void 0;
18501
18826
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18502
18827
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18524,7 +18849,7 @@ var Stack = React51__namespace.default.forwardRef(function Stack2({
18524
18849
  ...rest
18525
18850
  }, ref) {
18526
18851
  const { flatStyle, styleNode, sxAttr } = useSx(sx);
18527
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref9(rest.href);
18852
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref10(rest.href);
18528
18853
  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
18854
  return /* @__PURE__ */ jsxRuntime.jsxs(
18530
18855
  Tag2,
@@ -18579,7 +18904,7 @@ var CODE_CSS = `
18579
18904
  .twc-code__copy:focus-visible { outline: none; box-shadow: var(--ring); }
18580
18905
  .twc-code__copy svg { width: 15px; height: 15px; display: block; }
18581
18906
  `;
18582
- function safeHref10(url) {
18907
+ function safeHref11(url) {
18583
18908
  if (url == null) return void 0;
18584
18909
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18585
18910
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18596,7 +18921,7 @@ function Code({
18596
18921
  }) {
18597
18922
  const __twcStyles = useScopedStyles("twc-code-styles", CODE_CSS);
18598
18923
  const Tag2 = as ?? (block ? "pre" : "code");
18599
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref10(rest.href);
18924
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref11(rest.href);
18600
18925
  const rootRef = React51__namespace.default.useRef(null);
18601
18926
  const [copied, setCopied] = React51__namespace.default.useState(false);
18602
18927
  const timerRef = React51__namespace.default.useRef(null);
@@ -18621,7 +18946,7 @@ function Code({
18621
18946
  ] });
18622
18947
  }
18623
18948
  var LEVEL_SIZE = { 1: "4xl", 2: "2xl", 3: "xl", 4: "lg", 5: "base", 6: "sm" };
18624
- function safeHref11(url) {
18949
+ function safeHref12(url) {
18625
18950
  if (url == null) return void 0;
18626
18951
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18627
18952
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18643,7 +18968,7 @@ var Heading = React51__namespace.default.forwardRef(function Heading2({
18643
18968
  const { flatStyle, styleNode, sxAttr } = useSx(sx);
18644
18969
  const Tag2 = as || `h${level}`;
18645
18970
  const sz = size || (display ? "display" : LEVEL_SIZE[level]) || "2xl";
18646
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref11(rest.href);
18971
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref12(rest.href);
18647
18972
  return /* @__PURE__ */ jsxRuntime.jsxs(
18648
18973
  Tag2,
18649
18974
  {
@@ -18690,7 +19015,7 @@ var TONE = {
18690
19015
  // Adopt the surrounding color (e.g. inside a solid Button/Badge/Alert or a link).
18691
19016
  inherit: "inherit"
18692
19017
  };
18693
- function safeHref12(url) {
19018
+ function safeHref13(url) {
18694
19019
  if (url == null) return void 0;
18695
19020
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
18696
19021
  return s.startsWith("javascript:") || s.startsWith("data:") || s.startsWith("vbscript:") ? void 0 : url;
@@ -18710,7 +19035,7 @@ var Text = React51__namespace.default.forwardRef(function Text2({
18710
19035
  ...rest
18711
19036
  }, ref) {
18712
19037
  const { flatStyle, styleNode, sxAttr } = useSx(sx);
18713
- if (Tag2 === "a" && rest.href != null) rest.href = safeHref12(rest.href);
19038
+ if (Tag2 === "a" && rest.href != null) rest.href = safeHref13(rest.href);
18714
19039
  const toneToken = TONE[tone] || TONE.default;
18715
19040
  return /* @__PURE__ */ jsxRuntime.jsxs(
18716
19041
  Tag2,