@tensor_1/react 2.31.0 → 2.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +350 -0
  2. package/dist/attachment-labels.d.ts +16 -0
  3. package/dist/attachment-labels.js +9 -0
  4. package/dist/calendar-model.d.ts +141 -1
  5. package/dist/calendar-model.js +200 -11
  6. package/dist/chart-preset.cjs +127 -7
  7. package/dist/chart-preset.d.cts +2 -2
  8. package/dist/chart-preset.d.ts +1 -0
  9. package/dist/chart-preset.js +6 -1
  10. package/dist/chart-theme-Cl0IrkKQ.d.cts +112 -0
  11. package/dist/chart-theme.d.ts +51 -0
  12. package/dist/chart-theme.js +99 -0
  13. package/dist/chart.cjs +127 -7
  14. package/dist/chart.d.cts +2 -2
  15. package/dist/chart.d.ts +1 -0
  16. package/dist/chart.js +6 -2
  17. package/dist/components/Alert.d.ts +17 -1
  18. package/dist/components/Alert.js +9 -4
  19. package/dist/components/AlertDismiss.d.ts +14 -0
  20. package/dist/components/AlertDismiss.js +11 -0
  21. package/dist/components/Attachment.d.ts +15 -12
  22. package/dist/components/Attachment.js +17 -17
  23. package/dist/components/AttachmentParts.d.ts +24 -0
  24. package/dist/components/AttachmentParts.js +20 -0
  25. package/dist/components/Chart.d.ts +1 -0
  26. package/dist/components/Chart.js +6 -1
  27. package/dist/components/ChartCore.d.ts +6 -1
  28. package/dist/components/ChartCore.js +23 -5
  29. package/dist/components/Chip.d.ts +17 -2
  30. package/dist/components/Chip.js +35 -11
  31. package/dist/components/ChipRemove.d.ts +17 -0
  32. package/dist/components/ChipRemove.js +13 -0
  33. package/dist/components/CollapsibleParts.d.ts +29 -0
  34. package/dist/components/CollapsibleParts.js +20 -0
  35. package/dist/components/CollapsibleSection.d.ts +30 -3
  36. package/dist/components/CollapsibleSection.js +7 -11
  37. package/dist/components/CollapsibleSectionState.d.ts +1 -1
  38. package/dist/components/CollapsibleSectionState.js +54 -27
  39. package/dist/components/DashboardGrid.d.ts +60 -0
  40. package/dist/components/DashboardGrid.js +40 -0
  41. package/dist/components/DateRangePicker.d.ts +7 -1
  42. package/dist/components/DateRangePicker.js +5 -1
  43. package/dist/components/DescriptionList.d.ts +12 -1
  44. package/dist/components/DescriptionList.js +5 -5
  45. package/dist/components/Dialogs.d.ts +24 -3
  46. package/dist/components/Dialogs.js +74 -16
  47. package/dist/components/FileUpload.d.ts +10 -2
  48. package/dist/components/FileUpload.js +10 -4
  49. package/dist/components/FilterEditor.d.ts +4 -0
  50. package/dist/components/FilterEditor.js +13 -2
  51. package/dist/components/Heartbeat.d.ts +31 -0
  52. package/dist/components/Heartbeat.js +28 -0
  53. package/dist/components/Menu.d.ts +53 -5
  54. package/dist/components/Menu.js +104 -4
  55. package/dist/components/Meter.d.ts +7 -3
  56. package/dist/components/Meter.js +2 -1
  57. package/dist/components/Popover.d.ts +21 -2
  58. package/dist/components/Popover.js +94 -17
  59. package/dist/components/Prose.d.ts +6 -2
  60. package/dist/components/Prose.js +1 -1
  61. package/dist/components/RuleBuilder.d.ts +52 -4
  62. package/dist/components/RuleBuilder.js +109 -24
  63. package/dist/components/SchedulingCalendar.d.ts +142 -11
  64. package/dist/components/SchedulingCalendar.js +234 -57
  65. package/dist/components/SchedulingTimeline.d.ts +1 -0
  66. package/dist/components/Segmented.d.ts +17 -3
  67. package/dist/components/Segmented.js +59 -5
  68. package/dist/components/Select.d.ts +66 -2
  69. package/dist/components/Select.js +800 -10
  70. package/dist/components/Spinner.d.ts +2 -1
  71. package/dist/components/Spinner.js +1 -1
  72. package/dist/components/StatusPill.d.ts +8 -2
  73. package/dist/components/StatusPill.js +2 -2
  74. package/dist/components/Steps.d.ts +28 -4
  75. package/dist/components/Steps.js +46 -16
  76. package/dist/components/TableControls.js +4 -3
  77. package/dist/components/Tabs.d.ts +7 -2
  78. package/dist/components/Tabs.js +14 -1
  79. package/dist/components/TextDiff.d.ts +49 -0
  80. package/dist/components/TextDiff.js +124 -0
  81. package/dist/{emoji-image-6mPZ15Zy.d.cts → emoji-image-DjCKpdwE.d.cts} +1 -1
  82. package/dist/emoji.cjs +169 -27
  83. package/dist/emoji.d.cts +3 -3
  84. package/dist/file-model.d.ts +22 -3
  85. package/dist/file-model.js +6 -4
  86. package/dist/filter-model.d.ts +26 -2
  87. package/dist/filter-model.js +13 -1
  88. package/dist/hooks/useAnchoredPosition.d.ts +24 -1
  89. package/dist/hooks/useAnchoredPosition.js +58 -8
  90. package/dist/hooks/useVirtualRows.d.ts +17 -2
  91. package/dist/hooks/useVirtualRows.js +6 -5
  92. package/dist/icons/Icon.d.ts +14 -14
  93. package/dist/index.cjs +4179 -1678
  94. package/dist/index.d.cts +1706 -660
  95. package/dist/index.d.ts +16 -9
  96. package/dist/index.js +53 -4
  97. package/dist/labels-context.d.ts +28 -0
  98. package/dist/{overlay-position-Cjv5E7mn.d.cts → overlay-position-2Afke51l.d.cts} +20 -0
  99. package/dist/overlay-position.d.ts +20 -0
  100. package/dist/overlay-position.js +17 -2
  101. package/dist/rich-text.cjs +169 -27
  102. package/dist/rich-text.d.cts +3 -3
  103. package/dist/rule-builder-model.d.ts +34 -2
  104. package/dist/rule-builder-model.js +48 -12
  105. package/dist/scheduling-calendar-model.d.ts +66 -0
  106. package/dist/scheduling-calendar-model.js +82 -0
  107. package/dist/select-model.d.ts +80 -0
  108. package/dist/select-model.js +109 -0
  109. package/dist/table-engine.d.ts +7 -0
  110. package/dist/table-engine.js +2 -1
  111. package/dist/text-diff-model.d.ts +71 -0
  112. package/dist/text-diff-model.js +261 -0
  113. package/package.json +1 -1
  114. package/dist/ChartCore-DzWe10bN.d.cts +0 -59
