twico-ui 1.14.1 → 1.15.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/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.1](https://github.com/EncryptionL/twico-ui/compare/v1.15.0...v1.15.1) (2026-07-14)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **datatable:** keep pinned cells opaque in diff mode ([#242](https://github.com/EncryptionL/twico-ui/issues/242)) ([072b15e](https://github.com/EncryptionL/twico-ui/commit/072b15ea73443ad535b3f149d8b620618702b389)), closes [#239](https://github.com/EncryptionL/twico-ui/issues/239)
11
+
12
+ # [1.15.0](https://github.com/EncryptionL/twico-ui/compare/v1.14.1...v1.15.0) (2026-07-14)
13
+
14
+
15
+ ### Features
16
+
17
+ * **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)
18
+
5
19
  ## [1.14.1](https://github.com/EncryptionL/twico-ui/compare/v1.14.0...v1.14.1) (2026-07-14)
6
20
 
7
21
 
package/dist/index.cjs CHANGED
@@ -8034,6 +8034,78 @@ function Tooltip({
8034
8034
  }
8035
8035
  );
8036
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
+ }
8037
8109
  var DT_CSS = `
8038
8110
  .twc-dt { display: flex; flex-direction: column; font-family: var(--font-sans); color: var(--color-text);
8039
8111
  border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg);
@@ -8272,6 +8344,24 @@ var DT_CSS = `
8272
8344
  /* Row reordering */
8273
8345
  .twc-dt__row[data-reorderable] { cursor: grab; }
8274
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
+ /* #242: pinned cells in a diff row must stay OPAQUE (composite the tint over the surface, not over
8352
+ transparent) or the cells scrolling under a sticky column bleed through. Specificity (0,4,0) wins
8353
+ over the (0,3,0) tint above and matches the non-pinned cells' effective color (tint over surface). */
8354
+ .twc-dt__row[data-op="added"] > .twc-dt__td[data-pin] { background: color-mix(in srgb, var(--color-success) 7%, var(--color-surface)); }
8355
+ .twc-dt__row[data-op="removed"] > .twc-dt__td[data-pin] { background: color-mix(in srgb, var(--color-danger) 7%, var(--color-surface)); }
8356
+ .twc-dt__row[data-op="modified"] > .twc-dt__td[data-pin] { background: color-mix(in srgb, var(--color-warning) 6%, var(--color-surface)); }
8357
+ .twc-dt__diff-change { display: inline-flex; align-items: center; gap: 4px; min-width: 0; max-width: 100%; }
8358
+ .twc-dt__diff-old { text-decoration: line-through; color: var(--color-text-subtle); }
8359
+ .twc-dt__diff-new { color: var(--color-success-subtle-fg); font-weight: var(--font-medium); }
8360
+ .twc-dt__diff-arrow { color: var(--color-text-subtle); flex: none; }
8361
+ [dir="rtl"] .twc-dt__diff-arrow { transform: scaleX(-1); display: inline-block; }
8362
+ .twc-dt__diff-summary { display: inline-flex; align-items: center; gap: 4px; flex: none; }
8363
+ .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; }
8364
+ .twc-dt__diff-toggle input { width: 15px; height: 15px; accent-color: var(--color-primary); cursor: pointer; margin: 0; }
8275
8365
  .twc-dt__row[data-row-dropbefore] > .twc-dt__td { box-shadow: inset 0 3px 0 var(--color-primary); }
8276
8366
  .twc-dt__row[data-row-dropafter] > .twc-dt__td { box-shadow: inset 0 -3px 0 var(--color-primary); }
8277
8367
  /* Keyboard row reorder (grabbed) */
