@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.
- package/README.md +367 -0
- package/dist/SchedulingCalendar-C4XVbqZr.d.ts +339 -0
- package/dist/attachment-labels.d.ts +16 -0
- package/dist/attachment-labels.js +9 -0
- package/dist/calendar-model.d.ts +141 -1
- package/dist/calendar-model.js +200 -11
- package/dist/chart-preset.cjs +127 -7
- package/dist/chart-preset.d.cts +2 -2
- package/dist/chart-preset.d.ts +1 -0
- package/dist/chart-preset.js +6 -1
- package/dist/chart-theme-Cl0IrkKQ.d.cts +112 -0
- package/dist/chart-theme.d.ts +51 -0
- package/dist/chart-theme.js +99 -0
- package/dist/chart.cjs +127 -7
- package/dist/chart.d.cts +2 -2
- package/dist/chart.d.ts +1 -0
- package/dist/chart.js +6 -2
- package/dist/components/Alert.d.ts +17 -1
- package/dist/components/Alert.js +9 -4
- package/dist/components/AlertDismiss.d.ts +14 -0
- package/dist/components/AlertDismiss.js +11 -0
- package/dist/components/Attachment.d.ts +15 -12
- package/dist/components/Attachment.js +17 -17
- package/dist/components/AttachmentParts.d.ts +24 -0
- package/dist/components/AttachmentParts.js +20 -0
- package/dist/components/Chart.d.ts +1 -0
- package/dist/components/Chart.js +6 -1
- package/dist/components/ChartCore.d.ts +6 -1
- package/dist/components/ChartCore.js +23 -5
- package/dist/components/Chip.d.ts +17 -2
- package/dist/components/Chip.js +35 -11
- package/dist/components/ChipRemove.d.ts +17 -0
- package/dist/components/ChipRemove.js +13 -0
- package/dist/components/CollapsibleParts.d.ts +29 -0
- package/dist/components/CollapsibleParts.js +20 -0
- package/dist/components/CollapsibleSection.d.ts +30 -3
- package/dist/components/CollapsibleSection.js +7 -11
- package/dist/components/CollapsibleSectionState.d.ts +1 -1
- package/dist/components/CollapsibleSectionState.js +54 -27
- package/dist/components/DashboardGrid.d.ts +60 -0
- package/dist/components/DashboardGrid.js +40 -0
- package/dist/components/DateRangePicker.d.ts +7 -1
- package/dist/components/DateRangePicker.js +5 -1
- package/dist/components/DescriptionList.d.ts +12 -1
- package/dist/components/DescriptionList.js +5 -5
- package/dist/components/Dialogs.d.ts +24 -3
- package/dist/components/Dialogs.js +74 -16
- package/dist/components/FileUpload.d.ts +10 -2
- package/dist/components/FileUpload.js +10 -4
- package/dist/components/FilterEditor.d.ts +4 -0
- package/dist/components/FilterEditor.js +13 -2
- package/dist/components/Heartbeat.d.ts +31 -0
- package/dist/components/Heartbeat.js +28 -0
- package/dist/components/Menu.d.ts +53 -5
- package/dist/components/Menu.js +104 -4
- package/dist/components/Meter.d.ts +7 -3
- package/dist/components/Meter.js +2 -1
- package/dist/components/Popover.d.ts +21 -2
- package/dist/components/Popover.js +94 -17
- package/dist/components/Prose.d.ts +6 -2
- package/dist/components/Prose.js +1 -1
- package/dist/components/RuleBuilder.d.ts +52 -4
- package/dist/components/RuleBuilder.js +109 -24
- package/dist/components/SchedulingCalendar.d.ts +4 -110
- package/dist/components/SchedulingCalendar.js +299 -63
- package/dist/components/SchedulingTimeGrid.d.ts +4 -0
- package/dist/components/SchedulingTimeGrid.js +521 -0
- package/dist/components/SchedulingTimeline.d.ts +2 -1
- package/dist/components/Segmented.d.ts +17 -3
- package/dist/components/Segmented.js +59 -5
- package/dist/components/Select.d.ts +66 -2
- package/dist/components/Select.js +800 -10
- package/dist/components/Spinner.d.ts +2 -1
- package/dist/components/Spinner.js +1 -1
- package/dist/components/StatusPill.d.ts +8 -2
- package/dist/components/StatusPill.js +2 -2
- package/dist/components/Steps.d.ts +28 -4
- package/dist/components/Steps.js +46 -16
- package/dist/components/TableControls.js +4 -3
- package/dist/components/Tabs.d.ts +7 -2
- package/dist/components/Tabs.js +14 -1
- package/dist/components/TextDiff.d.ts +49 -0
- package/dist/components/TextDiff.js +124 -0
- package/dist/{emoji-image-6mPZ15Zy.d.cts → emoji-image-DjCKpdwE.d.cts} +1 -1
- package/dist/emoji.cjs +169 -27
- package/dist/emoji.d.cts +3 -3
- package/dist/file-model.d.ts +22 -3
- package/dist/file-model.js +6 -4
- package/dist/filter-model.d.ts +26 -2
- package/dist/filter-model.js +13 -1
- package/dist/hooks/useAnchoredPosition.d.ts +24 -1
- package/dist/hooks/useAnchoredPosition.js +58 -8
- package/dist/hooks/useVirtualRows.d.ts +17 -2
- package/dist/hooks/useVirtualRows.js +6 -5
- package/dist/icons/Icon.d.ts +14 -14
- package/dist/index.cjs +4888 -1714
- package/dist/index.d.cts +1871 -658
- package/dist/index.d.ts +16 -9
- package/dist/index.js +67 -4
- package/dist/labels-context.d.ts +29 -1
- package/dist/{overlay-position-Cjv5E7mn.d.cts → overlay-position-2Afke51l.d.cts} +20 -0
- package/dist/overlay-position.d.ts +20 -0
- package/dist/overlay-position.js +17 -2
- package/dist/rich-text.cjs +169 -27
- package/dist/rich-text.d.cts +3 -3
- package/dist/rule-builder-model.d.ts +34 -2
- package/dist/rule-builder-model.js +48 -12
- package/dist/scheduling-calendar-model.d.ts +169 -0
- package/dist/scheduling-calendar-model.js +176 -0
- package/dist/select-model.d.ts +80 -0
- package/dist/select-model.js +109 -0
- package/dist/table-engine.d.ts +7 -0
- package/dist/table-engine.js +2 -1
- package/dist/text-diff-model.d.ts +71 -0
- package/dist/text-diff-model.js +261 -0
- package/package.json +1 -1
- 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
|
+
};
|
package/dist/table-engine.d.ts
CHANGED
|
@@ -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;
|
package/dist/table-engine.js
CHANGED
|
@@ -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
|
|
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 };
|