@tensor_1/react 2.31.0 → 2.33.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 (117) hide show
  1. package/README.md +367 -0
  2. package/dist/SchedulingCalendar-C4XVbqZr.d.ts +339 -0
  3. package/dist/attachment-labels.d.ts +16 -0
  4. package/dist/attachment-labels.js +9 -0
  5. package/dist/calendar-model.d.ts +141 -1
  6. package/dist/calendar-model.js +200 -11
  7. package/dist/chart-preset.cjs +127 -7
  8. package/dist/chart-preset.d.cts +2 -2
  9. package/dist/chart-preset.d.ts +1 -0
  10. package/dist/chart-preset.js +6 -1
  11. package/dist/chart-theme-Cl0IrkKQ.d.cts +112 -0
  12. package/dist/chart-theme.d.ts +51 -0
  13. package/dist/chart-theme.js +99 -0
  14. package/dist/chart.cjs +127 -7
  15. package/dist/chart.d.cts +2 -2
  16. package/dist/chart.d.ts +1 -0
  17. package/dist/chart.js +6 -2
  18. package/dist/components/Alert.d.ts +17 -1
  19. package/dist/components/Alert.js +9 -4
  20. package/dist/components/AlertDismiss.d.ts +14 -0
  21. package/dist/components/AlertDismiss.js +11 -0
  22. package/dist/components/Attachment.d.ts +15 -12
  23. package/dist/components/Attachment.js +17 -17
  24. package/dist/components/AttachmentParts.d.ts +24 -0
  25. package/dist/components/AttachmentParts.js +20 -0
  26. package/dist/components/Chart.d.ts +1 -0
  27. package/dist/components/Chart.js +6 -1
  28. package/dist/components/ChartCore.d.ts +6 -1
  29. package/dist/components/ChartCore.js +23 -5
  30. package/dist/components/Chip.d.ts +17 -2
  31. package/dist/components/Chip.js +35 -11
  32. package/dist/components/ChipRemove.d.ts +17 -0
  33. package/dist/components/ChipRemove.js +13 -0
  34. package/dist/components/CollapsibleParts.d.ts +29 -0
  35. package/dist/components/CollapsibleParts.js +20 -0
  36. package/dist/components/CollapsibleSection.d.ts +30 -3
  37. package/dist/components/CollapsibleSection.js +7 -11
  38. package/dist/components/CollapsibleSectionState.d.ts +1 -1
  39. package/dist/components/CollapsibleSectionState.js +54 -27
  40. package/dist/components/DashboardGrid.d.ts +60 -0
  41. package/dist/components/DashboardGrid.js +40 -0
  42. package/dist/components/DateRangePicker.d.ts +7 -1
  43. package/dist/components/DateRangePicker.js +5 -1
  44. package/dist/components/DescriptionList.d.ts +12 -1
  45. package/dist/components/DescriptionList.js +5 -5
  46. package/dist/components/Dialogs.d.ts +24 -3
  47. package/dist/components/Dialogs.js +74 -16
  48. package/dist/components/FileUpload.d.ts +10 -2
  49. package/dist/components/FileUpload.js +10 -4
  50. package/dist/components/FilterEditor.d.ts +4 -0
  51. package/dist/components/FilterEditor.js +13 -2
  52. package/dist/components/Heartbeat.d.ts +31 -0
  53. package/dist/components/Heartbeat.js +28 -0
  54. package/dist/components/Menu.d.ts +53 -5
  55. package/dist/components/Menu.js +104 -4
  56. package/dist/components/Meter.d.ts +7 -3
  57. package/dist/components/Meter.js +2 -1
  58. package/dist/components/Popover.d.ts +21 -2
  59. package/dist/components/Popover.js +94 -17
  60. package/dist/components/Prose.d.ts +6 -2
  61. package/dist/components/Prose.js +1 -1
  62. package/dist/components/RuleBuilder.d.ts +52 -4
  63. package/dist/components/RuleBuilder.js +109 -24
  64. package/dist/components/SchedulingCalendar.d.ts +4 -110
  65. package/dist/components/SchedulingCalendar.js +299 -63
  66. package/dist/components/SchedulingTimeGrid.d.ts +4 -0
  67. package/dist/components/SchedulingTimeGrid.js +521 -0
  68. package/dist/components/SchedulingTimeline.d.ts +2 -1
  69. package/dist/components/Segmented.d.ts +17 -3
  70. package/dist/components/Segmented.js +59 -5
  71. package/dist/components/Select.d.ts +66 -2
  72. package/dist/components/Select.js +800 -10
  73. package/dist/components/Spinner.d.ts +2 -1
  74. package/dist/components/Spinner.js +1 -1
  75. package/dist/components/StatusPill.d.ts +8 -2
  76. package/dist/components/StatusPill.js +2 -2
  77. package/dist/components/Steps.d.ts +28 -4
  78. package/dist/components/Steps.js +46 -16
  79. package/dist/components/TableControls.js +4 -3
  80. package/dist/components/Tabs.d.ts +7 -2
  81. package/dist/components/Tabs.js +14 -1
  82. package/dist/components/TextDiff.d.ts +49 -0
  83. package/dist/components/TextDiff.js +124 -0
  84. package/dist/{emoji-image-6mPZ15Zy.d.cts → emoji-image-DjCKpdwE.d.cts} +1 -1
  85. package/dist/emoji.cjs +169 -27
  86. package/dist/emoji.d.cts +3 -3
  87. package/dist/file-model.d.ts +22 -3
  88. package/dist/file-model.js +6 -4
  89. package/dist/filter-model.d.ts +26 -2
  90. package/dist/filter-model.js +13 -1
  91. package/dist/hooks/useAnchoredPosition.d.ts +24 -1
  92. package/dist/hooks/useAnchoredPosition.js +58 -8
  93. package/dist/hooks/useVirtualRows.d.ts +17 -2
  94. package/dist/hooks/useVirtualRows.js +6 -5
  95. package/dist/icons/Icon.d.ts +14 -14
  96. package/dist/index.cjs +4888 -1714
  97. package/dist/index.d.cts +1871 -658
  98. package/dist/index.d.ts +16 -9
  99. package/dist/index.js +67 -4
  100. package/dist/labels-context.d.ts +29 -1
  101. package/dist/{overlay-position-Cjv5E7mn.d.cts → overlay-position-2Afke51l.d.cts} +20 -0
  102. package/dist/overlay-position.d.ts +20 -0
  103. package/dist/overlay-position.js +17 -2
  104. package/dist/rich-text.cjs +169 -27
  105. package/dist/rich-text.d.cts +3 -3
  106. package/dist/rule-builder-model.d.ts +34 -2
  107. package/dist/rule-builder-model.js +48 -12
  108. package/dist/scheduling-calendar-model.d.ts +169 -0
  109. package/dist/scheduling-calendar-model.js +176 -0
  110. package/dist/select-model.d.ts +80 -0
  111. package/dist/select-model.js +109 -0
  112. package/dist/table-engine.d.ts +7 -0
  113. package/dist/table-engine.js +2 -1
  114. package/dist/text-diff-model.d.ts +71 -0
  115. package/dist/text-diff-model.js +261 -0
  116. package/package.json +1 -1
  117. package/dist/ChartCore-DzWe10bN.d.cts +0 -59
