twico-ui 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to Twico UI are documented here (newest first). This file is generated automatically on each release by [semantic-release](https://semantic-release.gitbook.io/) from [Conventional Commits](https://www.conventionalcommits.org/) — do not edit it by hand. The same notes appear on each [GitHub Release](https://github.com/EncryptionL/twico-ui/releases).
4
4
 
5
+ # [1.15.0](https://github.com/EncryptionL/twico-ui/compare/v1.14.1...v1.15.0) (2026-07-14)
6
+
7
+
8
+ ### Features
9
+
10
+ * **datatable:** add `diff` mode — re-home DiffTable on the Datatable engine ([#239](https://github.com/EncryptionL/twico-ui/issues/239)) ([d7cf524](https://github.com/EncryptionL/twico-ui/commit/d7cf524e44e579318b36ead62aa13b832142cd89)), closes [#205](https://github.com/EncryptionL/twico-ui/issues/205)
11
+
12
+ ## [1.14.1](https://github.com/EncryptionL/twico-ui/compare/v1.14.0...v1.14.1) (2026-07-14)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **styles:** input renders invisibly under React 19 hoist dedup; add Datatable external-search + renderEditCell ([#234](https://github.com/EncryptionL/twico-ui/issues/234), [#235](https://github.com/EncryptionL/twico-ui/issues/235), [#236](https://github.com/EncryptionL/twico-ui/issues/236)) ([f661ea9](https://github.com/EncryptionL/twico-ui/commit/f661ea90dab1bf4b97ee02e8f103fb44ce668f43)), closes [#232](https://github.com/EncryptionL/twico-ui/issues/232)
18
+
5
19
  # [1.14.0](https://github.com/EncryptionL/twico-ui/compare/v1.13.0...v1.14.0) (2026-07-13)
6
20
 
7
21
 
package/dist/index.cjs CHANGED
@@ -7091,7 +7091,8 @@ var FIELD_CSS2 = `
7091
7091
  .twc-field__req { color: var(--color-danger); }
7092
7092
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
7093
7093
  .twc-field__error { font-size: var(--text-xs); color: var(--color-danger-subtle-fg); font-weight: var(--font-medium); }
7094
-
7094
+ `;
7095
+ var INPUT_EXTRA_CSS = `
7095
7096
  .twc-input {
7096
7097
  --_h: var(--control-h-md);
7097
7098
  display: flex; align-items: center; gap: var(--space-2);
@@ -7100,6 +7101,7 @@ var FIELD_CSS2 = `
7100
7101
  background: var(--color-surface);
7101
7102
  border: var(--border-thin) solid var(--color-border);
7102
7103
  border-radius: var(--radius-md);
7104
+ overflow: hidden;
7103
7105
  transition: border-color var(--duration-fast) var(--ease-standard),
7104
7106
  box-shadow var(--duration-fast) var(--ease-standard),
7105
7107
  background-color var(--duration-fast) var(--ease-standard);
@@ -7133,9 +7135,6 @@ var FIELD_CSS2 = `
7133
7135
  .twc-input__reveal:hover { background: var(--color-surface-sunken); color: var(--color-text); }
7134
7136
  .twc-input__reveal:focus-visible { outline: none; box-shadow: var(--ring); }
7135
7137
  .twc-input__reveal svg { width: 17px; height: 17px; }
7136
- `;
7137
- var INPUT_EXTRA_CSS = `
7138
- .twc-input { overflow: hidden; }
7139
7138
  .twc-input__addon { align-self: stretch; display: inline-flex; align-items: center; padding-inline: var(--space-3);
7140
7139
  background: var(--color-surface-sunken); color: var(--color-text-muted); font-size: var(--text-sm);
7141
7140
  font-weight: var(--font-semibold); white-space: nowrap; flex: none; }
@@ -8035,6 +8034,78 @@ function Tooltip({
8035
8034
  }
8036
8035
  );
8037
8036
  }
8037
+
8038
+ // components/_diff.js
8039
+ var DIFF_OPS = {
8040
+ added: { label: "Added", tone: "success" },
8041
+ removed: { label: "Removed", tone: "danger" },
8042
+ modified: { label: "Modified", tone: "warning" },
8043
+ moved: { label: "Moved", tone: "info" },
8044
+ unchanged: { label: "Unchanged", tone: "neutral" }
8045
+ };
8046
+ function lisIndexSet(seq) {
8047
+ const n = seq.length;
8048
+ if (n === 0) return /* @__PURE__ */ new Set();
8049
+ const dp = new Array(n).fill(1), prev = new Array(n).fill(-1);
8050
+ let best = 0;
8051
+ for (let i = 0; i < n; i++) {
8052
+ for (let j = 0; j < i; j++) {
8053
+ if (seq[j] < seq[i] && dp[j] + 1 > dp[i]) {
8054
+ dp[i] = dp[j] + 1;
8055
+ prev[i] = j;
8056
+ }
8057
+ }
8058
+ if (dp[i] > dp[best]) best = i;
8059
+ }
8060
+ const set = /* @__PURE__ */ new Set();
8061
+ for (let i = best; i !== -1; i = prev[i]) set.add(i);
8062
+ return set;
8063
+ }
8064
+ var cellVal = (col, row) => col.valueGetter ? col.valueGetter(row) : row == null ? void 0 : row[col.field];
8065
+ function classifyDiff(from = [], to = [], columns = [], rowKey, opts = {}) {
8066
+ const { moveDetection = true, compare } = opts;
8067
+ const k = rowKey || ((r2) => r2 == null ? r2 : r2.id);
8068
+ const valEq = (a, b, col) => {
8069
+ if (col.compare) return col.compare(a, b);
8070
+ if (compare) return compare(a, b, col.field);
8071
+ if (a === b) return true;
8072
+ if (a == null || b == null) return a === b;
8073
+ return JSON.stringify(a) === JSON.stringify(b);
8074
+ };
8075
+ const fromMap = /* @__PURE__ */ new Map(), toMap = /* @__PURE__ */ new Map();
8076
+ from.forEach((r2, i) => fromMap.set(k(r2), { row: r2, i }));
8077
+ to.forEach((r2, i) => toMap.set(k(r2), { row: r2, i }));
8078
+ const movedKeys = /* @__PURE__ */ new Set();
8079
+ if (moveDetection) {
8080
+ const commonFrom = from.filter((r2) => toMap.has(k(r2)));
8081
+ const seq = commonFrom.map((r2) => toMap.get(k(r2)).i);
8082
+ const stable = lisIndexSet(seq);
8083
+ commonFrom.forEach((r2, idx) => {
8084
+ if (!stable.has(idx)) movedKeys.add(k(r2));
8085
+ });
8086
+ }
8087
+ const classify = (key) => {
8088
+ const a = fromMap.get(key), b = toMap.get(key);
8089
+ if (a && !b) return { op: "removed", from: a.row, to: null, changed: /* @__PURE__ */ new Set() };
8090
+ if (!a && b) return { op: "added", from: null, to: b.row, changed: /* @__PURE__ */ new Set() };
8091
+ const changed = /* @__PURE__ */ new Set();
8092
+ for (const col of columns) if (col.field && !valEq(cellVal(col, a.row), cellVal(col, b.row), col)) changed.add(col.field);
8093
+ if (changed.size) return { op: "modified", from: a.row, to: b.row, changed };
8094
+ if (movedKeys.has(key)) return { op: "moved", from: a.row, to: b.row, changed };
8095
+ return { op: "unchanged", from: a.row, to: b.row, changed };
8096
+ };
8097
+ const rows = [];
8098
+ to.forEach((r2) => rows.push({ key: k(r2), ...classify(k(r2)) }));
8099
+ from.forEach((r2) => {
8100
+ const key = k(r2);
8101
+ if (!toMap.has(key)) rows.push({ key, ...classify(key) });
8102
+ });
8103
+ const counts = { added: 0, removed: 0, modified: 0, moved: 0, unchanged: 0 };
8104
+ rows.forEach((r2) => {
8105
+ counts[r2.op] += 1;
8106
+ });
8107
+ return { rows, counts };
8108
+ }
8038
8109
  var DT_CSS = `
8039
8110
  .twc-dt { display: flex; flex-direction: column; font-family: var(--font-sans); color: var(--color-text);
8040
8111
  border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg);
@@ -8273,6 +8344,18 @@ var DT_CSS = `
8273
8344
  /* Row reordering */
8274
8345
  .twc-dt__row[data-reorderable] { cursor: grab; }
8275
8346
  .twc-dt__row[data-row-dragging] { opacity: 0.4; }
8347
+ /* #239 diff mode: row op tint + before\u2192after cells (single-line, clipped by the td's own overflow). */
8348
+ .twc-dt__row[data-op="added"] > .twc-dt__td { background: color-mix(in srgb, var(--color-success) 7%, transparent); }
8349
+ .twc-dt__row[data-op="removed"] > .twc-dt__td { background: color-mix(in srgb, var(--color-danger) 7%, transparent); }
8350
+ .twc-dt__row[data-op="modified"] > .twc-dt__td { background: color-mix(in srgb, var(--color-warning) 6%, transparent); }
8351
+ .twc-dt__diff-change { display: inline-flex; align-items: center; gap: 4px; min-width: 0; max-width: 100%; }
8352
+ .twc-dt__diff-old { text-decoration: line-through; color: var(--color-text-subtle); }
8353
+ .twc-dt__diff-new { color: var(--color-success-subtle-fg); font-weight: var(--font-medium); }
8354
+ .twc-dt__diff-arrow { color: var(--color-text-subtle); flex: none; }
8355
+ [dir="rtl"] .twc-dt__diff-arrow { transform: scaleX(-1); display: inline-block; }
8356
+ .twc-dt__diff-summary { display: inline-flex; align-items: center; gap: 4px; flex: none; }
8357
+ .twc-dt__diff-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-sm); color: var(--color-text-muted); cursor: pointer; white-space: nowrap; flex: none; }
8358
+ .twc-dt__diff-toggle input { width: 15px; height: 15px; accent-color: var(--color-primary); cursor: pointer; margin: 0; }
8276
8359
  .twc-dt__row[data-row-dropbefore] > .twc-dt__td { box-shadow: inset 0 3px 0 var(--color-primary); }
8277
8360
  .twc-dt__row[data-row-dropafter] > .twc-dt__td { box-shadow: inset 0 -3px 0 var(--color-primary); }
8278
8361
  /* Keyboard row reorder (grabbed) */
@@ -8534,6 +8617,38 @@ var NUM_OPS = [
8534
8617
  var opsFor = (type) => type === "number" ? NUM_OPS : STR_OPS;
8535
8618
  var isMultiOp = (op) => op === "isAnyOf";
8536
8619
  var getColVal = (col, row) => col && col.valueGetter ? col.valueGetter(row) : row == null ? void 0 : row[col && col.field];
8620
+ var diffValueNode = (col, v, row) => col.renderCell ? col.renderCell(v, row) : col.valueFormatter ? col.valueFormatter(v, row) : v == null ? "" : v;
8621
+ function renderDiffCell(col, val, row) {
8622
+ const meta = row && row.__diffMeta;
8623
+ if (!meta) return diffValueNode(col, val, row);
8624
+ if (meta.op === "removed") return React51__namespace.default.createElement("span", { className: "twc-dt__diff-old" }, diffValueNode(col, getColVal(col, meta.from), meta.from));
8625
+ if (meta.op === "added") return React51__namespace.default.createElement("span", { className: "twc-dt__diff-new" }, diffValueNode(col, getColVal(col, meta.to), meta.to));
8626
+ if (meta.op === "modified" && meta.changed.has(col.field)) {
8627
+ return React51__namespace.default.createElement(
8628
+ "span",
8629
+ { className: "twc-dt__diff-change" },
8630
+ React51__namespace.default.createElement("span", { className: "twc-dt__diff-old" }, diffValueNode(col, getColVal(col, meta.from), meta.from)),
8631
+ React51__namespace.default.createElement("span", { className: "twc-dt__diff-arrow", "aria-hidden": "true" }, "\u2192"),
8632
+ React51__namespace.default.createElement("span", { className: "twc-dt__diff-new" }, diffValueNode(col, getColVal(col, meta.to), meta.to))
8633
+ );
8634
+ }
8635
+ return diffValueNode(col, val, row);
8636
+ }
8637
+ var DIFF_OP_COLUMN = {
8638
+ field: "__diffop",
8639
+ headerName: "Change",
8640
+ width: 122,
8641
+ pinned: "left",
8642
+ sortable: true,
8643
+ filterable: false,
8644
+ groupable: true,
8645
+ reorderable: false,
8646
+ resizable: true,
8647
+ wrappable: false,
8648
+ hideable: false,
8649
+ renderCell: (v) => v && DIFF_OPS[v] ? React51__namespace.default.createElement(Badge, { size: "sm", variant: "soft", tone: DIFF_OPS[v].tone }, DIFF_OPS[v].label) : null,
8650
+ exportValue: (v) => v && DIFF_OPS[v] ? DIFF_OPS[v].label : ""
8651
+ };
8537
8652
  function testFilter(raw, op, target, type) {
8538
8653
  if (op === "isEmpty") return raw == null || raw === "";
8539
8654
  if (op === "isNotEmpty") return !(raw == null || raw === "");
@@ -8852,11 +8967,12 @@ function xlsxPackage(sheetXml) {
8852
8967
  ]);
8853
8968
  }
8854
8969
  function Datatable({
8855
- columns,
8856
- rows,
8970
+ columns: columnsProp,
8971
+ rows: rowsProp = [],
8857
8972
  loading = false,
8858
- rowKey,
8973
+ rowKey: rowKeyProp,
8859
8974
  checkboxSelection = false,
8975
+ diff,
8860
8976
  density: densityProp = "comfortable",
8861
8977
  pageSize = 10,
8862
8978
  pageSizeOptions = [5, 10, 25, 50],
@@ -8893,6 +9009,9 @@ function Datatable({
8893
9009
  rowGrouping = [],
8894
9010
  rowNumbers = false,
8895
9011
  searchFields = null,
9012
+ searchable = true,
9013
+ quickFilter,
9014
+ onQuickFilterChange,
8896
9015
  rowPinning = false,
8897
9016
  rowReorder = false,
8898
9017
  rowResize = false,
@@ -8906,6 +9025,29 @@ function Datatable({
8906
9025
  ...rest
8907
9026
  }) {
8908
9027
  const __twcStyles = useScopedStyles("twc-dt-styles", DT_CSS);
9028
+ const isDiff = !!diff;
9029
+ const [diffOnly, setDiffOnly] = React51__namespace.default.useState(diff && diff.onlyChanged !== void 0 ? diff.onlyChanged : true);
9030
+ const diffRowKey = diff && diff.rowKey || rowKeyProp || ((r2) => r2 == null ? r2 : r2.id);
9031
+ const diffData = React51__namespace.default.useMemo(
9032
+ () => isDiff ? classifyDiff(diff.from || [], diff.to || [], columnsProp, diffRowKey, { moveDetection: diff.moveDetection, compare: diff.compare }) : null,
9033
+ [isDiff, diff, columnsProp]
9034
+ // eslint-disable-line react-hooks/exhaustive-deps
9035
+ );
9036
+ const onClassifiedRef = React51__namespace.default.useRef(diff && diff.onClassified);
9037
+ onClassifiedRef.current = diff && diff.onClassified;
9038
+ React51__namespace.default.useEffect(() => {
9039
+ if (diffData && onClassifiedRef.current) onClassifiedRef.current(diffData.counts);
9040
+ }, [diffData]);
9041
+ const diffRowKeyFn = React51__namespace.default.useCallback((r2) => r2.__diffKey, []);
9042
+ const columns = React51__namespace.default.useMemo(
9043
+ () => isDiff ? [DIFF_OP_COLUMN, ...columnsProp.map((c) => ({ ...c, renderCell: (val, row) => renderDiffCell(c, val, row) }))] : columnsProp,
9044
+ [isDiff, columnsProp]
9045
+ );
9046
+ const rows = React51__namespace.default.useMemo(
9047
+ () => isDiff ? diffData.rows.filter((r2) => !diffOnly || r2.op !== "unchanged").map((r2) => ({ ...r2.to || r2.from, __diffop: r2.op, __diffMeta: r2, __diffKey: r2.key })) : rowsProp,
9048
+ [isDiff, diffData, diffOnly, rowsProp]
9049
+ );
9050
+ const rowKey = isDiff ? diffRowKeyFn : rowKeyProp;
8909
9051
  const cols = React51__namespace.default.useMemo(() => {
8910
9052
  const out = columns.map((c) => {
8911
9053
  const isActions = c.type === "actions";
@@ -8975,7 +9117,13 @@ function Datatable({
8975
9117
  return { left, right };
8976
9118
  });
8977
9119
  const [density, setDensity] = React51__namespace.default.useState(densityProp);
8978
- const [quick, setQuick] = React51__namespace.default.useState("");
9120
+ const [internalQuick, setInternalQuick] = React51__namespace.default.useState("");
9121
+ const quick = quickFilter !== void 0 ? quickFilter : internalQuick;
9122
+ const commitQuick = (v) => {
9123
+ onQuickFilterChange?.(v);
9124
+ if (quickFilter === void 0) setInternalQuick(v);
9125
+ commitPage(0);
9126
+ };
8979
9127
  const [selected, setSelected] = React51__namespace.default.useState(() => /* @__PURE__ */ new Set());
8980
9128
  const [activeRow, setActiveRow] = React51__namespace.default.useState(null);
8981
9129
  const [activeCell, setActiveCell] = React51__namespace.default.useState(null);
@@ -9855,7 +10003,7 @@ function Datatable({
9855
10003
  setSelected(/* @__PURE__ */ new Set());
9856
10004
  }
9857
10005
  const [editing, setEditing] = React51__namespace.default.useState(null);
9858
- const isColEditable = (c) => c.type !== "actions" && (c.editable ?? (editMode && c.editable !== false));
10006
+ const isColEditable = (c) => c.type !== "actions" && c.editable !== false && (c.editable === true || c.renderEditCell != null || editMode);
9859
10007
  function beginEdit(rowK, col, row) {
9860
10008
  if (!isColEditable(col)) return;
9861
10009
  setEditing({ key: rowK, field: col.field, value: row[col.field] ?? "" });
@@ -10096,6 +10244,7 @@ function Datatable({
10096
10244
  role: "row",
10097
10245
  "aria-rowindex": (paginated && !serverMode ? pageVal * sizeVal : 0) + ri + 2,
10098
10246
  "aria-selected": (checkboxSelection ? sel : rowActive) || void 0,
10247
+ "data-op": row.__diffop || void 0,
10099
10248
  "data-selected": sel || void 0,
10100
10249
  "data-active": rowActive || void 0,
10101
10250
  "data-pinned-row": pinSide || void 0,
@@ -10186,7 +10335,13 @@ function Datatable({
10186
10335
  !checkboxSelection && reorderable && ci === 0 && !isEditing ? /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 6, maxWidth: "100%" }, children: [
10187
10336
  rowHandle(k, midIdx, row),
10188
10337
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis" }, children: isActions ? renderActions(c, row) : c.renderCell ? c.renderCell(val, row) : c.valueFormatter ? c.valueFormatter(val, row) : val })
10189
- ] }) : isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
10338
+ ] }) : isEditing ? c.renderEditCell ? (
10339
+ // #236: full escape hatch — the host drops in its own editor (searchable/creatable/
10340
+ // async Combobox, …) and drives the lifecycle via commit/cancel. Wrapped in
10341
+ // .twc-dt__editor-wrap so inline clicks (and any .twc-pop portal dropdown) don't
10342
+ // trip the outside-click auto-cancel.
10343
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__editor-wrap", children: c.renderEditCell({ value: editing.value, row, field: c.field, commit: (v) => commitEdit(v), cancel: cancelEdit }) })
10344
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
10190
10345
  EditCell,
10191
10346
  {
10192
10347
  col: c,
@@ -10340,6 +10495,28 @@ function Datatable({
10340
10495
  ))
10341
10496
  ] })
10342
10497
  ] }) : null,
10498
+ isDiff && diff.showSummary !== false ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__diff-summary", "aria-label": "Diff summary", children: [
10499
+ /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "success", children: [
10500
+ "+",
10501
+ diffData.counts.added
10502
+ ] }),
10503
+ /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "warning", children: [
10504
+ "~",
10505
+ diffData.counts.modified
10506
+ ] }),
10507
+ /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "danger", children: [
10508
+ "\u2212",
10509
+ diffData.counts.removed
10510
+ ] }),
10511
+ diffData.counts.moved ? /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "info", children: [
10512
+ "\u21C5",
10513
+ diffData.counts.moved
10514
+ ] }) : null
10515
+ ] }) : null,
10516
+ isDiff && diff.showToggle !== false ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "twc-dt__diff-toggle", children: [
10517
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: diffOnly, onChange: (e) => setDiffOnly(e.target.checked) }),
10518
+ diff.toggleLabel != null ? diff.toggleLabel : "Only changed"
10519
+ ] }) : null,
10343
10520
  /* @__PURE__ */ jsxRuntime.jsxs(
10344
10521
  "button",
10345
10522
  {
@@ -10476,13 +10653,10 @@ function Datatable({
10476
10653
  }
10477
10654
  )
10478
10655
  ] }) : null,
10479
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__search", children: [
10656
+ searchable ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__search", children: [
10480
10657
  /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.search }),
10481
- /* @__PURE__ */ jsxRuntime.jsx("input", { placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => {
10482
- setQuick(e.target.value);
10483
- commitPage(0);
10484
- } })
10485
- ] })
10658
+ /* @__PURE__ */ jsxRuntime.jsx("input", { placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => commitQuick(e.target.value) })
10659
+ ] }) : null
10486
10660
  ] }),
10487
10661
  !pivotActive && activeGroupBy.length ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__groupbar", children: [
10488
10662
  /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.group }),
@@ -11679,55 +11853,13 @@ function CardGrid({
11679
11853
  ] }) : null
11680
11854
  ] });
11681
11855
  }
11682
- var DIFFTABLE_CSS = `
11683
- .twc-difft { font-family: var(--font-sans); display: flex; flex-direction: column; gap: var(--space-3); color: var(--color-text); }
11684
- .twc-difft__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
11685
- .twc-difft__label { font-size: var(--text-sm); font-weight: var(--font-semibold); }
11686
- .twc-difft__summary { display: inline-flex; flex-wrap: wrap; gap: var(--space-1-5); }
11687
- .twc-difft__toggle { margin-inline-start: auto; }
11688
- .twc-difft__scroll { overflow-x: auto; border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg); }
11689
- .twc-difft__table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
11690
- .twc-difft__table th, .twc-difft__table td { text-align: start; padding: var(--space-2) var(--space-3); border-bottom: var(--border-thin) solid var(--color-divider); vertical-align: top; }
11691
- .twc-difft__table thead th { position: sticky; top: 0; background: var(--color-surface-sunken); font-weight: var(--font-semibold); color: var(--color-text-muted); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.02em; white-space: nowrap; z-index: 1; }
11692
- .twc-difft__table tbody tr:last-child td { border-bottom: none; }
11693
- .twc-difft__table tbody tr[data-op="added"] { background: color-mix(in srgb, var(--color-success) 8%, transparent); }
11694
- .twc-difft__table tbody tr[data-op="removed"] { background: color-mix(in srgb, var(--color-danger) 8%, transparent); }
11695
- .twc-difft__table tbody tr[data-op="modified"] { background: color-mix(in srgb, var(--color-warning) 8%, transparent); }
11696
- .twc-difft__op { white-space: nowrap; }
11697
- .twc-difft__change { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 2px; }
11698
- .twc-difft__old { text-decoration: line-through; color: var(--color-text-subtle); }
11699
- .twc-difft__new { color: var(--color-success-subtle-fg); font-weight: var(--font-medium); }
11700
- .twc-difft__arrow { margin-inline: 6px; color: var(--color-text-subtle); }
11701
- .twc-difft__empty { padding: var(--space-8) var(--space-4); text-align: center; color: var(--color-text-muted); }
11702
- [dir="rtl"] .twc-difft__arrow { transform: scaleX(-1); display: inline-block; }
11703
- `;
11704
- var OPS = {
11705
- added: { label: "Added", tone: "success" },
11706
- removed: { label: "Removed", tone: "danger" },
11707
- modified: { label: "Modified", tone: "warning" },
11708
- moved: { label: "Moved", tone: "info" },
11709
- unchanged: { label: "Unchanged", tone: "neutral" }
11710
- };
11711
- function lisIndexSet(seq) {
11712
- const n = seq.length;
11713
- if (n === 0) return /* @__PURE__ */ new Set();
11714
- const dp = new Array(n).fill(1), prev = new Array(n).fill(-1);
11715
- let best = 0;
11716
- for (let i = 0; i < n; i++) {
11717
- for (let j = 0; j < i; j++) {
11718
- if (seq[j] < seq[i] && dp[j] + 1 > dp[i]) {
11719
- dp[i] = dp[j] + 1;
11720
- prev[i] = j;
11721
- }
11722
- }
11723
- if (dp[i] > dp[best]) best = i;
11724
- }
11725
- const set = /* @__PURE__ */ new Set();
11726
- for (let i = best; i !== -1; i = prev[i]) set.add(i);
11727
- return set;
11728
- }
11729
- var getVal = (col, row) => col.valueGetter ? col.valueGetter(row) : row == null ? void 0 : row[col.field];
11730
- var fmtVal = (col, v) => col.valueFormatter ? col.valueFormatter(v) : v == null ? "" : String(v);
11856
+ var toDatatableColumn = (c) => ({
11857
+ field: c.field,
11858
+ headerName: typeof c.headerName === "string" ? c.headerName : c.field,
11859
+ valueGetter: c.valueGetter,
11860
+ valueFormatter: c.valueFormatter,
11861
+ compare: c.compare
11862
+ });
11731
11863
  function DiffTable({
11732
11864
  from = [],
11733
11865
  to = [],
@@ -11745,104 +11877,24 @@ function DiffTable({
11745
11877
  className = "",
11746
11878
  ...rest
11747
11879
  }) {
11748
- const styles = useScopedStyles("twc-difftable-styles", DIFFTABLE_CSS);
11749
- const autoId = React51__namespace.default.useId();
11750
- const fieldId = id || autoId;
11751
- const [only, setOnly] = React51__namespace.default.useState(onlyChanged);
11752
- const valEq = React51__namespace.default.useCallback((a, b, col) => {
11753
- if (col.compare) return col.compare(a, b);
11754
- if (compare) return compare(a, b, col.field);
11755
- if (a === b) return true;
11756
- if (a == null || b == null) return a === b;
11757
- return JSON.stringify(a) === JSON.stringify(b);
11758
- }, [compare]);
11759
- const diff = React51__namespace.default.useMemo(() => {
11760
- const k = rowKey || ((r2) => r2 == null ? r2 : r2.id);
11761
- const fromMap = /* @__PURE__ */ new Map(), toMap = /* @__PURE__ */ new Map();
11762
- from.forEach((r2, i) => fromMap.set(k(r2), { row: r2, i }));
11763
- to.forEach((r2, i) => toMap.set(k(r2), { row: r2, i }));
11764
- const movedKeys = /* @__PURE__ */ new Set();
11765
- if (moveDetection) {
11766
- const commonFrom = from.filter((r2) => toMap.has(k(r2)));
11767
- const seq = commonFrom.map((r2) => toMap.get(k(r2)).i);
11768
- const stable = lisIndexSet(seq);
11769
- commonFrom.forEach((r2, idx) => {
11770
- if (!stable.has(idx)) movedKeys.add(k(r2));
11771
- });
11772
- }
11773
- const classify = (key) => {
11774
- const a = fromMap.get(key), b = toMap.get(key);
11775
- if (a && !b) return { op: "removed", from: a.row, to: null, changed: /* @__PURE__ */ new Set() };
11776
- if (!a && b) return { op: "added", from: null, to: b.row, changed: /* @__PURE__ */ new Set() };
11777
- const changed = /* @__PURE__ */ new Set();
11778
- for (const col of columns) if (!valEq(getVal(col, a.row), getVal(col, b.row), col)) changed.add(col.field);
11779
- if (changed.size) return { op: "modified", from: a.row, to: b.row, changed };
11780
- if (movedKeys.has(key)) return { op: "moved", from: a.row, to: b.row, changed };
11781
- return { op: "unchanged", from: a.row, to: b.row, changed };
11782
- };
11783
- const rows = [];
11784
- to.forEach((r2) => rows.push({ key: k(r2), ...classify(k(r2)) }));
11785
- from.forEach((r2) => {
11786
- const key = k(r2);
11787
- if (!toMap.has(key)) rows.push({ key, ...classify(key) });
11788
- });
11789
- const counts = { added: 0, removed: 0, modified: 0, moved: 0, unchanged: 0 };
11790
- rows.forEach((r2) => {
11791
- counts[r2.op] += 1;
11792
- });
11793
- return { rows, counts };
11794
- }, [from, to, columns, rowKey, moveDetection, valEq]);
11795
- const visible = only ? diff.rows.filter((r2) => r2.op !== "unchanged") : diff.rows;
11796
- const renderCell = (r2, col) => {
11797
- if (r2.op === "removed") return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-difft__old", children: fmtVal(col, getVal(col, r2.from)) });
11798
- if (r2.op === "added") return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-difft__new", children: fmtVal(col, getVal(col, r2.to)) });
11799
- if (r2.op === "modified" && r2.changed.has(col.field)) {
11800
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-difft__change", children: [
11801
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-difft__old", children: fmtVal(col, getVal(col, r2.from)) }),
11802
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-difft__arrow", "aria-hidden": "true", children: "\u2192" }),
11803
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-difft__new", children: fmtVal(col, getVal(col, r2.to)) })
11804
- ] });
11880
+ const dtColumns = React51__namespace.default.useMemo(() => columns.map(toDatatableColumn), [columns]);
11881
+ const diff = React51__namespace.default.useMemo(
11882
+ () => ({ from, to, rowKey, onlyChanged, showToggle, showSummary, moveDetection, compare, toggleLabel }),
11883
+ [from, to, rowKey, onlyChanged, showToggle, showSummary, moveDetection, compare, toggleLabel]
11884
+ );
11885
+ const table = /* @__PURE__ */ jsxRuntime.jsx(
11886
+ Datatable,
11887
+ {
11888
+ columns: dtColumns,
11889
+ diff,
11890
+ emptyMessage: emptyState,
11891
+ ...label ? {} : { id, className, ...rest }
11805
11892
  }
11806
- return /* @__PURE__ */ jsxRuntime.jsx("span", { children: fmtVal(col, getVal(col, r2.to ?? r2.from)) });
11807
- };
11808
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `twc-difft ${className}`, id: fieldId, ...rest, children: [
11809
- styles,
11810
- label || showSummary || showToggle ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-difft__head", children: [
11811
- label ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-difft__label", children: label }) : null,
11812
- showSummary ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-difft__summary", children: [
11813
- /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "success", children: [
11814
- "+",
11815
- diff.counts.added,
11816
- " added"
11817
- ] }),
11818
- /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "warning", children: [
11819
- "~",
11820
- diff.counts.modified,
11821
- " modified"
11822
- ] }),
11823
- /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "danger", children: [
11824
- "-",
11825
- diff.counts.removed,
11826
- " removed"
11827
- ] }),
11828
- diff.counts.moved ? /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "info", children: [
11829
- "\u21C5 ",
11830
- diff.counts.moved,
11831
- " moved"
11832
- ] }) : null
11833
- ] }) : null,
11834
- showToggle ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-difft__toggle", children: /* @__PURE__ */ jsxRuntime.jsx(Checkbox, { label: toggleLabel, checked: only, onChange: (e) => setOnly(e.target.checked) }) }) : null
11835
- ] }) : null,
11836
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-difft__scroll", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "twc-difft__table", children: [
11837
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
11838
- /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: "Change" }),
11839
- columns.map((c) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: c.headerName || c.field }, c.field))
11840
- ] }) }),
11841
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { className: "twc-difft__empty", colSpan: columns.length + 1, children: emptyState }) }) : visible.map((r2) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { "data-op": r2.op, children: [
11842
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "twc-difft__op", children: /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "sm", variant: "soft", tone: OPS[r2.op].tone, children: OPS[r2.op].label }) }),
11843
- columns.map((c) => /* @__PURE__ */ jsxRuntime.jsx("td", { children: renderCell(r2, c) }, c.field))
11844
- ] }, r2.key)) })
11845
- ] }) })
11893
+ );
11894
+ if (!label) return table;
11895
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id, className, ...rest, style: { display: "flex", flexDirection: "column", gap: "var(--space-2)", fontFamily: "var(--font-sans)" }, children: [
11896
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: "var(--text-sm)", fontWeight: "var(--font-semibold)", color: "var(--color-text)" }, children: label }),
11897
+ table
11846
11898
  ] });
11847
11899
  }
11848
11900
  var FIELD_CSS3 = `