@tensor_1/react 2.11.0 → 2.13.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 +26 -1
- package/dist/ChartCore-Bt1C84ty.d.cts +22 -0
- package/dist/calendar-model.d.ts +31 -0
- package/dist/calendar-model.js +89 -0
- package/dist/chart-preset.cjs +168 -0
- package/dist/chart-preset.d.cts +9 -0
- package/dist/chart-preset.d.ts +9 -0
- package/dist/chart-preset.js +35 -0
- package/dist/chart.cjs +58 -32
- package/dist/chart.d.cts +6 -21
- package/dist/chart.d.ts +2 -1
- package/dist/color-model.d.ts +20 -0
- package/dist/color-model.js +74 -0
- package/dist/components/BrandProfiles.d.ts +45 -0
- package/dist/components/BrandProfiles.js +150 -0
- package/dist/components/BuilderCanvas.d.ts +34 -0
- package/dist/components/BuilderCanvas.js +117 -0
- package/dist/components/Chart.d.ts +6 -21
- package/dist/components/Chart.js +5 -76
- package/dist/components/ChartCore.d.ts +33 -0
- package/dist/components/ChartCore.js +100 -0
- package/dist/components/ColorPicker.d.ts +17 -0
- package/dist/components/ColorPicker.js +158 -0
- package/dist/components/DateRangePicker.d.ts +23 -0
- package/dist/components/DateRangePicker.js +123 -0
- package/dist/components/DiffViewer.d.ts +18 -0
- package/dist/components/DiffViewer.js +93 -0
- package/dist/components/Inbox.js +0 -1
- package/dist/components/LicensePositionBar.d.ts +17 -0
- package/dist/components/LicensePositionBar.js +50 -0
- package/dist/components/MatchReview.d.ts +39 -0
- package/dist/components/MatchReview.js +120 -0
- package/dist/components/Meter.js +0 -1
- package/dist/components/MetricInput.d.ts +23 -0
- package/dist/components/MetricInput.js +92 -0
- package/dist/components/NavFavourites.js +35 -108
- package/dist/components/RelationshipGraph.d.ts +30 -0
- package/dist/components/RelationshipGraph.js +138 -0
- package/dist/components/RuleBuilder.d.ts +45 -0
- package/dist/components/RuleBuilder.js +163 -0
- package/dist/components/SchedulingCalendar.d.ts +42 -0
- package/dist/components/SchedulingCalendar.js +124 -0
- package/dist/components/Segmented.js +3 -2
- package/dist/components/Table.d.ts +1 -1
- package/dist/components/Table.js +4 -3
- package/dist/components/Tabs.js +3 -2
- package/dist/diff-model.d.ts +23 -0
- package/dist/diff-model.js +50 -0
- package/dist/hooks/useTable.js +11 -7
- package/dist/hooks/useVirtualRows.d.ts +27 -0
- package/dist/hooks/useVirtualRows.js +50 -0
- package/dist/index.cjs +1930 -194
- package/dist/index.d.cts +464 -2
- package/dist/index.d.ts +19 -0
- package/dist/index.js +93 -1
- package/dist/json-value.d.ts +10 -0
- package/dist/json-value.js +19 -0
- package/dist/metric-model.d.ts +9 -0
- package/dist/metric-model.js +25 -0
- package/dist/relationship-graph-model.d.ts +31 -0
- package/dist/relationship-graph-model.js +68 -0
- package/dist/rule-builder-model.d.ts +41 -0
- package/dist/rule-builder-model.js +101 -0
- package/dist/table-engine.js +8 -6
- package/package.json +16 -2
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,9 @@ __export(src_exports, {
|
|
|
24
24
|
AppShell: () => AppShell,
|
|
25
25
|
Avatar: () => Avatar,
|
|
26
26
|
AvatarGroup: () => AvatarGroup,
|
|
27
|
+
BrandProfileEditor: () => BrandProfileEditor,
|
|
28
|
+
BrandProfiles: () => BrandProfiles,
|
|
29
|
+
BuilderCanvas: () => BuilderCanvas,
|
|
27
30
|
BulkBar: () => BulkBar,
|
|
28
31
|
Button: () => Button,
|
|
29
32
|
ButtonGroup: () => ButtonGroup,
|
|
@@ -31,6 +34,7 @@ __export(src_exports, {
|
|
|
31
34
|
CellStrong: () => CellStrong,
|
|
32
35
|
CellSub: () => CellSub,
|
|
33
36
|
Checkbox: () => Checkbox,
|
|
37
|
+
ColorPicker: () => ColorPicker,
|
|
34
38
|
CommandGroup: () => CommandGroup,
|
|
35
39
|
CommandItem: () => CommandItem,
|
|
36
40
|
CommandPalette: () => CommandPalette,
|
|
@@ -40,9 +44,11 @@ __export(src_exports, {
|
|
|
40
44
|
ComposerBar: () => ComposerBar,
|
|
41
45
|
ContentList: () => ContentList,
|
|
42
46
|
ContentListItem: () => ContentListItem,
|
|
47
|
+
DateRangePicker: () => DateRangePicker,
|
|
43
48
|
DescriptionItem: () => DescriptionItem,
|
|
44
49
|
DescriptionList: () => DescriptionList,
|
|
45
50
|
DetailLayout: () => DetailLayout,
|
|
51
|
+
DiffViewer: () => DiffViewer,
|
|
46
52
|
Drawer: () => Drawer,
|
|
47
53
|
EmptyState: () => EmptyState,
|
|
48
54
|
ErrorState: () => ErrorState,
|
|
@@ -63,11 +69,14 @@ __export(src_exports, {
|
|
|
63
69
|
KanbanCard: () => KanbanCard,
|
|
64
70
|
KanbanColumn: () => KanbanColumn,
|
|
65
71
|
Label: () => Label,
|
|
72
|
+
LicensePositionBar: () => LicensePositionBar,
|
|
66
73
|
List: () => List,
|
|
67
74
|
ListItem: () => ListItem,
|
|
68
75
|
LoadingState: () => LoadingState,
|
|
69
76
|
Mark: () => Mark,
|
|
77
|
+
MatchReview: () => MatchReview,
|
|
70
78
|
Meter: () => Meter,
|
|
79
|
+
MetricInput: () => MetricInput,
|
|
71
80
|
Modal: () => Modal,
|
|
72
81
|
NavFavourites: () => NavFavourites,
|
|
73
82
|
NavGroup: () => NavGroup,
|
|
@@ -84,9 +93,12 @@ __export(src_exports, {
|
|
|
84
93
|
Prose: () => Prose,
|
|
85
94
|
Radio: () => Radio,
|
|
86
95
|
RadioGroup: () => RadioGroup,
|
|
96
|
+
RelationshipGraph: () => RelationshipGraph,
|
|
87
97
|
ResourceGrid: () => ResourceGrid,
|
|
88
98
|
RowAction: () => RowAction,
|
|
89
99
|
RowActions: () => RowActions,
|
|
100
|
+
RuleBuilder: () => RuleBuilder,
|
|
101
|
+
SchedulingCalendar: () => SchedulingCalendar,
|
|
90
102
|
SectionHead: () => SectionHead,
|
|
91
103
|
Segmented: () => Segmented,
|
|
92
104
|
SegmentedOption: () => SegmentedOption,
|
|
@@ -117,30 +129,70 @@ __export(src_exports, {
|
|
|
117
129
|
Tr: () => Tr,
|
|
118
130
|
Tree: () => Tree,
|
|
119
131
|
UserChip: () => UserChip,
|
|
132
|
+
addCalendarDays: () => addCalendarDays,
|
|
133
|
+
addCalendarMonths: () => addCalendarMonths,
|
|
134
|
+
appendRuleNode: () => appendRuleNode,
|
|
135
|
+
applyBrandProfile: () => applyBrandProfile,
|
|
120
136
|
applyFilters: () => applyFilters,
|
|
137
|
+
boundRelationshipGraph: () => boundRelationshipGraph,
|
|
138
|
+
buildMonthGrid: () => buildMonthGrid,
|
|
139
|
+
buildThreeWayDiff: () => buildThreeWayDiff,
|
|
140
|
+
clampMetricValue: () => clampMetricValue,
|
|
121
141
|
clampWidth: () => clampWidth,
|
|
122
142
|
computePosition: () => computePosition,
|
|
143
|
+
contrastRatio: () => contrastRatio,
|
|
123
144
|
cx: () => cx,
|
|
145
|
+
enumerateDateSpan: () => enumerateDateSpan,
|
|
146
|
+
findRuleNodeDepth: () => findRuleNodeDepth,
|
|
124
147
|
flattenTree: () => flattenTree,
|
|
148
|
+
hexToRgb: () => hexToRgb,
|
|
125
149
|
highlightSegments: () => highlightSegments,
|
|
150
|
+
hsvToHex: () => hsvToHex,
|
|
151
|
+
hsvToRgb: () => hsvToRgb,
|
|
152
|
+
isDateInRange: () => isDateInRange,
|
|
153
|
+
isDateUnavailable: () => isDateUnavailable,
|
|
154
|
+
isRuleGroup: () => isRuleGroup,
|
|
155
|
+
layoutRelationshipGraph: () => layoutRelationshipGraph,
|
|
156
|
+
mapRuleGroup: () => mapRuleGroup,
|
|
126
157
|
matchFilter: () => matchFilter,
|
|
158
|
+
meetsContrast: () => meetsContrast,
|
|
127
159
|
mergePinned: () => mergePinned,
|
|
160
|
+
moveRuleNode: () => moveRuleNode,
|
|
128
161
|
multiSort: () => multiSort,
|
|
129
162
|
nextSortDir: () => nextSortDir,
|
|
163
|
+
normalizeHex: () => normalizeHex,
|
|
164
|
+
parseMetricValue: () => parseMetricValue,
|
|
130
165
|
parseView: () => parseView,
|
|
166
|
+
relationshipNeighbors: () => relationshipNeighbors,
|
|
167
|
+
removeRuleNode: () => removeRuleNode,
|
|
131
168
|
reorder: () => reorder,
|
|
169
|
+
restoreBrandProfile: () => restoreBrandProfile,
|
|
170
|
+
rgbToHex: () => rgbToHex,
|
|
171
|
+
rgbToHsv: () => rgbToHsv,
|
|
172
|
+
ruleDepth: () => ruleDepth,
|
|
132
173
|
searchRows: () => searchRows,
|
|
133
174
|
selectAllState: () => selectAllState,
|
|
175
|
+
selectRangeDate: () => selectRangeDate,
|
|
134
176
|
serializeView: () => serializeView,
|
|
135
177
|
shouldVirtualize: () => shouldVirtualize,
|
|
178
|
+
startOfMonth: () => startOfMonth,
|
|
179
|
+
stepMetricValue: () => stepMetricValue,
|
|
180
|
+
summarizeDiff: () => summarizeDiff,
|
|
181
|
+
summarizeRule: () => summarizeRule,
|
|
182
|
+
toDateKey: () => toDateKey,
|
|
136
183
|
toggleId: () => toggleId,
|
|
137
184
|
togglePage: () => togglePage,
|
|
138
185
|
toggleSort: () => toggleSort,
|
|
186
|
+
traverseRelationshipNode: () => traverseRelationshipNode,
|
|
139
187
|
treeNav: () => treeNav,
|
|
140
188
|
useAnchoredPosition: () => useAnchoredPosition,
|
|
141
189
|
useDialog: () => useDialog,
|
|
142
190
|
useTable: () => useTable,
|
|
143
|
-
|
|
191
|
+
useVirtualRows: () => useVirtualRows,
|
|
192
|
+
validateRuleDefinition: () => validateRuleDefinition,
|
|
193
|
+
virtualWindow: () => virtualWindow,
|
|
194
|
+
zonedDateKey: () => zonedDateKey,
|
|
195
|
+
zonedDateSpan: () => zonedDateSpan
|
|
144
196
|
});
|
|
145
197
|
module.exports = __toCommonJS(src_exports);
|
|
146
198
|
|
|
@@ -203,22 +255,23 @@ function useDialog(open) {
|
|
|
203
255
|
var import_react2 = require("react");
|
|
204
256
|
|
|
205
257
|
// src/table-engine.ts
|
|
258
|
+
var stringCollator = new Intl.Collator(void 0, { numeric: true, sensitivity: "base" });
|
|
206
259
|
function compare(a, b) {
|
|
207
260
|
if (a == null && b == null) return 0;
|
|
208
261
|
if (a == null) return -1;
|
|
209
262
|
if (b == null) return 1;
|
|
210
263
|
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
211
|
-
return String(a)
|
|
264
|
+
return stringCollator.compare(String(a), String(b));
|
|
212
265
|
}
|
|
213
266
|
function multiSort(rows, keys) {
|
|
214
267
|
if (!keys.length) return rows.slice();
|
|
215
|
-
return rows.
|
|
268
|
+
return rows.slice().sort((a, b) => {
|
|
216
269
|
for (const k of keys) {
|
|
217
|
-
const c = compare(
|
|
270
|
+
const c = compare(a[k.field], b[k.field]);
|
|
218
271
|
if (c !== 0) return k.dir === "ascending" ? c : -c;
|
|
219
272
|
}
|
|
220
|
-
return
|
|
221
|
-
})
|
|
273
|
+
return 0;
|
|
274
|
+
});
|
|
222
275
|
}
|
|
223
276
|
function nextSortDir(current) {
|
|
224
277
|
if (current === "ascending") return "descending";
|
|
@@ -418,7 +471,8 @@ function toggleId(set, id) {
|
|
|
418
471
|
return next;
|
|
419
472
|
}
|
|
420
473
|
function selectAllState(selected, pageIds) {
|
|
421
|
-
|
|
474
|
+
let on = 0;
|
|
475
|
+
for (const id of pageIds) if (selected.has(id)) on += 1;
|
|
422
476
|
if (on === 0) return "none";
|
|
423
477
|
return on === pageIds.length ? "all" : "some";
|
|
424
478
|
}
|
|
@@ -444,18 +498,22 @@ function useTable(data, options = {}) {
|
|
|
444
498
|
const [query, setQuery] = (0, import_react2.useState)(() => options.initialQuery ?? "");
|
|
445
499
|
const [selected, setSelected] = (0, import_react2.useState)(() => /* @__PURE__ */ new Set());
|
|
446
500
|
const [pinned, setPinned] = (0, import_react2.useState)(() => new Set(options.initialPinned ?? []));
|
|
447
|
-
const
|
|
448
|
-
const filtered = applyFilters(data, filters);
|
|
449
|
-
const searched = searchRows(filtered, query, searchFields);
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
501
|
+
const visibleRows = (0, import_react2.useMemo)(() => {
|
|
502
|
+
const filtered = filters.length ? applyFilters(data, filters) : data;
|
|
503
|
+
const searched = query.trim() ? searchRows(filtered, query, searchFields) : filtered;
|
|
504
|
+
return sort.length ? multiSort(searched, sort) : searched;
|
|
505
|
+
}, [data, filters, query, searchFields, sort]);
|
|
506
|
+
const rows = (0, import_react2.useMemo)(
|
|
507
|
+
() => pinned.size ? mergePinned(data, visibleRows, pinned, idField) : visibleRows.slice(),
|
|
508
|
+
[data, visibleRows, pinned, idField]
|
|
509
|
+
);
|
|
453
510
|
const pageIds = (0, import_react2.useMemo)(() => rows.map((r) => String(r[idField])), [rows, idField]);
|
|
454
511
|
const selectAll = selectAllState(selected, pageIds);
|
|
455
512
|
const doToggleSort = (0, import_react2.useCallback)((field, additive = false) => {
|
|
456
513
|
setSort((keys) => toggleSort(keys, field, additive));
|
|
457
514
|
}, []);
|
|
458
515
|
const toggleRow = (0, import_react2.useCallback)((id) => setSelected((s) => toggleId(s, id)), []);
|
|
516
|
+
const isSelected = (0, import_react2.useCallback)((id) => selected.has(id), [selected]);
|
|
459
517
|
const toggleAllRows = (0, import_react2.useCallback)(() => setSelected((s) => togglePage(s, pageIds)), [pageIds]);
|
|
460
518
|
const clearSelection = (0, import_react2.useCallback)(() => setSelected(/* @__PURE__ */ new Set()), []);
|
|
461
519
|
const togglePin = (0, import_react2.useCallback)((id) => setPinned((s) => toggleId(s, id)), []);
|
|
@@ -479,7 +537,7 @@ function useTable(data, options = {}) {
|
|
|
479
537
|
setSort,
|
|
480
538
|
setFilters,
|
|
481
539
|
setQuery,
|
|
482
|
-
isSelected
|
|
540
|
+
isSelected,
|
|
483
541
|
toggleRow,
|
|
484
542
|
toggleAllRows,
|
|
485
543
|
clearSelection,
|
|
@@ -490,8 +548,55 @@ function useTable(data, options = {}) {
|
|
|
490
548
|
};
|
|
491
549
|
}
|
|
492
550
|
|
|
493
|
-
// src/hooks/
|
|
551
|
+
// src/hooks/useVirtualRows.ts
|
|
494
552
|
var import_react3 = require("react");
|
|
553
|
+
function useVirtualRows(rows, { rowHeight, overscan = 6, threshold = 100 }) {
|
|
554
|
+
const containerRef = (0, import_react3.useRef)(null);
|
|
555
|
+
const [viewport, setViewport] = (0, import_react3.useState)({ scrollTop: 0, height: 0 });
|
|
556
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
557
|
+
const element = containerRef.current;
|
|
558
|
+
if (!element) return;
|
|
559
|
+
let frame = 0;
|
|
560
|
+
const commitMeasurement = () => {
|
|
561
|
+
const next = { scrollTop: element.scrollTop, height: element.clientHeight };
|
|
562
|
+
setViewport((current) => current.scrollTop === next.scrollTop && current.height === next.height ? current : next);
|
|
563
|
+
};
|
|
564
|
+
const measure = () => {
|
|
565
|
+
if (frame) return;
|
|
566
|
+
frame = requestAnimationFrame(() => {
|
|
567
|
+
frame = 0;
|
|
568
|
+
commitMeasurement();
|
|
569
|
+
});
|
|
570
|
+
};
|
|
571
|
+
commitMeasurement();
|
|
572
|
+
element.addEventListener("scroll", measure, { passive: true });
|
|
573
|
+
const observer = new ResizeObserver(measure);
|
|
574
|
+
observer.observe(element);
|
|
575
|
+
return () => {
|
|
576
|
+
element.removeEventListener("scroll", measure);
|
|
577
|
+
observer.disconnect();
|
|
578
|
+
if (frame) cancelAnimationFrame(frame);
|
|
579
|
+
};
|
|
580
|
+
}, []);
|
|
581
|
+
const enabled = viewport.height > 0 && shouldVirtualize(rows.length, threshold);
|
|
582
|
+
const window2 = (0, import_react3.useMemo)(
|
|
583
|
+
() => enabled ? virtualWindow(viewport.scrollTop, viewport.height, rowHeight, rows.length, overscan) : { start: 0, end: rows.length, padTop: 0, padBottom: 0, total: rows.length * rowHeight },
|
|
584
|
+
[enabled, overscan, rowHeight, rows.length, viewport.height, viewport.scrollTop]
|
|
585
|
+
);
|
|
586
|
+
const visibleRows = (0, import_react3.useMemo)(() => rows.slice(window2.start, window2.end), [rows, window2.start, window2.end]);
|
|
587
|
+
return {
|
|
588
|
+
containerRef,
|
|
589
|
+
rows: visibleRows,
|
|
590
|
+
startIndex: window2.start,
|
|
591
|
+
padTop: window2.padTop,
|
|
592
|
+
padBottom: window2.padBottom,
|
|
593
|
+
totalHeight: window2.total,
|
|
594
|
+
virtualized: enabled
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/hooks/useAnchoredPosition.ts
|
|
599
|
+
var import_react4 = require("react");
|
|
495
600
|
|
|
496
601
|
// src/overlay-position.ts
|
|
497
602
|
var OPPOSITE = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
@@ -555,12 +660,12 @@ function computePosition(anchor, floating, viewport, options = {}) {
|
|
|
555
660
|
}
|
|
556
661
|
|
|
557
662
|
// src/hooks/useAnchoredPosition.ts
|
|
558
|
-
var useIsomorphicLayoutEffect = typeof document !== "undefined" ?
|
|
663
|
+
var useIsomorphicLayoutEffect = typeof document !== "undefined" ? import_react4.useLayoutEffect : import_react4.useEffect;
|
|
559
664
|
var resolve = (a) => a && typeof a === "object" && "current" in a ? a.current : a ?? null;
|
|
560
665
|
function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8 }) {
|
|
561
|
-
const opts = (0,
|
|
666
|
+
const opts = (0, import_react4.useRef)({ placement, offset, padding });
|
|
562
667
|
opts.current = { placement, offset, padding };
|
|
563
|
-
const reposition = (0,
|
|
668
|
+
const reposition = (0, import_react4.useCallback)(() => {
|
|
564
669
|
const anchorEl = resolve(anchor);
|
|
565
670
|
const floating = floatingRef.current;
|
|
566
671
|
if (!anchorEl || !floating) return;
|
|
@@ -595,9 +700,9 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
595
700
|
}
|
|
596
701
|
|
|
597
702
|
// src/components/Button.tsx
|
|
598
|
-
var
|
|
703
|
+
var import_react5 = require("react");
|
|
599
704
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
600
|
-
var Button = (0,
|
|
705
|
+
var Button = (0, import_react5.forwardRef)(
|
|
601
706
|
({ variant = "secondary", size = "md", icon = false, loading = false, className, children, disabled, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
602
707
|
"button",
|
|
603
708
|
{
|
|
@@ -622,9 +727,9 @@ function ButtonGroup({ children, className }) {
|
|
|
622
727
|
}
|
|
623
728
|
|
|
624
729
|
// src/components/Input.tsx
|
|
625
|
-
var
|
|
730
|
+
var import_react6 = require("react");
|
|
626
731
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
627
|
-
var Input = (0,
|
|
732
|
+
var Input = (0, import_react6.forwardRef)(
|
|
628
733
|
({ size = "md", invalid, valid, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
629
734
|
"input",
|
|
630
735
|
{
|
|
@@ -657,9 +762,9 @@ function InputGroup({ children, leadingIcon, trailingIcon, className }) {
|
|
|
657
762
|
}
|
|
658
763
|
|
|
659
764
|
// src/components/Textarea.tsx
|
|
660
|
-
var
|
|
765
|
+
var import_react7 = require("react");
|
|
661
766
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
662
|
-
var Textarea = (0,
|
|
767
|
+
var Textarea = (0, import_react7.forwardRef)(
|
|
663
768
|
({ invalid, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
664
769
|
"textarea",
|
|
665
770
|
{
|
|
@@ -673,9 +778,9 @@ var Textarea = (0, import_react6.forwardRef)(
|
|
|
673
778
|
Textarea.displayName = "Textarea";
|
|
674
779
|
|
|
675
780
|
// src/components/Select.tsx
|
|
676
|
-
var
|
|
781
|
+
var import_react8 = require("react");
|
|
677
782
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
678
|
-
var Select = (0,
|
|
783
|
+
var Select = (0, import_react8.forwardRef)(
|
|
679
784
|
({ size = "md", className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
680
785
|
"select",
|
|
681
786
|
{
|
|
@@ -688,14 +793,14 @@ var Select = (0, import_react7.forwardRef)(
|
|
|
688
793
|
Select.displayName = "Select";
|
|
689
794
|
|
|
690
795
|
// src/components/Checkbox.tsx
|
|
691
|
-
var
|
|
796
|
+
var import_react9 = require("react");
|
|
692
797
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
693
798
|
var CheckIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "1.5,6 4.5,9 10.5,3" }) });
|
|
694
799
|
var IndeterminateIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { viewBox: "0 0 12 12", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: "2", y: "5", width: "8", height: "2", rx: "1", fill: "currentColor" }) });
|
|
695
|
-
var Checkbox = (0,
|
|
800
|
+
var Checkbox = (0, import_react9.forwardRef)(
|
|
696
801
|
({ label, indeterminate, className, ...props }, forwardedRef) => {
|
|
697
|
-
const innerRef = (0,
|
|
698
|
-
(0,
|
|
802
|
+
const innerRef = (0, import_react9.useRef)(null);
|
|
803
|
+
(0, import_react9.useEffect)(() => {
|
|
699
804
|
const el = innerRef.current;
|
|
700
805
|
if (el) el.indeterminate = !!indeterminate;
|
|
701
806
|
}, [indeterminate]);
|
|
@@ -720,9 +825,9 @@ var Checkbox = (0, import_react8.forwardRef)(
|
|
|
720
825
|
Checkbox.displayName = "Checkbox";
|
|
721
826
|
|
|
722
827
|
// src/components/Radio.tsx
|
|
723
|
-
var
|
|
828
|
+
var import_react10 = require("react");
|
|
724
829
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
725
|
-
var Radio = (0,
|
|
830
|
+
var Radio = (0, import_react10.forwardRef)(
|
|
726
831
|
({ label, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: cx("uix-radio", className), children: [
|
|
727
832
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "radio", ref, ...props }),
|
|
728
833
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "uix-radio__dot" }),
|
|
@@ -735,9 +840,9 @@ function RadioGroup({ children, className }) {
|
|
|
735
840
|
}
|
|
736
841
|
|
|
737
842
|
// src/components/Switch.tsx
|
|
738
|
-
var
|
|
843
|
+
var import_react11 = require("react");
|
|
739
844
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
740
|
-
var Switch = (0,
|
|
845
|
+
var Switch = (0, import_react11.forwardRef)(
|
|
741
846
|
({ label, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: cx("uix-switch", className), children: [
|
|
742
847
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", role: "switch", ref, ...props }),
|
|
743
848
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "uix-switch__track" }),
|
|
@@ -747,13 +852,13 @@ var Switch = (0, import_react10.forwardRef)(
|
|
|
747
852
|
Switch.displayName = "Switch";
|
|
748
853
|
|
|
749
854
|
// src/components/Field.tsx
|
|
750
|
-
var
|
|
855
|
+
var import_react12 = require("react");
|
|
751
856
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
752
857
|
function Field({ label, hint, error, success, required, children, className, htmlFor }) {
|
|
753
|
-
const uid = (0,
|
|
858
|
+
const uid = (0, import_react12.useId)();
|
|
754
859
|
const msgId = `${uid}-msg`;
|
|
755
860
|
const describedBy = error || success || hint ? msgId : void 0;
|
|
756
|
-
const control = (0,
|
|
861
|
+
const control = (0, import_react12.isValidElement)(children) ? (0, import_react12.cloneElement)(children, {
|
|
757
862
|
"aria-describedby": [children.props["aria-describedby"], describedBy].filter(Boolean).join(" ") || void 0,
|
|
758
863
|
"aria-invalid": error ? true : children.props["aria-invalid"]
|
|
759
864
|
}) : children;
|
|
@@ -890,11 +995,11 @@ function ListItem({ title, meta, trail, leading, children, className, ...props }
|
|
|
890
995
|
}
|
|
891
996
|
|
|
892
997
|
// src/components/DescriptionList.tsx
|
|
893
|
-
var
|
|
998
|
+
var import_react13 = require("react");
|
|
894
999
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
895
1000
|
function DescriptionList({ items, children, className, ...props }) {
|
|
896
1001
|
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("dl", { className: cx("uix-dl", className), ...props, children: [
|
|
897
|
-
items?.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
1002
|
+
items?.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react13.Fragment, { children: [
|
|
898
1003
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dt", { children: item.term }),
|
|
899
1004
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dd", { children: item.description })
|
|
900
1005
|
] }, item.id ?? i)),
|
|
@@ -909,14 +1014,15 @@ function DescriptionItem({ term, children }) {
|
|
|
909
1014
|
}
|
|
910
1015
|
|
|
911
1016
|
// src/components/Tabs.tsx
|
|
912
|
-
var
|
|
1017
|
+
var import_react14 = require("react");
|
|
913
1018
|
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
914
|
-
var TabsCtx = (0,
|
|
1019
|
+
var TabsCtx = (0, import_react14.createContext)({});
|
|
915
1020
|
function Tabs({ variant = "line", value, onChange, children, className }) {
|
|
916
|
-
|
|
1021
|
+
const contextValue = (0, import_react14.useMemo)(() => ({ value, onChange }), [value, onChange]);
|
|
1022
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TabsCtx.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { role: "tablist", className: cx("uix-tabs", `uix-tabs--${variant}`, className), children }) });
|
|
917
1023
|
}
|
|
918
1024
|
function Tab({ value, children, disabled, className, onClick, ...props }) {
|
|
919
|
-
const ctx = (0,
|
|
1025
|
+
const ctx = (0, import_react14.useContext)(TabsCtx);
|
|
920
1026
|
const selected = ctx.value === value;
|
|
921
1027
|
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
922
1028
|
"button",
|
|
@@ -936,7 +1042,7 @@ function Tab({ value, children, disabled, className, onClick, ...props }) {
|
|
|
936
1042
|
}
|
|
937
1043
|
|
|
938
1044
|
// src/components/AppShell.tsx
|
|
939
|
-
var
|
|
1045
|
+
var import_react15 = require("react");
|
|
940
1046
|
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
941
1047
|
function AppShell({
|
|
942
1048
|
nav,
|
|
@@ -951,7 +1057,7 @@ function AppShell({
|
|
|
951
1057
|
...props
|
|
952
1058
|
}) {
|
|
953
1059
|
const navState = nav ?? (collapsed ? "rail" : "full");
|
|
954
|
-
(0,
|
|
1060
|
+
(0, import_react15.useEffect)(() => {
|
|
955
1061
|
if (!focus || !onExitFocus) return;
|
|
956
1062
|
const onKey = (e) => {
|
|
957
1063
|
if (e.key === "Escape") onExitFocus();
|
|
@@ -978,7 +1084,7 @@ function AppShell({
|
|
|
978
1084
|
}
|
|
979
1085
|
|
|
980
1086
|
// src/components/Sidebar.tsx
|
|
981
|
-
var
|
|
1087
|
+
var import_react16 = require("react");
|
|
982
1088
|
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
983
1089
|
var ChevronIcon = () => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("svg", { className: "uix-navgroup__chevron", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M2 4l4 4 4-4" }) });
|
|
984
1090
|
function Sidebar({ collapsed, onToggle, brand, toggleIcon, children, className, ...props }) {
|
|
@@ -1032,7 +1138,7 @@ function NavItem({ icon, badge, active, as: Tag = "a", children, className, ...p
|
|
|
1032
1138
|
);
|
|
1033
1139
|
}
|
|
1034
1140
|
function NavGroup({ icon, label, children, defaultExpanded = true }) {
|
|
1035
|
-
const [expanded, setExpanded] = (0,
|
|
1141
|
+
const [expanded, setExpanded] = (0, import_react16.useState)(defaultExpanded);
|
|
1036
1142
|
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "uix-navgroup", children: [
|
|
1037
1143
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
1038
1144
|
"button",
|
|
@@ -1100,7 +1206,7 @@ function StarButton({
|
|
|
1100
1206
|
}
|
|
1101
1207
|
|
|
1102
1208
|
// src/components/NavFavourites.tsx
|
|
1103
|
-
var
|
|
1209
|
+
var import_react17 = require("react");
|
|
1104
1210
|
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1105
1211
|
var ChevronDownIcon = () => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1106
1212
|
"svg",
|
|
@@ -1114,7 +1220,7 @@ var ChevronDownIcon = () => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
|
1114
1220
|
strokeLinecap: "round",
|
|
1115
1221
|
strokeLinejoin: "round",
|
|
1116
1222
|
"aria-hidden": "true",
|
|
1117
|
-
|
|
1223
|
+
className: "uix-favourites__chevron",
|
|
1118
1224
|
children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "m6 9 6 6 6-6" })
|
|
1119
1225
|
}
|
|
1120
1226
|
);
|
|
@@ -1130,7 +1236,7 @@ var ChevronRightIcon = () => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
|
1130
1236
|
strokeLinecap: "round",
|
|
1131
1237
|
strokeLinejoin: "round",
|
|
1132
1238
|
"aria-hidden": "true",
|
|
1133
|
-
|
|
1239
|
+
className: "uix-favourites__chevron",
|
|
1134
1240
|
children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "m9 18 6-6-6-6" })
|
|
1135
1241
|
}
|
|
1136
1242
|
);
|
|
@@ -1153,7 +1259,6 @@ var MoreIcon = () => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
|
1153
1259
|
]
|
|
1154
1260
|
}
|
|
1155
1261
|
);
|
|
1156
|
-
var MUTED = "color-mix(in srgb, var(--uix-text) 50%, transparent)";
|
|
1157
1262
|
function NavFavourites({
|
|
1158
1263
|
items,
|
|
1159
1264
|
collapsed = false,
|
|
@@ -1164,20 +1269,20 @@ function NavFavourites({
|
|
|
1164
1269
|
emptyReason,
|
|
1165
1270
|
className
|
|
1166
1271
|
}) {
|
|
1167
|
-
const regionId = (0,
|
|
1168
|
-
const headerRef = (0,
|
|
1169
|
-
const regionRef = (0,
|
|
1170
|
-
const focusAfterRef = (0,
|
|
1171
|
-
const triggerRefs = (0,
|
|
1172
|
-
const order = items.map((
|
|
1173
|
-
(0,
|
|
1272
|
+
const regionId = (0, import_react17.useId)();
|
|
1273
|
+
const headerRef = (0, import_react17.useRef)(null);
|
|
1274
|
+
const regionRef = (0, import_react17.useRef)(null);
|
|
1275
|
+
const focusAfterRef = (0, import_react17.useRef)(null);
|
|
1276
|
+
const triggerRefs = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
|
|
1277
|
+
const order = (0, import_react17.useMemo)(() => items.map((item) => item.id), [items]);
|
|
1278
|
+
(0, import_react17.useEffect)(() => {
|
|
1174
1279
|
const id = focusAfterRef.current;
|
|
1175
1280
|
if (!id) return;
|
|
1176
1281
|
focusAfterRef.current = null;
|
|
1177
1282
|
const el = triggerRefs.current.get(id);
|
|
1178
1283
|
if (el) el.focus();
|
|
1179
1284
|
else headerRef.current?.focus();
|
|
1180
|
-
}, [order
|
|
1285
|
+
}, [order]);
|
|
1181
1286
|
const handleToggle = () => {
|
|
1182
1287
|
if (!collapsed) {
|
|
1183
1288
|
const active = document.activeElement;
|
|
@@ -1187,7 +1292,7 @@ function NavFavourites({
|
|
|
1187
1292
|
}
|
|
1188
1293
|
onToggleCollapsed?.();
|
|
1189
1294
|
};
|
|
1190
|
-
const move = (id, dir) => {
|
|
1295
|
+
const move = (0, import_react17.useCallback)((id, dir) => {
|
|
1191
1296
|
const idx = order.indexOf(id);
|
|
1192
1297
|
const next = idx + dir;
|
|
1193
1298
|
if (idx < 0 || next < 0 || next >= order.length) return;
|
|
@@ -1197,14 +1302,18 @@ function NavFavourites({
|
|
|
1197
1302
|
reordered.splice(next, 0, it);
|
|
1198
1303
|
focusAfterRef.current = id;
|
|
1199
1304
|
onReorder(reordered);
|
|
1200
|
-
};
|
|
1201
|
-
const remove = (id) => {
|
|
1305
|
+
}, [onReorder, order]);
|
|
1306
|
+
const remove = (0, import_react17.useCallback)((id) => {
|
|
1202
1307
|
const idx = order.indexOf(id);
|
|
1203
1308
|
const neighbour = order[idx + 1] ?? order[idx - 1] ?? null;
|
|
1204
1309
|
focusAfterRef.current = neighbour;
|
|
1205
1310
|
onRemove(id);
|
|
1206
|
-
};
|
|
1207
|
-
|
|
1311
|
+
}, [onRemove, order]);
|
|
1312
|
+
const registerTrigger = (0, import_react17.useCallback)((id, element) => {
|
|
1313
|
+
if (element) triggerRefs.current.set(id, element);
|
|
1314
|
+
else triggerRefs.current.delete(id);
|
|
1315
|
+
}, []);
|
|
1316
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: cx("uix-favourites", className), children: [
|
|
1208
1317
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
1209
1318
|
"button",
|
|
1210
1319
|
{
|
|
@@ -1213,18 +1322,7 @@ function NavFavourites({
|
|
|
1213
1322
|
"aria-expanded": !collapsed,
|
|
1214
1323
|
"aria-controls": regionId,
|
|
1215
1324
|
onClick: handleToggle,
|
|
1216
|
-
className: "uix-text-eyebrow",
|
|
1217
|
-
style: {
|
|
1218
|
-
display: "flex",
|
|
1219
|
-
width: "100%",
|
|
1220
|
-
alignItems: "center",
|
|
1221
|
-
gap: "6px",
|
|
1222
|
-
padding: "8px 4px 4px",
|
|
1223
|
-
border: 0,
|
|
1224
|
-
background: "transparent",
|
|
1225
|
-
cursor: "pointer",
|
|
1226
|
-
color: "inherit"
|
|
1227
|
-
},
|
|
1325
|
+
className: "uix-favourites__heading uix-text-eyebrow",
|
|
1228
1326
|
children: [
|
|
1229
1327
|
collapsed ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ChevronRightIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ChevronDownIcon, {}),
|
|
1230
1328
|
labels.heading
|
|
@@ -1237,20 +1335,12 @@ function NavFavourites({
|
|
|
1237
1335
|
ref: regionRef,
|
|
1238
1336
|
id: regionId,
|
|
1239
1337
|
hidden: collapsed,
|
|
1240
|
-
|
|
1241
|
-
display: "flex",
|
|
1242
|
-
flexDirection: "column",
|
|
1243
|
-
gap: "1px",
|
|
1244
|
-
listStyle: "none",
|
|
1245
|
-
margin: 0,
|
|
1246
|
-
padding: 0
|
|
1247
|
-
},
|
|
1338
|
+
className: "uix-favourites__list",
|
|
1248
1339
|
children: items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1249
1340
|
"li",
|
|
1250
1341
|
{
|
|
1251
|
-
className: "uix-text-meta",
|
|
1342
|
+
className: "uix-favourites__empty uix-text-meta",
|
|
1252
1343
|
"data-empty-reason": emptyReason,
|
|
1253
|
-
style: { padding: "6px 8px", color: MUTED },
|
|
1254
1344
|
children: labels.empty
|
|
1255
1345
|
}
|
|
1256
1346
|
) : items.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
@@ -1260,10 +1350,9 @@ function NavFavourites({
|
|
|
1260
1350
|
isFirst: idx === 0,
|
|
1261
1351
|
isLast: idx === items.length - 1,
|
|
1262
1352
|
labels,
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
registerTrigger: (el) => triggerRefs.current.set(item.id, el)
|
|
1353
|
+
onMove: move,
|
|
1354
|
+
onRemove: remove,
|
|
1355
|
+
registerTrigger
|
|
1267
1356
|
},
|
|
1268
1357
|
item.id
|
|
1269
1358
|
))
|
|
@@ -1271,28 +1360,26 @@ function NavFavourites({
|
|
|
1271
1360
|
)
|
|
1272
1361
|
] });
|
|
1273
1362
|
}
|
|
1274
|
-
|
|
1363
|
+
var FavouriteRow = (0, import_react17.memo)(function FavouriteRow2({
|
|
1275
1364
|
item,
|
|
1276
1365
|
isFirst,
|
|
1277
1366
|
isLast,
|
|
1278
1367
|
labels,
|
|
1279
|
-
|
|
1280
|
-
onMoveDown,
|
|
1368
|
+
onMove,
|
|
1281
1369
|
onRemove,
|
|
1282
1370
|
registerTrigger
|
|
1283
1371
|
}) {
|
|
1284
|
-
const [open, setOpen] = (0,
|
|
1285
|
-
const
|
|
1286
|
-
const
|
|
1287
|
-
const
|
|
1288
|
-
const itemRefs = (0, import_react16.useRef)([]);
|
|
1372
|
+
const [open, setOpen] = (0, import_react17.useState)(false);
|
|
1373
|
+
const menuId = (0, import_react17.useId)();
|
|
1374
|
+
const triggerRef = (0, import_react17.useRef)(null);
|
|
1375
|
+
const itemRefs = (0, import_react17.useRef)([]);
|
|
1289
1376
|
const optionsTemplate = labels.options ?? "{label} favourite options";
|
|
1290
1377
|
const actions = [
|
|
1291
|
-
...isFirst ? [] : [{ label: labels.moveUp, onSelect:
|
|
1292
|
-
...isLast ? [] : [{ label: labels.moveDown, onSelect:
|
|
1293
|
-
{ label: labels.remove.replace("{label}", item.label), onSelect: onRemove }
|
|
1378
|
+
...isFirst ? [] : [{ label: labels.moveUp, onSelect: () => onMove(item.id, -1) }],
|
|
1379
|
+
...isLast ? [] : [{ label: labels.moveDown, onSelect: () => onMove(item.id, 1) }],
|
|
1380
|
+
{ label: labels.remove.replace("{label}", item.label), onSelect: () => onRemove(item.id) }
|
|
1294
1381
|
];
|
|
1295
|
-
(0,
|
|
1382
|
+
(0, import_react17.useEffect)(() => {
|
|
1296
1383
|
if (open) itemRefs.current[0]?.focus();
|
|
1297
1384
|
}, [open]);
|
|
1298
1385
|
const closeAndReturnFocus = () => {
|
|
@@ -1319,34 +1406,27 @@ function FavouriteRow({
|
|
|
1319
1406
|
if (!e.altKey) return;
|
|
1320
1407
|
if (e.key === "ArrowUp" && !isFirst) {
|
|
1321
1408
|
e.preventDefault();
|
|
1322
|
-
|
|
1409
|
+
onMove(item.id, -1);
|
|
1323
1410
|
} else if (e.key === "ArrowDown" && !isLast) {
|
|
1324
1411
|
e.preventDefault();
|
|
1325
|
-
|
|
1412
|
+
onMove(item.id, 1);
|
|
1326
1413
|
}
|
|
1327
1414
|
};
|
|
1328
|
-
const triggerVisible = revealed || open;
|
|
1329
1415
|
return (
|
|
1330
1416
|
// biome-ignore lint/a11y/noStaticElementInteractions: keyboard accelerators on the row; the focusable controls inside carry their own semantics.
|
|
1331
1417
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
1332
1418
|
"li",
|
|
1333
1419
|
{
|
|
1334
|
-
|
|
1420
|
+
className: "uix-favourites__row",
|
|
1335
1421
|
onKeyDown: onRowKeyDown,
|
|
1336
|
-
onMouseEnter: () => setRevealed(true),
|
|
1337
|
-
onMouseLeave: () => setRevealed(false),
|
|
1338
|
-
onFocus: () => setRevealed(true),
|
|
1339
|
-
onBlur: (e) => {
|
|
1340
|
-
if (!e.currentTarget.contains(e.relatedTarget)) setRevealed(false);
|
|
1341
|
-
},
|
|
1342
1422
|
children: [
|
|
1343
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", {
|
|
1423
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "uix-favourites__link", children: item.link }),
|
|
1344
1424
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1345
1425
|
"button",
|
|
1346
1426
|
{
|
|
1347
1427
|
ref: (el) => {
|
|
1348
1428
|
triggerRef.current = el;
|
|
1349
|
-
registerTrigger(el);
|
|
1429
|
+
registerTrigger(item.id, el);
|
|
1350
1430
|
},
|
|
1351
1431
|
type: "button",
|
|
1352
1432
|
"aria-haspopup": "menu",
|
|
@@ -1354,22 +1434,7 @@ function FavouriteRow({
|
|
|
1354
1434
|
"aria-controls": open ? menuId : void 0,
|
|
1355
1435
|
"aria-label": optionsTemplate.replace("{label}", item.label),
|
|
1356
1436
|
onClick: () => setOpen((v) => !v),
|
|
1357
|
-
|
|
1358
|
-
marginLeft: "auto",
|
|
1359
|
-
display: "inline-flex",
|
|
1360
|
-
height: "20px",
|
|
1361
|
-
width: "20px",
|
|
1362
|
-
flexShrink: 0,
|
|
1363
|
-
alignItems: "center",
|
|
1364
|
-
justifyContent: "center",
|
|
1365
|
-
border: 0,
|
|
1366
|
-
borderRadius: "2px",
|
|
1367
|
-
background: "transparent",
|
|
1368
|
-
cursor: "pointer",
|
|
1369
|
-
color: MUTED,
|
|
1370
|
-
opacity: triggerVisible ? 1 : 0,
|
|
1371
|
-
transition: "opacity var(--uix-dur-fast) var(--uix-ease-out)"
|
|
1372
|
-
},
|
|
1437
|
+
className: "uix-favourites__menu-trigger",
|
|
1373
1438
|
children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(MoreIcon, {})
|
|
1374
1439
|
}
|
|
1375
1440
|
),
|
|
@@ -1380,7 +1445,7 @@ function FavouriteRow({
|
|
|
1380
1445
|
"aria-hidden": "true",
|
|
1381
1446
|
role: "presentation",
|
|
1382
1447
|
onClick: () => setOpen(false),
|
|
1383
|
-
|
|
1448
|
+
className: "uix-favourites__backdrop"
|
|
1384
1449
|
}
|
|
1385
1450
|
),
|
|
1386
1451
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
@@ -1389,21 +1454,7 @@ function FavouriteRow({
|
|
|
1389
1454
|
id: menuId,
|
|
1390
1455
|
role: "menu",
|
|
1391
1456
|
onKeyDown: onMenuKeyDown,
|
|
1392
|
-
|
|
1393
|
-
position: "absolute",
|
|
1394
|
-
right: 0,
|
|
1395
|
-
top: "calc(100% + 2px)",
|
|
1396
|
-
zIndex: 50,
|
|
1397
|
-
minWidth: "160px",
|
|
1398
|
-
overflow: "hidden",
|
|
1399
|
-
margin: 0,
|
|
1400
|
-
padding: "4px 0",
|
|
1401
|
-
listStyle: "none",
|
|
1402
|
-
borderRadius: "6px",
|
|
1403
|
-
border: "1px solid var(--uix-border)",
|
|
1404
|
-
background: "var(--uix-surface)",
|
|
1405
|
-
boxShadow: "var(--uix-shadow-popover)"
|
|
1406
|
-
},
|
|
1457
|
+
className: "uix-favourites__menu",
|
|
1407
1458
|
children: actions.map((action, i) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { role: "presentation", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1408
1459
|
"button",
|
|
1409
1460
|
{
|
|
@@ -1417,25 +1468,7 @@ function FavouriteRow({
|
|
|
1417
1468
|
setOpen(false);
|
|
1418
1469
|
action.onSelect();
|
|
1419
1470
|
},
|
|
1420
|
-
|
|
1421
|
-
display: "flex",
|
|
1422
|
-
width: "100%",
|
|
1423
|
-
alignItems: "center",
|
|
1424
|
-
padding: "6px 12px",
|
|
1425
|
-
border: 0,
|
|
1426
|
-
background: "transparent",
|
|
1427
|
-
cursor: "pointer",
|
|
1428
|
-
textAlign: "left",
|
|
1429
|
-
fontSize: "13px",
|
|
1430
|
-
color: "color-mix(in srgb, var(--uix-text) 85%, transparent)",
|
|
1431
|
-
transition: "background-color var(--uix-dur-fast) var(--uix-ease-out)"
|
|
1432
|
-
},
|
|
1433
|
-
onMouseEnter: (e) => {
|
|
1434
|
-
e.currentTarget.style.background = "var(--uix-bg-hover)";
|
|
1435
|
-
},
|
|
1436
|
-
onMouseLeave: (e) => {
|
|
1437
|
-
e.currentTarget.style.background = "transparent";
|
|
1438
|
-
},
|
|
1471
|
+
className: "uix-favourites__menu-item",
|
|
1439
1472
|
children: action.label
|
|
1440
1473
|
}
|
|
1441
1474
|
) }, action.label))
|
|
@@ -1446,7 +1479,7 @@ function FavouriteRow({
|
|
|
1446
1479
|
}
|
|
1447
1480
|
)
|
|
1448
1481
|
);
|
|
1449
|
-
}
|
|
1482
|
+
});
|
|
1450
1483
|
|
|
1451
1484
|
// src/components/Alert.tsx
|
|
1452
1485
|
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
@@ -1502,10 +1535,11 @@ function Toaster({ children, className, ...props }) {
|
|
|
1502
1535
|
}
|
|
1503
1536
|
|
|
1504
1537
|
// src/components/Table.tsx
|
|
1538
|
+
var import_react18 = require("react");
|
|
1505
1539
|
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
1506
|
-
|
|
1507
|
-
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: cx("uix-table-wrap", className), ...props, children });
|
|
1508
|
-
}
|
|
1540
|
+
var TableWrap = (0, import_react18.forwardRef)(function TableWrap2({ children, className, ...props }, ref) {
|
|
1541
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { ref, className: cx("uix-table-wrap", className), ...props, children });
|
|
1542
|
+
});
|
|
1509
1543
|
function Table({
|
|
1510
1544
|
density = "standard",
|
|
1511
1545
|
zebra = true,
|
|
@@ -1735,13 +1769,13 @@ function Stat({ label, value, icon, meta, trend, className, ...props }) {
|
|
|
1735
1769
|
}
|
|
1736
1770
|
|
|
1737
1771
|
// src/components/Tooltip.tsx
|
|
1738
|
-
var
|
|
1772
|
+
var import_react19 = require("react");
|
|
1739
1773
|
var import_jsx_runtime31 = require("react/jsx-runtime");
|
|
1740
1774
|
function Tooltip({ label, placement = "top", className, children, ...props }) {
|
|
1741
|
-
const triggerRef = (0,
|
|
1742
|
-
const bubbleRef = (0,
|
|
1743
|
-
const [open, setOpen] = (0,
|
|
1744
|
-
const id = (0,
|
|
1775
|
+
const triggerRef = (0, import_react19.useRef)(null);
|
|
1776
|
+
const bubbleRef = (0, import_react19.useRef)(null);
|
|
1777
|
+
const [open, setOpen] = (0, import_react19.useState)(false);
|
|
1778
|
+
const id = (0, import_react19.useId)();
|
|
1745
1779
|
useAnchoredPosition(triggerRef, bubbleRef, { open, placement, offset: 6 });
|
|
1746
1780
|
const show = () => {
|
|
1747
1781
|
const b = bubbleRef.current;
|
|
@@ -1763,7 +1797,7 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
|
|
|
1763
1797
|
}
|
|
1764
1798
|
setOpen(false);
|
|
1765
1799
|
};
|
|
1766
|
-
(0,
|
|
1800
|
+
(0, import_react19.useEffect)(() => () => {
|
|
1767
1801
|
const b = bubbleRef.current;
|
|
1768
1802
|
if (b && b.matches(":popover-open")) {
|
|
1769
1803
|
try {
|
|
@@ -1894,11 +1928,12 @@ function ComposerBar({ children, className, ...props }) {
|
|
|
1894
1928
|
}
|
|
1895
1929
|
|
|
1896
1930
|
// src/components/Segmented.tsx
|
|
1897
|
-
var
|
|
1931
|
+
var import_react20 = require("react");
|
|
1898
1932
|
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
1899
|
-
var SegmentedCtx = (0,
|
|
1933
|
+
var SegmentedCtx = (0, import_react20.createContext)({});
|
|
1900
1934
|
function Segmented({ value, onChange, children, className, ...props }) {
|
|
1901
|
-
|
|
1935
|
+
const contextValue = (0, import_react20.useMemo)(() => ({ value, onChange }), [value, onChange]);
|
|
1936
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(SegmentedCtx.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { role: "group", className: cx("uix-segmented", className), ...props, children }) });
|
|
1902
1937
|
}
|
|
1903
1938
|
function SegmentedOption({
|
|
1904
1939
|
value,
|
|
@@ -1907,7 +1942,7 @@ function SegmentedOption({
|
|
|
1907
1942
|
onClick,
|
|
1908
1943
|
...props
|
|
1909
1944
|
}) {
|
|
1910
|
-
const ctx = (0,
|
|
1945
|
+
const ctx = (0, import_react20.useContext)(SegmentedCtx);
|
|
1911
1946
|
const selected = ctx.value === value;
|
|
1912
1947
|
return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
1913
1948
|
"button",
|
|
@@ -1956,7 +1991,7 @@ function Note({ tone, icon, children, className, ...props }) {
|
|
|
1956
1991
|
}
|
|
1957
1992
|
|
|
1958
1993
|
// src/components/Popover.tsx
|
|
1959
|
-
var
|
|
1994
|
+
var import_react21 = require("react");
|
|
1960
1995
|
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
1961
1996
|
function Popover({
|
|
1962
1997
|
popover = "auto",
|
|
@@ -1967,15 +2002,15 @@ function Popover({
|
|
|
1967
2002
|
children,
|
|
1968
2003
|
...props
|
|
1969
2004
|
}) {
|
|
1970
|
-
const ref = (0,
|
|
1971
|
-
const [open, setOpen] = (0,
|
|
2005
|
+
const ref = (0, import_react21.useRef)(null);
|
|
2006
|
+
const [open, setOpen] = (0, import_react21.useState)(false);
|
|
1972
2007
|
const popoverAttr = { popover };
|
|
1973
2008
|
const reposition = useAnchoredPosition(anchor ?? null, ref, {
|
|
1974
2009
|
open: open && !!anchor,
|
|
1975
2010
|
placement,
|
|
1976
2011
|
offset
|
|
1977
2012
|
});
|
|
1978
|
-
(0,
|
|
2013
|
+
(0, import_react21.useEffect)(() => {
|
|
1979
2014
|
const el = ref.current;
|
|
1980
2015
|
if (!el || !anchor) return;
|
|
1981
2016
|
const sync = () => {
|
|
@@ -2073,7 +2108,7 @@ function KanbanCard({ title, meta, children, className, ...props }) {
|
|
|
2073
2108
|
}
|
|
2074
2109
|
|
|
2075
2110
|
// src/components/Tree.tsx
|
|
2076
|
-
var
|
|
2111
|
+
var import_react22 = require("react");
|
|
2077
2112
|
|
|
2078
2113
|
// src/tree-model.ts
|
|
2079
2114
|
function flattenTree(nodes, expanded, level = 1) {
|
|
@@ -2185,12 +2220,12 @@ function Tree({
|
|
|
2185
2220
|
className,
|
|
2186
2221
|
...props
|
|
2187
2222
|
}) {
|
|
2188
|
-
const [internalExpanded, setInternalExpanded] = (0,
|
|
2223
|
+
const [internalExpanded, setInternalExpanded] = (0, import_react22.useState)(defaultExpanded ?? /* @__PURE__ */ new Set());
|
|
2189
2224
|
const expanded = controlledExpanded ?? internalExpanded;
|
|
2190
|
-
const rootRef = (0,
|
|
2191
|
-
const [focusedId, setFocusedId] = (0,
|
|
2225
|
+
const rootRef = (0, import_react22.useRef)(null);
|
|
2226
|
+
const [focusedId, setFocusedId] = (0, import_react22.useState)(void 0);
|
|
2192
2227
|
const selectable = selected !== void 0 || onSelect != null;
|
|
2193
|
-
const handleToggle = (0,
|
|
2228
|
+
const handleToggle = (0, import_react22.useCallback)((id) => {
|
|
2194
2229
|
if (!controlledExpanded) {
|
|
2195
2230
|
setInternalExpanded((prev) => {
|
|
2196
2231
|
const next = new Set(prev);
|
|
@@ -2201,7 +2236,7 @@ function Tree({
|
|
|
2201
2236
|
}
|
|
2202
2237
|
onToggle?.(id);
|
|
2203
2238
|
}, [controlledExpanded, onToggle]);
|
|
2204
|
-
const flat = (0,
|
|
2239
|
+
const flat = (0, import_react22.useMemo)(() => flattenTree(nodes, expanded), [nodes, expanded]);
|
|
2205
2240
|
const useVirtual = virtualize ?? shouldVirtualize(flat.length);
|
|
2206
2241
|
const tabbableId = focusedId ?? selected ?? nodes[0]?.id;
|
|
2207
2242
|
if (useVirtual) {
|
|
@@ -2305,12 +2340,12 @@ function VirtualTreeView({
|
|
|
2305
2340
|
className,
|
|
2306
2341
|
...props
|
|
2307
2342
|
}) {
|
|
2308
|
-
const scrollRef = (0,
|
|
2309
|
-
const [scrollTop, setScrollTop] = (0,
|
|
2310
|
-
const pendingFocus = (0,
|
|
2343
|
+
const scrollRef = (0, import_react22.useRef)(null);
|
|
2344
|
+
const [scrollTop, setScrollTop] = (0, import_react22.useState)(0);
|
|
2345
|
+
const pendingFocus = (0, import_react22.useRef)(false);
|
|
2311
2346
|
const win = virtualWindow(scrollTop, maxHeight, rowHeight, flat.length);
|
|
2312
2347
|
const rows = flat.slice(win.start, win.end);
|
|
2313
|
-
(0,
|
|
2348
|
+
(0, import_react22.useEffect)(() => {
|
|
2314
2349
|
if (!pendingFocus.current || focusedId == null) return;
|
|
2315
2350
|
const el = scrollRef.current?.querySelector(`[role="treeitem"][data-id="${CSS.escape(focusedId)}"]`);
|
|
2316
2351
|
if (el) {
|
|
@@ -2565,12 +2600,1664 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
|
|
|
2565
2600
|
meta != null && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "uix-list-meta", children: meta })
|
|
2566
2601
|
] });
|
|
2567
2602
|
}
|
|
2603
|
+
|
|
2604
|
+
// src/rule-builder-model.ts
|
|
2605
|
+
function isRuleGroup(node) {
|
|
2606
|
+
return "conditions" in node;
|
|
2607
|
+
}
|
|
2608
|
+
function ruleDepth(group) {
|
|
2609
|
+
return 1 + Math.max(0, ...group.conditions.filter(isRuleGroup).map(ruleDepth));
|
|
2610
|
+
}
|
|
2611
|
+
function findRuleNodeDepth(group, id, depth = 1) {
|
|
2612
|
+
if (group.id === id) return depth;
|
|
2613
|
+
for (const node of group.conditions) {
|
|
2614
|
+
if (node.id === id) return depth + 1;
|
|
2615
|
+
if (isRuleGroup(node)) {
|
|
2616
|
+
const found = findRuleNodeDepth(node, id, depth + 1);
|
|
2617
|
+
if (found !== void 0) return found;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
return void 0;
|
|
2621
|
+
}
|
|
2622
|
+
function mapRuleGroup(group, id, update) {
|
|
2623
|
+
if (group.id === id) return update(group);
|
|
2624
|
+
return {
|
|
2625
|
+
...group,
|
|
2626
|
+
conditions: group.conditions.map((node) => {
|
|
2627
|
+
if (node.id === id) return update(node);
|
|
2628
|
+
return isRuleGroup(node) ? mapRuleGroup(node, id, update) : node;
|
|
2629
|
+
})
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
function appendRuleNode(group, parentId, node, maxDepth = 3) {
|
|
2633
|
+
const parentDepth = findRuleNodeDepth(group, parentId);
|
|
2634
|
+
if (parentDepth === void 0) return group;
|
|
2635
|
+
const addedDepth = isRuleGroup(node) ? ruleDepth(node) : 0;
|
|
2636
|
+
if (parentDepth + addedDepth > maxDepth) return group;
|
|
2637
|
+
return mapRuleGroup(group, parentId, (parent) => isRuleGroup(parent) ? { ...parent, conditions: [...parent.conditions, node] } : parent);
|
|
2638
|
+
}
|
|
2639
|
+
function removeRuleNode(group, id) {
|
|
2640
|
+
return {
|
|
2641
|
+
...group,
|
|
2642
|
+
conditions: group.conditions.filter((node) => node.id !== id).map((node) => isRuleGroup(node) ? removeRuleNode(node, id) : node)
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
function moveRuleNode(group, id, direction) {
|
|
2646
|
+
const index = group.conditions.findIndex((node) => node.id === id);
|
|
2647
|
+
if (index >= 0) {
|
|
2648
|
+
const target = index + direction;
|
|
2649
|
+
if (target < 0 || target >= group.conditions.length) return group;
|
|
2650
|
+
const conditions = [...group.conditions];
|
|
2651
|
+
[conditions[index], conditions[target]] = [conditions[target], conditions[index]];
|
|
2652
|
+
return { ...group, conditions };
|
|
2653
|
+
}
|
|
2654
|
+
return {
|
|
2655
|
+
...group,
|
|
2656
|
+
conditions: group.conditions.map((node) => isRuleGroup(node) ? moveRuleNode(node, id, direction) : node)
|
|
2657
|
+
};
|
|
2658
|
+
}
|
|
2659
|
+
function validateRuleDefinition(value, maxDepth = 3) {
|
|
2660
|
+
const issues = [];
|
|
2661
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2662
|
+
const visit = (group, path, depth) => {
|
|
2663
|
+
if (ids.has(group.id)) issues.push({ path, message: `Duplicate id \u201C${group.id}\u201D.` });
|
|
2664
|
+
ids.add(group.id);
|
|
2665
|
+
if (depth > maxDepth) issues.push({ path, message: `Nesting exceeds ${maxDepth} levels.` });
|
|
2666
|
+
if (group.conditions.length === 0) issues.push({ path, message: "Add at least one condition." });
|
|
2667
|
+
group.conditions.forEach((node, index) => {
|
|
2668
|
+
const nodePath = `${path}.conditions[${index}]`;
|
|
2669
|
+
if (ids.has(node.id)) issues.push({ path: nodePath, message: `Duplicate id \u201C${node.id}\u201D.` });
|
|
2670
|
+
if (isRuleGroup(node)) visit(node, nodePath, depth + 1);
|
|
2671
|
+
else {
|
|
2672
|
+
ids.add(node.id);
|
|
2673
|
+
if (!node.field) issues.push({ path: nodePath, message: "Choose a field." });
|
|
2674
|
+
if (!node.operator) issues.push({ path: nodePath, message: "Choose an operator." });
|
|
2675
|
+
}
|
|
2676
|
+
});
|
|
2677
|
+
};
|
|
2678
|
+
visit(value.when, "when", 1);
|
|
2679
|
+
if (value.then.length === 0) issues.push({ path: "then", message: "Add at least one action." });
|
|
2680
|
+
value.then.forEach((action, index) => {
|
|
2681
|
+
if (ids.has(action.id)) issues.push({ path: `then[${index}]`, message: `Duplicate id \u201C${action.id}\u201D.` });
|
|
2682
|
+
ids.add(action.id);
|
|
2683
|
+
if (!action.type) issues.push({ path: `then[${index}]`, message: "Choose an action." });
|
|
2684
|
+
});
|
|
2685
|
+
return issues;
|
|
2686
|
+
}
|
|
2687
|
+
function summarizeRule(value, labels = {}) {
|
|
2688
|
+
const formatValue = (input) => input === void 0 ? "" : ` ${JSON.stringify(input)}`;
|
|
2689
|
+
const formatGroup = (group) => group.conditions.map(
|
|
2690
|
+
(node) => isRuleGroup(node) ? `(${formatGroup(node)})` : `${labels.fields?.[node.field] ?? node.field} ${labels.operators?.[node.operator] ?? node.operator}${formatValue(node.value)}`
|
|
2691
|
+
).join(` ${group.combinator.toUpperCase()} `);
|
|
2692
|
+
const actions = value.then.map((action) => labels.actions?.[action.type] ?? action.type).join(", ");
|
|
2693
|
+
return `When ${formatGroup(value.when) || "no conditions"}, then ${actions || "no actions"}.`;
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
// src/components/RuleBuilder.tsx
|
|
2697
|
+
var import_jsx_runtime46 = require("react/jsx-runtime");
|
|
2698
|
+
var ruleId = 0;
|
|
2699
|
+
var nextId = (prefix) => `${prefix}-${Date.now().toString(36)}-${(++ruleId).toString(36)}`;
|
|
2700
|
+
function defaultCondition(fields, operators) {
|
|
2701
|
+
const field = fields[0];
|
|
2702
|
+
const operator = operators.find((item) => field?.operators?.includes(item.id)) ?? operators[0];
|
|
2703
|
+
return { id: nextId("condition"), field: field?.id ?? "", operator: operator?.id ?? "", value: "" };
|
|
2704
|
+
}
|
|
2705
|
+
function RuleGroupEditor({ group, depth, root, fields, operators, maxDepth, disabled, onRootChange, labels }) {
|
|
2706
|
+
const updateNode = (id, update) => onRootChange(mapRuleGroup(root, id, update));
|
|
2707
|
+
const addCondition = () => onRootChange(appendRuleNode(root, group.id, defaultCondition(fields, operators), maxDepth));
|
|
2708
|
+
const addGroup = () => onRootChange(appendRuleNode(root, group.id, {
|
|
2709
|
+
id: nextId("group"),
|
|
2710
|
+
combinator: "and",
|
|
2711
|
+
conditions: [defaultCondition(fields, operators)]
|
|
2712
|
+
}, maxDepth));
|
|
2713
|
+
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("fieldset", { className: "uix-rule-builder__group", "data-depth": depth, children: [
|
|
2714
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("legend", { className: "uix-rule-builder__group-heading", children: [
|
|
2715
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: depth === 1 ? "When" : "Group" }),
|
|
2716
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { children: [
|
|
2717
|
+
"Match",
|
|
2718
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("select", { className: "uix-select uix-select--sm", value: group.combinator, disabled, onChange: (event) => updateNode(group.id, (node) => ({ ...node, combinator: event.currentTarget.value })), children: [
|
|
2719
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("option", { value: "and", children: "all (AND)" }),
|
|
2720
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("option", { value: "or", children: "any (OR)" })
|
|
2721
|
+
] })
|
|
2722
|
+
] })
|
|
2723
|
+
] }),
|
|
2724
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "uix-rule-builder__rows", children: group.conditions.map((node, index) => isRuleGroup(node) ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "uix-rule-builder__nested", children: [
|
|
2725
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(RuleGroupEditor, { ...{ group: node, depth: depth + 1, root, fields, operators, maxDepth, disabled, onRootChange, labels } }),
|
|
2726
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(RowControls, { label: "group", first: index === 0, last: index === group.conditions.length - 1, disabled, onUp: () => onRootChange(moveRuleNode(root, node.id, -1)), onDown: () => onRootChange(moveRuleNode(root, node.id, 1)), onRemove: () => onRootChange(removeRuleNode(root, node.id)) })
|
|
2727
|
+
] }, node.id) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
2728
|
+
ConditionRow,
|
|
2729
|
+
{
|
|
2730
|
+
condition: node,
|
|
2731
|
+
fields,
|
|
2732
|
+
operators,
|
|
2733
|
+
disabled,
|
|
2734
|
+
onChange: (condition) => updateNode(node.id, () => condition),
|
|
2735
|
+
controls: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(RowControls, { label: "condition", first: index === 0, last: index === group.conditions.length - 1, disabled, onUp: () => onRootChange(moveRuleNode(root, node.id, -1)), onDown: () => onRootChange(moveRuleNode(root, node.id, 1)), onRemove: () => onRootChange(removeRuleNode(root, node.id)) })
|
|
2736
|
+
},
|
|
2737
|
+
node.id
|
|
2738
|
+
)) }),
|
|
2739
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "uix-rule-builder__add", children: [
|
|
2740
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", onClick: addCondition, disabled: disabled || fields.length === 0 || operators.length === 0, children: labels.addCondition }),
|
|
2741
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: addGroup, disabled: disabled || depth >= maxDepth, children: labels.addGroup })
|
|
2742
|
+
] })
|
|
2743
|
+
] });
|
|
2744
|
+
}
|
|
2745
|
+
function ConditionRow({ condition, fields, operators, disabled, onChange, controls }) {
|
|
2746
|
+
const field = fields.find((item) => item.id === condition.field);
|
|
2747
|
+
const availableOperators = field?.operators?.length ? operators.filter((item) => field.operators.includes(item.id)) : operators;
|
|
2748
|
+
const operator = operators.find((item) => item.id === condition.operator);
|
|
2749
|
+
const setField = (id) => {
|
|
2750
|
+
const nextField = fields.find((item) => item.id === id);
|
|
2751
|
+
const nextOperator = operators.find((item) => nextField?.operators?.includes(item.id)) ?? availableOperators[0] ?? operators[0];
|
|
2752
|
+
onChange({ ...condition, field: id, operator: nextOperator?.id ?? "" });
|
|
2753
|
+
};
|
|
2754
|
+
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "uix-rule-builder__condition", children: [
|
|
2755
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { children: [
|
|
2756
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "uix-visually-hidden", children: "Field" }),
|
|
2757
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("select", { className: "uix-select", value: condition.field, onChange: (event) => setField(event.currentTarget.value), disabled, children: fields.map((item) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("option", { value: item.id, children: item.label }, item.id)) })
|
|
2758
|
+
] }),
|
|
2759
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("label", { children: [
|
|
2760
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "uix-visually-hidden", children: "Operator" }),
|
|
2761
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("select", { className: "uix-select", value: condition.operator, onChange: (event) => onChange({ ...condition, operator: event.currentTarget.value }), disabled, children: availableOperators.map((item) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("option", { value: item.id, children: item.label }, item.id)) })
|
|
2762
|
+
] }),
|
|
2763
|
+
operator?.requiresValue !== false && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "uix-rule-builder__value", children: field?.renderValueEditor?.({ condition, onChange: (value) => onChange({ ...condition, value }), disabled }) ?? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("input", { className: "uix-input", value: typeof condition.value === "string" || typeof condition.value === "number" ? condition.value : condition.value == null ? "" : JSON.stringify(condition.value), onChange: (event) => onChange({ ...condition, value: event.currentTarget.value }), disabled, "aria-label": `Value for ${field?.label ?? "condition"}` }) }),
|
|
2764
|
+
controls
|
|
2765
|
+
] });
|
|
2766
|
+
}
|
|
2767
|
+
function RowControls({ label, first, last, disabled, onUp, onDown, onRemove }) {
|
|
2768
|
+
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { className: "uix-rule-builder__row-actions", children: [
|
|
2769
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: onUp, disabled: disabled || first, "aria-label": `Move ${label} up`, children: "Up" }),
|
|
2770
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: onDown, disabled: disabled || last, "aria-label": `Move ${label} down`, children: "Down" }),
|
|
2771
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: onRemove, disabled, "aria-label": `Remove ${label}`, children: "Remove" })
|
|
2772
|
+
] });
|
|
2773
|
+
}
|
|
2774
|
+
function RuleBuilder({
|
|
2775
|
+
value,
|
|
2776
|
+
onChange,
|
|
2777
|
+
fields,
|
|
2778
|
+
operators,
|
|
2779
|
+
actions,
|
|
2780
|
+
maxDepth = 3,
|
|
2781
|
+
readOnly = false,
|
|
2782
|
+
validate,
|
|
2783
|
+
className,
|
|
2784
|
+
addConditionLabel = "Add condition",
|
|
2785
|
+
addGroupLabel = "Add group",
|
|
2786
|
+
addActionLabel = "Add action"
|
|
2787
|
+
}) {
|
|
2788
|
+
const issues = [...validateRuleDefinition(value, maxDepth), ...validate?.(value) ?? []];
|
|
2789
|
+
const fieldLabels = Object.fromEntries(fields.map((field) => [field.id, field.label]));
|
|
2790
|
+
const operatorLabels = Object.fromEntries(operators.map((operator) => [operator.id, operator.label]));
|
|
2791
|
+
const actionLabels = Object.fromEntries(actions.map((action) => [action.id, action.label]));
|
|
2792
|
+
if (readOnly) return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("section", { className: cx("uix-rule-builder uix-rule-builder--summary", className), "aria-label": "Rule summary", children: [
|
|
2793
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { children: summarizeRule(value, { fields: fieldLabels, operators: operatorLabels, actions: actionLabels }) }),
|
|
2794
|
+
issues.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("p", { className: "uix-rule-builder__invalid", children: [
|
|
2795
|
+
"Rule has ",
|
|
2796
|
+
issues.length,
|
|
2797
|
+
" validation issue",
|
|
2798
|
+
issues.length === 1 ? "" : "s",
|
|
2799
|
+
"."
|
|
2800
|
+
] })
|
|
2801
|
+
] });
|
|
2802
|
+
const updateActions = (next) => onChange({ ...value, then: next });
|
|
2803
|
+
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("section", { className: cx("uix-rule-builder", className), "aria-label": "Rule builder", children: [
|
|
2804
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(RuleGroupEditor, { group: value.when, depth: 1, root: value.when, fields, operators, maxDepth, disabled: readOnly, onRootChange: (when) => onChange({ ...value, when }), labels: { addCondition: addConditionLabel, addGroup: addGroupLabel } }),
|
|
2805
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("fieldset", { className: "uix-rule-builder__actions", children: [
|
|
2806
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("legend", { children: "Then" }),
|
|
2807
|
+
value.then.map((action, index) => {
|
|
2808
|
+
const definition = actions.find((item) => item.id === action.type);
|
|
2809
|
+
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "uix-rule-builder__action", children: [
|
|
2810
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("select", { className: "uix-select", value: action.type, onChange: (event) => updateActions(value.then.map((item) => item.id === action.id ? { ...item, type: event.currentTarget.value } : item)), "aria-label": "Action type", children: actions.map((item) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("option", { value: item.id, children: item.label }, item.id)) }),
|
|
2811
|
+
definition?.renderParameters?.(action, (parameters) => updateActions(value.then.map((item) => item.id === action.id ? { ...item, parameters } : item)), false),
|
|
2812
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
2813
|
+
RowControls,
|
|
2814
|
+
{
|
|
2815
|
+
label: "action",
|
|
2816
|
+
first: index === 0,
|
|
2817
|
+
last: index === value.then.length - 1,
|
|
2818
|
+
disabled: false,
|
|
2819
|
+
onUp: () => {
|
|
2820
|
+
const next = [...value.then];
|
|
2821
|
+
[next[index - 1], next[index]] = [next[index], next[index - 1]];
|
|
2822
|
+
updateActions(next);
|
|
2823
|
+
},
|
|
2824
|
+
onDown: () => {
|
|
2825
|
+
const next = [...value.then];
|
|
2826
|
+
[next[index + 1], next[index]] = [next[index], next[index + 1]];
|
|
2827
|
+
updateActions(next);
|
|
2828
|
+
},
|
|
2829
|
+
onRemove: () => updateActions(value.then.filter((item) => item.id !== action.id))
|
|
2830
|
+
}
|
|
2831
|
+
)
|
|
2832
|
+
] }, action.id);
|
|
2833
|
+
}),
|
|
2834
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", disabled: actions.length === 0, onClick: () => updateActions([...value.then, { id: nextId("action"), type: actions[0]?.id ?? "", parameters: {} }]), children: addActionLabel })
|
|
2835
|
+
] }),
|
|
2836
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "uix-rule-builder__validation", "aria-live": "polite", children: issues.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: "Rule is valid." }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
|
|
2837
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("strong", { children: [
|
|
2838
|
+
issues.length,
|
|
2839
|
+
" validation issue",
|
|
2840
|
+
issues.length === 1 ? "" : "s"
|
|
2841
|
+
] }),
|
|
2842
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("ul", { children: issues.map((issue, index) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("li", { children: issue.message }, `${issue.path}-${index}`)) })
|
|
2843
|
+
] }) })
|
|
2844
|
+
] });
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
// src/components/BuilderCanvas.tsx
|
|
2848
|
+
var import_react23 = require("react");
|
|
2849
|
+
var import_jsx_runtime47 = require("react/jsx-runtime");
|
|
2850
|
+
function BuilderCanvas({
|
|
2851
|
+
items,
|
|
2852
|
+
onItemsChange,
|
|
2853
|
+
palette,
|
|
2854
|
+
selectedId: controlledSelectedId,
|
|
2855
|
+
onSelect,
|
|
2856
|
+
renderItem,
|
|
2857
|
+
renderProperties,
|
|
2858
|
+
loading,
|
|
2859
|
+
error,
|
|
2860
|
+
onRetry,
|
|
2861
|
+
emptyMessage = "Add an item from the palette to begin.",
|
|
2862
|
+
className
|
|
2863
|
+
}) {
|
|
2864
|
+
const [internalSelectedId, setInternalSelectedId] = (0, import_react23.useState)();
|
|
2865
|
+
const [draggedId, setDraggedId] = (0, import_react23.useState)();
|
|
2866
|
+
const propertyHeadingRef = (0, import_react23.useRef)(null);
|
|
2867
|
+
const shouldFocusProperties = (0, import_react23.useRef)(false);
|
|
2868
|
+
const selectedId = controlledSelectedId ?? internalSelectedId;
|
|
2869
|
+
const selected = items.find((item) => item.id === selectedId);
|
|
2870
|
+
(0, import_react23.useEffect)(() => {
|
|
2871
|
+
if (shouldFocusProperties.current && selected) {
|
|
2872
|
+
propertyHeadingRef.current?.focus();
|
|
2873
|
+
shouldFocusProperties.current = false;
|
|
2874
|
+
}
|
|
2875
|
+
}, [selected]);
|
|
2876
|
+
const select = (id, focusProperties = true) => {
|
|
2877
|
+
shouldFocusProperties.current = focusProperties;
|
|
2878
|
+
if (controlledSelectedId === void 0) setInternalSelectedId(id);
|
|
2879
|
+
onSelect?.(id);
|
|
2880
|
+
};
|
|
2881
|
+
const move = (id, direction) => {
|
|
2882
|
+
const index = items.findIndex((item) => item.id === id);
|
|
2883
|
+
const target = index + direction;
|
|
2884
|
+
if (index < 0 || target < 0 || target >= items.length) return;
|
|
2885
|
+
onItemsChange(reorder(items, index, target));
|
|
2886
|
+
};
|
|
2887
|
+
const add = (paletteItem) => {
|
|
2888
|
+
const item = paletteItem.create();
|
|
2889
|
+
onItemsChange([...items, item]);
|
|
2890
|
+
select(item.id);
|
|
2891
|
+
};
|
|
2892
|
+
const remove = (id) => {
|
|
2893
|
+
onItemsChange(items.filter((item) => item.id !== id));
|
|
2894
|
+
if (selectedId === id) select(void 0, false);
|
|
2895
|
+
};
|
|
2896
|
+
const drop = (event, targetId) => {
|
|
2897
|
+
event.preventDefault();
|
|
2898
|
+
if (!draggedId || draggedId === targetId) return;
|
|
2899
|
+
const from = items.findIndex((item) => item.id === draggedId);
|
|
2900
|
+
const to = items.findIndex((item) => item.id === targetId);
|
|
2901
|
+
if (from >= 0 && to >= 0) onItemsChange(reorder(items, from, to));
|
|
2902
|
+
setDraggedId(void 0);
|
|
2903
|
+
};
|
|
2904
|
+
return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("section", { className: cx("uix-builder-canvas", className), "aria-label": "Builder canvas", children: [
|
|
2905
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("aside", { className: "uix-builder-canvas__palette", "aria-label": "Item palette", children: [
|
|
2906
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("h3", { children: "Palette" }),
|
|
2907
|
+
palette.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { children: "No item types available." }) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("ul", { children: palette.map((item) => /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("button", { type: "button", onClick: () => add(item), children: [
|
|
2908
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("strong", { children: item.label }),
|
|
2909
|
+
item.description && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { children: item.description }),
|
|
2910
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { "aria-hidden": "true", children: "Add" })
|
|
2911
|
+
] }) }, item.id)) })
|
|
2912
|
+
] }),
|
|
2913
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "uix-builder-canvas__workspace", children: [
|
|
2914
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("h3", { children: "Canvas" }),
|
|
2915
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { className: "uix-builder-canvas__state", role: "status", children: "Loading canvas\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "uix-builder-canvas__state", role: "alert", children: [
|
|
2916
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { children: error }),
|
|
2917
|
+
onRetry && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
|
|
2918
|
+
] }) : items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "uix-builder-canvas__state", children: [
|
|
2919
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { children: emptyMessage }),
|
|
2920
|
+
palette[0] && /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("button", { type: "button", className: "uix-btn uix-btn--primary", onClick: () => add(palette[0]), children: [
|
|
2921
|
+
"Add ",
|
|
2922
|
+
palette[0].label
|
|
2923
|
+
] })
|
|
2924
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("ol", { className: "uix-builder-canvas__items", children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
|
|
2925
|
+
"li",
|
|
2926
|
+
{
|
|
2927
|
+
draggable: true,
|
|
2928
|
+
onDragStart: () => setDraggedId(item.id),
|
|
2929
|
+
onDragEnd: () => setDraggedId(void 0),
|
|
2930
|
+
onDragOver: (event) => event.preventDefault(),
|
|
2931
|
+
onDrop: (event) => drop(event, item.id),
|
|
2932
|
+
className: cx("uix-builder-canvas__item", selectedId === item.id && "uix-builder-canvas__item--selected"),
|
|
2933
|
+
children: [
|
|
2934
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("button", { type: "button", className: "uix-builder-canvas__item-main", "aria-pressed": selectedId === item.id, onClick: () => select(item.id), children: renderItem?.(item, selectedId === item.id) ?? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(import_jsx_runtime47.Fragment, { children: [
|
|
2935
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("strong", { children: item.label }),
|
|
2936
|
+
item.description && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { children: item.description })
|
|
2937
|
+
] }) }),
|
|
2938
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("span", { className: "uix-builder-canvas__item-actions", children: [
|
|
2939
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("button", { type: "button", onClick: () => move(item.id, -1), disabled: index === 0, "aria-label": `Move ${item.label} up`, children: "Up" }),
|
|
2940
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("button", { type: "button", onClick: () => move(item.id, 1), disabled: index === items.length - 1, "aria-label": `Move ${item.label} down`, children: "Down" }),
|
|
2941
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("button", { type: "button", onClick: () => remove(item.id), "aria-label": `Remove ${item.label}`, children: "Remove" })
|
|
2942
|
+
] })
|
|
2943
|
+
]
|
|
2944
|
+
},
|
|
2945
|
+
item.id
|
|
2946
|
+
)) })
|
|
2947
|
+
] }),
|
|
2948
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("aside", { className: "uix-builder-canvas__properties", "aria-label": "Item properties", children: [
|
|
2949
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("h3", { ref: propertyHeadingRef, tabIndex: -1, children: "Properties" }),
|
|
2950
|
+
!selected ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { children: "Select a canvas item to edit its properties." }) : renderProperties?.(selected, (updated) => onItemsChange(items.map((item) => item.id === updated.id ? updated : item))) ?? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("dl", { children: [
|
|
2951
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("dt", { children: "Type" }),
|
|
2952
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("dd", { children: selected.type }),
|
|
2953
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("dt", { children: "Label" }),
|
|
2954
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("dd", { children: selected.label })
|
|
2955
|
+
] })
|
|
2956
|
+
] })
|
|
2957
|
+
] });
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
// src/components/SchedulingCalendar.tsx
|
|
2961
|
+
var import_react24 = require("react");
|
|
2962
|
+
|
|
2963
|
+
// src/calendar-model.ts
|
|
2964
|
+
function fromDateKey(value) {
|
|
2965
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
2966
|
+
if (!match) throw new TypeError(`Invalid ISO date: ${value}`);
|
|
2967
|
+
return new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
|
|
2968
|
+
}
|
|
2969
|
+
function toDateKey(date) {
|
|
2970
|
+
return `${date.getUTCFullYear().toString().padStart(4, "0")}-${(date.getUTCMonth() + 1).toString().padStart(2, "0")}-${date.getUTCDate().toString().padStart(2, "0")}`;
|
|
2971
|
+
}
|
|
2972
|
+
function addCalendarDays(value, amount) {
|
|
2973
|
+
const date = fromDateKey(value);
|
|
2974
|
+
date.setUTCDate(date.getUTCDate() + amount);
|
|
2975
|
+
return toDateKey(date);
|
|
2976
|
+
}
|
|
2977
|
+
function addCalendarMonths(value, amount) {
|
|
2978
|
+
const date = fromDateKey(value);
|
|
2979
|
+
const day = date.getUTCDate();
|
|
2980
|
+
date.setUTCDate(1);
|
|
2981
|
+
date.setUTCMonth(date.getUTCMonth() + amount);
|
|
2982
|
+
const last = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0)).getUTCDate();
|
|
2983
|
+
date.setUTCDate(Math.min(day, last));
|
|
2984
|
+
return toDateKey(date);
|
|
2985
|
+
}
|
|
2986
|
+
function startOfMonth(value) {
|
|
2987
|
+
return `${value.slice(0, 7)}-01`;
|
|
2988
|
+
}
|
|
2989
|
+
function buildMonthGrid(month, weekStartsOn = 1) {
|
|
2990
|
+
const first = fromDateKey(startOfMonth(month));
|
|
2991
|
+
const offset = (first.getUTCDay() - weekStartsOn + 7) % 7;
|
|
2992
|
+
const start = addCalendarDays(toDateKey(first), -offset);
|
|
2993
|
+
return Array.from({ length: 42 }, (_, index) => {
|
|
2994
|
+
const date = addCalendarDays(start, index);
|
|
2995
|
+
const parsed = fromDateKey(date);
|
|
2996
|
+
return {
|
|
2997
|
+
date,
|
|
2998
|
+
day: parsed.getUTCDate(),
|
|
2999
|
+
inMonth: date.slice(0, 7) === month.slice(0, 7),
|
|
3000
|
+
weekday: parsed.getUTCDay()
|
|
3001
|
+
};
|
|
3002
|
+
});
|
|
3003
|
+
}
|
|
3004
|
+
function zonedDateKey(instant, timeZone) {
|
|
3005
|
+
const date = instant instanceof Date ? instant : new Date(instant);
|
|
3006
|
+
if (Number.isNaN(date.getTime())) throw new TypeError(`Invalid instant: ${String(instant)}`);
|
|
3007
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
3008
|
+
timeZone,
|
|
3009
|
+
year: "numeric",
|
|
3010
|
+
month: "2-digit",
|
|
3011
|
+
day: "2-digit"
|
|
3012
|
+
}).formatToParts(date);
|
|
3013
|
+
const get = (type) => parts.find((part) => part.type === type)?.value ?? "";
|
|
3014
|
+
return `${get("year")}-${get("month")}-${get("day")}`;
|
|
3015
|
+
}
|
|
3016
|
+
function zonedDateSpan(start, end, timeZone) {
|
|
3017
|
+
const startKey = zonedDateKey(start, timeZone);
|
|
3018
|
+
const endKey = zonedDateKey(end, timeZone);
|
|
3019
|
+
return startKey <= endKey ? { start: startKey, end: endKey } : { start: endKey, end: startKey };
|
|
3020
|
+
}
|
|
3021
|
+
function enumerateDateSpan(span, limit = 370) {
|
|
3022
|
+
const dates = [];
|
|
3023
|
+
let current = span.start;
|
|
3024
|
+
while (current <= span.end && dates.length < limit) {
|
|
3025
|
+
dates.push(current);
|
|
3026
|
+
current = addCalendarDays(current, 1);
|
|
3027
|
+
}
|
|
3028
|
+
return dates;
|
|
3029
|
+
}
|
|
3030
|
+
function selectRangeDate(current, date) {
|
|
3031
|
+
if (!current.start || current.end) return { start: date };
|
|
3032
|
+
return date < current.start ? { start: date, end: current.start } : { start: current.start, end: date };
|
|
3033
|
+
}
|
|
3034
|
+
function isDateInRange(date, value) {
|
|
3035
|
+
return !!value.start && !!value.end && date >= value.start && date <= value.end;
|
|
3036
|
+
}
|
|
3037
|
+
function isDateUnavailable(date, options) {
|
|
3038
|
+
return !!options.min && date < options.min || !!options.max && date > options.max || !!options.isDisabled?.(date);
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
// src/components/SchedulingCalendar.tsx
|
|
3042
|
+
var import_jsx_runtime48 = require("react/jsx-runtime");
|
|
3043
|
+
var stateLabel = { scheduled: "Scheduled", conflicted: "Conflicted", "in-progress": "In progress", "blackout-violation": "Blackout violation" };
|
|
3044
|
+
var stateTone = (state) => state === "scheduled" ? "info" : state === "in-progress" ? "success" : "danger";
|
|
3045
|
+
var dateLabel = (date, locale) => new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
|
|
3046
|
+
var EMPTY_OVERLAYS = [];
|
|
3047
|
+
function SchedulingCalendar({
|
|
3048
|
+
entries,
|
|
3049
|
+
anchorDate,
|
|
3050
|
+
timeZone,
|
|
3051
|
+
view: controlledView,
|
|
3052
|
+
onViewChange,
|
|
3053
|
+
onAnchorDateChange,
|
|
3054
|
+
onSelectEntry,
|
|
3055
|
+
overlays = EMPTY_OVERLAYS,
|
|
3056
|
+
filter,
|
|
3057
|
+
renderEntry,
|
|
3058
|
+
locale,
|
|
3059
|
+
loading,
|
|
3060
|
+
error,
|
|
3061
|
+
onRetry,
|
|
3062
|
+
className
|
|
3063
|
+
}) {
|
|
3064
|
+
const [internalView, setInternalView] = (0, import_react24.useState)("month");
|
|
3065
|
+
const view = controlledView ?? internalView;
|
|
3066
|
+
const [activeDate, setActiveDate] = (0, import_react24.useState)(anchorDate);
|
|
3067
|
+
const gridRef = (0, import_react24.useRef)(null);
|
|
3068
|
+
const visibleEntries = (0, import_react24.useMemo)(() => entries.filter((entry) => filter?.(entry) ?? true), [entries, filter]);
|
|
3069
|
+
const entrySpans = (0, import_react24.useMemo)(() => new Map(visibleEntries.map((entry) => [entry.id, zonedDateSpan(entry.start, entry.end, timeZone)])), [visibleEntries, timeZone]);
|
|
3070
|
+
const overlaySpans = (0, import_react24.useMemo)(() => new Map(overlays.map((overlay) => [overlay.id, { start: overlay.start.slice(0, 10), end: overlay.end.slice(0, 10) }])), [overlays]);
|
|
3071
|
+
const monthDays = (0, import_react24.useMemo)(() => buildMonthGrid(startOfMonth(anchorDate)), [anchorDate]);
|
|
3072
|
+
const weekday = ((/* @__PURE__ */ new Date(`${anchorDate}T00:00:00Z`)).getUTCDay() + 6) % 7;
|
|
3073
|
+
const weekStart = addCalendarDays(anchorDate, -weekday);
|
|
3074
|
+
const weekDays = (0, import_react24.useMemo)(() => Array.from({ length: 7 }, (_, index) => addCalendarDays(weekStart, index)), [weekStart]);
|
|
3075
|
+
const days = (0, import_react24.useMemo)(() => view === "month" ? monthDays.map((day) => day.date) : weekDays, [view, monthDays, weekDays]);
|
|
3076
|
+
(0, import_react24.useEffect)(() => {
|
|
3077
|
+
if (view !== "agenda" && !days.includes(activeDate)) setActiveDate(days[0]);
|
|
3078
|
+
}, [activeDate, days, view]);
|
|
3079
|
+
const setView = (next) => {
|
|
3080
|
+
if (controlledView === void 0) setInternalView(next);
|
|
3081
|
+
onViewChange?.(next);
|
|
3082
|
+
};
|
|
3083
|
+
const movePeriod = (direction) => onAnchorDateChange?.(view === "month" ? addCalendarMonths(anchorDate, direction) : addCalendarDays(anchorDate, direction * 7));
|
|
3084
|
+
const focusDate = (date) => {
|
|
3085
|
+
setActiveDate(date);
|
|
3086
|
+
requestAnimationFrame(() => gridRef.current?.querySelector(`[data-calendar-date="${date}"]`)?.focus());
|
|
3087
|
+
};
|
|
3088
|
+
const onDayKeyDown = (event, date) => {
|
|
3089
|
+
const offset = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[event.key];
|
|
3090
|
+
if (offset) {
|
|
3091
|
+
event.preventDefault();
|
|
3092
|
+
focusDate(addCalendarDays(date, offset));
|
|
3093
|
+
} else if (event.key === "Home") {
|
|
3094
|
+
event.preventDefault();
|
|
3095
|
+
focusDate(days[0]);
|
|
3096
|
+
} else if (event.key === "End") {
|
|
3097
|
+
event.preventDefault();
|
|
3098
|
+
focusDate(days[days.length - 1]);
|
|
3099
|
+
}
|
|
3100
|
+
};
|
|
3101
|
+
const entriesFor = (date) => visibleEntries.filter((entry) => {
|
|
3102
|
+
const span = entrySpans.get(entry.id);
|
|
3103
|
+
return date >= span.start && date <= span.end;
|
|
3104
|
+
});
|
|
3105
|
+
const overlaysFor = (date) => overlays.filter((overlay) => {
|
|
3106
|
+
const span = overlaySpans.get(overlay.id);
|
|
3107
|
+
return date >= span.start && date <= span.end;
|
|
3108
|
+
});
|
|
3109
|
+
const formatInstant = (instant) => new Intl.DateTimeFormat(locale, { timeZone, dateStyle: "medium", timeStyle: "short" }).format(new Date(instant));
|
|
3110
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("section", { className: cx("uix-scheduling-calendar", className), "aria-label": `Scheduling calendar in ${timeZone}`, children: [
|
|
3111
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "uix-scheduling-calendar__header", children: [
|
|
3112
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { children: [
|
|
3113
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(-1), disabled: !onAnchorDateChange, children: "Previous" }),
|
|
3114
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(1), disabled: !onAnchorDateChange, children: "Next" })
|
|
3115
|
+
] }),
|
|
3116
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("strong", { children: view === "month" ? new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${anchorDate}T00:00:00Z`)) : `${dateLabel(weekStart, locale)} \u2013 ${dateLabel(weekDays[weekDays.length - 1], locale)}` }),
|
|
3117
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "uix-segmented", role: "group", "aria-label": "Calendar view", children: ["month", "week", "agenda"].map((option) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("button", { type: "button", className: "uix-segmented__option", "aria-pressed": view === option, "data-selected": view === option || void 0, onClick: () => setView(option), children: [
|
|
3118
|
+
option[0].toUpperCase(),
|
|
3119
|
+
option.slice(1)
|
|
3120
|
+
] }, option)) })
|
|
3121
|
+
] }),
|
|
3122
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "uix-scheduling-calendar__state", role: "status", children: "Loading schedule\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "uix-scheduling-calendar__state", role: "alert", children: [
|
|
3123
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { children: error }),
|
|
3124
|
+
onRetry && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
|
|
3125
|
+
] }) : view === "agenda" ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "uix-scheduling-calendar__agenda", role: "region", "aria-label": "Schedule agenda", children: visibleEntries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { children: "No scheduled entries match the current filters." }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("ol", { children: [...visibleEntries].sort((a, b) => a.start.localeCompare(b.start)).map((entry) => {
|
|
3126
|
+
const state = entry.state ?? "scheduled";
|
|
3127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("button", { type: "button", onClick: () => onSelectEntry?.(entry), children: [
|
|
3128
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { children: [
|
|
3129
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("strong", { children: renderEntry?.(entry) ?? entry.title }),
|
|
3130
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { children: [
|
|
3131
|
+
formatInstant(entry.start),
|
|
3132
|
+
" \u2013 ",
|
|
3133
|
+
formatInstant(entry.end)
|
|
3134
|
+
] }),
|
|
3135
|
+
entry.meta && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { children: entry.meta })
|
|
3136
|
+
] }),
|
|
3137
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(StatusPill, { tone: stateTone(state), children: stateLabel[state] })
|
|
3138
|
+
] }) }, entry.id);
|
|
3139
|
+
}) }) }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { ref: gridRef, className: cx("uix-scheduling-calendar__grid", view === "week" && "uix-scheduling-calendar__grid--week"), role: "group", "aria-label": `${view} schedule`, children: days.map((date, index) => {
|
|
3140
|
+
const dayEntries = entriesFor(date);
|
|
3141
|
+
const dayOverlays = overlaysFor(date);
|
|
3142
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "uix-scheduling-calendar__day", "data-outside": view === "month" && !monthDays[index].inMonth || void 0, children: [
|
|
3143
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("button", { type: "button", className: "uix-scheduling-calendar__date", "data-calendar-date": date, tabIndex: activeDate === date ? 0 : -1, onFocus: () => setActiveDate(date), onKeyDown: (event) => onDayKeyDown(event, date), "aria-label": dateLabel(date, locale), children: Number(date.slice(-2)) }),
|
|
3144
|
+
dayOverlays.map((overlay) => /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "uix-scheduling-calendar__overlay", "data-kind": overlay.kind, children: overlay.label }, overlay.id)),
|
|
3145
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "uix-scheduling-calendar__entries", children: dayEntries.map((entry) => {
|
|
3146
|
+
const span = entrySpans.get(entry.id);
|
|
3147
|
+
const state = entry.state ?? "scheduled";
|
|
3148
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("button", { type: "button", className: "uix-scheduling-calendar__entry", "data-state": state, "data-range-start": date === span.start || void 0, "data-range-end": date === span.end || void 0, onClick: () => onSelectEntry?.(entry), "aria-label": `${entry.title}, ${stateLabel[state]}, ${formatInstant(entry.start)} to ${formatInstant(entry.end)}`, children: [
|
|
3149
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { children: renderEntry?.(entry) ?? entry.title }),
|
|
3150
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "uix-visually-hidden", children: stateLabel[state] })
|
|
3151
|
+
] }, entry.id);
|
|
3152
|
+
}) })
|
|
3153
|
+
] }, date);
|
|
3154
|
+
}) }),
|
|
3155
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "uix-scheduling-calendar__legend", "aria-label": "Schedule state legend", children: ["scheduled", "conflicted", "in-progress", "blackout-violation"].map((state) => /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { "data-state": state, children: stateLabel[state] }, state)) })
|
|
3156
|
+
] });
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
// src/components/DateRangePicker.tsx
|
|
3160
|
+
var import_react25 = require("react");
|
|
3161
|
+
var import_jsx_runtime49 = require("react/jsx-runtime");
|
|
3162
|
+
var monthLabel = (date, locale) => new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
|
|
3163
|
+
var dateLabel2 = (date, locale) => new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
|
|
3164
|
+
function DateRangePicker({
|
|
3165
|
+
value,
|
|
3166
|
+
onChange,
|
|
3167
|
+
visibleMonth: controlledMonth,
|
|
3168
|
+
onVisibleMonthChange,
|
|
3169
|
+
months = 2,
|
|
3170
|
+
min,
|
|
3171
|
+
max,
|
|
3172
|
+
isDateDisabled,
|
|
3173
|
+
locale,
|
|
3174
|
+
label = "Choose date range",
|
|
3175
|
+
invalid,
|
|
3176
|
+
invalidMessage = "The selected date range is invalid.",
|
|
3177
|
+
disabled,
|
|
3178
|
+
className
|
|
3179
|
+
}) {
|
|
3180
|
+
const id = (0, import_react25.useId)();
|
|
3181
|
+
const [internalMonth, setInternalMonth] = (0, import_react25.useState)(() => startOfMonth(controlledMonth ?? value.start ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)));
|
|
3182
|
+
const visibleMonth = startOfMonth(controlledMonth ?? internalMonth);
|
|
3183
|
+
const [activeDate, setActiveDate] = (0, import_react25.useState)(value.end ?? value.start ?? visibleMonth);
|
|
3184
|
+
const rootRef = (0, import_react25.useRef)(null);
|
|
3185
|
+
const pendingFocus = (0, import_react25.useRef)(false);
|
|
3186
|
+
const count = Math.max(1, Math.min(3, months));
|
|
3187
|
+
const calendars = (0, import_react25.useMemo)(() => Array.from({ length: count }, (_, index) => {
|
|
3188
|
+
const month = startOfMonth(addCalendarMonths(visibleMonth, index));
|
|
3189
|
+
return { month, days: buildMonthGrid(month).filter((day) => day.inMonth) };
|
|
3190
|
+
}), [visibleMonth, count]);
|
|
3191
|
+
const rangeInvalid = invalid || !!value.start && !!value.end && value.start > value.end;
|
|
3192
|
+
(0, import_react25.useEffect)(() => {
|
|
3193
|
+
const first = calendars[0]?.days[0]?.date;
|
|
3194
|
+
const finalDays = calendars[calendars.length - 1]?.days;
|
|
3195
|
+
const last = finalDays?.[finalDays.length - 1]?.date;
|
|
3196
|
+
if (first && last && (activeDate < first || activeDate > last)) setActiveDate(first);
|
|
3197
|
+
}, [activeDate, calendars]);
|
|
3198
|
+
(0, import_react25.useEffect)(() => {
|
|
3199
|
+
if (!pendingFocus.current) return;
|
|
3200
|
+
rootRef.current?.querySelector(`[data-date="${activeDate}"]`)?.focus();
|
|
3201
|
+
pendingFocus.current = false;
|
|
3202
|
+
}, [activeDate, visibleMonth]);
|
|
3203
|
+
const setMonth = (month) => {
|
|
3204
|
+
const next = startOfMonth(month);
|
|
3205
|
+
if (controlledMonth === void 0) setInternalMonth(next);
|
|
3206
|
+
onVisibleMonthChange?.(next);
|
|
3207
|
+
};
|
|
3208
|
+
const focusDate = (date) => {
|
|
3209
|
+
setActiveDate(date);
|
|
3210
|
+
if (date < calendars[0].month || date.slice(0, 7) > calendars[calendars.length - 1].month.slice(0, 7)) setMonth(startOfMonth(date));
|
|
3211
|
+
pendingFocus.current = true;
|
|
3212
|
+
};
|
|
3213
|
+
const choose = (date) => {
|
|
3214
|
+
if (disabled || isDateUnavailable(date, { min, max, isDisabled: isDateDisabled })) return;
|
|
3215
|
+
onChange(selectRangeDate(value, date));
|
|
3216
|
+
setActiveDate(date);
|
|
3217
|
+
};
|
|
3218
|
+
const onDateKeyDown = (event, date) => {
|
|
3219
|
+
const offsets = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 };
|
|
3220
|
+
if (event.key in offsets) {
|
|
3221
|
+
event.preventDefault();
|
|
3222
|
+
focusDate(addCalendarDays(date, offsets[event.key]));
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
3225
|
+
if (event.key === "Home" || event.key === "End") {
|
|
3226
|
+
event.preventDefault();
|
|
3227
|
+
const weekday = (/* @__PURE__ */ new Date(`${date}T00:00:00Z`)).getUTCDay();
|
|
3228
|
+
const mondayIndex = (weekday + 6) % 7;
|
|
3229
|
+
focusDate(addCalendarDays(date, event.key === "Home" ? -mondayIndex : 6 - mondayIndex));
|
|
3230
|
+
} else if (event.key === "PageUp" || event.key === "PageDown") {
|
|
3231
|
+
event.preventDefault();
|
|
3232
|
+
focusDate(addCalendarMonths(date, event.key === "PageUp" ? -1 : 1));
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
const announcement = !value.start ? "No date range selected." : !value.end ? `Start date ${dateLabel2(value.start, locale)} selected. Choose an end date.` : `${dateLabel2(value.start, locale)} to ${dateLabel2(value.end, locale)} selected.`;
|
|
3236
|
+
return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("section", { className: cx("uix-date-range-picker", rangeInvalid && "uix-date-range-picker--invalid", className), "aria-labelledby": `${id}-label`, children: [
|
|
3237
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "uix-date-range-picker__header", children: [
|
|
3238
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("h3", { id: `${id}-label`, children: label }),
|
|
3239
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { children: [
|
|
3240
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => setMonth(addCalendarMonths(visibleMonth, -1)), "aria-label": "Show previous month", children: "Previous" }),
|
|
3241
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => setMonth(addCalendarMonths(visibleMonth, 1)), "aria-label": "Show next month", children: "Next" })
|
|
3242
|
+
] })
|
|
3243
|
+
] }),
|
|
3244
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { ref: rootRef, className: "uix-date-range-picker__months", children: calendars.map(({ month, days }) => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "uix-date-range-picker__month", children: [
|
|
3245
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("h4", { children: monthLabel(month, locale) }),
|
|
3246
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: "uix-date-range-picker__weekdays", "aria-hidden": "true", children: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map((day) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { children: day }, day)) }),
|
|
3247
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "uix-date-range-picker__grid", role: "group", "aria-label": monthLabel(month, locale), children: [
|
|
3248
|
+
Array.from({ length: ((/* @__PURE__ */ new Date(`${month}T00:00:00Z`)).getUTCDay() + 6) % 7 }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { "aria-hidden": "true" }, `blank-${index}`)),
|
|
3249
|
+
days.map((day) => {
|
|
3250
|
+
const unavailable = disabled || isDateUnavailable(day.date, { min, max, isDisabled: isDateDisabled });
|
|
3251
|
+
const edge = day.date === value.start ? "start" : day.date === value.end ? "end" : void 0;
|
|
3252
|
+
return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
3253
|
+
"button",
|
|
3254
|
+
{
|
|
3255
|
+
type: "button",
|
|
3256
|
+
"data-date": day.date,
|
|
3257
|
+
"data-range-edge": edge,
|
|
3258
|
+
"data-in-range": isDateInRange(day.date, value) || void 0,
|
|
3259
|
+
"aria-pressed": edge != null || isDateInRange(day.date, value),
|
|
3260
|
+
"aria-label": `${dateLabel2(day.date, locale)}${edge ? `, ${edge} of selected range` : isDateInRange(day.date, value) ? ", in selected range" : ""}`,
|
|
3261
|
+
tabIndex: activeDate === day.date ? 0 : -1,
|
|
3262
|
+
disabled: unavailable,
|
|
3263
|
+
onFocus: () => setActiveDate(day.date),
|
|
3264
|
+
onKeyDown: (event) => onDateKeyDown(event, day.date),
|
|
3265
|
+
onClick: () => choose(day.date),
|
|
3266
|
+
children: day.day
|
|
3267
|
+
},
|
|
3268
|
+
day.date
|
|
3269
|
+
);
|
|
3270
|
+
})
|
|
3271
|
+
] })
|
|
3272
|
+
] }, month)) }),
|
|
3273
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "uix-date-range-picker__selection", "aria-live": "polite", children: announcement }),
|
|
3274
|
+
rangeInvalid && /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "uix-field__error", role: "alert", children: invalidMessage })
|
|
3275
|
+
] });
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
// src/components/RelationshipGraph.tsx
|
|
3279
|
+
var import_react26 = require("react");
|
|
3280
|
+
|
|
3281
|
+
// src/relationship-graph-model.ts
|
|
3282
|
+
function boundRelationshipGraph(nodes, edges, maxNodes = 40) {
|
|
3283
|
+
const boundedNodes = nodes.slice(0, Math.max(1, maxNodes));
|
|
3284
|
+
const ids = new Set(boundedNodes.map((node) => node.id));
|
|
3285
|
+
return {
|
|
3286
|
+
nodes: boundedNodes,
|
|
3287
|
+
edges: edges.filter((edge) => ids.has(edge.source) && ids.has(edge.target)),
|
|
3288
|
+
omittedNodeCount: Math.max(0, nodes.length - boundedNodes.length)
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
function relationshipNeighbors(nodeId, edges) {
|
|
3292
|
+
const result = [];
|
|
3293
|
+
for (const edge of edges) {
|
|
3294
|
+
if (edge.source === nodeId) result.push(edge.target);
|
|
3295
|
+
else if (edge.target === nodeId) result.push(edge.source);
|
|
3296
|
+
}
|
|
3297
|
+
return [...new Set(result)];
|
|
3298
|
+
}
|
|
3299
|
+
function layoutRelationshipGraph(nodes, edges, rootId = nodes[0]?.id) {
|
|
3300
|
+
if (nodes.length === 0) return [];
|
|
3301
|
+
const root = nodes.some((node) => node.id === rootId) ? rootId : nodes[0].id;
|
|
3302
|
+
const depth = /* @__PURE__ */ new Map([[root, 0]]);
|
|
3303
|
+
const queue = [root];
|
|
3304
|
+
while (queue.length) {
|
|
3305
|
+
const current = queue.shift();
|
|
3306
|
+
for (const neighbor of relationshipNeighbors(current, edges)) {
|
|
3307
|
+
if (!depth.has(neighbor) && nodes.some((node) => node.id === neighbor)) {
|
|
3308
|
+
depth.set(neighbor, (depth.get(current) ?? 0) + 1);
|
|
3309
|
+
queue.push(neighbor);
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
let disconnectedDepth = Math.max(0, ...depth.values()) + 1;
|
|
3314
|
+
nodes.forEach((node) => {
|
|
3315
|
+
if (!depth.has(node.id)) depth.set(node.id, disconnectedDepth);
|
|
3316
|
+
});
|
|
3317
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3318
|
+
nodes.forEach((node) => {
|
|
3319
|
+
const level = depth.get(node.id);
|
|
3320
|
+
groups.set(level, [...groups.get(level) ?? [], node]);
|
|
3321
|
+
});
|
|
3322
|
+
const maxDepth = Math.max(1, ...groups.keys());
|
|
3323
|
+
return nodes.map((node) => {
|
|
3324
|
+
const level = depth.get(node.id);
|
|
3325
|
+
if (node.x !== void 0 && node.y !== void 0) return { ...node, x: node.x, y: node.y, depth: level };
|
|
3326
|
+
if (level === 0) return { ...node, x: 50, y: 50, depth: 0 };
|
|
3327
|
+
const peers = groups.get(level);
|
|
3328
|
+
const index = peers.findIndex((peer) => peer.id === node.id);
|
|
3329
|
+
const angle = Math.PI * 2 * index / peers.length - Math.PI / 2;
|
|
3330
|
+
const radius = 16 + level / maxDepth * 26;
|
|
3331
|
+
return {
|
|
3332
|
+
...node,
|
|
3333
|
+
x: Number((50 + Math.cos(angle) * radius).toFixed(3)),
|
|
3334
|
+
y: Number((50 + Math.sin(angle) * radius).toFixed(3)),
|
|
3335
|
+
depth: level
|
|
3336
|
+
};
|
|
3337
|
+
});
|
|
3338
|
+
}
|
|
3339
|
+
function traverseRelationshipNode(nodes, currentId, direction) {
|
|
3340
|
+
if (nodes.length === 0) return void 0;
|
|
3341
|
+
const current = Math.max(0, nodes.findIndex((node) => node.id === currentId));
|
|
3342
|
+
return nodes[(current + direction + nodes.length) % nodes.length]?.id;
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
// src/components/RelationshipGraph.tsx
|
|
3346
|
+
var import_jsx_runtime50 = require("react/jsx-runtime");
|
|
3347
|
+
var EMPTY_IDS = /* @__PURE__ */ new Set();
|
|
3348
|
+
var EMPTY_LEGEND = [];
|
|
3349
|
+
function RelationshipGraph({
|
|
3350
|
+
nodes,
|
|
3351
|
+
edges,
|
|
3352
|
+
selectedId,
|
|
3353
|
+
onSelect,
|
|
3354
|
+
onExpandNeighbors,
|
|
3355
|
+
onOpenDetails,
|
|
3356
|
+
maxNodes = 40,
|
|
3357
|
+
density = "standard",
|
|
3358
|
+
highlightedNodeIds = EMPTY_IDS,
|
|
3359
|
+
conflictedNodeIds = EMPTY_IDS,
|
|
3360
|
+
highlightedEdgeIds = EMPTY_IDS,
|
|
3361
|
+
conflictedEdgeIds = EMPTY_IDS,
|
|
3362
|
+
legend = EMPTY_LEGEND,
|
|
3363
|
+
loading,
|
|
3364
|
+
error,
|
|
3365
|
+
onRetry,
|
|
3366
|
+
className
|
|
3367
|
+
}) {
|
|
3368
|
+
const bounded = (0, import_react26.useMemo)(() => boundRelationshipGraph(nodes, edges, maxNodes), [nodes, edges, maxNodes]);
|
|
3369
|
+
const positioned = (0, import_react26.useMemo)(() => layoutRelationshipGraph(bounded.nodes, bounded.edges, selectedId), [bounded, selectedId]);
|
|
3370
|
+
const positionedById = (0, import_react26.useMemo)(() => new Map(positioned.map((node) => [node.id, node])), [positioned]);
|
|
3371
|
+
const [zoom, setZoom] = (0, import_react26.useState)(1);
|
|
3372
|
+
const [pan, setPan] = (0, import_react26.useState)({ x: 0, y: 0 });
|
|
3373
|
+
const [focusedId, setFocusedId] = (0, import_react26.useState)(selectedId ?? positioned[0]?.id);
|
|
3374
|
+
const svgRef = (0, import_react26.useRef)(null);
|
|
3375
|
+
const select = (id) => {
|
|
3376
|
+
const node = bounded.nodes.find((item) => item.id === id);
|
|
3377
|
+
if (node) onSelect?.(node);
|
|
3378
|
+
};
|
|
3379
|
+
const focusNode = (id) => {
|
|
3380
|
+
if (!id) return;
|
|
3381
|
+
setFocusedId(id);
|
|
3382
|
+
requestAnimationFrame(() => svgRef.current?.querySelector(`[data-node-id="${id}"]`)?.focus());
|
|
3383
|
+
};
|
|
3384
|
+
const onNodeKeyDown = (event, id) => {
|
|
3385
|
+
if (["ArrowRight", "ArrowDown", "ArrowLeft", "ArrowUp"].includes(event.key)) {
|
|
3386
|
+
event.preventDefault();
|
|
3387
|
+
focusNode(traverseRelationshipNode(positioned, id, event.key === "ArrowRight" || event.key === "ArrowDown" ? 1 : -1));
|
|
3388
|
+
} else if (event.key === "Enter" || event.key === " ") {
|
|
3389
|
+
event.preventDefault();
|
|
3390
|
+
select(id);
|
|
3391
|
+
}
|
|
3392
|
+
};
|
|
3393
|
+
const reset = () => {
|
|
3394
|
+
setZoom(1);
|
|
3395
|
+
setPan({ x: 0, y: 0 });
|
|
3396
|
+
};
|
|
3397
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), role: "status", children: "Loading relationships\u2026" });
|
|
3398
|
+
if (error) return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), role: "alert", children: [
|
|
3399
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("p", { children: error }),
|
|
3400
|
+
onRetry && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
|
|
3401
|
+
] });
|
|
3402
|
+
if (bounded.nodes.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), children: [
|
|
3403
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("p", { children: "No relationships to display." }),
|
|
3404
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("p", { children: "Adjust the neighborhood or filters to include a node." })
|
|
3405
|
+
] });
|
|
3406
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("section", { className: cx("uix-relationship-graph", `uix-relationship-graph--${density}`, className), "aria-label": "Relationship graph", children: [
|
|
3407
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "uix-relationship-graph__toolbar", "aria-label": "Graph view controls", children: [
|
|
3408
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x - 24 })), "aria-label": "Pan left", children: "Left" }),
|
|
3409
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y - 24 })), "aria-label": "Pan up", children: "Up" }),
|
|
3410
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y + 24 })), "aria-label": "Pan down", children: "Down" }),
|
|
3411
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x + 24 })), "aria-label": "Pan right", children: "Right" }),
|
|
3412
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", onClick: () => setZoom((value) => Math.min(2, Number((value + 0.2).toFixed(1)))), "aria-label": "Zoom in", children: "Zoom in" }),
|
|
3413
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", onClick: () => setZoom((value) => Math.max(0.5, Number((value - 0.2).toFixed(1)))), "aria-label": "Zoom out", children: "Zoom out" }),
|
|
3414
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", onClick: reset, children: "Reset view" }),
|
|
3415
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("span", { "aria-live": "polite", children: [
|
|
3416
|
+
"Zoom ",
|
|
3417
|
+
Math.round(zoom * 100),
|
|
3418
|
+
"%"
|
|
3419
|
+
] })
|
|
3420
|
+
] }),
|
|
3421
|
+
bounded.omittedNodeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("p", { className: "uix-relationship-graph__bounded", role: "status", children: [
|
|
3422
|
+
"Showing ",
|
|
3423
|
+
bounded.nodes.length,
|
|
3424
|
+
" nodes; ",
|
|
3425
|
+
bounded.omittedNodeCount,
|
|
3426
|
+
" outside this bounded neighborhood."
|
|
3427
|
+
] }),
|
|
3428
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "uix-relationship-graph__visual", children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("svg", { ref: svgRef, viewBox: "0 0 600 400", role: "group", "aria-label": `Graph with ${bounded.nodes.length} nodes and ${bounded.edges.length} relationships`, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("g", { transform: `translate(${pan.x} ${pan.y}) scale(${zoom})`, children: [
|
|
3429
|
+
bounded.edges.map((edge) => {
|
|
3430
|
+
const source = positionedById.get(edge.source);
|
|
3431
|
+
const target = positionedById.get(edge.target);
|
|
3432
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("line", { x1: source.x * 6, y1: source.y * 4, x2: target.x * 6, y2: target.y * 4, className: "uix-relationship-graph__edge", "data-highlighted": highlightedEdgeIds.has(edge.id) || void 0, "data-conflicted": conflictedEdgeIds.has(edge.id) || void 0, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("title", { children: edge.label ?? edge.type ?? "Relationship" }) }, edge.id);
|
|
3433
|
+
}),
|
|
3434
|
+
positioned.map((node) => /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(
|
|
3435
|
+
"g",
|
|
3436
|
+
{
|
|
3437
|
+
transform: `translate(${node.x * 6} ${node.y * 4})`,
|
|
3438
|
+
role: "button",
|
|
3439
|
+
"aria-label": `${node.label}${node.type ? `, ${node.type}` : ""}${conflictedNodeIds.has(node.id) ? ", conflict" : ""}`,
|
|
3440
|
+
tabIndex: focusedId === node.id ? 0 : -1,
|
|
3441
|
+
"data-node-id": node.id,
|
|
3442
|
+
className: "uix-relationship-graph__node",
|
|
3443
|
+
"data-selected": selectedId === node.id || void 0,
|
|
3444
|
+
"data-highlighted": highlightedNodeIds.has(node.id) || void 0,
|
|
3445
|
+
"data-conflicted": conflictedNodeIds.has(node.id) || void 0,
|
|
3446
|
+
onFocus: () => setFocusedId(node.id),
|
|
3447
|
+
onKeyDown: (event) => onNodeKeyDown(event, node.id),
|
|
3448
|
+
onClick: () => select(node.id),
|
|
3449
|
+
children: [
|
|
3450
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("rect", { x: "-55", y: "-18", width: "110", height: "36", rx: "8" }),
|
|
3451
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("text", { textAnchor: "middle", dominantBaseline: "middle", children: node.label.length > 16 ? `${node.label.slice(0, 15)}\u2026` : node.label })
|
|
3452
|
+
]
|
|
3453
|
+
},
|
|
3454
|
+
node.id
|
|
3455
|
+
))
|
|
3456
|
+
] }) }) }),
|
|
3457
|
+
legend.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("ul", { className: "uix-relationship-graph__legend", "aria-label": "Relationship types", children: legend.map((item) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("li", { "data-type": item.id, children: item.label }, item.id)) }),
|
|
3458
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "uix-relationship-graph__list", role: "region", "aria-label": "Accessible relationship list", children: [
|
|
3459
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("h3", { children: "Relationship list" }),
|
|
3460
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("ul", { children: bounded.nodes.map((node) => {
|
|
3461
|
+
const neighbors = relationshipNeighbors(node.id, bounded.edges).map((id) => bounded.nodes.find((item) => item.id === id)?.label ?? id);
|
|
3462
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("li", { "data-selected": selectedId === node.id || void 0, children: [
|
|
3463
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("button", { type: "button", onClick: () => select(node.id), "aria-pressed": selectedId === node.id, children: [
|
|
3464
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("strong", { children: node.label }),
|
|
3465
|
+
node.type && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { children: node.type }),
|
|
3466
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { children: neighbors.length ? `Connected to ${neighbors.join(", ")}` : "No visible connections" })
|
|
3467
|
+
] }),
|
|
3468
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("span", { className: "uix-relationship-graph__list-actions", children: [
|
|
3469
|
+
onExpandNeighbors && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", onClick: () => onExpandNeighbors(node), children: "Expand neighbors" }),
|
|
3470
|
+
onOpenDetails && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("button", { type: "button", onClick: () => onOpenDetails(node), children: "Open details" })
|
|
3471
|
+
] })
|
|
3472
|
+
] }, node.id);
|
|
3473
|
+
}) })
|
|
3474
|
+
] })
|
|
3475
|
+
] });
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
// src/components/MatchReview.tsx
|
|
3479
|
+
var import_react27 = require("react");
|
|
3480
|
+
var import_jsx_runtime51 = require("react/jsx-runtime");
|
|
3481
|
+
var valueText = (value) => value == null ? "\u2014" : typeof value === "string" ? value : JSON.stringify(value);
|
|
3482
|
+
function MatchReview({
|
|
3483
|
+
incoming,
|
|
3484
|
+
incomingLabel = "Incoming record",
|
|
3485
|
+
fields,
|
|
3486
|
+
candidates,
|
|
3487
|
+
selectedIds,
|
|
3488
|
+
onSelectionChange,
|
|
3489
|
+
onDecision,
|
|
3490
|
+
onBulkDecision,
|
|
3491
|
+
loading,
|
|
3492
|
+
error,
|
|
3493
|
+
onRetry,
|
|
3494
|
+
className
|
|
3495
|
+
}) {
|
|
3496
|
+
const [internalSelection, setInternalSelection] = (0, import_react27.useState)(/* @__PURE__ */ new Set());
|
|
3497
|
+
const [announcement, setAnnouncement] = (0, import_react27.useState)("");
|
|
3498
|
+
const [bulkPending, setBulkPending] = (0, import_react27.useState)(false);
|
|
3499
|
+
const selection = selectedIds ?? internalSelection;
|
|
3500
|
+
const pending = (0, import_react27.useMemo)(() => candidates.filter((candidate) => candidate.status !== "resolved"), [candidates]);
|
|
3501
|
+
const setSelection = (next) => {
|
|
3502
|
+
if (selectedIds === void 0) setInternalSelection(next);
|
|
3503
|
+
onSelectionChange?.(next);
|
|
3504
|
+
};
|
|
3505
|
+
const toggle = (id) => {
|
|
3506
|
+
const next = new Set(selection);
|
|
3507
|
+
if (next.has(id)) next.delete(id);
|
|
3508
|
+
else next.add(id);
|
|
3509
|
+
setSelection(next);
|
|
3510
|
+
};
|
|
3511
|
+
const runBulk = async (action) => {
|
|
3512
|
+
if (!onBulkDecision || selection.size === 0 || bulkPending) return;
|
|
3513
|
+
const ids = [...selection];
|
|
3514
|
+
setBulkPending(true);
|
|
3515
|
+
setAnnouncement(`${action === "accept" ? "Accepting" : "Dismissing"} ${ids.length} candidate${ids.length === 1 ? "" : "s"}\u2026`);
|
|
3516
|
+
try {
|
|
3517
|
+
const result = await onBulkDecision(ids, action);
|
|
3518
|
+
const succeeded = result?.succeeded ?? ids.length;
|
|
3519
|
+
const failed = result?.failed ?? 0;
|
|
3520
|
+
setAnnouncement(`${succeeded} candidate${succeeded === 1 ? "" : "s"} ${action === "accept" ? "accepted" : "dismissed"}${failed ? `; ${failed} failed` : ""}.`);
|
|
3521
|
+
if (!failed) setSelection(/* @__PURE__ */ new Set());
|
|
3522
|
+
} catch {
|
|
3523
|
+
setAnnouncement(`Bulk ${action} failed. Try again.`);
|
|
3524
|
+
} finally {
|
|
3525
|
+
setBulkPending(false);
|
|
3526
|
+
}
|
|
3527
|
+
};
|
|
3528
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: cx("uix-match-review uix-match-review--state", className), role: "status", children: "Loading match candidates\u2026" });
|
|
3529
|
+
if (error) return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: cx("uix-match-review uix-match-review--state", className), role: "alert", children: [
|
|
3530
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { children: error }),
|
|
3531
|
+
onRetry && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
|
|
3532
|
+
] });
|
|
3533
|
+
if (candidates.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: cx("uix-match-review uix-match-review--state", className), children: [
|
|
3534
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { children: "No candidate matches found." }),
|
|
3535
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { children: "Choose another record source or continue without a match." })
|
|
3536
|
+
] });
|
|
3537
|
+
return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("section", { className: cx("uix-match-review", className), "aria-label": "Match review", children: [
|
|
3538
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "uix-match-review__summary", children: [
|
|
3539
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { children: [
|
|
3540
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("strong", { children: incomingLabel }),
|
|
3541
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { children: [
|
|
3542
|
+
pending.length,
|
|
3543
|
+
" pending of ",
|
|
3544
|
+
candidates.length
|
|
3545
|
+
] })
|
|
3546
|
+
] }),
|
|
3547
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "uix-match-review__incoming", children: fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { children: [
|
|
3548
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("b", { children: field.label }),
|
|
3549
|
+
field.render?.(incoming[field.id], incoming) ?? valueText(incoming[field.id])
|
|
3550
|
+
] }, field.id)) })
|
|
3551
|
+
] }),
|
|
3552
|
+
onBulkDecision && /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "uix-match-review__bulk", "aria-label": "Bulk candidate actions", children: [
|
|
3553
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { children: [
|
|
3554
|
+
selection.size,
|
|
3555
|
+
" selected"
|
|
3556
|
+
] }),
|
|
3557
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("accept"), children: bulkPending ? "Applying\u2026" : "Accept selected" }),
|
|
3558
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("dismiss"), children: bulkPending ? "Applying\u2026" : "Dismiss selected" })
|
|
3559
|
+
] }),
|
|
3560
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "uix-match-review__table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("table", { className: "uix-table uix-match-review__table", children: [
|
|
3561
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("caption", { className: "uix-visually-hidden", children: "Candidate matches and available decisions" }),
|
|
3562
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("tr", { children: [
|
|
3563
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("th", { scope: "col", children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "uix-visually-hidden", children: "Select" }) }),
|
|
3564
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("th", { scope: "col", children: "Confidence" }),
|
|
3565
|
+
fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("th", { scope: "col", children: field.label }, field.id)),
|
|
3566
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("th", { scope: "col", children: "Status" }),
|
|
3567
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("th", { scope: "col", children: "Actions" })
|
|
3568
|
+
] }) }),
|
|
3569
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("tbody", { children: candidates.map((candidate) => {
|
|
3570
|
+
const resolved = candidate.status === "resolved";
|
|
3571
|
+
return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("tr", { "data-status": candidate.status ?? "pending", children: [
|
|
3572
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("input", { type: "checkbox", checked: selection.has(candidate.id), onChange: () => toggle(candidate.id), "aria-label": `Select candidate ${candidate.id}` }) }),
|
|
3573
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "uix-match-review__confidence", children: candidate.confidence == null ? "Not scored" : `${Math.round(candidate.confidence * 100)}%` }) }),
|
|
3574
|
+
fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("td", { children: field.render?.(candidate.record[field.id], candidate.record) ?? valueText(candidate.record[field.id]) }, field.id)),
|
|
3575
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(StatusPill, { tone: resolved ? "success" : "warning", children: resolved ? candidate.decision ?? "Resolved" : "Pending" }) }),
|
|
3576
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "uix-match-review__actions", children: ["accept", "dismiss", "split", "pick-other"].map((action) => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
3577
|
+
"button",
|
|
3578
|
+
{
|
|
3579
|
+
type: "button",
|
|
3580
|
+
className: "uix-btn uix-btn--ghost uix-btn--sm",
|
|
3581
|
+
disabled: resolved,
|
|
3582
|
+
onClick: () => onDecision(candidate.id, action),
|
|
3583
|
+
children: action === "pick-other" ? "Pick other" : `${action[0].toUpperCase()}${action.slice(1)}`
|
|
3584
|
+
},
|
|
3585
|
+
action
|
|
3586
|
+
)) }) })
|
|
3587
|
+
] }, candidate.id);
|
|
3588
|
+
}) })
|
|
3589
|
+
] }) }),
|
|
3590
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
|
|
3591
|
+
] });
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
// src/components/MetricInput.tsx
|
|
3595
|
+
var import_react28 = require("react");
|
|
3596
|
+
|
|
3597
|
+
// src/metric-model.ts
|
|
3598
|
+
function decimals(value) {
|
|
3599
|
+
const text = String(value).toLowerCase();
|
|
3600
|
+
if (text.includes("e-")) return Number(text.split("e-")[1]);
|
|
3601
|
+
return text.includes(".") ? text.split(".")[1].length : 0;
|
|
3602
|
+
}
|
|
3603
|
+
function clampMetricValue(value, min, max) {
|
|
3604
|
+
return Math.min(max ?? Number.POSITIVE_INFINITY, Math.max(min ?? Number.NEGATIVE_INFINITY, value));
|
|
3605
|
+
}
|
|
3606
|
+
function stepMetricValue(value, direction, options = {}) {
|
|
3607
|
+
const step = options.step && options.step > 0 ? options.step : 1;
|
|
3608
|
+
const base = value ?? options.min ?? 0;
|
|
3609
|
+
const precision = Math.max(decimals(base), decimals(step), decimals(options.min ?? 0), decimals(options.max ?? 0));
|
|
3610
|
+
const next = Number((base + direction * step).toFixed(precision));
|
|
3611
|
+
return clampMetricValue(next, options.min, options.max);
|
|
3612
|
+
}
|
|
3613
|
+
function parseMetricValue(value) {
|
|
3614
|
+
if (value.trim() === "") return null;
|
|
3615
|
+
const parsed = Number(value);
|
|
3616
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
// src/components/MetricInput.tsx
|
|
3620
|
+
var import_jsx_runtime52 = require("react/jsx-runtime");
|
|
3621
|
+
var defaultMetricFormat = (input) => input == null ? "" : String(input);
|
|
3622
|
+
function MetricInput({
|
|
3623
|
+
value,
|
|
3624
|
+
onValueChange,
|
|
3625
|
+
min,
|
|
3626
|
+
max,
|
|
3627
|
+
step = 1,
|
|
3628
|
+
unit,
|
|
3629
|
+
label,
|
|
3630
|
+
invalid,
|
|
3631
|
+
errorMessage,
|
|
3632
|
+
disabled,
|
|
3633
|
+
readOnly,
|
|
3634
|
+
parse = parseMetricValue,
|
|
3635
|
+
format = defaultMetricFormat,
|
|
3636
|
+
className,
|
|
3637
|
+
id: providedId
|
|
3638
|
+
}) {
|
|
3639
|
+
const generatedId = (0, import_react28.useId)();
|
|
3640
|
+
const id = providedId ?? generatedId;
|
|
3641
|
+
const errorId = `${id}-error`;
|
|
3642
|
+
const [text, setText] = (0, import_react28.useState)(() => format(value));
|
|
3643
|
+
(0, import_react28.useEffect)(() => setText(format(value)), [value, format]);
|
|
3644
|
+
const commit = (input) => {
|
|
3645
|
+
const parsed = parse(input);
|
|
3646
|
+
if (input.trim() === "" || parsed !== null) {
|
|
3647
|
+
onValueChange(parsed === null ? null : clampMetricValue(parsed, min, max));
|
|
3648
|
+
}
|
|
3649
|
+
};
|
|
3650
|
+
const stepBy = (direction) => {
|
|
3651
|
+
const next = stepMetricValue(value, direction, { min, max, step });
|
|
3652
|
+
onValueChange(next);
|
|
3653
|
+
setText(format(next));
|
|
3654
|
+
};
|
|
3655
|
+
const onKeyDown = (event) => {
|
|
3656
|
+
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
3657
|
+
event.preventDefault();
|
|
3658
|
+
stepBy(event.key === "ArrowUp" ? 1 : -1);
|
|
3659
|
+
}
|
|
3660
|
+
};
|
|
3661
|
+
const onChange = (event) => {
|
|
3662
|
+
setText(event.currentTarget.value);
|
|
3663
|
+
commit(event.currentTarget.value);
|
|
3664
|
+
};
|
|
3665
|
+
const onBlur = (event) => {
|
|
3666
|
+
const parsed = parse(event.currentTarget.value);
|
|
3667
|
+
setText(parsed === null && event.currentTarget.value.trim() !== "" ? format(value) : format(parsed));
|
|
3668
|
+
};
|
|
3669
|
+
return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: cx("uix-metric-input", invalid && "uix-metric-input--invalid", className), children: [
|
|
3670
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("label", { className: "uix-field__label", htmlFor: id, children: label }),
|
|
3671
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "uix-metric-input__control", children: [
|
|
3672
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
3673
|
+
Input,
|
|
3674
|
+
{
|
|
3675
|
+
id,
|
|
3676
|
+
value: text,
|
|
3677
|
+
onChange,
|
|
3678
|
+
onBlur,
|
|
3679
|
+
onKeyDown,
|
|
3680
|
+
inputMode: "decimal",
|
|
3681
|
+
role: "spinbutton",
|
|
3682
|
+
"aria-valuenow": value ?? void 0,
|
|
3683
|
+
"aria-valuemin": min,
|
|
3684
|
+
"aria-valuemax": max,
|
|
3685
|
+
"aria-describedby": invalid && errorMessage ? errorId : void 0,
|
|
3686
|
+
invalid,
|
|
3687
|
+
disabled,
|
|
3688
|
+
readOnly
|
|
3689
|
+
}
|
|
3690
|
+
),
|
|
3691
|
+
unit && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { className: "uix-metric-input__unit", "aria-hidden": "true", children: unit }),
|
|
3692
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { className: "uix-metric-input__steps", children: [
|
|
3693
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(1), disabled: disabled || readOnly, "aria-label": `Increase ${label}`, children: "+" }),
|
|
3694
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(-1), disabled: disabled || readOnly, "aria-label": `Decrease ${label}`, children: "\u2212" })
|
|
3695
|
+
] })
|
|
3696
|
+
] }),
|
|
3697
|
+
unit && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { className: "uix-visually-hidden", children: [
|
|
3698
|
+
"Unit: ",
|
|
3699
|
+
unit
|
|
3700
|
+
] }),
|
|
3701
|
+
invalid && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { id: errorId, className: "uix-field__error", children: errorMessage })
|
|
3702
|
+
] });
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3705
|
+
// src/components/LicensePositionBar.tsx
|
|
3706
|
+
var import_jsx_runtime53 = require("react/jsx-runtime");
|
|
3707
|
+
var defaultCapacityFormat = (value) => new Intl.NumberFormat().format(value);
|
|
3708
|
+
function LicensePositionBar({
|
|
3709
|
+
consumed,
|
|
3710
|
+
entitled,
|
|
3711
|
+
consumedLabel = "Consumed",
|
|
3712
|
+
entitledLabel = "Entitled",
|
|
3713
|
+
availableLabel = "available",
|
|
3714
|
+
overLabel = "over limit",
|
|
3715
|
+
unit = "",
|
|
3716
|
+
formatValue = defaultCapacityFormat,
|
|
3717
|
+
className,
|
|
3718
|
+
...props
|
|
3719
|
+
}) {
|
|
3720
|
+
const safeConsumed = Math.max(0, consumed);
|
|
3721
|
+
const safeEntitled = Math.max(0, entitled);
|
|
3722
|
+
const over = Math.max(0, safeConsumed - safeEntitled);
|
|
3723
|
+
const available = Math.max(0, safeEntitled - safeConsumed);
|
|
3724
|
+
const percent = safeEntitled === 0 ? safeConsumed > 0 ? 100 : 0 : safeConsumed / safeEntitled * 100;
|
|
3725
|
+
const format = (value) => `${formatValue(value)}${unit ? ` ${unit}` : ""}`;
|
|
3726
|
+
const summary = `${consumedLabel}: ${format(safeConsumed)}. ${entitledLabel}: ${format(safeEntitled)}. ${over > 0 ? `${format(over)} ${overLabel}` : `${format(available)} ${availableLabel}`}.`;
|
|
3727
|
+
return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: cx("uix-license-position", over > 0 && "uix-license-position--over", className), ...props, children: [
|
|
3728
|
+
/* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "uix-license-position__header", children: [
|
|
3729
|
+
/* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("span", { className: "uix-license-position__value", children: [
|
|
3730
|
+
format(safeConsumed),
|
|
3731
|
+
" / ",
|
|
3732
|
+
format(safeEntitled)
|
|
3733
|
+
] }),
|
|
3734
|
+
/* @__PURE__ */ (0, import_jsx_runtime53.jsx)(StatusPill, { tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", children: over > 0 ? `${format(over)} ${overLabel}` : `${format(available)} ${availableLabel}` })
|
|
3735
|
+
] }),
|
|
3736
|
+
/* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "uix-license-position__track", children: [
|
|
3737
|
+
/* @__PURE__ */ (0, import_jsx_runtime53.jsx)(Meter, { value: Math.min(100, percent), tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", "aria-label": summary, "aria-valuetext": summary }),
|
|
3738
|
+
over > 0 && /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
|
|
3739
|
+
"span",
|
|
3740
|
+
{
|
|
3741
|
+
className: "uix-license-position__overflow",
|
|
3742
|
+
style: { width: `${Math.min(100, safeEntitled === 0 ? 100 : over / safeEntitled * 100)}%` },
|
|
3743
|
+
"aria-hidden": "true"
|
|
3744
|
+
}
|
|
3745
|
+
)
|
|
3746
|
+
] }),
|
|
3747
|
+
/* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "uix-visually-hidden", children: summary })
|
|
3748
|
+
] });
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
// src/components/BrandProfiles.tsx
|
|
3752
|
+
var import_react30 = require("react");
|
|
3753
|
+
|
|
3754
|
+
// src/color-model.ts
|
|
3755
|
+
var clamp2 = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
3756
|
+
function normalizeHex(value) {
|
|
3757
|
+
const raw = value.trim().replace(/^#/, "");
|
|
3758
|
+
if (/^[\da-f]{3}$/i.test(raw)) return `#${raw.split("").map((char) => char + char).join("").toUpperCase()}`;
|
|
3759
|
+
if (/^[\da-f]{6}$/i.test(raw)) return `#${raw.toUpperCase()}`;
|
|
3760
|
+
return void 0;
|
|
3761
|
+
}
|
|
3762
|
+
function hexToRgb(value) {
|
|
3763
|
+
const hex = normalizeHex(value);
|
|
3764
|
+
if (!hex) return void 0;
|
|
3765
|
+
return {
|
|
3766
|
+
r: Number.parseInt(hex.slice(1, 3), 16),
|
|
3767
|
+
g: Number.parseInt(hex.slice(3, 5), 16),
|
|
3768
|
+
b: Number.parseInt(hex.slice(5, 7), 16)
|
|
3769
|
+
};
|
|
3770
|
+
}
|
|
3771
|
+
function rgbToHex({ r, g, b }) {
|
|
3772
|
+
return `#${[r, g, b].map((value) => Math.round(clamp2(value, 0, 255)).toString(16).padStart(2, "0")).join("").toUpperCase()}`;
|
|
3773
|
+
}
|
|
3774
|
+
function rgbToHsv({ r, g, b }) {
|
|
3775
|
+
const values = [r, g, b].map((value) => clamp2(value, 0, 255) / 255);
|
|
3776
|
+
const max = Math.max(...values);
|
|
3777
|
+
const min = Math.min(...values);
|
|
3778
|
+
const delta = max - min;
|
|
3779
|
+
let h = 0;
|
|
3780
|
+
if (delta !== 0) {
|
|
3781
|
+
if (max === values[0]) h = 60 * ((values[1] - values[2]) / delta % 6);
|
|
3782
|
+
else if (max === values[1]) h = 60 * ((values[2] - values[0]) / delta + 2);
|
|
3783
|
+
else h = 60 * ((values[0] - values[1]) / delta + 4);
|
|
3784
|
+
}
|
|
3785
|
+
if (h < 0) h += 360;
|
|
3786
|
+
return { h, s: max === 0 ? 0 : delta / max, v: max };
|
|
3787
|
+
}
|
|
3788
|
+
function hsvToRgb({ h, s, v }) {
|
|
3789
|
+
const hue = (h % 360 + 360) % 360;
|
|
3790
|
+
const sat = clamp2(s, 0, 1);
|
|
3791
|
+
const value = clamp2(v, 0, 1);
|
|
3792
|
+
const chroma = value * sat;
|
|
3793
|
+
const x = chroma * (1 - Math.abs(hue / 60 % 2 - 1));
|
|
3794
|
+
const m = value - chroma;
|
|
3795
|
+
const sector = Math.floor(hue / 60);
|
|
3796
|
+
const rgb = sector === 0 ? [chroma, x, 0] : sector === 1 ? [x, chroma, 0] : sector === 2 ? [0, chroma, x] : sector === 3 ? [0, x, chroma] : sector === 4 ? [x, 0, chroma] : [chroma, 0, x];
|
|
3797
|
+
return { r: (rgb[0] + m) * 255, g: (rgb[1] + m) * 255, b: (rgb[2] + m) * 255 };
|
|
3798
|
+
}
|
|
3799
|
+
function hsvToHex(value) {
|
|
3800
|
+
return rgbToHex(hsvToRgb(value));
|
|
3801
|
+
}
|
|
3802
|
+
function relativeLuminance({ r, g, b }) {
|
|
3803
|
+
const linear = [r, g, b].map((channel) => {
|
|
3804
|
+
const value = channel / 255;
|
|
3805
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
3806
|
+
});
|
|
3807
|
+
return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2];
|
|
3808
|
+
}
|
|
3809
|
+
function contrastRatio(background, foreground) {
|
|
3810
|
+
const bg = hexToRgb(background);
|
|
3811
|
+
const fg = hexToRgb(foreground);
|
|
3812
|
+
if (!bg || !fg) return void 0;
|
|
3813
|
+
const [light, dark] = [relativeLuminance(bg), relativeLuminance(fg)].sort((a, b) => b - a);
|
|
3814
|
+
return (light + 0.05) / (dark + 0.05);
|
|
3815
|
+
}
|
|
3816
|
+
function meetsContrast(background, foreground, minimum = 4.5) {
|
|
3817
|
+
return (contrastRatio(background, foreground) ?? 0) >= minimum;
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
// src/components/ColorPicker.tsx
|
|
3821
|
+
var import_react29 = require("react");
|
|
3822
|
+
var import_jsx_runtime54 = require("react/jsx-runtime");
|
|
3823
|
+
var EMPTY_COLORS = [];
|
|
3824
|
+
function ColorPicker({
|
|
3825
|
+
value,
|
|
3826
|
+
onChange,
|
|
3827
|
+
foreground,
|
|
3828
|
+
presets = EMPTY_COLORS,
|
|
3829
|
+
recent = EMPTY_COLORS,
|
|
3830
|
+
minimumContrast = 4.5,
|
|
3831
|
+
label = "Choose color",
|
|
3832
|
+
disabled,
|
|
3833
|
+
className
|
|
3834
|
+
}) {
|
|
3835
|
+
const id = (0, import_react29.useId)();
|
|
3836
|
+
const triggerRef = (0, import_react29.useRef)(null);
|
|
3837
|
+
const dialogRef = (0, import_react29.useRef)(null);
|
|
3838
|
+
const [open, setOpen] = (0, import_react29.useState)(false);
|
|
3839
|
+
const normalized = normalizeHex(value) ?? "#000000";
|
|
3840
|
+
const [hexText, setHexText] = (0, import_react29.useState)(normalized);
|
|
3841
|
+
const hsv = (0, import_react29.useMemo)(() => rgbToHsv(hexToRgb(normalized)), [normalized]);
|
|
3842
|
+
const ratio = contrastRatio(normalized, foreground) ?? 0;
|
|
3843
|
+
const passes = ratio >= minimumContrast;
|
|
3844
|
+
(0, import_react29.useEffect)(() => setHexText(normalized), [normalized]);
|
|
3845
|
+
(0, import_react29.useEffect)(() => {
|
|
3846
|
+
if (!open) return;
|
|
3847
|
+
const dialog = dialogRef.current;
|
|
3848
|
+
dialog?.querySelector("input,button")?.focus();
|
|
3849
|
+
const onPointer = (event) => {
|
|
3850
|
+
if (!dialog?.contains(event.target) && !triggerRef.current?.contains(event.target)) setOpen(false);
|
|
3851
|
+
};
|
|
3852
|
+
document.addEventListener("mousedown", onPointer);
|
|
3853
|
+
return () => document.removeEventListener("mousedown", onPointer);
|
|
3854
|
+
}, [open]);
|
|
3855
|
+
const close = () => {
|
|
3856
|
+
setOpen(false);
|
|
3857
|
+
requestAnimationFrame(() => triggerRef.current?.focus());
|
|
3858
|
+
};
|
|
3859
|
+
const onDialogKeyDown = (event) => {
|
|
3860
|
+
if (event.key === "Escape") {
|
|
3861
|
+
event.preventDefault();
|
|
3862
|
+
close();
|
|
3863
|
+
return;
|
|
3864
|
+
}
|
|
3865
|
+
if (event.key !== "Tab") return;
|
|
3866
|
+
const focusable = Array.from(dialogRef.current?.querySelectorAll('button:not([disabled]),input:not([disabled]),[tabindex="0"]') ?? []);
|
|
3867
|
+
if (!focusable.length) return;
|
|
3868
|
+
const first = focusable[0];
|
|
3869
|
+
const last = focusable[focusable.length - 1];
|
|
3870
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
3871
|
+
event.preventDefault();
|
|
3872
|
+
last.focus();
|
|
3873
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
3874
|
+
event.preventDefault();
|
|
3875
|
+
first.focus();
|
|
3876
|
+
}
|
|
3877
|
+
};
|
|
3878
|
+
const updateHsv = (part) => onChange(hsvToHex({ ...hsv, ...part }));
|
|
3879
|
+
const commitHex = () => {
|
|
3880
|
+
const next = normalizeHex(hexText);
|
|
3881
|
+
if (next) onChange(next);
|
|
3882
|
+
};
|
|
3883
|
+
const choose = (candidate) => {
|
|
3884
|
+
const next = normalizeHex(candidate);
|
|
3885
|
+
if (next) onChange(next);
|
|
3886
|
+
};
|
|
3887
|
+
const sampleStyle = { backgroundColor: normalized };
|
|
3888
|
+
return /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: cx("uix-color-picker", className), children: [
|
|
3889
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(
|
|
3890
|
+
"button",
|
|
3891
|
+
{
|
|
3892
|
+
ref: triggerRef,
|
|
3893
|
+
type: "button",
|
|
3894
|
+
className: "uix-color-picker__trigger",
|
|
3895
|
+
"aria-label": `${label}: ${normalized}`,
|
|
3896
|
+
"aria-expanded": open,
|
|
3897
|
+
"aria-controls": `${id}-dialog`,
|
|
3898
|
+
onClick: () => setOpen((current) => !current),
|
|
3899
|
+
disabled,
|
|
3900
|
+
children: [
|
|
3901
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "uix-color-picker__swatch", style: sampleStyle, "aria-hidden": "true" }),
|
|
3902
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { children: normalized })
|
|
3903
|
+
]
|
|
3904
|
+
}
|
|
3905
|
+
),
|
|
3906
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(
|
|
3907
|
+
"div",
|
|
3908
|
+
{
|
|
3909
|
+
ref: dialogRef,
|
|
3910
|
+
id: `${id}-dialog`,
|
|
3911
|
+
role: "dialog",
|
|
3912
|
+
"aria-modal": "true",
|
|
3913
|
+
"aria-label": label,
|
|
3914
|
+
className: "uix-color-picker__popover",
|
|
3915
|
+
onKeyDown: onDialogKeyDown,
|
|
3916
|
+
children: [
|
|
3917
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { className: "uix-color-picker__preview", style: sampleStyle, children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { style: { color: foreground }, children: "Aa" }) }),
|
|
3918
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("label", { className: "uix-color-picker__range", children: [
|
|
3919
|
+
"Hue ",
|
|
3920
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("span", { children: [
|
|
3921
|
+
Math.round(hsv.h),
|
|
3922
|
+
"\xB0"
|
|
3923
|
+
] }),
|
|
3924
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("input", { type: "range", min: "0", max: "359", value: Math.round(hsv.h), onChange: (event) => updateHsv({ h: Number(event.currentTarget.value) }) })
|
|
3925
|
+
] }),
|
|
3926
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("label", { className: "uix-color-picker__range", children: [
|
|
3927
|
+
"Saturation ",
|
|
3928
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("span", { children: [
|
|
3929
|
+
Math.round(hsv.s * 100),
|
|
3930
|
+
"%"
|
|
3931
|
+
] }),
|
|
3932
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.s * 100), onChange: (event) => updateHsv({ s: Number(event.currentTarget.value) / 100 }) })
|
|
3933
|
+
] }),
|
|
3934
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("label", { className: "uix-color-picker__range", children: [
|
|
3935
|
+
"Brightness ",
|
|
3936
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("span", { children: [
|
|
3937
|
+
Math.round(hsv.v * 100),
|
|
3938
|
+
"%"
|
|
3939
|
+
] }),
|
|
3940
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.v * 100), onChange: (event) => updateHsv({ v: Number(event.currentTarget.value) / 100 }) })
|
|
3941
|
+
] }),
|
|
3942
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("label", { className: "uix-color-picker__hex", children: [
|
|
3943
|
+
"Hex",
|
|
3944
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("span", { className: "uix-color-picker__hex-row", children: [
|
|
3945
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("input", { className: "uix-input", value: hexText, onChange: (event) => setHexText(event.currentTarget.value), onBlur: commitHex, onKeyDown: (event) => {
|
|
3946
|
+
if (event.key === "Enter") commitHex();
|
|
3947
|
+
}, "aria-invalid": !normalizeHex(hexText) }),
|
|
3948
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", onClick: commitHex, disabled: !normalizeHex(hexText), children: "Set" })
|
|
3949
|
+
] })
|
|
3950
|
+
] }),
|
|
3951
|
+
(presets.length > 0 || recent.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "uix-color-picker__palettes", children: [
|
|
3952
|
+
presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("fieldset", { children: [
|
|
3953
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("legend", { children: "Presets" }),
|
|
3954
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { children: presets.map((color) => /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use preset ${color}` }, color)) })
|
|
3955
|
+
] }),
|
|
3956
|
+
recent.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("fieldset", { children: [
|
|
3957
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("legend", { children: "Recent" }),
|
|
3958
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { children: recent.map((color) => /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use recent color ${color}` }, color)) })
|
|
3959
|
+
] })
|
|
3960
|
+
] }),
|
|
3961
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("p", { className: cx("uix-color-picker__contrast", passes ? "uix-color-picker__contrast--pass" : "uix-color-picker__contrast--fail"), role: "status", "aria-live": "polite", children: [
|
|
3962
|
+
"Contrast ",
|
|
3963
|
+
ratio.toFixed(2),
|
|
3964
|
+
":1 \u2014 ",
|
|
3965
|
+
passes ? `passes WCAG AA (${minimumContrast}:1)` : `does not meet WCAG AA (${minimumContrast}:1)`
|
|
3966
|
+
] }),
|
|
3967
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", onClick: close, children: "Done" })
|
|
3968
|
+
]
|
|
3969
|
+
}
|
|
3970
|
+
)
|
|
3971
|
+
] });
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
// src/components/BrandProfiles.tsx
|
|
3975
|
+
var import_jsx_runtime55 = require("react/jsx-runtime");
|
|
3976
|
+
var PROFILE_PROPERTIES = {
|
|
3977
|
+
brand: "--uix-brand",
|
|
3978
|
+
brandForeground: "--uix-brand-fg",
|
|
3979
|
+
sans: "--uix-font-sans",
|
|
3980
|
+
heading: "--uix-font-heading",
|
|
3981
|
+
mono: "--uix-font-mono"
|
|
3982
|
+
};
|
|
3983
|
+
var EMPTY_PROFILES = [];
|
|
3984
|
+
var EMPTY_COLORS2 = [];
|
|
3985
|
+
function applyBrandProfile(profile, target) {
|
|
3986
|
+
const element = target ?? (typeof document === "undefined" ? void 0 : document.documentElement);
|
|
3987
|
+
if (!element) return void 0;
|
|
3988
|
+
const brand = normalizeHex(profile.brand);
|
|
3989
|
+
const foreground = normalizeHex(profile.brandForeground);
|
|
3990
|
+
if (!brand || !foreground) throw new TypeError("Brand profiles require valid hex brand and foreground colors.");
|
|
3991
|
+
const properties = {};
|
|
3992
|
+
Object.values(PROFILE_PROPERTIES).forEach((property) => {
|
|
3993
|
+
properties[property] = element.style.getPropertyValue(property);
|
|
3994
|
+
});
|
|
3995
|
+
const snapshot = {
|
|
3996
|
+
profileId: element.dataset.uixBrandProfile,
|
|
3997
|
+
properties,
|
|
3998
|
+
logoSrc: element.dataset.uixBrandLogoSrc,
|
|
3999
|
+
logoAlt: element.dataset.uixBrandLogoAlt
|
|
4000
|
+
};
|
|
4001
|
+
element.style.setProperty(PROFILE_PROPERTIES.brand, brand);
|
|
4002
|
+
element.style.setProperty(PROFILE_PROPERTIES.brandForeground, foreground);
|
|
4003
|
+
const typography = profile.typography ?? {};
|
|
4004
|
+
for (const [key, property] of Object.entries({ sans: PROFILE_PROPERTIES.sans, heading: PROFILE_PROPERTIES.heading, mono: PROFILE_PROPERTIES.mono })) {
|
|
4005
|
+
const value = typography[key];
|
|
4006
|
+
if (value) element.style.setProperty(property, value);
|
|
4007
|
+
else element.style.removeProperty(property);
|
|
4008
|
+
}
|
|
4009
|
+
element.dataset.uixBrandProfile = profile.id;
|
|
4010
|
+
if (profile.logo) {
|
|
4011
|
+
element.dataset.uixBrandLogoSrc = profile.logo.src;
|
|
4012
|
+
element.dataset.uixBrandLogoAlt = profile.logo.alt;
|
|
4013
|
+
} else {
|
|
4014
|
+
delete element.dataset.uixBrandLogoSrc;
|
|
4015
|
+
delete element.dataset.uixBrandLogoAlt;
|
|
4016
|
+
}
|
|
4017
|
+
return snapshot;
|
|
4018
|
+
}
|
|
4019
|
+
function restoreBrandProfile(snapshot, target) {
|
|
4020
|
+
const element = target ?? (typeof document === "undefined" ? void 0 : document.documentElement);
|
|
4021
|
+
if (!element) return;
|
|
4022
|
+
Object.entries(snapshot.properties).forEach(([property, value]) => {
|
|
4023
|
+
if (value) element.style.setProperty(property, value);
|
|
4024
|
+
else element.style.removeProperty(property);
|
|
4025
|
+
});
|
|
4026
|
+
if (snapshot.profileId) element.dataset.uixBrandProfile = snapshot.profileId;
|
|
4027
|
+
else delete element.dataset.uixBrandProfile;
|
|
4028
|
+
if (snapshot.logoSrc) element.dataset.uixBrandLogoSrc = snapshot.logoSrc;
|
|
4029
|
+
else delete element.dataset.uixBrandLogoSrc;
|
|
4030
|
+
if (snapshot.logoAlt) element.dataset.uixBrandLogoAlt = snapshot.logoAlt;
|
|
4031
|
+
else delete element.dataset.uixBrandLogoAlt;
|
|
4032
|
+
}
|
|
4033
|
+
function BrandProfileEditor({
|
|
4034
|
+
value,
|
|
4035
|
+
onChange,
|
|
4036
|
+
profiles = EMPTY_PROFILES,
|
|
4037
|
+
onSelectProfile,
|
|
4038
|
+
onApply,
|
|
4039
|
+
applyTarget,
|
|
4040
|
+
onLogoFile,
|
|
4041
|
+
colorPresets = EMPTY_COLORS2,
|
|
4042
|
+
className
|
|
4043
|
+
}) {
|
|
4044
|
+
const [announcement, setAnnouncement] = (0, import_react30.useState)("");
|
|
4045
|
+
const update = (patch) => onChange({ ...value, ...patch });
|
|
4046
|
+
const apply = () => {
|
|
4047
|
+
if (applyTarget !== null) applyBrandProfile(value, applyTarget);
|
|
4048
|
+
onApply?.(value);
|
|
4049
|
+
setAnnouncement(`${value.name} applied.`);
|
|
4050
|
+
};
|
|
4051
|
+
const onFile = (event) => {
|
|
4052
|
+
const file = event.currentTarget.files?.[0];
|
|
4053
|
+
if (file) onLogoFile?.(file);
|
|
4054
|
+
};
|
|
4055
|
+
const previewStyle = {
|
|
4056
|
+
"--uix-brand": normalizeHex(value.brand) ?? value.brand,
|
|
4057
|
+
"--uix-brand-fg": normalizeHex(value.brandForeground) ?? value.brandForeground,
|
|
4058
|
+
...value.typography?.sans ? { "--uix-font-sans": value.typography.sans } : {},
|
|
4059
|
+
...value.typography?.heading ? { "--uix-font-heading": value.typography.heading } : {}
|
|
4060
|
+
};
|
|
4061
|
+
return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("section", { className: cx("uix-brand-profiles", className), "aria-label": "Brand profile editor", children: [
|
|
4062
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "uix-brand-profiles__form", children: [
|
|
4063
|
+
profiles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("label", { className: "uix-field__label", children: [
|
|
4064
|
+
"Profile",
|
|
4065
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("select", { className: "uix-select", value: value.id, onChange: (event) => {
|
|
4066
|
+
const selected = profiles.find((profile) => profile.id === event.currentTarget.value);
|
|
4067
|
+
if (selected) onSelectProfile?.(selected);
|
|
4068
|
+
}, children: profiles.map((profile) => /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("option", { value: profile.id, children: profile.name }, profile.id)) })
|
|
4069
|
+
] }),
|
|
4070
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("label", { className: "uix-field__label", children: [
|
|
4071
|
+
"Name",
|
|
4072
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("input", { className: "uix-input", value: value.name, onChange: (event) => update({ name: event.currentTarget.value }) })
|
|
4073
|
+
] }),
|
|
4074
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "uix-brand-profiles__colors", children: [
|
|
4075
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("label", { children: [
|
|
4076
|
+
"Brand",
|
|
4077
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(ColorPicker, { label: "Brand color", value: value.brand, foreground: value.brandForeground, presets: colorPresets, onChange: (brand) => update({ brand }) })
|
|
4078
|
+
] }),
|
|
4079
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("label", { children: [
|
|
4080
|
+
"On brand",
|
|
4081
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(ColorPicker, { label: "Brand foreground color", value: value.brandForeground, foreground: value.brand, presets: colorPresets, onChange: (brandForeground) => update({ brandForeground }) })
|
|
4082
|
+
] })
|
|
4083
|
+
] }),
|
|
4084
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("label", { className: "uix-field__label", children: [
|
|
4085
|
+
"Body font",
|
|
4086
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("input", { className: "uix-input", value: value.typography?.sans ?? "", onChange: (event) => update({ typography: { ...value.typography, sans: event.currentTarget.value || void 0 } }) })
|
|
4087
|
+
] }),
|
|
4088
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("label", { className: "uix-field__label", children: [
|
|
4089
|
+
"Heading font",
|
|
4090
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("input", { className: "uix-input", value: value.typography?.heading ?? "", onChange: (event) => update({ typography: { ...value.typography, heading: event.currentTarget.value || void 0 } }) })
|
|
4091
|
+
] }),
|
|
4092
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("label", { className: "uix-file-upload uix-brand-profiles__upload", children: [
|
|
4093
|
+
"Logo file",
|
|
4094
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("input", { type: "file", accept: "image/*", onChange: onFile })
|
|
4095
|
+
] }),
|
|
4096
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary", onClick: apply, children: "Apply profile" }),
|
|
4097
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
|
|
4098
|
+
] }),
|
|
4099
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "uix-brand-profiles__preview", style: previewStyle, "aria-label": `Live preview of ${value.name}`, children: [
|
|
4100
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "uix-brand-profiles__preview-header", children: [
|
|
4101
|
+
value.logo ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("img", { src: value.logo.src, alt: value.logo.alt }) : /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "uix-brand-profiles__logo-placeholder", children: "Logo" }),
|
|
4102
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("strong", { children: value.name || "Brand preview" })
|
|
4103
|
+
] }),
|
|
4104
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "uix-brand-profiles__preview-body", children: [
|
|
4105
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("h3", { children: "Readable, serializable branding" }),
|
|
4106
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { children: "Accent, links, rings, and muted states continue through the existing UIx token chain." }),
|
|
4107
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary", children: "Primary action" }),
|
|
4108
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("a", { href: "#brand-profile-preview", children: "Preview link" })
|
|
4109
|
+
] })
|
|
4110
|
+
] })
|
|
4111
|
+
] });
|
|
4112
|
+
}
|
|
4113
|
+
var BrandProfiles = BrandProfileEditor;
|
|
4114
|
+
|
|
4115
|
+
// src/components/DiffViewer.tsx
|
|
4116
|
+
var import_react31 = require("react");
|
|
4117
|
+
|
|
4118
|
+
// src/diff-model.ts
|
|
4119
|
+
function isJsonObject(value) {
|
|
4120
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4121
|
+
}
|
|
4122
|
+
function jsonEqual(a, b) {
|
|
4123
|
+
if (a === b) return true;
|
|
4124
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
4125
|
+
return a.length === b.length && a.every((value, index) => jsonEqual(value, b[index]));
|
|
4126
|
+
}
|
|
4127
|
+
if (isJsonObject(a) && isJsonObject(b)) {
|
|
4128
|
+
const aKeys = Object.keys(a).sort();
|
|
4129
|
+
const bKeys = Object.keys(b).sort();
|
|
4130
|
+
return aKeys.length === bKeys.length && aKeys.every((key, index) => key === bKeys[index] && jsonEqual(a[key], b[key]));
|
|
4131
|
+
}
|
|
4132
|
+
return false;
|
|
4133
|
+
}
|
|
4134
|
+
function classify(base, current, incoming) {
|
|
4135
|
+
if (base === void 0) return "added";
|
|
4136
|
+
if (incoming === void 0) return "removed";
|
|
4137
|
+
const currentChanged = !jsonEqual(base, current);
|
|
4138
|
+
const incomingChanged = !jsonEqual(base, incoming);
|
|
4139
|
+
if (currentChanged && incomingChanged && !jsonEqual(current, incoming)) return "conflicted";
|
|
4140
|
+
return "changed";
|
|
4141
|
+
}
|
|
4142
|
+
function buildThreeWayDiff(base, current, incoming, path = "$") {
|
|
4143
|
+
if (jsonEqual(current, incoming)) return [];
|
|
4144
|
+
if (isJsonObject(base) || isJsonObject(current) || isJsonObject(incoming)) {
|
|
4145
|
+
const objects = [base, current, incoming].filter(isJsonObject);
|
|
4146
|
+
const keys = [...new Set(objects.flatMap((value) => Object.keys(value)))].sort();
|
|
4147
|
+
return keys.flatMap((key) => buildThreeWayDiff(
|
|
4148
|
+
isJsonObject(base) ? base[key] : void 0,
|
|
4149
|
+
isJsonObject(current) ? current[key] : void 0,
|
|
4150
|
+
isJsonObject(incoming) ? incoming[key] : void 0,
|
|
4151
|
+
`${path}.${key}`
|
|
4152
|
+
));
|
|
4153
|
+
}
|
|
4154
|
+
return [{ path, kind: classify(base, current, incoming), base, current, incoming }];
|
|
4155
|
+
}
|
|
4156
|
+
function summarizeDiff(entries, resolutions = {}) {
|
|
4157
|
+
const summary = { added: 0, removed: 0, changed: 0, conflicted: 0, resolved: 0, pending: 0 };
|
|
4158
|
+
entries.forEach((entry) => {
|
|
4159
|
+
summary[entry.kind] += 1;
|
|
4160
|
+
if ((resolutions[entry.path] ?? "pending") === "pending") summary.pending += 1;
|
|
4161
|
+
else summary.resolved += 1;
|
|
4162
|
+
});
|
|
4163
|
+
return summary;
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
// src/components/DiffViewer.tsx
|
|
4167
|
+
var import_jsx_runtime56 = require("react/jsx-runtime");
|
|
4168
|
+
var GROUPS = [
|
|
4169
|
+
{ kind: "conflicted", label: "Conflicted" },
|
|
4170
|
+
{ kind: "changed", label: "Changed" },
|
|
4171
|
+
{ kind: "added", label: "Added" },
|
|
4172
|
+
{ kind: "removed", label: "Removed" }
|
|
4173
|
+
];
|
|
4174
|
+
var DEFAULT_LABELS = {};
|
|
4175
|
+
var renderValue = (value) => value === void 0 ? "Not present" : JSON.stringify(value, null, 2);
|
|
4176
|
+
function DiffViewer({
|
|
4177
|
+
base,
|
|
4178
|
+
current,
|
|
4179
|
+
incoming,
|
|
4180
|
+
entries: providedEntries,
|
|
4181
|
+
resolutions: controlledResolutions,
|
|
4182
|
+
onResolutionChange,
|
|
4183
|
+
labels = DEFAULT_LABELS,
|
|
4184
|
+
className
|
|
4185
|
+
}) {
|
|
4186
|
+
const id = (0, import_react31.useId)();
|
|
4187
|
+
const entries = (0, import_react31.useMemo)(() => providedEntries ?? buildThreeWayDiff(base, current, incoming), [providedEntries, base, current, incoming]);
|
|
4188
|
+
const [internalResolutions, setInternalResolutions] = (0, import_react31.useState)({});
|
|
4189
|
+
const resolutions = controlledResolutions ?? internalResolutions;
|
|
4190
|
+
const summary = summarizeDiff(entries, resolutions);
|
|
4191
|
+
const resolve2 = (path, resolution) => {
|
|
4192
|
+
if (controlledResolutions === void 0) setInternalResolutions((currentState) => ({ ...currentState, [path]: resolution }));
|
|
4193
|
+
onResolutionChange?.(path, resolution);
|
|
4194
|
+
};
|
|
4195
|
+
if (entries.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("section", { className: cx("uix-diff-viewer uix-diff-viewer--empty", className), "aria-label": "Configuration differences", children: [
|
|
4196
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("h3", { children: "No differences" }),
|
|
4197
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("p", { children: "Base, current, and incoming values are aligned." })
|
|
4198
|
+
] });
|
|
4199
|
+
return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("section", { className: cx("uix-diff-viewer", className), "aria-label": "Configuration differences", children: [
|
|
4200
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "uix-diff-viewer__summary", "aria-label": "Difference summary", children: [
|
|
4201
|
+
GROUPS.map((group) => /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("span", { children: [
|
|
4202
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("b", { children: summary[group.kind] }),
|
|
4203
|
+
" ",
|
|
4204
|
+
group.label.toLowerCase()
|
|
4205
|
+
] }, group.kind)),
|
|
4206
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("span", { children: [
|
|
4207
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("b", { children: summary.resolved }),
|
|
4208
|
+
" resolved"
|
|
4209
|
+
] }),
|
|
4210
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("span", { children: [
|
|
4211
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("b", { children: summary.pending }),
|
|
4212
|
+
" pending"
|
|
4213
|
+
] })
|
|
4214
|
+
] }),
|
|
4215
|
+
GROUPS.map((group) => {
|
|
4216
|
+
const grouped = entries.filter((entry) => entry.kind === group.kind);
|
|
4217
|
+
if (!grouped.length) return null;
|
|
4218
|
+
return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("section", { className: "uix-diff-viewer__group", "aria-labelledby": `${id}-${group.kind}`, children: [
|
|
4219
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("h3", { id: `${id}-${group.kind}`, children: [
|
|
4220
|
+
group.label,
|
|
4221
|
+
" ",
|
|
4222
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { children: grouped.length })
|
|
4223
|
+
] }),
|
|
4224
|
+
grouped.map((entry) => {
|
|
4225
|
+
const resolution = resolutions[entry.path] ?? "pending";
|
|
4226
|
+
return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("details", { className: "uix-diff-viewer__entry", "data-kind": entry.kind, open: entry.kind === "conflicted", children: [
|
|
4227
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("summary", { children: [
|
|
4228
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("code", { children: entry.path }),
|
|
4229
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)(StatusPill, { tone: resolution === "pending" ? "warning" : "success", children: resolution })
|
|
4230
|
+
] }),
|
|
4231
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("div", { className: "uix-diff-viewer__versions", children: ["base", "current", "incoming"].map((version) => /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { role: "region", "aria-label": `${labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}`} value for ${entry.path}`, children: [
|
|
4232
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("strong", { children: labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}` }),
|
|
4233
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("pre", { children: renderValue(entry[version]) })
|
|
4234
|
+
] }, version)) }),
|
|
4235
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "uix-diff-viewer__actions", "aria-label": `Resolve ${entry.path}`, children: [
|
|
4236
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", "aria-pressed": resolution === "accept", onClick: () => resolve2(entry.path, "accept"), children: "Accept incoming" }),
|
|
4237
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", "aria-pressed": resolution === "skip", onClick: () => resolve2(entry.path, "skip"), children: "Keep current" }),
|
|
4238
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", "aria-pressed": resolution === "pending", onClick: () => resolve2(entry.path, "pending"), children: "Mark pending" })
|
|
4239
|
+
] })
|
|
4240
|
+
] }, entry.path);
|
|
4241
|
+
})
|
|
4242
|
+
] }, group.kind);
|
|
4243
|
+
}),
|
|
4244
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("p", { className: "uix-visually-hidden", "aria-live": "polite", children: [
|
|
4245
|
+
summary.resolved,
|
|
4246
|
+
" of ",
|
|
4247
|
+
entries.length,
|
|
4248
|
+
" differences resolved."
|
|
4249
|
+
] })
|
|
4250
|
+
] });
|
|
4251
|
+
}
|
|
2568
4252
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2569
4253
|
0 && (module.exports = {
|
|
2570
4254
|
Alert,
|
|
2571
4255
|
AppShell,
|
|
2572
4256
|
Avatar,
|
|
2573
4257
|
AvatarGroup,
|
|
4258
|
+
BrandProfileEditor,
|
|
4259
|
+
BrandProfiles,
|
|
4260
|
+
BuilderCanvas,
|
|
2574
4261
|
BulkBar,
|
|
2575
4262
|
Button,
|
|
2576
4263
|
ButtonGroup,
|
|
@@ -2578,6 +4265,7 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
|
|
|
2578
4265
|
CellStrong,
|
|
2579
4266
|
CellSub,
|
|
2580
4267
|
Checkbox,
|
|
4268
|
+
ColorPicker,
|
|
2581
4269
|
CommandGroup,
|
|
2582
4270
|
CommandItem,
|
|
2583
4271
|
CommandPalette,
|
|
@@ -2587,9 +4275,11 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
|
|
|
2587
4275
|
ComposerBar,
|
|
2588
4276
|
ContentList,
|
|
2589
4277
|
ContentListItem,
|
|
4278
|
+
DateRangePicker,
|
|
2590
4279
|
DescriptionItem,
|
|
2591
4280
|
DescriptionList,
|
|
2592
4281
|
DetailLayout,
|
|
4282
|
+
DiffViewer,
|
|
2593
4283
|
Drawer,
|
|
2594
4284
|
EmptyState,
|
|
2595
4285
|
ErrorState,
|
|
@@ -2610,11 +4300,14 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
|
|
|
2610
4300
|
KanbanCard,
|
|
2611
4301
|
KanbanColumn,
|
|
2612
4302
|
Label,
|
|
4303
|
+
LicensePositionBar,
|
|
2613
4304
|
List,
|
|
2614
4305
|
ListItem,
|
|
2615
4306
|
LoadingState,
|
|
2616
4307
|
Mark,
|
|
4308
|
+
MatchReview,
|
|
2617
4309
|
Meter,
|
|
4310
|
+
MetricInput,
|
|
2618
4311
|
Modal,
|
|
2619
4312
|
NavFavourites,
|
|
2620
4313
|
NavGroup,
|
|
@@ -2631,9 +4324,12 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
|
|
|
2631
4324
|
Prose,
|
|
2632
4325
|
Radio,
|
|
2633
4326
|
RadioGroup,
|
|
4327
|
+
RelationshipGraph,
|
|
2634
4328
|
ResourceGrid,
|
|
2635
4329
|
RowAction,
|
|
2636
4330
|
RowActions,
|
|
4331
|
+
RuleBuilder,
|
|
4332
|
+
SchedulingCalendar,
|
|
2637
4333
|
SectionHead,
|
|
2638
4334
|
Segmented,
|
|
2639
4335
|
SegmentedOption,
|
|
@@ -2664,28 +4360,68 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
|
|
|
2664
4360
|
Tr,
|
|
2665
4361
|
Tree,
|
|
2666
4362
|
UserChip,
|
|
4363
|
+
addCalendarDays,
|
|
4364
|
+
addCalendarMonths,
|
|
4365
|
+
appendRuleNode,
|
|
4366
|
+
applyBrandProfile,
|
|
2667
4367
|
applyFilters,
|
|
4368
|
+
boundRelationshipGraph,
|
|
4369
|
+
buildMonthGrid,
|
|
4370
|
+
buildThreeWayDiff,
|
|
4371
|
+
clampMetricValue,
|
|
2668
4372
|
clampWidth,
|
|
2669
4373
|
computePosition,
|
|
4374
|
+
contrastRatio,
|
|
2670
4375
|
cx,
|
|
4376
|
+
enumerateDateSpan,
|
|
4377
|
+
findRuleNodeDepth,
|
|
2671
4378
|
flattenTree,
|
|
4379
|
+
hexToRgb,
|
|
2672
4380
|
highlightSegments,
|
|
4381
|
+
hsvToHex,
|
|
4382
|
+
hsvToRgb,
|
|
4383
|
+
isDateInRange,
|
|
4384
|
+
isDateUnavailable,
|
|
4385
|
+
isRuleGroup,
|
|
4386
|
+
layoutRelationshipGraph,
|
|
4387
|
+
mapRuleGroup,
|
|
2673
4388
|
matchFilter,
|
|
4389
|
+
meetsContrast,
|
|
2674
4390
|
mergePinned,
|
|
4391
|
+
moveRuleNode,
|
|
2675
4392
|
multiSort,
|
|
2676
4393
|
nextSortDir,
|
|
4394
|
+
normalizeHex,
|
|
4395
|
+
parseMetricValue,
|
|
2677
4396
|
parseView,
|
|
4397
|
+
relationshipNeighbors,
|
|
4398
|
+
removeRuleNode,
|
|
2678
4399
|
reorder,
|
|
4400
|
+
restoreBrandProfile,
|
|
4401
|
+
rgbToHex,
|
|
4402
|
+
rgbToHsv,
|
|
4403
|
+
ruleDepth,
|
|
2679
4404
|
searchRows,
|
|
2680
4405
|
selectAllState,
|
|
4406
|
+
selectRangeDate,
|
|
2681
4407
|
serializeView,
|
|
2682
4408
|
shouldVirtualize,
|
|
4409
|
+
startOfMonth,
|
|
4410
|
+
stepMetricValue,
|
|
4411
|
+
summarizeDiff,
|
|
4412
|
+
summarizeRule,
|
|
4413
|
+
toDateKey,
|
|
2683
4414
|
toggleId,
|
|
2684
4415
|
togglePage,
|
|
2685
4416
|
toggleSort,
|
|
4417
|
+
traverseRelationshipNode,
|
|
2686
4418
|
treeNav,
|
|
2687
4419
|
useAnchoredPosition,
|
|
2688
4420
|
useDialog,
|
|
2689
4421
|
useTable,
|
|
2690
|
-
|
|
4422
|
+
useVirtualRows,
|
|
4423
|
+
validateRuleDefinition,
|
|
4424
|
+
virtualWindow,
|
|
4425
|
+
zonedDateKey,
|
|
4426
|
+
zonedDateSpan
|
|
2691
4427
|
});
|