@tensor_1/react 2.11.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +26 -1
  2. package/dist/ChartCore-Bt1C84ty.d.cts +22 -0
  3. package/dist/calendar-model.d.ts +31 -0
  4. package/dist/calendar-model.js +89 -0
  5. package/dist/chart-preset.cjs +168 -0
  6. package/dist/chart-preset.d.cts +9 -0
  7. package/dist/chart-preset.d.ts +9 -0
  8. package/dist/chart-preset.js +35 -0
  9. package/dist/chart.cjs +58 -32
  10. package/dist/chart.d.cts +6 -21
  11. package/dist/chart.d.ts +2 -1
  12. package/dist/color-model.d.ts +20 -0
  13. package/dist/color-model.js +74 -0
  14. package/dist/components/BrandProfiles.d.ts +45 -0
  15. package/dist/components/BrandProfiles.js +150 -0
  16. package/dist/components/BuilderCanvas.d.ts +34 -0
  17. package/dist/components/BuilderCanvas.js +117 -0
  18. package/dist/components/Chart.d.ts +6 -21
  19. package/dist/components/Chart.js +5 -76
  20. package/dist/components/ChartCore.d.ts +33 -0
  21. package/dist/components/ChartCore.js +100 -0
  22. package/dist/components/ColorPicker.d.ts +17 -0
  23. package/dist/components/ColorPicker.js +158 -0
  24. package/dist/components/DateRangePicker.d.ts +23 -0
  25. package/dist/components/DateRangePicker.js +123 -0
  26. package/dist/components/DiffViewer.d.ts +18 -0
  27. package/dist/components/DiffViewer.js +93 -0
  28. package/dist/components/Inbox.js +0 -1
  29. package/dist/components/LicensePositionBar.d.ts +17 -0
  30. package/dist/components/LicensePositionBar.js +50 -0
  31. package/dist/components/MatchReview.d.ts +39 -0
  32. package/dist/components/MatchReview.js +120 -0
  33. package/dist/components/Meter.js +0 -1
  34. package/dist/components/MetricInput.d.ts +23 -0
  35. package/dist/components/MetricInput.js +92 -0
  36. package/dist/components/NavFavourites.js +35 -108
  37. package/dist/components/RelationshipGraph.d.ts +30 -0
  38. package/dist/components/RelationshipGraph.js +138 -0
  39. package/dist/components/RuleBuilder.d.ts +45 -0
  40. package/dist/components/RuleBuilder.js +163 -0
  41. package/dist/components/SchedulingCalendar.d.ts +42 -0
  42. package/dist/components/SchedulingCalendar.js +124 -0
  43. package/dist/components/Segmented.js +3 -2
  44. package/dist/components/Table.d.ts +1 -1
  45. package/dist/components/Table.js +4 -3
  46. package/dist/components/Tabs.js +3 -2
  47. package/dist/diff-model.d.ts +23 -0
  48. package/dist/diff-model.js +50 -0
  49. package/dist/hooks/useTable.js +11 -7
  50. package/dist/hooks/useVirtualRows.d.ts +27 -0
  51. package/dist/hooks/useVirtualRows.js +50 -0
  52. package/dist/index.cjs +1930 -194
  53. package/dist/index.d.cts +464 -2
  54. package/dist/index.d.ts +19 -0
  55. package/dist/index.js +93 -1
  56. package/dist/json-value.d.ts +10 -0
  57. package/dist/json-value.js +19 -0
  58. package/dist/metric-model.d.ts +9 -0
  59. package/dist/metric-model.js +25 -0
  60. package/dist/relationship-graph-model.d.ts +31 -0
  61. package/dist/relationship-graph-model.js +68 -0
  62. package/dist/rule-builder-model.d.ts +41 -0
  63. package/dist/rule-builder-model.js +101 -0
  64. package/dist/table-engine.js +8 -6
  65. package/package.json +16 -2