@@ -8533,6 +8623,38 @@ var NUM_OPS = [
8533
8623
  var opsFor = (type) => type === "number" ? NUM_OPS : STR_OPS;
8534
8624
  var isMultiOp = (op) => op === "isAnyOf";
8535
8625
  var getColVal = (col, row) => col && col.valueGetter ? col.valueGetter(row) : row == null ? void 0 : row[col && col.field];
8626
+ var diffValueNode = (col, v, row) => col.renderCell ? col.renderCell(v, row) : col.valueFormatter ? col.valueFormatter(v, row) : v == null ? "" : v;
8627
+ function renderDiffCell(col, val, row) {
8628
+ const meta = row && row.__diffMeta;
8629
+ if (!meta) return diffValueNode(col, val, row);
8630
+ if (meta.op === "removed") return React51__namespace.default.createElement("span", { className: "twc-dt__diff-old" }, diffValueNode(col, getColVal(col, meta.from), meta.from));
8631
+ if (meta.op === "added") return React51__namespace.default.createElement("span", { className: "twc-dt__diff-new" }, diffValueNode(col, getColVal(col, meta.to), meta.to));
8632
+ if (meta.op === "modified" && meta.changed.has(col.field)) {
8633
+ return React51__namespace.default.createElement(
8634
+ "span",
8635
+ { className: "twc-dt__diff-change" },
8636
+ React51__namespace.default.createElement("span", { className: "twc-dt__diff-old" }, diffValueNode(col, getColVal(col, meta.from), meta.from)),
8637
+ React51__namespace.default.createElement("span", { className: "twc-dt__diff-arrow", "aria-hidden": "true" }, "\u2192"),
8638
+ React51__namespace.default.createElement("span", { className: "twc-dt__diff-new" }, diffValueNode(col, getColVal(col, meta.to), meta.to))
8639
+ );
8640
+ }
8641
+ return diffValueNode(col, val, row);
8642
+ }
8643
+ var DIFF_OP_COLUMN = {
8644
+ field: "__diffop",
8645
+ headerName: "Change",
8646
+ width: 122,
8647
+ pinned: "left",
8648
+ sortable: true,
8649
+ filterable: false,
8650
+ groupable: true,
8651
+ reorderable: false,
8652
+ resizable: true,
8653
+ wrappable: false,
8654
+ hideable: false,
8655
+ 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,
8656
+ exportValue: (v) => v && DIFF_OPS[v] ? DIFF_OPS[v].label : ""
8657
+ };
8536
8658
  function testFilter(raw, op, target, type) {
8537
8659
  if (op === "isEmpty") return raw == null || raw === "";
8538
8660
  if (op === "isNotEmpty") return !(raw == null || raw === "");
@@ -8851,11 +8973,12 @@ function xlsxPackage(sheetXml) {
8851
8973
  ]);
8852
8974
  }
