@tensor_1/react 2.12.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.
Files changed (42) hide show
  1. package/README.md +16 -0
  2. package/dist/calendar-model.d.ts +31 -0
  3. package/dist/calendar-model.js +89 -0
  4. package/dist/color-model.d.ts +20 -0
  5. package/dist/color-model.js +74 -0
  6. package/dist/components/BrandProfiles.d.ts +45 -0
  7. package/dist/components/BrandProfiles.js +150 -0
  8. package/dist/components/BuilderCanvas.d.ts +34 -0
  9. package/dist/components/BuilderCanvas.js +117 -0
  10. package/dist/components/ColorPicker.d.ts +17 -0
  11. package/dist/components/ColorPicker.js +158 -0
  12. package/dist/components/DateRangePicker.d.ts +23 -0
  13. package/dist/components/DateRangePicker.js +123 -0
  14. package/dist/components/DiffViewer.d.ts +18 -0
  15. package/dist/components/DiffViewer.js +93 -0
  16. package/dist/components/LicensePositionBar.d.ts +17 -0
  17. package/dist/components/LicensePositionBar.js +50 -0
  18. package/dist/components/MatchReview.d.ts +39 -0
  19. package/dist/components/MatchReview.js +120 -0
  20. package/dist/components/MetricInput.d.ts +23 -0
  21. package/dist/components/MetricInput.js +92 -0
  22. package/dist/components/RelationshipGraph.d.ts +30 -0
  23. package/dist/components/RelationshipGraph.js +138 -0
  24. package/dist/components/RuleBuilder.d.ts +45 -0
  25. package/dist/components/RuleBuilder.js +163 -0
  26. package/dist/components/SchedulingCalendar.d.ts +42 -0
  27. package/dist/components/SchedulingCalendar.js +124 -0
  28. package/dist/diff-model.d.ts +23 -0
  29. package/dist/diff-model.js +50 -0
  30. package/dist/index.cjs +1753 -2
  31. package/dist/index.d.cts +439 -1
  32. package/dist/index.d.ts +18 -0
  33. package/dist/index.js +91 -1
  34. package/dist/json-value.d.ts +10 -0
  35. package/dist/json-value.js +19 -0
  36. package/dist/metric-model.d.ts +9 -0
  37. package/dist/metric-model.js +25 -0
  38. package/dist/relationship-graph-model.d.ts +31 -0
  39. package/dist/relationship-graph-model.js +68 -0
  40. package/dist/rule-builder-model.d.ts +41 -0
  41. package/dist/rule-builder-model.js +101 -0
  42. package/package.json +1 -1
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,31 +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,
144
- virtualWindow: () => virtualWindow
192
+ validateRuleDefinition: () => validateRuleDefinition,
193
+ virtualWindow: () => virtualWindow,
194
+ zonedDateKey: () => zonedDateKey,
195
+ zonedDateSpan: () => zonedDateSpan
145
196
  });
146
197
  module.exports = __toCommonJS(src_exports);
147
198
 
@@ -2549,12 +2600,1664 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
2549
2600
  meta != null && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "uix-list-meta", children: meta })
2550
2601
  ] });
2551
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
+ }
2552
4252
  // Annotate the CommonJS export names for ESM import in node:
