@godxjp/ui 31.15.0 → 31.17.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.
Files changed (45) hide show
  1. package/agent/START-HERE.md +5 -5
  2. package/agent/components/Badge.json +2 -2
  3. package/agent/components/DataTable.json +3 -3
  4. package/agent/components/Table.json +1 -1
  5. package/agent/components/Text.json +1 -1
  6. package/agent/components/TextDiff.json +86 -0
  7. package/agent/components-index.json +6 -1
  8. package/agent/components.json +93 -7
  9. package/agent/index.json +6 -6
  10. package/agent/llms.txt +6 -6
  11. package/agent/tokens.json +48 -0
  12. package/dist/components/data-display/badge.js +17 -1
  13. package/dist/components/data-display/data-table.js +1 -1
  14. package/dist/components/data-display/image.js +6 -1
  15. package/dist/components/data-display/index.d.ts +2 -0
  16. package/dist/components/data-display/index.js +4 -0
  17. package/dist/components/data-display/table.js +1 -1
  18. package/dist/components/data-display/text-diff.d.ts +45 -0
  19. package/dist/components/data-display/text-diff.js +240 -0
  20. package/dist/components/general/typography.js +3 -3
  21. package/dist/contracts/measurement.json +1 -1
  22. package/dist/i18n/messages/en.json +12 -0
  23. package/dist/i18n/messages/ja.json +9 -0
  24. package/dist/i18n/messages/vi.json +9 -0
  25. package/dist/props/components/data-display.prop.d.ts +43 -0
  26. package/dist/props/registry.d.ts +46 -1
  27. package/dist/props/registry.js +58 -1
  28. package/dist/props/vocabulary/data.prop.d.ts +6 -0
  29. package/dist/props/vocabulary/interaction.prop.d.ts +1 -0
  30. package/dist/styles/badge-layout.css +12 -0
  31. package/dist/styles/data-display-layout.css +52 -0
  32. package/dist/styles/layers.json +6 -1
  33. package/dist/styles/navigation-layout.css +3 -0
  34. package/dist/styles/table-layout.css +33 -0
  35. package/dist/styles/text-layout.css +4 -0
  36. package/dist/tokens/components/data-display.css +8 -0
  37. package/dist/tokens/components/navigation.css +2 -0
  38. package/dist/tokens/components/table.css +4 -0
  39. package/docs/FRAME-COVERAGE-REPORT.md +3 -2
  40. package/docs/data-display/badge.tsx +15 -0
  41. package/docs/data-display/text-diff.tsx +89 -0
  42. package/docs/i18n/messages/en.json +24 -0
  43. package/docs/i18n/messages/ja.json +24 -0
  44. package/docs/i18n/messages/vi.json +24 -0
  45. package/package.json +2 -2
@@ -31,6 +31,8 @@ export { EmptyState } from "./empty-state.js";
31
31
  export { Progress } from "./progress.js";
32
32
  export { CodeBlock } from "./code-block.js";
33
33
  export type { CodeBlockProp, CodeBlockProps } from "./code-block.js";
34
+ export { TextDiff, diffText, tokenizeText } from "./text-diff.js";
35
+ export type { TextDiffGranularity, TextDiffProp, TextDiffProps, TextDiffSegment, } from "./text-diff.js";
34
36
  export { Prose } from "./prose.js";
35
37
  export type { ProseProp, ProseProps } from "./prose.js";
36
38
  export type { ProgressProps, ProgressSegment, ProgressTone } from "./progress.js";
@@ -31,6 +31,7 @@ import { DataTable, flexRender } from "./data-table.js";
31
31
  import { EmptyState } from "./empty-state.js";
32
32
  import { Progress } from "./progress.js";
33
33
  import { CodeBlock } from "./code-block.js";
34
+ import { TextDiff, diffText, tokenizeText } from "./text-diff.js";
34
35
  import { Prose } from "./prose.js";
35
36
  import { Timeline } from "./timeline.js";
36
37
  import { TimelineGrid } from "./timeline-grid.js";