8853
8975
  function Datatable({
8854
- columns,
8855
- rows,
8976
+ columns: columnsProp,
8977
+ rows: rowsProp = [],
8856
8978
  loading = false,
8857
- rowKey,
8979
+ rowKey: rowKeyProp,
8858
8980
  checkboxSelection = false,
8981
+ diff,
8859
8982
  density: densityProp = "comfortable",
8860
8983
  pageSize = 10,
8861
8984
  pageSizeOptions = [5, 10, 25, 50],
@@ -8908,6 +9031,29 @@ function Datatable({
8908
9031
  ...rest
8909
9032
  }) {
8910
9033
  const __twcStyles = useScopedStyles("twc-dt-styles", DT_CSS);
9034
+ const isDiff = !!diff;
9035
+ const [diffOnly, setDiffOnly] = React51__namespace.default.useState(diff && diff.onlyChanged !== void 0 ? diff.onlyChanged : true);
9036
+ const diffRowKey = diff && diff.rowKey || rowKeyProp || ((r2) => r2 == null ? r2 : r2.id);
9037
+ const diffData = React51__namespace.default.useMemo(
9038
+ () => isDiff ? classifyDiff(diff.from || [], diff.to || [], columnsProp, diffRowKey, { moveDetection: diff.moveDetection, compare: diff.compare }) : null,
9039
+ [isDiff, diff, columnsProp]
9040
+ // eslint-disable-line react-hooks/exhaustive-deps
9041
+ );
9042
+ const onClassifiedRef = React51__namespace.default.useRef(diff && diff.onClassified);
9043
+ onClassifiedRef.current = diff && diff.onClassified;
9044
+ React51__namespace.default.useEffect(() => {
9045
+ if (diffData && onClassifiedRef.current) onClassifiedRef.current(diffData.counts);
9046
+ }, [diffData]);
9047
+ const diffRowKeyFn = React51__namespace.default.useCallback((r2) => r2.__diffKey, []);
9048
+ const columns = React51__namespace.default.useMemo(
9049
+ () => isDiff ? [DIFF_OP_COLUMN, ...columnsProp.map((c) => ({ ...c, renderCell: (val, row) => renderDiffCell(c, val, row) }))] : columnsProp,
9050
+ [isDiff, columnsProp]
9051
+ );
9052
+ const rows = React51__namespace.default.useMemo(
9053
+ () => isDiff ? diffData.rows.filter((r2) => !diffOnly || r2.op !== "unchanged").map((r2) => ({ ...r2.to || r2.from, __diffop: r2.op, __diffMeta: r2, __diffKey: r2.key })) : rowsProp,
9054
+ [isDiff, diffData, diffOnly, rowsProp]
9055
+ );
9056
+ const rowKey = isDiff ? diffRowKeyFn : rowKeyProp;
8911
9057
  const cols = React51__namespace.default.useMemo(() => {
8912
9058
  const out = columns.map((c) => {
8913
9059
  const isActions = c.type === "actions";
@@ -10104,6 +10250,7 @@ function Datatable({
10104
10250
  role: "row",
10105
10251
  "aria-rowindex": (paginated && !serverMode ? pageVal * sizeVal : 0) + ri + 2,
10106
10252
  "aria-selected": (checkboxSelection ? sel : rowActive) || void 0,
10253
+ "data-op": row.__diffop || void 0,
10107
10254
  "data-selected": sel || void 0,
10108
10255
  "data-active": rowActive || void 0,
10109
10256
  "data-pinned-row": pinSide || void 0,
@@ -10354,6 +10501,28 @@ function Datatable({
10354
10501
  ))
10355
10502
  ] })
10356
10503
  ] }) : null,
10504
+ isDiff && diff.showSummary !== false ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__diff-summary", "aria-label": "Diff summary", children: [
10505
+ /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "success", children: [
10506
+ "+",
10507
+ diffData.counts.added
10508
+ ] }),
10509
+ /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "warning", children: [
10510
+ "~",
10511
+ diffData.counts.modified
10512
+ ] }),
10513
+ /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "danger", children: [
10514
+ "\u2212",
10515
+ diffData.counts.removed
10516
+ ] }),
10517
+ diffData.counts.moved ? /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "info", children: [
10518
+ "\u21C5",
10519
+ diffData.counts.moved
10520
+ ] }) : null
10521
+ ] }) : null,
10522
+ isDiff && diff.showToggle !== false ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "twc-dt__diff-toggle", children: [
10523
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: diffOnly, onChange: (e) => setDiffOnly(e.target.checked) }),
10524
+ diff.toggleLabel != null ? diff.toggleLabel : "Only changed"
10525
+ ] }) : null,
10357
10526
  /* @__PURE__ */ jsxRuntime.jsxs(
10358
10527
  "button",
10359
10528
  {
@@ -11690,55 +11859,13 @@ function CardGrid({
11690
11859
  ] }) : null
11691
11860
  ] });
11692
11861
  }