@@ -0,0 +1,176 @@
1
+ import { addCalendarDays, addZonedDays, buildMonthGrid, packLanes, startOfMonth, zonedDateKey, zonedDayBounds, zonedDaySpan } from "./calendar-model.js";
2
+ const DEFAULT_LANE_CAP = 2;
3
+ function itemDaySpan(start, end, timeZone) {
4
+ const from = new Date(start);
5
+ const to = new Date(end);
6
+ if (from.getTime() === to.getTime()) {
7
+ const day = zonedDateKey(from, timeZone);
8
+ return { start: day, end: addCalendarDays(day, 1) };
9
+ }
10
+ return zonedDaySpan(from, to, timeZone);
11
+ }
12
+ function schedulingGridDays(anchorDate, view, weekStartsOn = 1) {
13
+ if (view === "month") return buildMonthGrid(startOfMonth(anchorDate), weekStartsOn).map((day) => day.date);
14
+ const weekday = (/* @__PURE__ */ new Date(`${anchorDate}T00:00:00Z`)).getUTCDay();
15
+ const first = addCalendarDays(anchorDate, -((weekday - weekStartsOn + 7) % 7));
16
+ return Array.from({ length: 7 }, (_, index) => addCalendarDays(first, index));
17
+ }
18
+ function layoutMonthSpans(spans, days, options) {
19
+ if (days.length === 0 || days.length % 7 !== 0) throw new RangeError(`layoutMonthSpans: days must be whole weeks, got ${days.length} day(s)`);
20
+ for (let index = 1; index < days.length; index++) {
21
+ if (days[index] !== addCalendarDays(days[index - 1], 1)) throw new RangeError(`layoutMonthSpans: days must be consecutive (${days[index - 1]} \u2192 ${days[index]})`);
22
+ }
23
+ if (options.weekStartsOn !== void 0 && (/* @__PURE__ */ new Date(`${days[0]}T00:00:00Z`)).getUTCDay() !== options.weekStartsOn) {
24
+ throw new RangeError(`layoutMonthSpans: days[0] (${days[0]}) does not fall on weekday ${options.weekStartsOn}`);
25
+ }
26
+ const capFor = (group) => {
27
+ const cap = typeof options.laneCap === "number" ? options.laneCap : options.laneCap?.[group] ?? DEFAULT_LANE_CAP;
28
+ if (!(cap === Infinity || Number.isInteger(cap) && cap >= 0)) throw new RangeError(`layoutMonthSpans: laneCap for ${group} must be a non-negative integer, got ${cap}`);
29
+ return cap;
30
+ };
31
+ const caps = { window: capFor("window"), item: capFor("item") };
32
+ const rows = days.length / 7;
33
+ const gridEnd = addCalendarDays(days[days.length - 1], 1);
34
+ const seen = /* @__PURE__ */ new Set();
35
+ const covered = spans.map((span) => {
36
+ const group = span.group ?? "item";
37
+ if (seen.has(`${group} ${span.id}`)) throw new TypeError(`layoutMonthSpans: duplicate ${group} id ${span.id}`);
38
+ seen.add(`${group} ${span.id}`);
39
+ return { id: span.id, group, days: itemDaySpan(span.start, span.end, options.timeZone) };
40
+ });
41
+ const placed = [];
42
+ const hiddenByRow = {};
43
+ const firstHiddenDayByRow = {};
44
+ for (let row = 0; row < rows; row++) {
45
+ const rowStart = days[row * 7];
46
+ const rowEnd = row + 1 < rows ? days[(row + 1) * 7] : gridEnd;
47
+ for (const group of ["window", "item"]) {
48
+ const inRow = covered.filter((span) => span.group === group && span.days.start < rowEnd && span.days.end > rowStart).map((span) => {
49
+ const from = span.days.start > rowStart ? span.days.start : rowStart;
50
+ const to = span.days.end < rowEnd ? span.days.end : rowEnd;
51
+ const startCol = days.indexOf(from, row * 7) - row * 7;
52
+ const endCol = days.indexOf(addCalendarDays(to, -1), row * 7) - row * 7;
53
+ return { span, from, startCol, endCol };
54
+ });
55
+ const packed = packLanes(inRow.map(({ span, startCol, endCol }) => ({ id: span.id, start: startCol, end: endCol + 1 })), caps[group], { order: "given" });
56
+ for (const { span, from, startCol, endCol } of inRow) {
57
+ const lane = packed.lanes[span.id].lane;
58
+ if (lane === null) {
59
+ (hiddenByRow[row] ?? (hiddenByRow[row] = [])).push(span.id);
60
+ if (firstHiddenDayByRow[row] === void 0 || from < firstHiddenDayByRow[row]) firstHiddenDayByRow[row] = from;
61
+ continue;
62
+ }
63
+ placed.push({
64
+ id: span.id,
65
+ group,
66
+ weekRow: row,
67
+ lane,
68
+ startCol,
69
+ endCol,
70
+ continuesBefore: span.days.start < rowStart,
71
+ continuesAfter: span.days.end > rowEnd
72
+ });
73
+ }
74
+ }
75
+ }
76
+ return { placed, hiddenByRow, firstHiddenDayByRow };
77
+ }
78
+ const MINUTE_MS = 6e4;
79
+ const HOUR_MS = 36e5;
80
+ const instantMs = (value) => new Date(value).getTime();
81
+ const TOP_LANE_CROSS_MIDNIGHT_MINUTES = 360;
82
+ function placesInTopLane(entry, timeZone, options = {}) {
83
+ if (entry.allDay) return true;
84
+ const start = instantMs(entry.start);
85
+ const end = instantMs(entry.end);
86
+ if (!(end > start)) return false;
87
+ if (end > addZonedDays(new Date(start), 1, timeZone).instant.getTime()) return true;
88
+ const firstMidnight = zonedDayBounds(zonedDateKey(new Date(start), timeZone), timeZone).end.getTime();
89
+ return end > firstMidnight + (options.crossMidnightMinutes ?? TOP_LANE_CROSS_MIDNIGHT_MINUTES) * MINUTE_MS;
90
+ }
91
+ function layoutTimeGridDay(entries, date, timeZone, options = {}) {
92
+ const bounds = zonedDayBounds(date, timeZone);
93
+ const dayStart = bounds.start.getTime();
94
+ const dayEnd = bounds.end.getTime();
95
+ const parts = /* @__PURE__ */ new Map();
96
+ for (const entry of entries) {
97
+ const start = instantMs(entry.start);
98
+ const end = Math.max(instantMs(entry.end), start);
99
+ const touches = end > start ? start < dayEnd && end > dayStart : start >= dayStart && start < dayEnd;
100
+ if (!touches || placesInTopLane(entry, timeZone, options)) continue;
101
+ parts.set(entry.id, {
102
+ start: Math.max(start, dayStart),
103
+ end: Math.min(end, dayEnd),
104
+ part: start < dayStart ? "continuation" : end > dayEnd ? "start" : "whole"
105
+ });
106
+ }
107
+ const packed = packLanes([...parts].map(([id, part]) => ({ id, start: part.start, end: part.end })), options.maxLanes ?? Infinity);
108
+ const segments = [];
109
+ const hidden = [];
110
+ for (const cluster of packed.clusters) {
111
+ for (const id of cluster.ids) {
112
+ const part = parts.get(id);
113
+ const lane = packed.lanes[id].lane;
114
+ if (lane === null) {
115
+ hidden.push(id);
116
+ continue;
117
+ }
118
+ segments.push({
119
+ id,
120
+ part: part.part,
121
+ start: part.start,
122
+ end: part.end,
123
+ offsetHours: (part.start - dayStart) / HOUR_MS,
124
+ lengthHours: (part.end - part.start) / HOUR_MS,
125
+ lane,
126
+ laneCount: Math.max(cluster.laneCount, 1)
127
+ });
128
+ }
129
+ }
130
+ return { date, dayStart, dayHours: (dayEnd - dayStart) / HOUR_MS, segments, hidden };
131
+ }
132
+ function layoutDaySpans(spans, days, options) {
133
+ if (days.length === 0) throw new RangeError("layoutDaySpans: days is empty");
134
+ const rowStart = days[0];
135
+ const rowEnd = addCalendarDays(days[days.length - 1], 1);
136
+ const seen = /* @__PURE__ */ new Set();
137
+ const inRow = spans.filter((span) => seen.has(span.id) ? false : (seen.add(span.id), true)).map((span) => ({ id: span.id, days: itemDaySpan(span.start, instantMs(span.end) < instantMs(span.start) ? span.start : span.end, options.timeZone) })).filter((span) => span.days.start < rowEnd && span.days.end > rowStart).map((span) => {
138
+ const from = span.days.start > rowStart ? span.days.start : rowStart;
139
+ const to = span.days.end < rowEnd ? span.days.end : rowEnd;
140
+ return { span, from, startCol: days.indexOf(from), endCol: days.indexOf(addCalendarDays(to, -1)) };
141
+ });
142
+ const packed = packLanes(inRow.map(({ span, startCol, endCol }) => ({ id: span.id, start: startCol, end: endCol + 1 })), options.laneCap ?? Infinity, { order: "given" });
143
+ const layout = { placed: [], hidden: [] };
144
+ for (const { span, from, startCol, endCol } of inRow) {
145
+ const lane = packed.lanes[span.id].lane;
146
+ if (lane === null) {
147
+ layout.hidden.push(span.id);
148
+ if (layout.firstHiddenDay === void 0 || from < layout.firstHiddenDay) layout.firstHiddenDay = from;
149
+ continue;
150
+ }
151
+ layout.placed.push({ id: span.id, lane, startCol, endCol, continuesBefore: span.days.start < rowStart, continuesAfter: span.days.end > rowEnd });
152
+ }
153
+ return layout;
154
+ }
155
+ function proposeMove(span, delta, timeZone) {
156
+ const shift = (delta.minutes ?? 0) * MINUTE_MS;
157
+ const start = new Date(instantMs(span.start) + shift);
158
+ const end = new Date(instantMs(span.end) + shift);
159
+ if (!delta.days) return { start: start.toISOString(), end: end.toISOString(), adjusted: null };
160
+ const movedStart = addZonedDays(start, delta.days, timeZone);
161
+ const movedEnd = addZonedDays(end, delta.days, timeZone);
162
+ const length = end.getTime() - start.getTime();
163
+ const collapsed = length > 0 && movedEnd.instant.getTime() <= movedStart.instant.getTime();
164
+ const proposedEnd = collapsed ? new Date(movedStart.instant.getTime() + length) : movedEnd.instant;
165
+ return { start: movedStart.instant.toISOString(), end: proposedEnd.toISOString(), adjusted: movedStart.adjusted ?? movedEnd.adjusted };
166
+ }
167
+ export {
168
+ TOP_LANE_CROSS_MIDNIGHT_MINUTES,
169
+ itemDaySpan,
170
+ layoutDaySpans,
171
+ layoutMonthSpans,
172
+ layoutTimeGridDay,
173
+ placesInTopLane,
174
+ proposeMove,
175
+ schedulingGridDays
176
+ };
@@ -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 };