@@ -134,6 +135,7 @@ export {
134
135
  TableHead,
135
136
  TableHeader,
136
137
  TableRow,
138
+ TextDiff,
137
139
  ThoughtChain,
138
140
  ThoughtChainItem,
139
141
  Thumbnail,
@@ -141,6 +143,8 @@ export {
141
143
  TimelineGrid,
142
144
  Tree,
143
145
  Welcome,
146
+ diffText,
144
147
  flexRender,
148
+ tokenizeText,
145
149
  useCarousel
146
150
  };
@@ -247,7 +247,7 @@ const TableCell = React.forwardRef(
247
247
  },
248
248
  ...props,
249
249
  children: [
250
- label !== void 0 ? /* @__PURE__ */ jsx("span", { className: "ui-table-stacked-collection-label", "aria-hidden": "true", children: label }) : null,
250
+ label !== void 0 && children != null && children !== false && children !== "" ? /* @__PURE__ */ jsx("span", { className: "ui-table-stacked-collection-label", "aria-hidden": "true", children: label }) : null,
251
251
  priority === "actions" && preset === "action-collection" ? /* @__PURE__ */ jsx("span", { className: "ui-table-actions-content", children }) : children
252
252
  ]
253
253
  }
@@ -0,0 +1,45 @@
1
+ import * as React from "react";
2
+ import type { TextDiffProp, TextDiffSegment } from "../../props/components/data-display.prop.js";
3
+ export type { TextDiffProp, TextDiffProp as TextDiffProps, TextDiffSegment, } from "../../props/components/data-display.prop.js";
4
+ /** The unit a change is measured in — see `TextDiffProp["granularity"]`. */
5
+ export type TextDiffGranularity = NonNullable<TextDiffProp["granularity"]>;
6
+ /**
7
+ * Cut a text into diff tokens. `line` keeps each line with its newline; `char` is one grapheme
8
+ * cluster per token (an emoji or a combining mark stays whole); `word` is `Intl.Segmenter` word
9
+ * segmentation in `lang`; `auto` is `word`, except that a CJK run is cut per character, because a
10
+ * dictionary word boundary in Japanese moves when one particle changes. A newline is always its
11
+ * own token so a changed line never swallows the line after it.
12
+ */
13
+ export declare function tokenizeText(text: string, granularity?: TextDiffGranularity, lang?: string): string[];
14
+ /**
15
+ * The diff `TextDiff` renders, for a caller that needs the runs themselves (a count of changes, a
16
+ * plain-text export). A longest-common-subsequence over {@link tokenizeText} tokens, with the
17
+ * common prefix and suffix trimmed first; adjacent runs of one kind are merged, and a removal is
18
+ * always emitted before the addition that replaces it. Past `maxCells` LCS cells the changed
19
+ * middle is one removal plus one addition — a bounded cost instead of a frozen tab.
20
+ */
21
+ export declare function diffText(before: string, after: string, options?: {
22
+ granularity?: TextDiffGranularity;
23
+ lang?: string;
24
+ maxCells?: number;
25
+ }): TextDiffSegment[];
26
+ /**
27
+ * TextDiff — what changed between two versions of a text (gh#1096): removed runs in `<del>`,
28
+ * added runs in `<ins>`, each with a struck-through or underlined mark AND a spoken start/end
29
+ * label, so the change is never carried by colour alone. Inline (one flow, GitHub's "unified")
30
+ * or split (before | after side by side); per line, per word, per character, or `auto` (words,
31
+ * with CJK cut per character). Long unchanged runs fold behind a button that reveals them.
32
+ */
33
+ export declare const TextDiff: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
34
+ before: string;
35
+ after: string;
36
+ mode?: "inline" | "split";
37
+ granularity?: "auto" | "word" | "char" | "line";
38
+ collapseUnchanged?: boolean;
39
+ contextLines?: number;
40
+ maxCells?: number;
41
+ beforeLabel?: React.ReactNode;
42
+ afterLabel?: React.ReactNode;
43
+ lang?: string;
44
+ className?: import("../../props/index.js").ClassNameProp;
45
+ } & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,240 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useTranslation } from "../../i18n/use-translation.js";
5
+ import { cn } from "../../lib/utils.js";
6
+ import { Button } from "../general/button.js";
7
+ import { VisuallyHidden } from "../general/visually-hidden.js";
8
+ const CJK = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
9
+ const DEFAULT_MAX_CELLS = 4e6;
10
+ const MIN_HIDDEN_LINES = 2;
11
+ function segmenter(lang, granularity) {
12
+ if (typeof Intl === "undefined" || typeof Intl.Segmenter !== "function") return null;
13
+ try {
14
+ return new Intl.Segmenter(lang, { granularity });
15
+ } catch {
16
+ return new Intl.Segmenter(void 0, { granularity });
17
+ }
18
+ }
19
+ function graphemes(text, lang) {
20
+ const s = segmenter(lang, "grapheme");
21
+ return s ? Array.from(s.segment(text), (part) => part.segment) : Array.from(text);
22
+ }
23
+ function tokenizeText(text, granularity = "auto", lang) {
24
+ if (text === "") return [];
25
+ if (granularity === "line") return text.match(/[^\n]*\n|[^\n]+/g) ?? [];
26
+ if (granularity === "char") return graphemes(text, lang);
27
+ const s = segmenter(lang, "word");
28
+ const words = s ? Array.from(s.segment(text), (part) => part.segment) : text.match(/\s+|[^\s]+/g) ?? [];
29
+ const tokens = [];
30
+ for (const word of words) {
31
+ const pieces = word.length > 1 && word.includes("\n") ? word.split(/(\n)/) : [word];
32
+ for (const piece of pieces) {
33
+ if (piece === "") continue;
34
+ if (granularity === "auto" && CJK.test(piece)) tokens.push(...graphemes(piece, lang));
35
+ else tokens.push(piece);
36
+ }
37
+ }
38
+ return tokens;
39
+ }
40
+ function push(segments, kind, text) {
41
+ if (text === "") return;
42
+ const last = segments[segments.length - 1];
43
+ if (last !== void 0 && last.kind === kind) last.text += text;
44
+ else segments.push({ kind, text });
45
+ }
46
+ function diffText(before, after, options = {}) {
47
+ const { granularity = "auto", lang, maxCells = DEFAULT_MAX_CELLS } = options;
48
+ const a = tokenizeText(before, granularity, lang);
49
+ const b = tokenizeText(after, granularity, lang);
50
+ let start = 0;
51
+ while (start < a.length && start < b.length && a[start] === b[start]) start++;
52
+ let endA = a.length;
53
+ let endB = b.length;
54
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
55
+ endA--;
56
+ endB--;
57
+ }
58
+ const segments = [];
59
+ push(segments, "same", a.slice(0, start).join(""));
60
+ const midA = a.slice(start, endA);
61
+ const midB = b.slice(start, endB);
62
+ if ((midA.length + 1) * (midB.length + 1) > maxCells) {
63
+ push(segments, "removed", midA.join(""));
64
+ push(segments, "added", midB.join(""));
65
+ } else {
66
+ const width = midB.length + 1;
67
+ const lengths = new Uint32Array((midA.length + 1) * width);
68
+ for (let i2 = midA.length - 1; i2 >= 0; i2--) {
69
+ for (let j2 = midB.length - 1; j2 >= 0; j2--) {
70
+ lengths[i2 * width + j2] = midA[i2] === midB[j2] ? lengths[(i2 + 1) * width + j2 + 1] + 1 : Math.max(lengths[(i2 + 1) * width + j2], lengths[i2 * width + j2 + 1]);
71
+ }
72
+ }
73
+ let i = 0;
74
+ let j = 0;
75
+ while (i < midA.length || j < midB.length) {
76
+ if (i < midA.length && j < midB.length && midA[i] === midB[j]) {
77
+ push(segments, "same", midA[i]);
78
+ i++;
79
+ j++;
80
+ } else if (i < midA.length && (j === midB.length || lengths[(i + 1) * width + j] >= lengths[i * width + j + 1])) {
81
+ push(segments, "removed", midA[i]);
82
+ i++;
83
+ } else {
84
+ push(segments, "added", midB[j]);
85
+ j++;
86
+ }
87
+ }
88
+ }
89
+ push(segments, "same", a.slice(endA).join(""));
90
+ return segments;
91
+ }
92
+ function toLines(segments) {
93
+ const lines = [];
94
+ let current = [];
95
+ for (const segment of segments) {
96
+ for (const piece of segment.text.split(/(?<=\n)/)) {
97
+ if (piece === "") continue;
98
+ current.push({ kind: segment.kind, text: piece });
99
+ if (piece.endsWith("\n")) {
100
+ lines.push(current);
101
+ current = [];
102
+ }
103
+ }
104
+ }
105
+ if (current.length > 0) lines.push(current);
106
+ return lines;
107
+ }
108
+ function toItems(lines, collapse, context) {
109
+ const runs = [];
110
+ for (const line of lines) {
111
+ const changed = line.some((piece) => piece.kind !== "same");
112
+ const last = runs[runs.length - 1];
113
+ if (last !== void 0 && last.changed === changed) last.lines.push(line);
114
+ else runs.push({ changed, lines: [line] });
115
+ }
116
+ const anyChange = runs.some((run) => run.changed);
117
+ const items = [];
118
+ runs.forEach((run, index) => {
119
+ if (run.changed || !collapse || !anyChange) {
120
+ items.push({ type: "lines", ...run });
121
+ return;
122
+ }
123
+ const head = index === 0 ? 0 : context;
124
+ const tail = index === runs.length - 1 ? 0 : context;
125
+ const hidden = run.lines.length - head - tail;
126
+ if (hidden < MIN_HIDDEN_LINES) {
127
+ items.push({ type: "lines", ...run });
128
+ return;
129
+ }
130
+ if (head > 0) items.push({ type: "lines", changed: false, lines: run.lines.slice(0, head) });
131
+ items.push({ type: "gap", id: index, lines: run.lines.slice(head, head + hidden) });
132
+ if (tail > 0) {
133
+ items.push({ type: "lines", changed: false, lines: run.lines.slice(head + hidden) });
134
+ }
135
+ });
136
+ return items;
137
+ }
138
+ function Runs({ lines, side }) {
139
+ const { t } = useTranslation();
140
+ const runs = [];
141
+ for (const piece of lines.flat()) {
142
+ if (side === "before" && piece.kind === "added") continue;
143
+ if (side === "after" && piece.kind === "removed") continue;
144
+ push(runs, piece.kind, piece.text);
145
+ }
146
+ return /* @__PURE__ */ jsx(Fragment, { children: runs.map(
147
+ (run, index) => run.kind === "same" ? /* @__PURE__ */ jsx(React.Fragment, { children: run.text }, index) : run.kind === "removed" ? /* @__PURE__ */ jsxs("del", { className: "ui-text-diff-removed", children: [
148
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: t("ui.textDiff.removedStart") }),
149
+ run.text,
150
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: t("ui.textDiff.removedEnd") })
151
+ ] }, index) : /* @__PURE__ */ jsxs("ins", { className: "ui-text-diff-added", children: [
152
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: t("ui.textDiff.addedStart") }),
153
+ run.text,
154
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: t("ui.textDiff.addedEnd") })
155
+ ] }, index)
156
+ ) });
157
+ }
158
+ const TextDiff = React.forwardRef(function TextDiff2({
159
+ before,
160
+ after,
161
+ mode = "inline",
162
+ granularity = "auto",
163
+ collapseUnchanged = true,
164
+ contextLines = 2,
165
+ maxCells,
166
+ beforeLabel,
167
+ afterLabel,
168
+ lang,
169
+ className,
170
+ ...props
171
+ }, ref) {
172
+ const { t } = useTranslation();
173
+ const items = React.useMemo(
174
+ () => toItems(
175
+ toLines(diffText(before, after, { granularity, lang, maxCells })),
176
+ collapseUnchanged,
177
+ Math.max(0, contextLines)
178
+ ),
179
+ [before, after, granularity, lang, maxCells, collapseUnchanged, contextLines]
180
+ );
181
+ const [expanded, setExpanded] = React.useState(() => /* @__PURE__ */ new Set());
182
+ React.useEffect(() => setExpanded(/* @__PURE__ */ new Set()), [items]);
183
+ const gap = (item, split) => /* @__PURE__ */ jsx("div", { className: "ui-text-diff-gap", "data-span": split || void 0, children: /* @__PURE__ */ jsx(
184
+ Button,
185
+ {
186
+ type: "button",
187
+ variant: "ghost",
188
+ size: "xs",
189
+ onClick: () => setExpanded((previous) => new Set(previous).add(item.id)),
190
+ children: t("ui.textDiff.showUnchanged", { count: item.lines.length })
191
+ }
192
+ ) }, `gap-${item.id}`);
193
+ const resolved = items.map(
194
+ (item) => item.type === "gap" && expanded.has(item.id) ? { type: "lines", changed: false, lines: item.lines } : item
195
+ );
196
+ return /* @__PURE__ */ jsx(
197
+ "div",
198
+ {
199
+ ref,
200
+ "data-slot": "text-diff",
201
+ "data-mode": mode,
202
+ lang,
203
+ className: cn("ui-text-diff", className),
204
+ ...props,
205
+ children: mode === "split" ? /* @__PURE__ */ jsxs("div", { className: "ui-text-diff-split", children: [
206
+ /* @__PURE__ */ jsx("div", { className: "ui-text-diff-heading", children: beforeLabel ?? t("ui.textDiff.before") }),
207
+ /* @__PURE__ */ jsx("div", { className: "ui-text-diff-heading", children: afterLabel ?? t("ui.textDiff.after") }),
208
+ resolved.map(
209
+ (item, index) => item.type === "gap" ? gap(item, true) : /* @__PURE__ */ jsxs(React.Fragment, { children: [
210
+ /* @__PURE__ */ jsx(
211
+ "div",
212
+ {
213
+ className: "ui-text-diff-cell",
214
+ "data-side": "before",
215
+ "data-changed": item.changed || void 0,
216
+ children: /* @__PURE__ */ jsx(Runs, { lines: item.lines, side: "before" })
217
+ }
218
+ ),
219
+ /* @__PURE__ */ jsx(
220
+ "div",
221
+ {
222
+ className: "ui-text-diff-cell",
223
+ "data-side": "after",
224
+ "data-changed": item.changed || void 0,
225
+ children: /* @__PURE__ */ jsx(Runs, { lines: item.lines, side: "after" })
226
+ }
227
+ )
228
+ ] }, index)
229
+ )
230
+ ] }) : /* @__PURE__ */ jsx("div", { className: "ui-text-diff-body", children: resolved.map(
231
+ (item, index) => item.type === "gap" ? gap(item, false) : /* @__PURE__ */ jsx(Runs, { lines: item.lines, side: "both" }, index)
232
+ ) })
233
+ }
234
+ );
235
+ });
236
+ export {
237
+ TextDiff,
238
+ diffText,
239
+ tokenizeText
240
+ };
@@ -469,9 +469,9 @@ const TextBase = React.forwardRef((props, ref) => {
469
469
  // always been, on the element itself.
470
470
  "data-truncate": truncating && !block.wrapsContent ? "" : void 0,
471
471
  "data-clamp": effectiveClamp !== void 0 && !block.wrapsContent ? "" : void 0,
472
- // Inert default: `normal` is CSS's own behaviour, so it emits no attribute and there is no
473
- // `[data-whitespace="normal"]` rule to lose a specificity argument with anything.
474
- "data-whitespace": whitespace === "pre-wrap" && !truncating ? "pre-wrap" : void 0,
472
+ // Unset emits nothing. An EXPLICIT `normal` is emitted (gh#1083): it releases a `nowrap` the
473
+ // Text inherits, e.g. from a table cell. `truncate` still wins by emitting nothing.
474
+ "data-whitespace": whitespace && !truncating ? whitespace : void 0,
475
475
  // Same shape and the same precedence as `whitespace`: `normal` is inert, and one line has
476
476
  // nowhere to break to, so `truncate` simply does not emit it (gh#927).
477
477
  "data-break": breakMode === "anywhere" && !truncating ? "anywhere" : void 0,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "31.15.0",
3
+ "version": "31.17.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -457,6 +457,18 @@
457
457
  "copy": "Copy code",
458
458
  "copied": "Copied",
459
459
  "copiedAnnounce": "Code copied to clipboard"
460
+ },
461
+ "textDiff": {
462
+ "before": "Before",
463
+ "after": "After",
464
+ "removedStart": "[removed: ",
465
+ "removedEnd": "]",
466
+ "addedStart": "[added: ",
467
+ "addedEnd": "]",
468
+ "showUnchanged": {
469
+ "one": "Show {count} unchanged line",
470
+ "other": "Show {count} unchanged lines"
471
+ }
460
472
  }
461
473
  },