@@ -0,0 +1,80 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /**
4
+ * Select model (HAR-1572) — pure, DOM-free logic behind the UIx `Select` listbox:
5
+ * flattening options and groups, keyboard movement (APG select-only combobox), typeahead,
6
+ * filtering and the multi-select value helpers. Unit-tested in select-model.test.mjs.
7
+ */
8
+
9
+ interface SelectOption {
10
+ value: string;
11
+ label: string;
12
+ disabled?: boolean;
13
+ /** A second, muted line under the label. */
14
+ description?: string;
15
+ /** Shown before the label, in the list and on the trigger. */
16
+ icon?: ReactNode;
17
+ /** Extra words that match a search or typeahead (never shown). */
18
+ keywords?: readonly string[];
19
+ }
20
+ interface SelectGroup {
21
+ label: string;
22
+ options: readonly SelectOption[];
23
+ /** Disables every option in the group. */
24
+ disabled?: boolean;
25
+ }
26
+ type SelectItem = SelectOption | SelectGroup;
27
+ declare const isSelectGroup: (item: SelectItem) => item is SelectGroup;
28
+ /** @internal One option in list order, with what navigation needs. */
29
+ interface SelectEntry {
30
+ option: SelectOption;
31
+ /** Position among all entries (the `aria-activedescendant` target index). */
32
+ index: number;
33
+ /** Index into `sections`. */
34
+ section: number;
35
+ disabled: boolean;
36
+ /** In the form value only (an `<option hidden>` placeholder); never listed or focused. */
37
+ hidden: boolean;
38
+ }
39
+ /** @internal A run of entries: one group, or the ungrouped options between groups. */
40
+ interface SelectSection {
41
+ label?: string;
42
+ disabled: boolean;
43
+ entries: SelectEntry[];
44
+ }
45
+ /** @internal */
46
+ interface SelectModel {
47
+ sections: SelectSection[];
48
+ /** Every visible entry, in order (hidden placeholder options are left out). */
49
+ entries: SelectEntry[];
50
+ /** Every option including hidden ones, for the form proxy and value lookups. */
51
+ all: SelectEntry[];
52
+ }
53
+ /** @internal Flatten options and groups into sections and a navigable entry list. */
54
+ declare function buildSelectModel(items: readonly SelectItem[]): SelectModel;
55
+ declare const firstEnabled: (entries: readonly SelectEntry[]) => number;
56
+ declare const lastEnabled: (entries: readonly SelectEntry[]) => number;
57
+ /**
58
+ * Move the active option by `delta` (±1 for arrows, ±10 for PageUp/PageDown), skipping
59
+ * disabled options and stopping at the ends (the APG list does not wrap). With no active
60
+ * option, moving down lands on the first enabled option and moving up on the last.
61
+ */
62
+ declare function moveActive(entries: readonly SelectEntry[], from: number, delta: number): number;
63
+ /**
64
+ * Typeahead (APG): the typed buffer matches the start of a label. Repeating one character
65
+ * ("s", "s") cycles through the options that start with it; a longer string ("re") keeps the
66
+ * current option while it still matches. Disabled options are skipped. Returns -1 for no match.
67
+ */
68
+ declare function typeahead(entries: readonly SelectEntry[], buffer: string, from: number): number;
69
+ /** Does the option match a search query (label, value-free keywords and description)? */
70
+ declare function selectOptionMatches(option: SelectOption, query: string): boolean;
71
+ /** Keep only the options (and the groups holding them) that match `query`. */
72
+ declare function filterSelectItems(items: readonly SelectItem[], query: string): SelectItem[];
73
+ /** Count the options in a list of options and groups. */
74
+ declare const countSelectOptions: (items: readonly SelectItem[]) => number;
75
+ /** @internal A form value (string, number, array or nothing) as a list of strings. */
76
+ declare function toValueList(value: unknown): string[];
77
+ /** @internal Toggle `value` in a multi-select value, keeping list order stable. */
78
+ declare function toggleValue(values: readonly string[], value: string): string[];
79
+
80
+ export { type SelectEntry, type SelectGroup, type SelectItem, type SelectModel, type SelectOption, type SelectSection, buildSelectModel, countSelectOptions, filterSelectItems, firstEnabled, isSelectGroup, lastEnabled, moveActive, selectOptionMatches, toValueList, toggleValue, typeahead };
@@ -0,0 +1,109 @@
1
+ import { foldForSearch } from "./search-suggest-model.js";
2
+ const isSelectGroup = (item) => Array.isArray(item.options);
3
+ function buildSelectModel(items) {
4
+ const sections = [];
5
+ const entries = [];
6
+ const all = [];
7
+ let loose = null;
8
+ const add = (section, sectionIndex, option, groupDisabled) => {
9
+ const hidden = !!option.hidden;
10
+ const entry = {
11
+ option,
12
+ index: hidden ? -1 : entries.length,
13
+ section: sectionIndex,
14
+ disabled: groupDisabled || !!option.disabled,
15
+ hidden
16
+ };
17
+ all.push(entry);
18
+ if (hidden) return;
19
+ entries.push(entry);
20
+ section.entries.push(entry);
21
+ };
22
+ for (const item of items) {
23
+ if (isSelectGroup(item)) {
24
+ loose = null;
25
+ const section = { label: item.label, disabled: !!item.disabled, entries: [] };
26
+ sections.push(section);
27
+ for (const option of item.options) add(section, sections.length - 1, option, !!item.disabled);
28
+ } else {
29
+ if (!loose) {
30
+ loose = { disabled: false, entries: [] };
31
+ sections.push(loose);
32
+ }
33
+ add(loose, sections.length - 1, item, false);
34
+ }
35
+ }
36
+ return { sections: sections.filter((s) => s.entries.length > 0 || s.label != null), entries, all };
37
+ }
38
+ function scan(entries, from, step) {
39
+ for (let i = from; i >= 0 && i < entries.length; i += step) if (!entries[i].disabled) return i;
40
+ return -1;
41
+ }
42
+ const firstEnabled = (entries) => scan(entries, 0, 1);
43
+ const lastEnabled = (entries) => scan(entries, entries.length - 1, -1);
44
+ function moveActive(entries, from, delta) {
45
+ if (!entries.length) return -1;
46
+ if (from < 0) return delta > 0 ? firstEnabled(entries) : lastEnabled(entries);
47
+ const step = delta > 0 ? 1 : -1;
48
+ const target = Math.max(0, Math.min(entries.length - 1, from + delta));
49
+ const found = scan(entries, target, step);
50
+ if (found >= 0 && found !== from) return found;
51
+ const back = scan(entries, target, step === 1 ? -1 : 1);
52
+ return back >= 0 ? step === 1 ? Math.max(back, from) : Math.min(back, from) : from;
53
+ }
54
+ const fold = (text) => foldForSearch(text).text;
55
+ function typeahead(entries, buffer, from) {
56
+ const query = fold(buffer);
57
+ if (!query || !entries.length) return -1;
58
+ const chars = [...query];
59
+ const repeated = chars.length > 1 && chars.every((c) => c === chars[0]);
60
+ const needle = repeated ? chars[0] : query;
61
+ const start = repeated || chars.length === 1 ? from + 1 : Math.max(0, from);
62
+ const n = entries.length;
63
+ for (let k = 0; k < n; k += 1) {
64
+ const i = ((start + k) % n + n) % n;
65
+ const entry = entries[i];
66
+ if (!entry.disabled && fold(entry.option.label).trimStart().startsWith(needle)) return i;
67
+ }
68
+ return -1;
69
+ }
70
+ function selectOptionMatches(option, query) {
71
+ const needle = fold(query).trim();
72
+ if (!needle) return true;
73
+ return [option.label, ...option.keywords ?? []].some((part) => fold(part).includes(needle));
74
+ }
75
+ function filterSelectItems(items, query) {
76
+ if (!fold(query).trim()) return [...items];
77
+ const out = [];
78
+ for (const item of items) {
79
+ if (isSelectGroup(item)) {
80
+ const options = item.options.filter((o) => selectOptionMatches(o, query));
81
+ if (options.length) out.push({ ...item, options });
82
+ } else if (selectOptionMatches(item, query)) {
83
+ out.push(item);
84
+ }
85
+ }
86
+ return out;
87
+ }
88
+ const countSelectOptions = (items) => items.reduce((n, item) => n + (isSelectGroup(item) ? item.options.length : 1), 0);
89
+ function toValueList(value) {
90
+ if (value == null) return [];
91
+ if (Array.isArray(value)) return value.map((v) => String(v));
92
+ return [String(value)];
93
+ }
94
+ function toggleValue(values, value) {
95
+ return values.includes(value) ? values.filter((v) => v !== value) : [...values, value];
96
+ }
97
+ export {
98
+ buildSelectModel,
99
+ countSelectOptions,
100
+ filterSelectItems,
101
+ firstEnabled,
102
+ isSelectGroup,
103
+ lastEnabled,
104
+ moveActive,
105
+ selectOptionMatches,
106
+ toValueList,
107
+ toggleValue,
108
+ typeahead
109
+ };
@@ -71,8 +71,15 @@ interface VirtualWindow {
71
71
  * [start, end) plus `overscan` rows on each side; padTop/padBottom are spacer
72
72
  * heights (px) so the scrollbar stays correct. Below `threshold` rows, callers
73
73
  * should skip virtualization (render all) — see shouldVirtualize.
74
+ *
75
+ * `scrollTop` is clamped to what a list of `count` rows can scroll to. A scroll position left
76
+ * over from a longer list (5,000 rows filtered down to 150 while scrolled far down) then gives
77
+ * the last rows and a spacer no taller than the list, in the same pass — not an empty window
78
+ * under a spacer taller than everything, which the browser only corrects scroll by scroll
79
+ * (HAR-1616).
74
80
  */
75
81
  declare function virtualWindow(scrollTop: number, viewportH: number, rowH: number, count: number, overscan?: number): VirtualWindow;
82
+ /** `count > threshold`: a list of exactly `threshold` rows is still rendered whole. */
76
83
  declare function shouldVirtualize(count: number, threshold?: number): boolean;
77
84
  /** Clamp a dragged column width to a sane minimum (and optional maximum). */
78
85
  declare function clampWidth(width: number, min?: number, max?: number): number;
@@ -190,7 +190,8 @@ function parseView(qs) {
190
190
  }
191
191
  function virtualWindow(scrollTop, viewportH, rowH, count, overscan = 6) {
192
192
  if (rowH <= 0 || count <= 0) return { start: 0, end: count, padTop: 0, padBottom: 0, total: 0 };
193
- const first = Math.max(0, Math.floor(scrollTop / rowH) - overscan);
193
+ const top = Math.max(0, Math.min(scrollTop, count * rowH - viewportH));
194
+ const first = Math.max(0, Math.floor(top / rowH) - overscan);
194
195
  const visible = Math.ceil(viewportH / rowH) + overscan * 2;
195
196
  const end = Math.min(count, first + visible);
196
197
  return { start: first, end, padTop: first * rowH, padBottom: (count - end) * rowH, total: count * rowH };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Two-way text diff for `TextDiff` (HAR-1368). Pure. Myers' O(ND) diff on lines or words,
3
+ * after trimming the common prefix and suffix. Past `maxEdits` the changed middle is shown as
4
+ * one replacement instead of searching further, so a rewrite never stalls the page.
5
+ */
6
+ type TextDiffGranularity = 'line' | 'word';
7
+ interface TextDiffOp {
8
+ type: 'equal' | 'insert' | 'delete';
9
+ /** Lines (granularity `line`) or word, space and punctuation tokens (`word`). */
10
+ tokens: string[];
11
+ }
12
+ interface TextDiffOptions {
13
+ granularity?: TextDiffGranularity;
14
+ /** More tokens than this on either side gives `status: 'too-large'`. Default 20 000. */
15
+ maxTokens?: number;
16
+ /** Edit budget for the search; beyond it the middle becomes one replacement. Default 2 000. */
17
+ maxEdits?: number;
18
+ }
19
+ interface TextDiffResult {
20
+ status: 'ok' | 'too-large';
21
+ granularity: TextDiffGranularity;
22
+ ops: TextDiffOp[];
23
+ /** Lines, or words (not spaces or punctuation), added and removed. */
24
+ added: number;
25
+ removed: number;
26
+ }
27
+ /** Splits text into lines (without their `\r\n` / `\n`) or into word, space and punctuation tokens. */
28
+ declare function tokenizeText(text: string, granularity: TextDiffGranularity): string[];
29
+ /** Myers diff of two token lists. Tokens compare by string equality. */
30
+ declare function diffTokens(a: readonly string[], b: readonly string[], maxEdits?: number): TextDiffOp[];
31
+ /** Diffs two texts by line or by word, with added/removed counts. */
32
+ declare function diffText(before: string, after: string, { granularity, maxTokens, maxEdits }?: TextDiffOptions): TextDiffResult;
33
+ /** A run of text inside one line; `changed` marks the words that differ from the other side. */
34
+ interface TextDiffSegment {
35
+ text: string;
36
+ changed: boolean;
37
+ }
38
+ interface TextDiffLine {
39
+ /** 1-based line number on its side. */
40
+ number: number;
41
+ /** Word-level marks when the line was paired with a similar counterpart; otherwise one changed segment. */
42
+ segments: TextDiffSegment[];
43
+ }
44
+ type TextDiffRow = {
45
+ kind: 'equal';
46
+ before: TextDiffLine;
47
+ after: TextDiffLine;
48
+ } | {
49
+ kind: 'change';
50
+ before: TextDiffLine;
51
+ after: TextDiffLine;
52
+ } | {
53
+ kind: 'delete';
54
+ before: TextDiffLine;
55
+ } | {
56
+ kind: 'insert';
57
+ after: TextDiffLine;
58
+ } | {
59
+ kind: 'fold';
60
+ count: number;
61
+ rows: TextDiffRow[];
62
+ };
63
+ /**
64
+ * Rows for a line diff: unchanged lines, removed/added lines, and changed lines paired side by
65
+ * side with word marks. With `context`, unchanged runs longer than needed fold into one row.
66
+ */
67
+ declare function textDiffRows(result: TextDiffResult, { context }?: {
68
+ context?: number;
69
+ }): TextDiffRow[];
70
+
71
+ export { type TextDiffGranularity, type TextDiffLine, type TextDiffOp, type TextDiffOptions, type TextDiffResult, type TextDiffRow, type TextDiffSegment, diffText, diffTokens, textDiffRows, tokenizeText };
@@ -0,0 +1,261 @@
1
+ const WORD_TOKENS = /\s+|[\p{L}\p{M}\p{N}_]+|[^\s\p{L}\p{M}\p{N}_]/gu;
2
+ function tokenizeText(text, granularity) {
3
+ if (granularity === "line") return text === "" ? [] : text.split("\n").map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
4
+ return text.match(WORD_TOKENS) ?? [];
5
+ }
6
+ function myers(a, b, maxEdits) {
7
+ const n = a.length;
8
+ const m = b.length;
9
+ const limit = Math.min(n + m, maxEdits);
10
+ const offset = limit + 1;
11
+ const v = new Int32Array(2 * limit + 3);
12
+ const trace = [];
13
+ let found = -1;
14
+ for (let d = 0; d <= limit && found < 0; d += 1) {
15
+ for (let k = -d; k <= d; k += 2) {
16
+ let x2 = k === -d || k !== d && v[offset + k - 1] < v[offset + k + 1] ? v[offset + k + 1] : v[offset + k - 1] + 1;
17
+ let y2 = x2 - k;
18
+ while (x2 < n && y2 < m && a[x2] === b[y2]) {
19
+ x2 += 1;
20
+ y2 += 1;
21
+ }
22
+ v[offset + k] = x2;
23
+ if (x2 >= n && y2 >= m) {
24
+ found = d;
25
+ break;
26
+ }
27
+ }
28
+ trace.push(v.slice(offset - d, offset + d + 1));
29
+ }
30
+ if (found < 0) return null;
31
+ const steps = [];
32
+ let x = n;
33
+ let y = m;
34
+ for (let d = found; d > 0; d -= 1) {
35
+ const prev = trace[d - 1];
36
+ const at = (k2) => prev[k2 + d - 1];
37
+ const k = x - y;
38
+ const prevK = k === -d || k !== d && at(k - 1) < at(k + 1) ? k + 1 : k - 1;
39
+ const prevX = at(prevK);
40
+ const prevY = prevX - prevK;
41
+ while (x > prevX && y > prevY) {
42
+ steps.push("equal");
43
+ x -= 1;
44
+ y -= 1;
45
+ }
46
+ if (x === prevX) {
47
+ steps.push("insert");
48
+ y -= 1;
49
+ } else {
50
+ steps.push("delete");
51
+ x -= 1;
52
+ }
53
+ }
54
+ while (x > 0 && y > 0) {
55
+ steps.push("equal");
56
+ x -= 1;
57
+ y -= 1;
58
+ }
59
+ return steps.reverse();
60
+ }
61
+ function push(ops, type, token) {
62
+ const last = ops[ops.length - 1];
63
+ if (last?.type === type) last.tokens.push(token);
64
+ else ops.push({ type, tokens: [token] });
65
+ }
66
+ function normalize(ops) {
67
+ const out = [];
68
+ let del = [];
69
+ let ins = [];
70
+ const flush = () => {
71
+ if (del.length) out.push({ type: "delete", tokens: del });
72
+ if (ins.length) out.push({ type: "insert", tokens: ins });
73
+ del = [];
74
+ ins = [];
75
+ };
76
+ for (const op of ops) {
77
+ if (op.type === "delete") del = del.concat(op.tokens);
78
+ else if (op.type === "insert") ins = ins.concat(op.tokens);
79
+ else {
80
+ flush();
81
+ const last = out[out.length - 1];
82
+ if (last?.type === "equal") last.tokens.push(...op.tokens);
83
+ else out.push({ type: "equal", tokens: [...op.tokens] });
84
+ }
85
+ }
86
+ flush();
87
+ return out;
88
+ }
89
+ function absorbSpaces(ops) {
90
+ const out = ops.map((op) => ({ ...op, tokens: [...op.tokens] }));
91
+ for (let i = 1; i < out.length - 1; i += 1) {
92
+ const op = out[i];
93
+ if (op.type === "equal" && out[i - 1].type !== "equal" && out[i + 1].type !== "equal" && op.tokens.every((t) => /^\s+$/.test(t))) {
94
+ out.splice(i, 1, { type: "delete", tokens: op.tokens }, { type: "insert", tokens: [...op.tokens] });
95
+ }
96
+ }
97
+ return normalize(out);
98
+ }
99
+ function diffTokens(a, b, maxEdits = 2e3) {
100
+ let start = 0;
101
+ while (start < a.length && start < b.length && a[start] === b[start]) start += 1;
102
+ let endA = a.length;
103
+ let endB = b.length;
104
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
105
+ endA -= 1;
106
+ endB -= 1;
107
+ }
108
+ const ids = /* @__PURE__ */ new Map();
109
+ const id = (t) => {
110
+ let v = ids.get(t);
111
+ if (v === void 0) {
112
+ v = ids.size;
113
+ ids.set(t, v);
114
+ }
115
+ return v;
116
+ };
117
+ const midA = a.slice(start, endA);
118
+ const midB = b.slice(start, endB);
119
+ const steps = myers(midA.map(id), midB.map(id), maxEdits);
120
+ const ops = [];
121
+ for (const t of a.slice(0, start)) push(ops, "equal", t);
122
+ if (steps) {
123
+ let i = 0;
124
+ let j = 0;
125
+ for (const step of steps) {
126
+ if (step === "equal") {
127
+ push(ops, "equal", midA[i]);
128
+ i += 1;
129
+ j += 1;
130
+ } else if (step === "delete") {
131
+ push(ops, "delete", midA[i]);
132
+ i += 1;
133
+ } else {
134
+ push(ops, "insert", midB[j]);
135
+ j += 1;
136
+ }
137
+ }
138
+ } else {
139
+ for (const t of midA) push(ops, "delete", t);
140
+ for (const t of midB) push(ops, "insert", t);
141
+ }
142
+ for (const t of a.slice(endA)) push(ops, "equal", t);
143
+ return normalize(ops);
144
+ }
145
+ const counts = (tokens, granularity) => granularity === "line" ? tokens.length : tokens.filter((t) => /[\p{L}\p{N}]/u.test(t)).length;
146
+ function diffText(before, after, { granularity = "line", maxTokens = 2e4, maxEdits = 2e3 } = {}) {
147
+ const a = tokenizeText(before, granularity);
148
+ const b = tokenizeText(after, granularity);
149
+ if (a.length > maxTokens || b.length > maxTokens) return { status: "too-large", granularity, ops: [], added: 0, removed: 0 };
150
+ let ops = diffTokens(a, b, maxEdits);
151
+ if (granularity === "word") ops = absorbSpaces(ops);
152
+ let added = 0;
153
+ let removed = 0;
154
+ for (const op of ops) {
155
+ if (op.type === "insert") added += counts(op.tokens, granularity);
156
+ if (op.type === "delete") removed += counts(op.tokens, granularity);
157
+ }
158
+ return { status: "ok", granularity, ops, added, removed };
159
+ }
160
+ const SIMILAR = 0.4;
161
+ function compareLines(before, after) {
162
+ const longest = Math.max(before.length, after.length);
163
+ if (longest === 0) return { similarity: 1, left: [{ text: "", changed: false }], right: [{ text: "", changed: false }] };
164
+ if (before.length + after.length > 4e3) return { similarity: 0, left: [{ text: before, changed: true }], right: [{ text: after, changed: true }] };
165
+ const { ops } = diffText(before, after, { granularity: "word", maxEdits: 400 });
166
+ const left = [];
167
+ const right = [];
168
+ let same = 0;
169
+ for (const op of ops) {
170
+ const text = op.tokens.join("");
171
+ if (op.type === "equal") {
172
+ same += text.replace(/\s/g, "").length;
173
+ left.push({ text, changed: false });
174
+ right.push({ text, changed: false });
175
+ } else (op.type === "delete" ? left : right).push({ text, changed: true });
176
+ }
177
+ const visible = Math.max(before.replace(/\s/g, "").length, after.replace(/\s/g, "").length) || 1;
178
+ return { similarity: same / visible, left, right };
179
+ }
180
+ function alignBlock(deleted, inserted) {
181
+ const n = deleted.length;
182
+ const m = inserted.length;
183
+ const out = [];
184
+ if (n * m > 2500) {
185
+ deleted.forEach((_, i2) => out.push([i2, null, null]));
186
+ inserted.forEach((_, j2) => out.push([null, j2, null]));
187
+ return out;
188
+ }
189
+ const cmp = deleted.map((d) => inserted.map((i2) => compareLines(d, i2)));
190
+ const score = Array.from({ length: n + 1 }, () => new Float64Array(m + 1));
191
+ for (let i2 = n - 1; i2 >= 0; i2 -= 1) {
192
+ for (let j2 = m - 1; j2 >= 0; j2 -= 1) {
193
+ const sim = cmp[i2][j2].similarity;
194
+ score[i2][j2] = Math.max(score[i2 + 1][j2], score[i2][j2 + 1], sim >= SIMILAR ? sim + score[i2 + 1][j2 + 1] : 0);
195
+ }
196
+ }
197
+ let i = 0;
198
+ let j = 0;
199
+ while (i < n || j < m) {
200
+ if (i < n && j < m && cmp[i][j].similarity >= SIMILAR && score[i][j] === cmp[i][j].similarity + score[i + 1][j + 1]) {
201
+ out.push([i, j, cmp[i][j]]);
202
+ i += 1;
203
+ j += 1;
204
+ } else if (i < n && (j >= m || score[i][j] === score[i + 1][j])) {
205
+ out.push([i, null, null]);
206
+ i += 1;
207
+ } else {
208
+ out.push([null, j, null]);
209
+ j += 1;
210
+ }
211
+ }
212
+ return out;
213
+ }
214
+ function textDiffRows(result, { context } = {}) {
215
+ const rows = [];
216
+ let left = 1;
217
+ let right = 1;
218
+ const ops = result.ops;
219
+ for (let i = 0; i < ops.length; i += 1) {
220
+ const op = ops[i];
221
+ if (op.type === "equal") {
222
+ for (const text of op.tokens) rows.push({ kind: "equal", before: { number: left++, segments: [{ text, changed: false }] }, after: { number: right++, segments: [{ text, changed: false }] } });
223
+ continue;
224
+ }
225
+ const deleted = op.type === "delete" ? op.tokens : [];
226
+ const inserted = op.type === "insert" ? op.tokens : ops[i + 1]?.type === "insert" ? ops[i + 1].tokens : [];
227
+ if (op.type === "delete" && ops[i + 1]?.type === "insert") i += 1;
228
+ for (const [d, n, pair] of alignBlock(deleted, inserted)) {
229
+ if (pair && d !== null && n !== null) rows.push({ kind: "change", before: { number: left++, segments: pair.left }, after: { number: right++, segments: pair.right } });
230
+ else if (d !== null) rows.push({ kind: "delete", before: { number: left++, segments: [{ text: deleted[d], changed: true }] } });
231
+ else if (n !== null) rows.push({ kind: "insert", after: { number: right++, segments: [{ text: inserted[n], changed: true }] } });
232
+ }
233
+ }
234
+ if (context === void 0 || context < 0) return rows;
235
+ const out = [];
236
+ for (let i = 0; i < rows.length; ) {
237
+ if (rows[i].kind !== "equal") {
238
+ out.push(rows[i]);
239
+ i += 1;
240
+ continue;
241
+ }
242
+ let j = i;
243
+ while (j < rows.length && rows[j].kind === "equal") j += 1;
244
+ const keepHead = i === 0 ? 0 : context;
245
+ const keepTail = j === rows.length ? 0 : context;
246
+ const hidden = j - i - keepHead - keepTail;
247
+ if (hidden > 1) {
248
+ out.push(...rows.slice(i, i + keepHead));
249
+ out.push({ kind: "fold", count: hidden, rows: rows.slice(i + keepHead, j - keepTail) });
250
+ out.push(...rows.slice(j - keepTail, j));
251
+ } else out.push(...rows.slice(i, j));
252
+ i = j;
253
+ }
254
+ return out;
255
+ }
256
+ export {
257
+ diffText,
258
+ diffTokens,
259
+ textDiffRows,
260
+ tokenizeText
261
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tensor_1/react",
3
- "version": "2.31.0",
3
+ "version": "2.32.0",
4
4
  "description": "Thin React wrappers over the UIx v2 CSS component library.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1,59 +0,0 @@
1
- import * as react from 'react';
2
- import { CSSProperties, ReactNode, HTMLAttributes } from 'react';
3
- import { EChartsOption, ECharts } from 'echarts';
4
-
5
- declare function uixChartPalette(): string[];
6
- interface ChartTableRow {
7
- [key: string]: string | number;
8
- }
9
- /**
10
- * Every word the chart renders itself (TENSOR RX-125, UIX-04 / UIX-05). The
11
- * loading text used to live in CSS `content:`, where nothing could translate it.
12
- */
13
- interface ChartLabels {
14
- /** Shown (and announced inside the busy region) while `loading`. */
15
- loading: ReactNode;
16
- /** Accessible name when neither `ariaLabel` nor `title` is given. */
17
- chart: string;
18
- /** Appended to the chart's name for its data-table alternative. */
19
- dataTableSuffix: string;
20
- }
21
- declare const DEFAULT_CHART_LABELS: ChartLabels;
22
- interface ChartProps {
23
- option: EChartsOption;
24
- title?: string;
25
- subtitle?: string;
26
- height?: string | number;
27
- className?: string;
28
- style?: CSSProperties;
29
- ariaLabel?: string;
30
- tableData?: ChartTableRow[];
31
- tableHeaders?: string[];
32
- onReady?: (chart: ECharts) => void;
33
- header?: ReactNode;
34
- headerAction?: ReactNode;
35
- footer?: ReactNode;
36
- loading?: boolean;
37
- empty?: ReactNode;
38
- /** Translatable words; English defaults in {@link DEFAULT_CHART_LABELS}. */
39
- labels?: Partial<ChartLabels>;
40
- }
41
- interface ChartMetricProps extends HTMLAttributes<HTMLDivElement> {
42
- value: ReactNode;
43
- label?: ReactNode;
44
- delta?: ReactNode;
45
- deltaTone?: 'neutral' | 'positive' | 'negative' | 'warning';
46
- }
47
- declare function ChartMetric({ value, label, delta, deltaTone, className, ...props }: ChartMetricProps): react.JSX.Element;
48
- interface ChartLegendProps extends HTMLAttributes<HTMLUListElement> {
49
- children?: ReactNode;
50
- }
51
- declare function ChartLegend({ children, className, ...props }: ChartLegendProps): react.JSX.Element;
52
- interface ChartLegendItemProps extends HTMLAttributes<HTMLLIElement> {
53
- label: ReactNode;
54
- color?: string;
55
- swatch?: 'block' | 'line' | 'dash';
56
- }
57
- declare function ChartLegendItem({ label, color, swatch, className, style, ...props }: ChartLegendItemProps): react.JSX.Element;
58
-
59
- export { type ChartProps as C, DEFAULT_CHART_LABELS as D, type ChartLabels as a, ChartLegend as b, ChartLegendItem as c, type ChartLegendItemProps as d, type ChartLegendProps as e, ChartMetric as f, type ChartMetricProps as g, type ChartTableRow as h, uixChartPalette as u };