2553
4253
  0 && (module.exports = {
2554
4254
  Alert,
2555
4255
  AppShell,
2556
4256
  Avatar,
2557
4257
  AvatarGroup,
4258
+ BrandProfileEditor,
4259
+ BrandProfiles,
4260
+ BuilderCanvas,
2558
4261
  BulkBar,
2559
4262
  Button,
2560
4263
  ButtonGroup,
@@ -2562,6 +4265,7 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
2562
4265
  CellStrong,
2563
4266
  CellSub,
2564
4267
  Checkbox,
4268
+ ColorPicker,
2565
4269
  CommandGroup,
2566
4270
  CommandItem,
2567
4271
  CommandPalette,
@@ -2571,9 +4275,11 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
2571
4275
  ComposerBar,
2572
4276
  ContentList,
2573
4277
  ContentListItem,
4278
+ DateRangePicker,
2574
4279
  DescriptionItem,
2575
4280
  DescriptionList,
2576
4281
  DetailLayout,
4282
+ DiffViewer,
2577
4283
  Drawer,
2578
4284
  EmptyState,
2579
4285
  ErrorState,
@@ -2594,11 +4300,14 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
2594
4300
  KanbanCard,
2595
4301
  KanbanColumn,
2596
4302
  Label,
4303
+ LicensePositionBar,
2597
4304
  List,
2598
4305
  ListItem,
2599
4306
  LoadingState,
2600
4307
  Mark,
4308
+ MatchReview,
2601
4309
  Meter,
4310
+ MetricInput,
2602
4311
  Modal,
2603
4312
  NavFavourites,
2604
4313
  NavGroup,
@@ -2615,9 +4324,12 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
2615
4324
  Prose,
2616
4325
  Radio,
2617
4326
  RadioGroup,
4327
+ RelationshipGraph,
2618
4328
  ResourceGrid,
2619
4329
  RowAction,
2620
4330
  RowActions,
4331
+ RuleBuilder,
4332
+ SchedulingCalendar,
2621
4333
  SectionHead,
2622
4334
  Segmented,
2623
4335
  SegmentedOption,
@@ -2648,29 +4360,68 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
2648
4360
  Tr,
2649
4361
  Tree,
2650
4362
  UserChip,
4363
+ addCalendarDays,
4364
+ addCalendarMonths,
4365
+ appendRuleNode,
4366
+ applyBrandProfile,
2651
4367
  applyFilters,
4368
+ boundRelationshipGraph,
4369
+ buildMonthGrid,
4370
+ buildThreeWayDiff,
4371
+ clampMetricValue,
2652
4372
  clampWidth,
2653
4373
  computePosition,
4374
+ contrastRatio,
2654
4375
  cx,
4376
+ enumerateDateSpan,
4377
+ findRuleNodeDepth,
2655
4378
  flattenTree,
4379
+ hexToRgb,
2656
4380
  highlightSegments,
4381
+ hsvToHex,
4382
+ hsvToRgb,
4383
+ isDateInRange,
4384
+ isDateUnavailable,
4385
+ isRuleGroup,
4386
+ layoutRelationshipGraph,
4387
+ mapRuleGroup,
2657
4388
  matchFilter,
4389
+ meetsContrast,
2658
4390
  mergePinned,
4391
+ moveRuleNode,
2659
4392
  multiSort,
2660
4393
  nextSortDir,
4394
+ normalizeHex,
4395
+ parseMetricValue,
2661
4396
  parseView,
4397
+ relationshipNeighbors,
4398
+ removeRuleNode,
2662
4399
  reorder,
4400
+ restoreBrandProfile,
4401
+ rgbToHex,
4402
+ rgbToHsv,
4403
+ ruleDepth,
2663
4404
  searchRows,
2664
4405
  selectAllState,
4406
+ selectRangeDate,
2665
4407
  serializeView,
2666
4408
  shouldVirtualize,
4409
+ startOfMonth,
4410
+ stepMetricValue,
4411
+ summarizeDiff,
4412
+ summarizeRule,
4413
+ toDateKey,
2667
4414
  toggleId,
2668
4415
  togglePage,
2669
4416
  toggleSort,
4417
+ traverseRelationshipNode,
2670
4418
  treeNav,
2671
4419
  useAnchoredPosition,
2672
4420
  useDialog,
2673
4421
  useTable,
2674
4422
  useVirtualRows,
2675
- virtualWindow
4423
+ validateRuleDefinition,
4424
+ virtualWindow,
4425
+ zonedDateKey,
4426
+ zonedDateSpan
2676
4427
  });