462
474
  "navigation": {
@@ -450,6 +450,15 @@
450
450
  "copy": "コードをコピー",
451
451
  "copied": "コピーしました",
452
452
  "copiedAnnounce": "コードをクリップボードにコピーしました"
453
+ },
454
+ "textDiff": {
455
+ "before": "変更前",
456
+ "after": "変更後",
457
+ "removedStart": "[削除: ",
458
+ "removedEnd": "]",
459
+ "addedStart": "[追加: ",
460
+ "addedEnd": "]",
461
+ "showUnchanged": "変更のない{count}行を表示"
453
462
  }
454
463
  },
455
464
  "navigation": {
@@ -451,6 +451,15 @@
451
451
  "copy": "Sao chép mã",
452
452
  "copied": "Đã sao chép",
453
453
  "copiedAnnounce": "Đã sao chép mã vào bộ nhớ tạm"
454
+ },
455
+ "textDiff": {
456
+ "before": "Trước",
457
+ "after": "Sau",
458
+ "removedStart": "[đã xoá: ",
459
+ "removedEnd": "]",
460
+ "addedStart": "[đã thêm: ",
461
+ "addedEnd": "]",
462
+ "showUnchanged": "Hiện {count} dòng không đổi"
454
463
  }
455
464
  },
456
465
  "navigation": {
@@ -1334,3 +1334,46 @@ export type ImagePreviewGroupProp = {
1334
1334
  preview?: boolean | ImagePreviewGroupConfigProp;
1335
1335
  children?: React.ReactNode;
1336
1336
  };
1337
+ /** @see diffText — one run of a diff: kept, taken out of `before`, or put into `after`. */
1338
+ export type TextDiffSegment = {
1339
+ kind: "same" | "removed" | "added";
1340
+ text: string;
1341
+ };
1342
+ /**
1343
+ * @see TextDiff — what changed between two versions of a text (gh#1096).
1344
+ *
1345
+ * antd ships no diff, so the shape follows GitHub/GitLab: `<del>`/`<ins>` with a spoken start/end
1346
+ * label (never colour alone), inline ("unified") or split, unchanged runs folded around the
1347
+ * changes. Text keeps its newlines (`pre-wrap`) and a long token breaks anywhere.
1348
+ */
1349
+ export type TextDiffProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
1350
+ /** The earlier text — what a translation was written against, the saved version. */
1351
+ before: string;
1352
+ /** The current text. */
1353
+ after: string;
1354
+ /** `inline` (default) — one flow; `split` — two columns, before | after, hunks aligned. */
1355
+ mode?: "inline" | "split";
1356
+ /**
1357
+ * The unit a change is measured in. `auto` (default) is words for alphabetic text and one
1358
+ * character per token for Han / Hiragana / Katakana / Hangul runs, which have no spaces to split
1359
+ * on. `word` is `Intl.Segmenter` word segmentation in `lang` (dictionary words for Japanese),
1360
+ * `char` is one grapheme cluster per token, `line` compares whole lines.
1361
+ */
1362
+ granularity?: "auto" | "word" | "char" | "line";
1363
+ /** Fold long unchanged runs of lines behind a "show N unchanged lines" button. Default `true`. */
1364
+ collapseUnchanged?: boolean;
1365
+ /** Unchanged lines kept visible next to each change when folding. Default `2`. */
1366
+ contextLines?: number;
1367
+ /**
1368
+ * The LCS cost cap (tokens before × tokens after). Past it the changed middle is shown as one
1369
+ * removal and one addition instead of freezing the tab. Default `4_000_000`.
1370
+ */
1371
+ maxCells?: number;
1372
+ /** Heading of the before column in `split` mode. Default: the localized "Before". */
1373
+ beforeLabel?: React.ReactNode;
1374
+ /** Heading of the after column in `split` mode. Default: the localized "After". */
1375
+ afterLabel?: React.ReactNode;
1376
+ /** BCP-47 language of the text — set on the element and used for `Intl.Segmenter`. */
1377
+ lang?: string;
1378
+ className?: ClassNameProp;
1379
+ };
@@ -322,7 +322,7 @@ export declare const VOCABULARY_REGISTRY: {
322
322
  readonly TableExpandableProp: {
323
323
  readonly file: "vocabulary/data.prop.ts";
324
324
  readonly category: "data";
325
- readonly description: "DataTable expandable-row config — expandedRowRender | rowExpandable | defaultExpandAllRows | expandedRowKeys | onExpandedRowsChange | expandRowByClick (antd/rc-table ExpandableConfig)";
325
+ readonly description: "DataTable expandable-row config — expandedRowRender | rowExpandable | defaultExpandAllRows | expandedRowKeys | onExpandedRowsChange | expandRowByClick | flush (antd/rc-table ExpandableConfig; the detail cell is padded by default, gh#1083)";
326
326
  };
327
327
  readonly TableSummaryProp: {
328
328
  readonly file: "vocabulary/data.prop.ts";
@@ -2881,6 +2881,51 @@ export declare const COMPONENT_PROP_REGISTRY: {
2881
2881
  readonly reason: "The sample's accessible NAME, which is what lets a read-only colour be shown with no visible label. Absent, the mark is aria-hidden (Legend's rule) — so colour is never the sole carrier either way.";
2882
2882
  }];
2883
2883
  };