11693
- var DIFFTABLE_CSS = `
11694
- .twc-difft { font-family: var(--font-sans); display: flex; flex-direction: column; gap: var(--space-3); color: var(--color-text); }
11695
- .twc-difft__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
11696
- .twc-difft__label { font-size: var(--text-sm); font-weight: var(--font-semibold); }
11697
- .twc-difft__summary { display: inline-flex; flex-wrap: wrap; gap: var(--space-1-5); }
11698
- .twc-difft__toggle { margin-inline-start: auto; }
11699
- .twc-difft__scroll { overflow-x: auto; border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg); }
11700
- .twc-difft__table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
11701
- .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; }
11702
- .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; }
11703
- .twc-difft__table tbody tr:last-child td { border-bottom: none; }
11704
- .twc-difft__table tbody tr[data-op="added"] { background: color-mix(in srgb, var(--color-success) 8%, transparent); }
11705
- .twc-difft__table tbody tr[data-op="removed"] { background: color-mix(in srgb, var(--color-danger) 8%, transparent); }
11706
- .twc-difft__table tbody tr[data-op="modified"] { background: color-mix(in srgb, var(--color-warning) 8%, transparent); }
11707
- .twc-difft__op { white-space: nowrap; }
11708
- .twc-difft__change { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 2px; }
11709
- .twc-difft__old { text-decoration: line-through; color: var(--color-text-subtle); }
11710
- .twc-difft__new { color: var(--color-success-subtle-fg); font-weight: var(--font-medium); }
11711
- .twc-difft__arrow { margin-inline: 6px; color: var(--color-text-subtle); }
11712
- .twc-difft__empty { padding: var(--space-8) var(--space-4); text-align: center; color: var(--color-text-muted); }
11713
- [dir="rtl"] .twc-difft__arrow { transform: scaleX(-1); display: inline-block; }
11714
- `;
11715
- var OPS = {
11716
- added: { label: "Added", tone: "success" },
11717
- removed: { label: "Removed", tone: "danger" },
11718
- modified: { label: "Modified", tone: "warning" },
11719
- moved: { label: "Moved", tone: "info" },
11720
- unchanged: { label: "Unchanged", tone: "neutral" }
11721
- };
11722
- function lisIndexSet(seq) {
11723
- const n = seq.length;
11724
- if (n === 0) return /* @__PURE__ */ new Set();
11725
- const dp = new Array(n).fill(1), prev = new Array(n).fill(-1);
11726
- let best = 0;
11727
- for (let i = 0; i < n; i++) {
11728
- for (let j = 0; j < i; j++) {
11729
- if (seq[j] < seq[i] && dp[j] + 1 > dp[i]) {
11730
- dp[i] = dp[j] + 1;
11731
- prev[i] = j;
11732
- }
11733
- }
11734
- if (dp[i] > dp[best]) best = i;
11735
- }
11736
- const set = /* @__PURE__ */ new Set();
11737
- for (let i = best; i !== -1; i = prev[i]) set.add(i);
11738
- return set;
11739
- }
11740
- var getVal = (col, row) => col.valueGetter ? col.valueGetter(row) : row == null ? void 0 : row[col.field];
11741
- var fmtVal = (col, v) => col.valueFormatter ? col.valueFormatter(v) : v == null ? "" : String(v);
11862
+ var toDatatableColumn = (c) => ({
11863
+ field: c.field,
11864
+ headerName: typeof c.headerName === "string" ? c.headerName : c.field,
11865
+ valueGetter: c.valueGetter,
11866
+ valueFormatter: c.valueFormatter,
11867
+ compare: c.compare
11868
+ });
11742
11869
  function DiffTable({
11743
11870
  from = [],
11744
11871
  to = [],
@@ -11756,104 +11883,24 @@ function DiffTable({
11756
11883
  className = "",
11757
11884
  ...rest
11758
11885
  }) {
11759
- const styles = useScopedStyles("twc-difftable-styles", DIFFTABLE_CSS);
11760
- const autoId = React51__namespace.default.useId();
11761
- const fieldId = id || autoId;
11762
- const [only, setOnly] = React51__namespace.default.useState(onlyChanged);
11763
- const valEq = React51__namespace.default.useCallback((a, b, col) => {
11764
- if (col.compare) return col.compare(a, b);
11765
- if (compare) return compare(a, b, col.field);
11766
- if (a === b) return true;
11767
- if (a == null || b == null) return a === b;
11768
- return JSON.stringify(a) === JSON.stringify(b);
11769
- }, [compare]);
11770
- const diff = React51__namespace.default.useMemo(() => {
11771
- const k = rowKey || ((r2) => r2 == null ? r2 : r2.id);
11772
- const fromMap = /* @__PURE__ */ new Map(), toMap = /* @__PURE__ */ new Map();
11773
- from.forEach((r2, i) => fromMap.set(k(r2), { row: r2, i }));
11774
- to.forEach((r2, i) => toMap.set(k(r2), { row: r2, i }));
11775
- const movedKeys = /* @__PURE__ */ new Set();
11776
- if (moveDetection) {
11777
- const commonFrom = from.filter((r2) => toMap.has(k(r2)));
11778
- const seq = commonFrom.map((r2) => toMap.get(k(r2)).i);
11779
- const stable = lisIndexSet(seq);
11780
- commonFrom.forEach((r2, idx) => {
11781
- if (!stable.has(idx)) movedKeys.add(k(r2));
11782
- });
11783
- }
11784
- const classify = (key) => {
11785
- const a = fromMap.get(key), b = toMap.get(key);
11786
- if (a && !b) return { op: "removed", from: a.row, to: null, changed: /* @__PURE__ */ new Set() };
11787
- if (!a && b) return { op: "added", from: null, to: b.row, changed: /* @__PURE__ */ new Set() };
11788
- const changed = /* @__PURE__ */ new Set();
11789
- for (const col of columns) if (!valEq(getVal(col, a.row), getVal(col, b.row), col)) changed.add(col.field);
11790
- if (changed.size) return { op: "modified", from: a.row, to: b.row, changed };
11791
- if (movedKeys.has(key)) return { op: "moved", from: a.row, to: b.row, changed };
11792
- return { op: "unchanged", from: a.row, to: b.row, changed };
11793
- };
11794
- const rows = [];
11795
- to.forEach((r2) => rows.push({ key: k(r2), ...classify(k(r2)) }));
11796
- from.forEach((r2) => {
11797
- const key = k(r2);
11798
- if (!toMap.has(key)) rows.push({ key, ...classify(key) });
11799
- });
11800
- const counts = { added: 0, removed: 0, modified: 0, moved: 0, unchanged: 0 };
11801
- rows.forEach((r2) => {
11802
- counts[r2.op] += 1;
11803
- });
11804
- return { rows, counts };
11805
- }, [from, to, columns, rowKey, moveDetection, valEq]);
11806
- const visible = only ? diff.rows.filter((r2) => r2.op !== "unchanged") : diff.rows;
11807
- const renderCell = (r2, col) => {
11808
- if (r2.op === "removed") return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-difft__old", children: fmtVal(col, getVal(col, r2.from)) });
11809
- if (r2.op === "added") return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-difft__new", children: fmtVal(col, getVal(col, r2.to)) });
11810
- if (r2.op === "modified" && r2.changed.has(col.field)) {
11811
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-difft__change", children: [
11812
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-difft__old", children: fmtVal(col, getVal(col, r2.from)) }),
11813
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-difft__arrow", "aria-hidden": "true", children: "\u2192" }),
11814
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-difft__new", children: fmtVal(col, getVal(col, r2.to)) })
11815
- ] });
11886
+ const dtColumns = React51__namespace.default.useMemo(() => columns.map(toDatatableColumn), [columns]);
11887
+ const diff = React51__namespace.default.useMemo(
11888
+ () => ({ from, to, rowKey, onlyChanged, showToggle, showSummary, moveDetection, compare, toggleLabel }),
11889
+ [from, to, rowKey, onlyChanged, showToggle, showSummary, moveDetection, compare, toggleLabel]
11890
+ );
11891
+ const table = /* @__PURE__ */ jsxRuntime.jsx(
11892
+ Datatable,
11893
+ {
11894
+ columns: dtColumns,
11895
+ diff,
11896
+ emptyMessage: emptyState,
11897
+ ...label ? {} : { id, className, ...rest }
11816
11898
  }
11817
- return /* @__PURE__ */ jsxRuntime.jsx("span", { children: fmtVal(col, getVal(col, r2.to ?? r2.from)) });
11818
- };
11819
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `twc-difft ${className}`, id: fieldId, ...rest, children: [
11820
- styles,
11821
- label || showSummary || showToggle ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-difft__head", children: [
11822
- label ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-difft__label", children: label }) : null,
11823
- showSummary ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-difft__summary", children: [
11824
- /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "success", children: [
11825
- "+",
11826
- diff.counts.added,
11827
- " added"
11828
- ] }),
11829
- /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "warning", children: [
11830
- "~",
11831
- diff.counts.modified,
11832
- " modified"
11833
- ] }),
11834
- /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "danger", children: [
11835
- "-",
11836
- diff.counts.removed,
11837
- " removed"
11838
- ] }),
11839
- diff.counts.moved ? /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", variant: "soft", tone: "info", children: [
11840
- "\u21C5 ",
11841
- diff.counts.moved,
11842
- " moved"
11843
- ] }) : null
11844
- ] }) : null,
11845
- 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
11846
- ] }) : null,
11847
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-difft__scroll", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "twc-difft__table", children: [
11848
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
11849
- /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: "Change" }),
11850
- columns.map((c) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: c.headerName || c.field }, c.field))
11851
- ] }) }),
11852
- /* @__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: [
11853
- /* @__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 }) }),
11854
- columns.map((c) => /* @__PURE__ */ jsxRuntime.jsx("td", { children: renderCell(r2, c) }, c.field))
11855
- ] }, r2.key)) })
11856
- ] }) })
11899
+ );
11900
+ if (!label) return table;
11901
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id, className, ...rest, style: { display: "flex", flexDirection: "column", gap: "var(--space-2)", fontFamily: "var(--font-sans)" }, children: [
11902
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: "var(--text-sm)", fontWeight: "var(--font-semibold)", color: "var(--color-text)" }, children: label }),
11903
+ table
11857
11904
  ] });
11858
11905
  }
11859
11906
  var FIELD_CSS3 = `