@@ -0,0 +1,138 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useMemo, useRef, useState } from "react";
4
+ import { boundRelationshipGraph, layoutRelationshipGraph, relationshipNeighbors, traverseRelationshipNode } from "../relationship-graph-model.js";
5
+ import { cx } from "../cx.js";
6
+ const EMPTY_IDS = /* @__PURE__ */ new Set();
7
+ const EMPTY_LEGEND = [];
8
+ function RelationshipGraph({
9
+ nodes,
10
+ edges,
11
+ selectedId,
12
+ onSelect,
13
+ onExpandNeighbors,
14
+ onOpenDetails,
15
+ maxNodes = 40,
16
+ density = "standard",
17
+ highlightedNodeIds = EMPTY_IDS,
18
+ conflictedNodeIds = EMPTY_IDS,
19
+ highlightedEdgeIds = EMPTY_IDS,
20
+ conflictedEdgeIds = EMPTY_IDS,
21
+ legend = EMPTY_LEGEND,
22
+ loading,
23
+ error,
24
+ onRetry,
25
+ className
26
+ }) {
27
+ const bounded = useMemo(() => boundRelationshipGraph(nodes, edges, maxNodes), [nodes, edges, maxNodes]);
28
+ const positioned = useMemo(() => layoutRelationshipGraph(bounded.nodes, bounded.edges, selectedId), [bounded, selectedId]);
29
+ const positionedById = useMemo(() => new Map(positioned.map((node) => [node.id, node])), [positioned]);
30
+ const [zoom, setZoom] = useState(1);
31
+ const [pan, setPan] = useState({ x: 0, y: 0 });
32
+ const [focusedId, setFocusedId] = useState(selectedId ?? positioned[0]?.id);
33
+ const svgRef = useRef(null);
34
+ const select = (id) => {
35
+ const node = bounded.nodes.find((item) => item.id === id);
36
+ if (node) onSelect?.(node);
37
+ };
38
+ const focusNode = (id) => {
39
+ if (!id) return;
40
+ setFocusedId(id);
41
+ requestAnimationFrame(() => svgRef.current?.querySelector(`[data-node-id="${id}"]`)?.focus());
42
+ };
43
+ const onNodeKeyDown = (event, id) => {
44
+ if (["ArrowRight", "ArrowDown", "ArrowLeft", "ArrowUp"].includes(event.key)) {
45
+ event.preventDefault();
46
+ focusNode(traverseRelationshipNode(positioned, id, event.key === "ArrowRight" || event.key === "ArrowDown" ? 1 : -1));
47
+ } else if (event.key === "Enter" || event.key === " ") {
48
+ event.preventDefault();
49
+ select(id);
50
+ }
51
+ };
52
+ const reset = () => {
53
+ setZoom(1);
54
+ setPan({ x: 0, y: 0 });
55
+ };
56
+ if (loading) return /* @__PURE__ */ jsx("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), role: "status", children: "Loading relationships\u2026" });
57
+ if (error) return /* @__PURE__ */ jsxs("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), role: "alert", children: [
58
+ /* @__PURE__ */ jsx("p", { children: error }),
59
+ onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
60
+ ] });
61
+ if (bounded.nodes.length === 0) return /* @__PURE__ */ jsxs("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), children: [
62
+ /* @__PURE__ */ jsx("p", { children: "No relationships to display." }),
63
+ /* @__PURE__ */ jsx("p", { children: "Adjust the neighborhood or filters to include a node." })
64
+ ] });
65
+ return /* @__PURE__ */ jsxs("section", { className: cx("uix-relationship-graph", `uix-relationship-graph--${density}`, className), "aria-label": "Relationship graph", children: [
66
+ /* @__PURE__ */ jsxs("div", { className: "uix-relationship-graph__toolbar", "aria-label": "Graph view controls", children: [
67
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x - 24 })), "aria-label": "Pan left", children: "Left" }),
68
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y - 24 })), "aria-label": "Pan up", children: "Up" }),
69
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y + 24 })), "aria-label": "Pan down", children: "Down" }),
70
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x + 24 })), "aria-label": "Pan right", children: "Right" }),
71
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setZoom((value) => Math.min(2, Number((value + 0.2).toFixed(1)))), "aria-label": "Zoom in", children: "Zoom in" }),
72
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setZoom((value) => Math.max(0.5, Number((value - 0.2).toFixed(1)))), "aria-label": "Zoom out", children: "Zoom out" }),
73
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: reset, children: "Reset view" }),
74
+ /* @__PURE__ */ jsxs("span", { "aria-live": "polite", children: [
75
+ "Zoom ",
76
+ Math.round(zoom * 100),
77
+ "%"
78
+ ] })
79
+ ] }),
80
+ bounded.omittedNodeCount > 0 && /* @__PURE__ */ jsxs("p", { className: "uix-relationship-graph__bounded", role: "status", children: [
81
+ "Showing ",
82
+ bounded.nodes.length,
83
+ " nodes; ",
84
+ bounded.omittedNodeCount,
85
+ " outside this bounded neighborhood."
86
+ ] }),
87
+ /* @__PURE__ */ jsx("div", { className: "uix-relationship-graph__visual", children: /* @__PURE__ */ 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__ */ jsxs("g", { transform: `translate(${pan.x} ${pan.y}) scale(${zoom})`, children: [
88
+ bounded.edges.map((edge) => {
89
+ const source = positionedById.get(edge.source);
90
+ const target = positionedById.get(edge.target);
91
+ return /* @__PURE__ */ 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__ */ jsx("title", { children: edge.label ?? edge.type ?? "Relationship" }) }, edge.id);
92
+ }),
93
+ positioned.map((node) => /* @__PURE__ */ jsxs(
94
+ "g",
95
+ {
96
+ transform: `translate(${node.x * 6} ${node.y * 4})`,
97
+ role: "button",
98
+ "aria-label": `${node.label}${node.type ? `, ${node.type}` : ""}${conflictedNodeIds.has(node.id) ? ", conflict" : ""}`,
99
+ tabIndex: focusedId === node.id ? 0 : -1,
100
+ "data-node-id": node.id,
101
+ className: "uix-relationship-graph__node",
102
+ "data-selected": selectedId === node.id || void 0,
103
+ "data-highlighted": highlightedNodeIds.has(node.id) || void 0,
104
+ "data-conflicted": conflictedNodeIds.has(node.id) || void 0,
105
+ onFocus: () => setFocusedId(node.id),
106
+ onKeyDown: (event) => onNodeKeyDown(event, node.id),
107
+ onClick: () => select(node.id),
108
+ children: [
109
+ /* @__PURE__ */ jsx("rect", { x: "-55", y: "-18", width: "110", height: "36", rx: "8" }),
110
+ /* @__PURE__ */ jsx("text", { textAnchor: "middle", dominantBaseline: "middle", children: node.label.length > 16 ? `${node.label.slice(0, 15)}\u2026` : node.label })
111
+ ]
112
+ },
113
+ node.id
114
+ ))
115
+ ] }) }) }),
116
+ legend.length > 0 && /* @__PURE__ */ jsx("ul", { className: "uix-relationship-graph__legend", "aria-label": "Relationship types", children: legend.map((item) => /* @__PURE__ */ jsx("li", { "data-type": item.id, children: item.label }, item.id)) }),
117
+ /* @__PURE__ */ jsxs("div", { className: "uix-relationship-graph__list", role: "region", "aria-label": "Accessible relationship list", children: [
118
+ /* @__PURE__ */ jsx("h3", { children: "Relationship list" }),
119
+ /* @__PURE__ */ jsx("ul", { children: bounded.nodes.map((node) => {
120
+ const neighbors = relationshipNeighbors(node.id, bounded.edges).map((id) => bounded.nodes.find((item) => item.id === id)?.label ?? id);
121
+ return /* @__PURE__ */ jsxs("li", { "data-selected": selectedId === node.id || void 0, children: [
122
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => select(node.id), "aria-pressed": selectedId === node.id, children: [
123
+ /* @__PURE__ */ jsx("strong", { children: node.label }),
124
+ node.type && /* @__PURE__ */ jsx("span", { children: node.type }),
125
+ /* @__PURE__ */ jsx("span", { children: neighbors.length ? `Connected to ${neighbors.join(", ")}` : "No visible connections" })
126
+ ] }),
127
+ /* @__PURE__ */ jsxs("span", { className: "uix-relationship-graph__list-actions", children: [
128
+ onExpandNeighbors && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => onExpandNeighbors(node), children: "Expand neighbors" }),
129
+ onOpenDetails && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => onOpenDetails(node), children: "Open details" })
130
+ ] })
131
+ ] }, node.id);
132
+ }) })
133
+ ] })
134
+ ] });
135
+ }
136
+ export {
137
+ RelationshipGraph
138
+ };
@@ -0,0 +1,45 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { JsonValue } from '../json-value.js';
4
+ import { RuleAction, RuleDefinition, RuleCondition, RuleValidationIssue } from '../rule-builder-model.js';
5
+
6
+ interface RuleValueEditorProps {
7
+ condition: RuleCondition;
8
+ onChange: (value: JsonValue | undefined) => void;
9
+ disabled?: boolean;
10
+ }
11
+ interface RuleFieldDefinition {
12
+ id: string;
13
+ label: string;
14
+ description?: string;
15
+ operators?: string[];
16
+ renderValueEditor?: (props: RuleValueEditorProps) => ReactNode;
17
+ }
18
+ interface RuleOperatorDefinition {
19
+ id: string;
20
+ label: string;
21
+ requiresValue?: boolean;
22
+ }
23
+ interface RuleActionDefinition {
24
+ id: string;
25
+ label: string;
26
+ renderParameters?: (action: RuleAction, onChange: (parameters: Record<string, JsonValue>) => void, disabled: boolean) => ReactNode;
27
+ }
28
+ interface RuleBuilderProps {
29
+ value: RuleDefinition;
30
+ onChange: (value: RuleDefinition) => void;
31
+ fields: RuleFieldDefinition[];
32
+ operators: RuleOperatorDefinition[];
33
+ actions: RuleActionDefinition[];
34
+ maxDepth?: number;
35
+ readOnly?: boolean;
36
+ validate?: (value: RuleDefinition) => RuleValidationIssue[];
37
+ className?: string;
38
+ addConditionLabel?: string;
39
+ addGroupLabel?: string;
40
+ addActionLabel?: string;
41
+ }
42
+ /** Declarative controlled when-conditions-to-actions editor with bounded nesting. */
43
+ declare function RuleBuilder({ value, onChange, fields, operators, actions, maxDepth, readOnly, validate, className, addConditionLabel, addGroupLabel, addActionLabel, }: RuleBuilderProps): react.JSX.Element;
44
+
45
+ export { type RuleActionDefinition, RuleBuilder, type RuleBuilderProps, type RuleFieldDefinition, type RuleOperatorDefinition, type RuleValueEditorProps };
@@ -0,0 +1,163 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { cx } from "../cx.js";
4
+ import {
5
+ appendRuleNode,
6
+ isRuleGroup,
7
+ mapRuleGroup,
8
+ moveRuleNode,
9
+ removeRuleNode,
10
+ summarizeRule,
11
+ validateRuleDefinition
12
+ } from "../rule-builder-model.js";
13
+ let ruleId = 0;
14
+ const nextId = (prefix) => `${prefix}-${Date.now().toString(36)}-${(++ruleId).toString(36)}`;
15
+ function defaultCondition(fields, operators) {
16
+ const field = fields[0];
17
+ const operator = operators.find((item) => field?.operators?.includes(item.id)) ?? operators[0];
18
+ return { id: nextId("condition"), field: field?.id ?? "", operator: operator?.id ?? "", value: "" };
19
+ }
20
+ function RuleGroupEditor({ group, depth, root, fields, operators, maxDepth, disabled, onRootChange, labels }) {
21
+ const updateNode = (id, update) => onRootChange(mapRuleGroup(root, id, update));
22
+ const addCondition = () => onRootChange(appendRuleNode(root, group.id, defaultCondition(fields, operators), maxDepth));
23
+ const addGroup = () => onRootChange(appendRuleNode(root, group.id, {
24
+ id: nextId("group"),
25
+ combinator: "and",
26
+ conditions: [defaultCondition(fields, operators)]
27
+ }, maxDepth));
28
+ return /* @__PURE__ */ jsxs("fieldset", { className: "uix-rule-builder__group", "data-depth": depth, children: [
29
+ /* @__PURE__ */ jsxs("legend", { className: "uix-rule-builder__group-heading", children: [
30
+ /* @__PURE__ */ jsx("span", { children: depth === 1 ? "When" : "Group" }),
31
+ /* @__PURE__ */ jsxs("label", { children: [
32
+ "Match",
33
+ /* @__PURE__ */ jsxs("select", { className: "uix-select uix-select--sm", value: group.combinator, disabled, onChange: (event) => updateNode(group.id, (node) => ({ ...node, combinator: event.currentTarget.value })), children: [
34
+ /* @__PURE__ */ jsx("option", { value: "and", children: "all (AND)" }),
35
+ /* @__PURE__ */ jsx("option", { value: "or", children: "any (OR)" })
36
+ ] })
37
+ ] })
38
+ ] }),
39
+ /* @__PURE__ */ jsx("div", { className: "uix-rule-builder__rows", children: group.conditions.map((node, index) => isRuleGroup(node) ? /* @__PURE__ */ jsxs("div", { className: "uix-rule-builder__nested", children: [
40
+ /* @__PURE__ */ jsx(RuleGroupEditor, { ...{ group: node, depth: depth + 1, root, fields, operators, maxDepth, disabled, onRootChange, labels } }),
41
+ /* @__PURE__ */ 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)) })
42
+ ] }, node.id) : /* @__PURE__ */ jsx(
43
+ ConditionRow,
44
+ {
45
+ condition: node,
46
+ fields,
47
+ operators,
48
+ disabled,
49
+ onChange: (condition) => updateNode(node.id, () => condition),
50
+ controls: /* @__PURE__ */ 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)) })
51
+ },
52
+ node.id
53
+ )) }),
54
+ /* @__PURE__ */ jsxs("div", { className: "uix-rule-builder__add", children: [
55
+ /* @__PURE__ */ 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 }),
56
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: addGroup, disabled: disabled || depth >= maxDepth, children: labels.addGroup })
57
+ ] })
58
+ ] });
59
+ }
60
+ function ConditionRow({ condition, fields, operators, disabled, onChange, controls }) {
61
+ const field = fields.find((item) => item.id === condition.field);
62
+ const availableOperators = field?.operators?.length ? operators.filter((item) => field.operators.includes(item.id)) : operators;
63
+ const operator = operators.find((item) => item.id === condition.operator);
64
+ const setField = (id) => {
65
+ const nextField = fields.find((item) => item.id === id);
66
+ const nextOperator = operators.find((item) => nextField?.operators?.includes(item.id)) ?? availableOperators[0] ?? operators[0];
67
+ onChange({ ...condition, field: id, operator: nextOperator?.id ?? "" });
68
+ };
69
+ return /* @__PURE__ */ jsxs("div", { className: "uix-rule-builder__condition", children: [
70
+ /* @__PURE__ */ jsxs("label", { children: [
71
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: "Field" }),
72
+ /* @__PURE__ */ jsx("select", { className: "uix-select", value: condition.field, onChange: (event) => setField(event.currentTarget.value), disabled, children: fields.map((item) => /* @__PURE__ */ jsx("option", { value: item.id, children: item.label }, item.id)) })
73
+ ] }),
74
+ /* @__PURE__ */ jsxs("label", { children: [
75
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: "Operator" }),
76
+ /* @__PURE__ */ jsx("select", { className: "uix-select", value: condition.operator, onChange: (event) => onChange({ ...condition, operator: event.currentTarget.value }), disabled, children: availableOperators.map((item) => /* @__PURE__ */ jsx("option", { value: item.id, children: item.label }, item.id)) })
77
+ ] }),
78
+ operator?.requiresValue !== false && /* @__PURE__ */ jsx("div", { className: "uix-rule-builder__value", children: field?.renderValueEditor?.({ condition, onChange: (value) => onChange({ ...condition, value }), disabled }) ?? /* @__PURE__ */ 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"}` }) }),
79
+ controls
80
+ ] });
81
+ }
82
+ function RowControls({ label, first, last, disabled, onUp, onDown, onRemove }) {
83
+ return /* @__PURE__ */ jsxs("span", { className: "uix-rule-builder__row-actions", children: [
84
+ /* @__PURE__ */ 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" }),
85
+ /* @__PURE__ */ 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" }),
86
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: onRemove, disabled, "aria-label": `Remove ${label}`, children: "Remove" })
87
+ ] });
88
+ }
89
+ function RuleBuilder({
90
+ value,
91
+ onChange,
92
+ fields,
93
+ operators,
94
+ actions,
95
+ maxDepth = 3,
96
+ readOnly = false,
97
+ validate,
98
+ className,
99
+ addConditionLabel = "Add condition",
100
+ addGroupLabel = "Add group",
101
+ addActionLabel = "Add action"
102
+ }) {
103
+ const issues = [...validateRuleDefinition(value, maxDepth), ...validate?.(value) ?? []];
104
+ const fieldLabels = Object.fromEntries(fields.map((field) => [field.id, field.label]));
105
+ const operatorLabels = Object.fromEntries(operators.map((operator) => [operator.id, operator.label]));
106
+ const actionLabels = Object.fromEntries(actions.map((action) => [action.id, action.label]));
107
+ if (readOnly) return /* @__PURE__ */ jsxs("section", { className: cx("uix-rule-builder uix-rule-builder--summary", className), "aria-label": "Rule summary", children: [
108
+ /* @__PURE__ */ jsx("p", { children: summarizeRule(value, { fields: fieldLabels, operators: operatorLabels, actions: actionLabels }) }),
109
+ issues.length > 0 && /* @__PURE__ */ jsxs("p", { className: "uix-rule-builder__invalid", children: [
110
+ "Rule has ",
111
+ issues.length,
112
+ " validation issue",
113
+ issues.length === 1 ? "" : "s",
114
+ "."
115
+ ] })
116
+ ] });
117
+ const updateActions = (next) => onChange({ ...value, then: next });
118
+ return /* @__PURE__ */ jsxs("section", { className: cx("uix-rule-builder", className), "aria-label": "Rule builder", children: [
119
+ /* @__PURE__ */ 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 } }),
120
+ /* @__PURE__ */ jsxs("fieldset", { className: "uix-rule-builder__actions", children: [
121
+ /* @__PURE__ */ jsx("legend", { children: "Then" }),
122
+ value.then.map((action, index) => {
123
+ const definition = actions.find((item) => item.id === action.type);
124
+ return /* @__PURE__ */ jsxs("div", { className: "uix-rule-builder__action", children: [
125
+ /* @__PURE__ */ 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__ */ jsx("option", { value: item.id, children: item.label }, item.id)) }),
126
+ definition?.renderParameters?.(action, (parameters) => updateActions(value.then.map((item) => item.id === action.id ? { ...item, parameters } : item)), false),
127
+ /* @__PURE__ */ jsx(
128
+ RowControls,
129
+ {
130
+ label: "action",
131
+ first: index === 0,
132
+ last: index === value.then.length - 1,
133
+ disabled: false,
134
+ onUp: () => {
135
+ const next = [...value.then];
136
+ [next[index - 1], next[index]] = [next[index], next[index - 1]];
137
+ updateActions(next);
138
+ },
139
+ onDown: () => {
140
+ const next = [...value.then];
141
+ [next[index + 1], next[index]] = [next[index], next[index + 1]];
142
+ updateActions(next);
143
+ },
144
+ onRemove: () => updateActions(value.then.filter((item) => item.id !== action.id))
145
+ }
146
+ )
147
+ ] }, action.id);
148
+ }),
149
+ /* @__PURE__ */ 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 })
150
+ ] }),
151
+ /* @__PURE__ */ jsx("div", { className: "uix-rule-builder__validation", "aria-live": "polite", children: issues.length === 0 ? /* @__PURE__ */ jsx("span", { children: "Rule is valid." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
152
+ /* @__PURE__ */ jsxs("strong", { children: [
153
+ issues.length,
154
+ " validation issue",
155
+ issues.length === 1 ? "" : "s"
156
+ ] }),
157
+ /* @__PURE__ */ jsx("ul", { children: issues.map((issue, index) => /* @__PURE__ */ jsx("li", { children: issue.message }, `${issue.path}-${index}`)) })
158
+ ] }) })
159
+ ] });
160
+ }
161
+ export {
162
+ RuleBuilder
163
+ };
@@ -0,0 +1,42 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ type SchedulingCalendarView = 'month' | 'week' | 'agenda';
5
+ type SchedulingEntryState = 'scheduled' | 'conflicted' | 'in-progress' | 'blackout-violation';
6
+ type SchedulingOverlayKind = 'maintenance' | 'blackout';
7
+ interface SchedulingCalendarEntry {
8
+ id: string;
9
+ title: string;
10
+ start: string;
11
+ end: string;
12
+ state?: SchedulingEntryState;
13
+ meta?: string;
14
+ }
15
+ interface SchedulingCalendarOverlay {
16
+ id: string;
17
+ label: string;
18
+ start: string;
19
+ end: string;
20
+ kind: SchedulingOverlayKind;
21
+ }
22
+ interface SchedulingCalendarProps {
23
+ entries: SchedulingCalendarEntry[];
24
+ anchorDate: string;
25
+ timeZone: string;
26
+ view?: SchedulingCalendarView;
27
+ onViewChange?: (view: SchedulingCalendarView) => void;
28
+ onAnchorDateChange?: (date: string) => void;
29
+ onSelectEntry?: (entry: SchedulingCalendarEntry) => void;
30
+ overlays?: SchedulingCalendarOverlay[];
31
+ filter?: (entry: SchedulingCalendarEntry) => boolean;
32
+ renderEntry?: (entry: SchedulingCalendarEntry) => ReactNode;
33
+ locale?: string;
34
+ loading?: boolean;
35
+ error?: string;
36
+ onRetry?: () => void;
37
+ className?: string;
38
+ }
39
+ /** Month/week/agenda calendar for UTC ranges rendered in an explicit IANA time zone. */
40
+ declare function SchedulingCalendar({ entries, anchorDate, timeZone, view: controlledView, onViewChange, onAnchorDateChange, onSelectEntry, overlays, filter, renderEntry, locale, loading, error, onRetry, className, }: SchedulingCalendarProps): react.JSX.Element;
41
+
42
+ export { SchedulingCalendar, type SchedulingCalendarEntry, type SchedulingCalendarOverlay, type SchedulingCalendarProps, type SchedulingCalendarView, type SchedulingEntryState, type SchedulingOverlayKind };
@@ -0,0 +1,124 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useRef, useState } from "react";
4
+ import { addCalendarDays, addCalendarMonths, buildMonthGrid, startOfMonth, zonedDateSpan } from "../calendar-model.js";
5
+ import { cx } from "../cx.js";
6
+ import { StatusPill } from "./StatusPill.js";
7
+ const stateLabel = { scheduled: "Scheduled", conflicted: "Conflicted", "in-progress": "In progress", "blackout-violation": "Blackout violation" };
8
+ const stateTone = (state) => state === "scheduled" ? "info" : state === "in-progress" ? "success" : "danger";
9
+ const dateLabel = (date, locale) => new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
10
+ const EMPTY_OVERLAYS = [];
11
+ function SchedulingCalendar({
12
+ entries,
13
+ anchorDate,
14
+ timeZone,
15
+ view: controlledView,
16
+ onViewChange,
17
+ onAnchorDateChange,
18
+ onSelectEntry,
19
+ overlays = EMPTY_OVERLAYS,
20
+ filter,
21
+ renderEntry,
22
+ locale,
23
+ loading,
24
+ error,
25
+ onRetry,
26
+ className
27
+ }) {
28
+ const [internalView, setInternalView] = useState("month");
29
+ const view = controlledView ?? internalView;
30
+ const [activeDate, setActiveDate] = useState(anchorDate);
31
+ const gridRef = useRef(null);
32
+ const visibleEntries = useMemo(() => entries.filter((entry) => filter?.(entry) ?? true), [entries, filter]);
33
+ const entrySpans = useMemo(() => new Map(visibleEntries.map((entry) => [entry.id, zonedDateSpan(entry.start, entry.end, timeZone)])), [visibleEntries, timeZone]);
34
+ const overlaySpans = useMemo(() => new Map(overlays.map((overlay) => [overlay.id, { start: overlay.start.slice(0, 10), end: overlay.end.slice(0, 10) }])), [overlays]);
35
+ const monthDays = useMemo(() => buildMonthGrid(startOfMonth(anchorDate)), [anchorDate]);
36
+ const weekday = ((/* @__PURE__ */ new Date(`${anchorDate}T00:00:00Z`)).getUTCDay() + 6) % 7;
37
+ const weekStart = addCalendarDays(anchorDate, -weekday);
38
+ const weekDays = useMemo(() => Array.from({ length: 7 }, (_, index) => addCalendarDays(weekStart, index)), [weekStart]);
39
+ const days = useMemo(() => view === "month" ? monthDays.map((day) => day.date) : weekDays, [view, monthDays, weekDays]);
40
+ useEffect(() => {
41
+ if (view !== "agenda" && !days.includes(activeDate)) setActiveDate(days[0]);
42
+ }, [activeDate, days, view]);
43
+ const setView = (next) => {
44
+ if (controlledView === void 0) setInternalView(next);
45
+ onViewChange?.(next);
46
+ };
47
+ const movePeriod = (direction) => onAnchorDateChange?.(view === "month" ? addCalendarMonths(anchorDate, direction) : addCalendarDays(anchorDate, direction * 7));
48
+ const focusDate = (date) => {
49
+ setActiveDate(date);
50
+ requestAnimationFrame(() => gridRef.current?.querySelector(`[data-calendar-date="${date}"]`)?.focus());
51
+ };
52
+ const onDayKeyDown = (event, date) => {
53
+ const offset = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[event.key];
54
+ if (offset) {
55
+ event.preventDefault();
56
+ focusDate(addCalendarDays(date, offset));
57
+ } else if (event.key === "Home") {
58
+ event.preventDefault();
59
+ focusDate(days[0]);
60
+ } else if (event.key === "End") {
61
+ event.preventDefault();
62
+ focusDate(days[days.length - 1]);
63
+ }
64
+ };
65
+ const entriesFor = (date) => visibleEntries.filter((entry) => {
66
+ const span = entrySpans.get(entry.id);
67
+ return date >= span.start && date <= span.end;
68
+ });
69
+ const overlaysFor = (date) => overlays.filter((overlay) => {
70
+ const span = overlaySpans.get(overlay.id);
71
+ return date >= span.start && date <= span.end;
72
+ });
73
+ const formatInstant = (instant) => new Intl.DateTimeFormat(locale, { timeZone, dateStyle: "medium", timeStyle: "short" }).format(new Date(instant));
74
+ return /* @__PURE__ */ jsxs("section", { className: cx("uix-scheduling-calendar", className), "aria-label": `Scheduling calendar in ${timeZone}`, children: [
75
+ /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__header", children: [
76
+ /* @__PURE__ */ jsxs("div", { children: [
77
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(-1), disabled: !onAnchorDateChange, children: "Previous" }),
78
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(1), disabled: !onAnchorDateChange, children: "Next" })
79
+ ] }),
80
+ /* @__PURE__ */ 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)}` }),
81
+ /* @__PURE__ */ jsx("div", { className: "uix-segmented", role: "group", "aria-label": "Calendar view", children: ["month", "week", "agenda"].map((option) => /* @__PURE__ */ jsxs("button", { type: "button", className: "uix-segmented__option", "aria-pressed": view === option, "data-selected": view === option || void 0, onClick: () => setView(option), children: [
82
+ option[0].toUpperCase(),
83
+ option.slice(1)
84
+ ] }, option)) })
85
+ ] }),
86
+ loading ? /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__state", role: "status", children: "Loading schedule\u2026" }) : error ? /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__state", role: "alert", children: [
87
+ /* @__PURE__ */ jsx("p", { children: error }),
88
+ onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
89
+ ] }) : view === "agenda" ? /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__agenda", role: "region", "aria-label": "Schedule agenda", children: visibleEntries.length === 0 ? /* @__PURE__ */ jsx("p", { children: "No scheduled entries match the current filters." }) : /* @__PURE__ */ jsx("ol", { children: [...visibleEntries].sort((a, b) => a.start.localeCompare(b.start)).map((entry) => {
90
+ const state = entry.state ?? "scheduled";
91
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => onSelectEntry?.(entry), children: [
92
+ /* @__PURE__ */ jsxs("span", { children: [
93
+ /* @__PURE__ */ jsx("strong", { children: renderEntry?.(entry) ?? entry.title }),
94
+ /* @__PURE__ */ jsxs("span", { children: [
95
+ formatInstant(entry.start),
96
+ " \u2013 ",
97
+ formatInstant(entry.end)
98
+ ] }),
99
+ entry.meta && /* @__PURE__ */ jsx("span", { children: entry.meta })
100
+ ] }),
101
+ /* @__PURE__ */ jsx(StatusPill, { tone: stateTone(state), children: stateLabel[state] })
102
+ ] }) }, entry.id);
103
+ }) }) }) : /* @__PURE__ */ 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) => {
104
+ const dayEntries = entriesFor(date);
105
+ const dayOverlays = overlaysFor(date);
106
+ return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__day", "data-outside": view === "month" && !monthDays[index].inMonth || void 0, children: [
107
+ /* @__PURE__ */ 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)) }),
108
+ dayOverlays.map((overlay) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__overlay", "data-kind": overlay.kind, children: overlay.label }, overlay.id)),
109
+ /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__entries", children: dayEntries.map((entry) => {
110
+ const span = entrySpans.get(entry.id);
111
+ const state = entry.state ?? "scheduled";
112
+ return /* @__PURE__ */ 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: [
113
+ /* @__PURE__ */ jsx("span", { children: renderEntry?.(entry) ?? entry.title }),
114
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: stateLabel[state] })
115
+ ] }, entry.id);
116
+ }) })
117
+ ] }, date);
118
+ }) }),
119
+ /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__legend", "aria-label": "Schedule state legend", children: ["scheduled", "conflicted", "in-progress", "blackout-violation"].map((state) => /* @__PURE__ */ jsx("span", { "data-state": state, children: stateLabel[state] }, state)) })
120
+ ] });
121
+ }
122
+ export {
123
+ SchedulingCalendar
124
+ };
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { createContext, useContext } from "react";
3
+ import { createContext, useContext, useMemo } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  const SegmentedCtx = createContext({});
6
6
  function Segmented({ value, onChange, children, className, ...props }) {
7
- return /* @__PURE__ */ jsx(SegmentedCtx.Provider, { value: { value, onChange }, children: /* @__PURE__ */ jsx("div", { role: "group", className: cx("uix-segmented", className), ...props, children }) });
7
+ const contextValue = useMemo(() => ({ value, onChange }), [value, onChange]);
8
+ return /* @__PURE__ */ jsx(SegmentedCtx.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { role: "group", className: cx("uix-segmented", className), ...props, children }) });
8
9
  }
9
10
  function SegmentedOption({
10
11
  value,
@@ -4,7 +4,7 @@ import { HTMLAttributes, ReactNode, ButtonHTMLAttributes, TdHTMLAttributes, Tabl
4
4
  interface TableWrapProps extends HTMLAttributes<HTMLDivElement> {
5
5
  children?: ReactNode;
6
6
  }
7
- declare function TableWrap({ children, className, ...props }: TableWrapProps): react.JSX.Element;
7
+ declare const TableWrap: react.ForwardRefExoticComponent<TableWrapProps & react.RefAttributes<HTMLDivElement>>;
8
8
  type TableDensity = 'compact' | 'standard' | 'comfortable';
9
9
  type SortDirection = 'ascending' | 'descending' | 'none';
10
10
  interface TableProps extends TableHTMLAttributes<HTMLTableElement> {
@@ -1,9 +1,10 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
2
3
  import { cx } from "../cx.js";
3
4
  import { highlightSegments } from "../table-engine.js";
4
- function TableWrap({ children, className, ...props }) {
5
- return /* @__PURE__ */ jsx("div", { className: cx("uix-table-wrap", className), ...props, children });
6
- }
5
+ const TableWrap = forwardRef(function TableWrap2({ children, className, ...props }, ref) {
6
+ return /* @__PURE__ */ jsx("div", { ref, className: cx("uix-table-wrap", className), ...props, children });
7
+ });
7
8
  function Table({
8
9
  density = "standard",
9
10
  zebra = true,
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { createContext, useContext } from "react";
3
+ import { createContext, useContext, useMemo } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  const TabsCtx = createContext({});
6
6
  function Tabs({ variant = "line", value, onChange, children, className }) {
7
- return /* @__PURE__ */ jsx(TabsCtx.Provider, { value: { value, onChange }, children: /* @__PURE__ */ jsx("div", { role: "tablist", className: cx("uix-tabs", `uix-tabs--${variant}`, className), children }) });
7
+ const contextValue = useMemo(() => ({ value, onChange }), [value, onChange]);
8
+ return /* @__PURE__ */ jsx(TabsCtx.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { role: "tablist", className: cx("uix-tabs", `uix-tabs--${variant}`, className), children }) });
8
9
  }
9
10
  function Tab({ value, children, disabled, className, onClick, ...props }) {
10
11
  const ctx = useContext(TabsCtx);
@@ -0,0 +1,23 @@
1
+ import { JsonValue } from './json-value.js';
2
+
3
+ type DiffKind = 'added' | 'removed' | 'changed' | 'conflicted';
4
+ type DiffResolution = 'accept' | 'skip' | 'pending';
5
+ interface DiffEntry {
6
+ path: string;
7
+ kind: DiffKind;
8
+ base?: JsonValue;
9
+ current?: JsonValue;
10
+ incoming?: JsonValue;
11
+ }
12
+ interface DiffSummary {
13
+ added: number;
14
+ removed: number;
15
+ changed: number;
16
+ conflicted: number;
17
+ resolved: number;
18
+ pending: number;
19
+ }
20
+ declare function buildThreeWayDiff(base: JsonValue | undefined, current: JsonValue | undefined, incoming: JsonValue | undefined, path?: string): DiffEntry[];
21
+ declare function summarizeDiff(entries: DiffEntry[], resolutions?: Record<string, DiffResolution>): DiffSummary;
22
+
23
+ export { type DiffEntry, type DiffKind, type DiffResolution, type DiffSummary, buildThreeWayDiff, summarizeDiff };