2884
+ readonly TextDiffProp: {
2885
+ readonly group: "data-display";
2886
+ readonly file: "components/data-display.prop.ts";
2887
+ readonly vocabulary: readonly ["ClassNameProp", {
2888
+ readonly field: "before";
2889
+ readonly local: true;
2890
+ readonly reason: "The earlier version of the text being compared — DATA, the left side of a diff (gh#1096).";
2891
+ }, {
2892
+ readonly field: "after";
2893
+ readonly local: true;
2894
+ readonly reason: "The current version of the text being compared — DATA, the right side of a diff (gh#1096).";
2895
+ }, {
2896
+ readonly field: "mode";
2897
+ readonly local: true;
2898
+ readonly reason: "inline | split — GitHub's unified vs side-by-side diff layout; not a size or tone axis.";
2899
+ }, {
2900
+ readonly field: "granularity";
2901
+ readonly local: true;
2902
+ readonly reason: "auto | word | char | line — the token unit, the same word Intl.Segmenter uses for it.";
2903
+ }, {
2904
+ readonly field: "collapseUnchanged";
2905
+ readonly local: true;
2906
+ readonly reason: "Positive boolean: fold long unchanged runs of lines behind a reveal button.";
2907
+ }, {
2908
+ readonly field: "contextLines";
2909
+ readonly local: true;
2910
+ readonly reason: "Unchanged lines kept beside each change when folding — git's -U<n> context.";
2911
+ }, {
2912
+ readonly field: "maxCells";
2913
+ readonly local: true;
2914
+ readonly reason: "Cost cap of the LCS table; past it the changed middle is one removal plus one addition.";
2915
+ }, {
2916
+ readonly field: "beforeLabel";
2917
+ readonly local: true;
2918
+ readonly reason: "Heading of the before column in split mode; defaults to the localized word.";
2919
+ }, {
2920
+ readonly field: "afterLabel";
2921
+ readonly local: true;
2922
+ readonly reason: "Heading of the after column in split mode; defaults to the localized word.";
2923
+ }, {
2924
+ readonly field: "lang";
2925
+ readonly local: true;
2926
+ readonly reason: "The HTML lang attribute, also the Intl.Segmenter locale for word/char segmentation.";
2927
+ }];
2928
+ };
2884
2929
  readonly FeatureStateProp: {
2885
2930
  readonly group: "data-display";
2886
2931
  readonly file: "components/data-display.prop.ts";
@@ -320,7 +320,7 @@ const VOCABULARY_REGISTRY = {
320
320
  TableExpandableProp: {
321
321
  file: "vocabulary/data.prop.ts",
322
322
  category: "data",
323
- description: "DataTable expandable-row config \u2014 expandedRowRender | rowExpandable | defaultExpandAllRows | expandedRowKeys | onExpandedRowsChange | expandRowByClick (antd/rc-table ExpandableConfig)"
323
+ description: "DataTable expandable-row config \u2014 expandedRowRender | rowExpandable | defaultExpandAllRows | expandedRowKeys | onExpandedRowsChange | expandRowByClick | flush (antd/rc-table ExpandableConfig; the detail cell is padded by default, gh#1083)"
324
324
  },
325
325
  TableSummaryProp: {
326
326
  file: "vocabulary/data.prop.ts",
@@ -3398,6 +3398,63 @@ const COMPONENT_PROP_REGISTRY = {
3398
3398
  }
3399
3399
  ]
3400
3400
  },
3401
+ TextDiffProp: {
3402
+ group: "data-display",
3403
+ file: "components/data-display.prop.ts",
3404
+ vocabulary: [
3405
+ "ClassNameProp",
3406
+ {
3407
+ field: "before",
3408
+ local: true,
3409
+ reason: "The earlier version of the text being compared \u2014 DATA, the left side of a diff (gh#1096)."
3410
+ },
3411
+ {
3412
+ field: "after",
3413
+ local: true,
3414
+ reason: "The current version of the text being compared \u2014 DATA, the right side of a diff (gh#1096)."
3415
+ },
3416
+ {
3417
+ field: "mode",
3418
+ local: true,
3419
+ reason: "inline | split \u2014 GitHub's unified vs side-by-side diff layout; not a size or tone axis."
3420
+ },
3421
+ {
3422
+ field: "granularity",
3423
+ local: true,
3424
+ reason: "auto | word | char | line \u2014 the token unit, the same word Intl.Segmenter uses for it."
3425
+ },
3426
+ {
3427
+ field: "collapseUnchanged",
3428
+ local: true,
3429
+ reason: "Positive boolean: fold long unchanged runs of lines behind a reveal button."
3430
+ },
3431
+ {
3432
+ field: "contextLines",
3433
+ local: true,
3434
+ reason: "Unchanged lines kept beside each change when folding \u2014 git's -U<n> context."
3435
+ },
3436
+ {
3437
+ field: "maxCells",
3438
+ local: true,
3439
+ reason: "Cost cap of the LCS table; past it the changed middle is one removal plus one addition."
3440
+ },
3441
+ {
3442
+ field: "beforeLabel",
3443
+ local: true,
3444
+ reason: "Heading of the before column in split mode; defaults to the localized word."
3445
+ },
3446
+ {
3447
+ field: "afterLabel",
3448
+ local: true,
3449
+ reason: "Heading of the after column in split mode; defaults to the localized word."
3450
+ },
3451
+ {
3452
+ field: "lang",
3453
+ local: true,
3454
+ reason: "The HTML lang attribute, also the Intl.Segmenter locale for word/char segmentation."
3455
+ }
3456
+ ]
3457
+ },
3401
3458
  FeatureStateProp: {
3402
3459
  group: "data-display",
3403
3460
  file: "components/data-display.prop.ts",
@@ -271,6 +271,12 @@ export type TableExpandableProp<T> = {
271
271
  /** Clicking anywhere on the row toggles it (antd `expandRowByClick`). */
272
272
  expandRowByClick?: boolean;
273
273
  columnTitle?: React.ReactNode;
274
+ /**
275
+ * The detail reaches the cell edges: no inset (gh#1083). Off by default — the expanded cell is
276
+ * padded like any other cell (antd), retuned by `--table-row-expanded-padding`. Set it for a
277
+ * detail that owns its inset, such as a nested table or a `ListRow` list.
278
+ */
279
+ flush?: FlushProp;
274
280
  };
275
281
  /** Footer totals row — receives the rows currently rendered (antd/rc-table `summary`). */
276
282
  export type TableSummaryProp<T> = (rows: readonly T[]) => React.ReactNode;
@@ -90,6 +90,7 @@ export type TextAlignProp = "start" | "center" | "end";
90
90
  * `normal` is CSS's own behaviour and the default: newlines and runs of spaces collapse. That is
91
91
  * right for a label and wrong for text a PERSON typed into a textarea — a plain-text note, a
92
92
  * pasted log, an issue description — where the line breaks and the indentation ARE content.
93
+ * Passed explicitly, `normal` also releases a `nowrap` the text inherits (a table cell's, gh#1083).
93
94
  *
94
95
  * `pre-wrap` keeps both and still wraps at the container's edge, so the block cannot force a
95
96
  * horizontal scrollbar the way `pre` would. It is a closed union rather than a boolean because the
@@ -5,6 +5,11 @@
5
5
  display: inline-flex;
6
6
 
7
7
  inline-size: fit-content;
8
+
9
+ max-inline-size: 100%;
10
+ min-inline-size: 0;
11
+
12
+ box-sizing: border-box;
8
13
  align-items: center;
9
14
 
10
15
  vertical-align: middle;
@@ -21,6 +26,13 @@
21
26
  white-space: nowrap;
22
27
  }
23
28
 
29
+ [data-slot="badge-label"] {
30
+ min-inline-size: 0;
31
+ overflow-x: clip;
32
+ overflow-y: visible;
33
+ text-overflow: ellipsis;
34
+ }
35
+
24
36
  @supports (text-box-trim: trim-both) and (text-box-edge: cap alphabetic) {
25
37
  [data-slot="badge-label"] {
26
38
  text-box-trim: trim-both;