@tensor_1/react 2.11.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1
- package/dist/ChartCore-Bt1C84ty.d.cts +22 -0
- package/dist/calendar-model.d.ts +31 -0
- package/dist/calendar-model.js +89 -0
- package/dist/chart-preset.cjs +168 -0
- package/dist/chart-preset.d.cts +9 -0
- package/dist/chart-preset.d.ts +9 -0
- package/dist/chart-preset.js +35 -0
- package/dist/chart.cjs +58 -32
- package/dist/chart.d.cts +6 -21
- package/dist/chart.d.ts +2 -1
- package/dist/color-model.d.ts +20 -0
- package/dist/color-model.js +74 -0
- package/dist/components/BrandProfiles.d.ts +45 -0
- package/dist/components/BrandProfiles.js +150 -0
- package/dist/components/BuilderCanvas.d.ts +34 -0
- package/dist/components/BuilderCanvas.js +117 -0
- package/dist/components/Chart.d.ts +6 -21
- package/dist/components/Chart.js +5 -76
- package/dist/components/ChartCore.d.ts +33 -0
- package/dist/components/ChartCore.js +100 -0
- package/dist/components/ColorPicker.d.ts +17 -0
- package/dist/components/ColorPicker.js +158 -0
- package/dist/components/DateRangePicker.d.ts +23 -0
- package/dist/components/DateRangePicker.js +123 -0
- package/dist/components/DiffViewer.d.ts +18 -0
- package/dist/components/DiffViewer.js +93 -0
- package/dist/components/Inbox.js +0 -1
- package/dist/components/LicensePositionBar.d.ts +17 -0
- package/dist/components/LicensePositionBar.js +50 -0
- package/dist/components/MatchReview.d.ts +39 -0
- package/dist/components/MatchReview.js +120 -0
- package/dist/components/Meter.js +0 -1
- package/dist/components/MetricInput.d.ts +23 -0
- package/dist/components/MetricInput.js +92 -0
- package/dist/components/NavFavourites.js +35 -108
- package/dist/components/RelationshipGraph.d.ts +30 -0
- package/dist/components/RelationshipGraph.js +138 -0
- package/dist/components/RuleBuilder.d.ts +45 -0
- package/dist/components/RuleBuilder.js +163 -0
- package/dist/components/SchedulingCalendar.d.ts +42 -0
- package/dist/components/SchedulingCalendar.js +124 -0
- package/dist/components/Segmented.js +3 -2
- package/dist/components/Table.d.ts +1 -1
- package/dist/components/Table.js +4 -3
- package/dist/components/Tabs.js +3 -2
- package/dist/diff-model.d.ts +23 -0
- package/dist/diff-model.js +50 -0
- package/dist/hooks/useTable.js +11 -7
- package/dist/hooks/useVirtualRows.d.ts +27 -0
- package/dist/hooks/useVirtualRows.js +50 -0
- package/dist/index.cjs +1930 -194
- package/dist/index.d.cts +464 -2
- package/dist/index.d.ts +19 -0
- package/dist/index.js +93 -1
- package/dist/json-value.d.ts +10 -0
- package/dist/json-value.js +19 -0
- package/dist/metric-model.d.ts +9 -0
- package/dist/metric-model.js +25 -0
- package/dist/relationship-graph-model.d.ts +31 -0
- package/dist/relationship-graph-model.js +68 -0
- package/dist/rule-builder-model.d.ts +41 -0
- package/dist/rule-builder-model.js +101 -0
- package/dist/table-engine.js +8 -6
- package/package.json +16 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
2
|
+
function normalizeHex(value) {
|
|
3
|
+
const raw = value.trim().replace(/^#/, "");
|
|
4
|
+
if (/^[\da-f]{3}$/i.test(raw)) return `#${raw.split("").map((char) => char + char).join("").toUpperCase()}`;
|
|
5
|
+
if (/^[\da-f]{6}$/i.test(raw)) return `#${raw.toUpperCase()}`;
|
|
6
|
+
return void 0;
|
|
7
|
+
}
|
|
8
|
+
function hexToRgb(value) {
|
|
9
|
+
const hex = normalizeHex(value);
|
|
10
|
+
if (!hex) return void 0;
|
|
11
|
+
return {
|
|
12
|
+
r: Number.parseInt(hex.slice(1, 3), 16),
|
|
13
|
+
g: Number.parseInt(hex.slice(3, 5), 16),
|
|
14
|
+
b: Number.parseInt(hex.slice(5, 7), 16)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function rgbToHex({ r, g, b }) {
|
|
18
|
+
return `#${[r, g, b].map((value) => Math.round(clamp(value, 0, 255)).toString(16).padStart(2, "0")).join("").toUpperCase()}`;
|
|
19
|
+
}
|
|
20
|
+
function rgbToHsv({ r, g, b }) {
|
|
21
|
+
const values = [r, g, b].map((value) => clamp(value, 0, 255) / 255);
|
|
22
|
+
const max = Math.max(...values);
|
|
23
|
+
const min = Math.min(...values);
|
|
24
|
+
const delta = max - min;
|
|
25
|
+
let h = 0;
|
|
26
|
+
if (delta !== 0) {
|
|
27
|
+
if (max === values[0]) h = 60 * ((values[1] - values[2]) / delta % 6);
|
|
28
|
+
else if (max === values[1]) h = 60 * ((values[2] - values[0]) / delta + 2);
|
|
29
|
+
else h = 60 * ((values[0] - values[1]) / delta + 4);
|
|
30
|
+
}
|
|
31
|
+
if (h < 0) h += 360;
|
|
32
|
+
return { h, s: max === 0 ? 0 : delta / max, v: max };
|
|
33
|
+
}
|
|
34
|
+
function hsvToRgb({ h, s, v }) {
|
|
35
|
+
const hue = (h % 360 + 360) % 360;
|
|
36
|
+
const sat = clamp(s, 0, 1);
|
|
37
|
+
const value = clamp(v, 0, 1);
|
|
38
|
+
const chroma = value * sat;
|
|
39
|
+
const x = chroma * (1 - Math.abs(hue / 60 % 2 - 1));
|
|
40
|
+
const m = value - chroma;
|
|
41
|
+
const sector = Math.floor(hue / 60);
|
|
42
|
+
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];
|
|
43
|
+
return { r: (rgb[0] + m) * 255, g: (rgb[1] + m) * 255, b: (rgb[2] + m) * 255 };
|
|
44
|
+
}
|
|
45
|
+
function hsvToHex(value) {
|
|
46
|
+
return rgbToHex(hsvToRgb(value));
|
|
47
|
+
}
|
|
48
|
+
function relativeLuminance({ r, g, b }) {
|
|
49
|
+
const linear = [r, g, b].map((channel) => {
|
|
50
|
+
const value = channel / 255;
|
|
51
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
52
|
+
});
|
|
53
|
+
return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2];
|
|
54
|
+
}
|
|
55
|
+
function contrastRatio(background, foreground) {
|
|
56
|
+
const bg = hexToRgb(background);
|
|
57
|
+
const fg = hexToRgb(foreground);
|
|
58
|
+
if (!bg || !fg) return void 0;
|
|
59
|
+
const [light, dark] = [relativeLuminance(bg), relativeLuminance(fg)].sort((a, b) => b - a);
|
|
60
|
+
return (light + 0.05) / (dark + 0.05);
|
|
61
|
+
}
|
|
62
|
+
function meetsContrast(background, foreground, minimum = 4.5) {
|
|
63
|
+
return (contrastRatio(background, foreground) ?? 0) >= minimum;
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
contrastRatio,
|
|
67
|
+
hexToRgb,
|
|
68
|
+
hsvToHex,
|
|
69
|
+
hsvToRgb,
|
|
70
|
+
meetsContrast,
|
|
71
|
+
normalizeHex,
|
|
72
|
+
rgbToHex,
|
|
73
|
+
rgbToHsv
|
|
74
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
interface BrandProfileTypography {
|
|
4
|
+
sans?: string;
|
|
5
|
+
heading?: string;
|
|
6
|
+
mono?: string;
|
|
7
|
+
}
|
|
8
|
+
interface BrandProfileLogo {
|
|
9
|
+
src: string;
|
|
10
|
+
alt: string;
|
|
11
|
+
}
|
|
12
|
+
interface BrandProfile {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
brand: string;
|
|
16
|
+
brandForeground: string;
|
|
17
|
+
typography?: BrandProfileTypography;
|
|
18
|
+
logo?: BrandProfileLogo;
|
|
19
|
+
}
|
|
20
|
+
interface AppliedBrandProfileSnapshot {
|
|
21
|
+
profileId?: string;
|
|
22
|
+
properties: Record<string, string>;
|
|
23
|
+
logoSrc?: string;
|
|
24
|
+
logoAlt?: string;
|
|
25
|
+
}
|
|
26
|
+
/** Apply only the existing UIx brand/font slots and readable logo metadata. */
|
|
27
|
+
declare function applyBrandProfile(profile: BrandProfile, target?: HTMLElement | null): AppliedBrandProfileSnapshot | undefined;
|
|
28
|
+
declare function restoreBrandProfile(snapshot: AppliedBrandProfileSnapshot, target?: HTMLElement | null): void;
|
|
29
|
+
interface BrandProfileEditorProps {
|
|
30
|
+
value: BrandProfile;
|
|
31
|
+
onChange: (profile: BrandProfile) => void;
|
|
32
|
+
profiles?: BrandProfile[];
|
|
33
|
+
onSelectProfile?: (profile: BrandProfile) => void;
|
|
34
|
+
onApply?: (profile: BrandProfile) => void;
|
|
35
|
+
applyTarget?: HTMLElement | null;
|
|
36
|
+
onLogoFile?: (file: File) => void;
|
|
37
|
+
colorPresets?: string[];
|
|
38
|
+
className?: string;
|
|
39
|
+
}
|
|
40
|
+
/** Small controlled editor composition for serializable UIx brand profiles. */
|
|
41
|
+
declare function BrandProfileEditor({ value, onChange, profiles, onSelectProfile, onApply, applyTarget, onLogoFile, colorPresets, className, }: BrandProfileEditorProps): react.JSX.Element;
|
|
42
|
+
/** Canonical Phase 46.9 export; equivalent to BrandProfileEditor. */
|
|
43
|
+
declare const BrandProfiles: typeof BrandProfileEditor;
|
|
44
|
+
|
|
45
|
+
export { type AppliedBrandProfileSnapshot, type BrandProfile, BrandProfileEditor, type BrandProfileEditorProps, type BrandProfileLogo, type BrandProfileTypography, BrandProfiles, applyBrandProfile, restoreBrandProfile };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
5
|
+
import { normalizeHex } from "../color-model.js";
|
|
6
|
+
import { ColorPicker } from "./ColorPicker.js";
|
|
7
|
+
const PROFILE_PROPERTIES = {
|
|
8
|
+
brand: "--uix-brand",
|
|
9
|
+
brandForeground: "--uix-brand-fg",
|
|
10
|
+
sans: "--uix-font-sans",
|
|
11
|
+
heading: "--uix-font-heading",
|
|
12
|
+
mono: "--uix-font-mono"
|
|
13
|
+
};
|
|
14
|
+
const EMPTY_PROFILES = [];
|
|
15
|
+
const EMPTY_COLORS = [];
|
|
16
|
+
function applyBrandProfile(profile, target) {
|
|
17
|
+
const element = target ?? (typeof document === "undefined" ? void 0 : document.documentElement);
|
|
18
|
+
if (!element) return void 0;
|
|
19
|
+
const brand = normalizeHex(profile.brand);
|
|
20
|
+
const foreground = normalizeHex(profile.brandForeground);
|
|
21
|
+
if (!brand || !foreground) throw new TypeError("Brand profiles require valid hex brand and foreground colors.");
|
|
22
|
+
const properties = {};
|
|
23
|
+
Object.values(PROFILE_PROPERTIES).forEach((property) => {
|
|
24
|
+
properties[property] = element.style.getPropertyValue(property);
|
|
25
|
+
});
|
|
26
|
+
const snapshot = {
|
|
27
|
+
profileId: element.dataset.uixBrandProfile,
|
|
28
|
+
properties,
|
|
29
|
+
logoSrc: element.dataset.uixBrandLogoSrc,
|
|
30
|
+
logoAlt: element.dataset.uixBrandLogoAlt
|
|
31
|
+
};
|
|
32
|
+
element.style.setProperty(PROFILE_PROPERTIES.brand, brand);
|
|
33
|
+
element.style.setProperty(PROFILE_PROPERTIES.brandForeground, foreground);
|
|
34
|
+
const typography = profile.typography ?? {};
|
|
35
|
+
for (const [key, property] of Object.entries({ sans: PROFILE_PROPERTIES.sans, heading: PROFILE_PROPERTIES.heading, mono: PROFILE_PROPERTIES.mono })) {
|
|
36
|
+
const value = typography[key];
|
|
37
|
+
if (value) element.style.setProperty(property, value);
|
|
38
|
+
else element.style.removeProperty(property);
|
|
39
|
+
}
|
|
40
|
+
element.dataset.uixBrandProfile = profile.id;
|
|
41
|
+
if (profile.logo) {
|
|
42
|
+
element.dataset.uixBrandLogoSrc = profile.logo.src;
|
|
43
|
+
element.dataset.uixBrandLogoAlt = profile.logo.alt;
|
|
44
|
+
} else {
|
|
45
|
+
delete element.dataset.uixBrandLogoSrc;
|
|
46
|
+
delete element.dataset.uixBrandLogoAlt;
|
|
47
|
+
}
|
|
48
|
+
return snapshot;
|
|
49
|
+
}
|
|
50
|
+
function restoreBrandProfile(snapshot, target) {
|
|
51
|
+
const element = target ?? (typeof document === "undefined" ? void 0 : document.documentElement);
|
|
52
|
+
if (!element) return;
|
|
53
|
+
Object.entries(snapshot.properties).forEach(([property, value]) => {
|
|
54
|
+
if (value) element.style.setProperty(property, value);
|
|
55
|
+
else element.style.removeProperty(property);
|
|
56
|
+
});
|
|
57
|
+
if (snapshot.profileId) element.dataset.uixBrandProfile = snapshot.profileId;
|
|
58
|
+
else delete element.dataset.uixBrandProfile;
|
|
59
|
+
if (snapshot.logoSrc) element.dataset.uixBrandLogoSrc = snapshot.logoSrc;
|
|
60
|
+
else delete element.dataset.uixBrandLogoSrc;
|
|
61
|
+
if (snapshot.logoAlt) element.dataset.uixBrandLogoAlt = snapshot.logoAlt;
|
|
62
|
+
else delete element.dataset.uixBrandLogoAlt;
|
|
63
|
+
}
|
|
64
|
+
function BrandProfileEditor({
|
|
65
|
+
value,
|
|
66
|
+
onChange,
|
|
67
|
+
profiles = EMPTY_PROFILES,
|
|
68
|
+
onSelectProfile,
|
|
69
|
+
onApply,
|
|
70
|
+
applyTarget,
|
|
71
|
+
onLogoFile,
|
|
72
|
+
colorPresets = EMPTY_COLORS,
|
|
73
|
+
className
|
|
74
|
+
}) {
|
|
75
|
+
const [announcement, setAnnouncement] = useState("");
|
|
76
|
+
const update = (patch) => onChange({ ...value, ...patch });
|
|
77
|
+
const apply = () => {
|
|
78
|
+
if (applyTarget !== null) applyBrandProfile(value, applyTarget);
|
|
79
|
+
onApply?.(value);
|
|
80
|
+
setAnnouncement(`${value.name} applied.`);
|
|
81
|
+
};
|
|
82
|
+
const onFile = (event) => {
|
|
83
|
+
const file = event.currentTarget.files?.[0];
|
|
84
|
+
if (file) onLogoFile?.(file);
|
|
85
|
+
};
|
|
86
|
+
const previewStyle = {
|
|
87
|
+
"--uix-brand": normalizeHex(value.brand) ?? value.brand,
|
|
88
|
+
"--uix-brand-fg": normalizeHex(value.brandForeground) ?? value.brandForeground,
|
|
89
|
+
...value.typography?.sans ? { "--uix-font-sans": value.typography.sans } : {},
|
|
90
|
+
...value.typography?.heading ? { "--uix-font-heading": value.typography.heading } : {}
|
|
91
|
+
};
|
|
92
|
+
return /* @__PURE__ */ jsxs("section", { className: cx("uix-brand-profiles", className), "aria-label": "Brand profile editor", children: [
|
|
93
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-brand-profiles__form", children: [
|
|
94
|
+
profiles.length > 0 && /* @__PURE__ */ jsxs("label", { className: "uix-field__label", children: [
|
|
95
|
+
"Profile",
|
|
96
|
+
/* @__PURE__ */ jsx("select", { className: "uix-select", value: value.id, onChange: (event) => {
|
|
97
|
+
const selected = profiles.find((profile) => profile.id === event.currentTarget.value);
|
|
98
|
+
if (selected) onSelectProfile?.(selected);
|
|
99
|
+
}, children: profiles.map((profile) => /* @__PURE__ */ jsx("option", { value: profile.id, children: profile.name }, profile.id)) })
|
|
100
|
+
] }),
|
|
101
|
+
/* @__PURE__ */ jsxs("label", { className: "uix-field__label", children: [
|
|
102
|
+
"Name",
|
|
103
|
+
/* @__PURE__ */ jsx("input", { className: "uix-input", value: value.name, onChange: (event) => update({ name: event.currentTarget.value }) })
|
|
104
|
+
] }),
|
|
105
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-brand-profiles__colors", children: [
|
|
106
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
107
|
+
"Brand",
|
|
108
|
+
/* @__PURE__ */ jsx(ColorPicker, { label: "Brand color", value: value.brand, foreground: value.brandForeground, presets: colorPresets, onChange: (brand) => update({ brand }) })
|
|
109
|
+
] }),
|
|
110
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
111
|
+
"On brand",
|
|
112
|
+
/* @__PURE__ */ jsx(ColorPicker, { label: "Brand foreground color", value: value.brandForeground, foreground: value.brand, presets: colorPresets, onChange: (brandForeground) => update({ brandForeground }) })
|
|
113
|
+
] })
|
|
114
|
+
] }),
|
|
115
|
+
/* @__PURE__ */ jsxs("label", { className: "uix-field__label", children: [
|
|
116
|
+
"Body font",
|
|
117
|
+
/* @__PURE__ */ jsx("input", { className: "uix-input", value: value.typography?.sans ?? "", onChange: (event) => update({ typography: { ...value.typography, sans: event.currentTarget.value || void 0 } }) })
|
|
118
|
+
] }),
|
|
119
|
+
/* @__PURE__ */ jsxs("label", { className: "uix-field__label", children: [
|
|
120
|
+
"Heading font",
|
|
121
|
+
/* @__PURE__ */ jsx("input", { className: "uix-input", value: value.typography?.heading ?? "", onChange: (event) => update({ typography: { ...value.typography, heading: event.currentTarget.value || void 0 } }) })
|
|
122
|
+
] }),
|
|
123
|
+
/* @__PURE__ */ jsxs("label", { className: "uix-file-upload uix-brand-profiles__upload", children: [
|
|
124
|
+
"Logo file",
|
|
125
|
+
/* @__PURE__ */ jsx("input", { type: "file", accept: "image/*", onChange: onFile })
|
|
126
|
+
] }),
|
|
127
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--primary", onClick: apply, children: "Apply profile" }),
|
|
128
|
+
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
|
|
129
|
+
] }),
|
|
130
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-brand-profiles__preview", style: previewStyle, "aria-label": `Live preview of ${value.name}`, children: [
|
|
131
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-brand-profiles__preview-header", children: [
|
|
132
|
+
value.logo ? /* @__PURE__ */ jsx("img", { src: value.logo.src, alt: value.logo.alt }) : /* @__PURE__ */ jsx("span", { className: "uix-brand-profiles__logo-placeholder", children: "Logo" }),
|
|
133
|
+
/* @__PURE__ */ jsx("strong", { children: value.name || "Brand preview" })
|
|
134
|
+
] }),
|
|
135
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-brand-profiles__preview-body", children: [
|
|
136
|
+
/* @__PURE__ */ jsx("h3", { children: "Readable, serializable branding" }),
|
|
137
|
+
/* @__PURE__ */ jsx("p", { children: "Accent, links, rings, and muted states continue through the existing UIx token chain." }),
|
|
138
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--primary", children: "Primary action" }),
|
|
139
|
+
/* @__PURE__ */ jsx("a", { href: "#brand-profile-preview", children: "Preview link" })
|
|
140
|
+
] })
|
|
141
|
+
] })
|
|
142
|
+
] });
|
|
143
|
+
}
|
|
144
|
+
const BrandProfiles = BrandProfileEditor;
|
|
145
|
+
export {
|
|
146
|
+
BrandProfileEditor,
|
|
147
|
+
BrandProfiles,
|
|
148
|
+
applyBrandProfile,
|
|
149
|
+
restoreBrandProfile
|
|
150
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface BuilderCanvasItem {
|
|
5
|
+
id: string;
|
|
6
|
+
type: string;
|
|
7
|
+
label: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
properties?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
interface BuilderPaletteItem {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
create: () => BuilderCanvasItem;
|
|
16
|
+
}
|
|
17
|
+
interface BuilderCanvasProps {
|
|
18
|
+
items: BuilderCanvasItem[];
|
|
19
|
+
onItemsChange: (items: BuilderCanvasItem[]) => void;
|
|
20
|
+
palette: BuilderPaletteItem[];
|
|
21
|
+
selectedId?: string;
|
|
22
|
+
onSelect?: (id: string | undefined) => void;
|
|
23
|
+
renderItem?: (item: BuilderCanvasItem, selected: boolean) => ReactNode;
|
|
24
|
+
renderProperties?: (item: BuilderCanvasItem, onChange: (item: BuilderCanvasItem) => void) => ReactNode;
|
|
25
|
+
loading?: boolean;
|
|
26
|
+
error?: string;
|
|
27
|
+
onRetry?: () => void;
|
|
28
|
+
emptyMessage?: string;
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Palette + sortable canvas + property panel scaffold. Owns interaction, never persistence. */
|
|
32
|
+
declare function BuilderCanvas({ items, onItemsChange, palette, selectedId: controlledSelectedId, onSelect, renderItem, renderProperties, loading, error, onRetry, emptyMessage, className, }: BuilderCanvasProps): react.JSX.Element;
|
|
33
|
+
|
|
34
|
+
export { BuilderCanvas, type BuilderCanvasItem, type BuilderCanvasProps, type BuilderPaletteItem };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
5
|
+
import { reorder } from "../table-engine.js";
|
|
6
|
+
function BuilderCanvas({
|
|
7
|
+
items,
|
|
8
|
+
onItemsChange,
|
|
9
|
+
palette,
|
|
10
|
+
selectedId: controlledSelectedId,
|
|
11
|
+
onSelect,
|
|
12
|
+
renderItem,
|
|
13
|
+
renderProperties,
|
|
14
|
+
loading,
|
|
15
|
+
error,
|
|
16
|
+
onRetry,
|
|
17
|
+
emptyMessage = "Add an item from the palette to begin.",
|
|
18
|
+
className
|
|
19
|
+
}) {
|
|
20
|
+
const [internalSelectedId, setInternalSelectedId] = useState();
|
|
21
|
+
const [draggedId, setDraggedId] = useState();
|
|
22
|
+
const propertyHeadingRef = useRef(null);
|
|
23
|
+
const shouldFocusProperties = useRef(false);
|
|
24
|
+
const selectedId = controlledSelectedId ?? internalSelectedId;
|
|
25
|
+
const selected = items.find((item) => item.id === selectedId);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (shouldFocusProperties.current && selected) {
|
|
28
|
+
propertyHeadingRef.current?.focus();
|
|
29
|
+
shouldFocusProperties.current = false;
|
|
30
|
+
}
|
|
31
|
+
}, [selected]);
|
|
32
|
+
const select = (id, focusProperties = true) => {
|
|
33
|
+
shouldFocusProperties.current = focusProperties;
|
|
34
|
+
if (controlledSelectedId === void 0) setInternalSelectedId(id);
|
|
35
|
+
onSelect?.(id);
|
|
36
|
+
};
|
|
37
|
+
const move = (id, direction) => {
|
|
38
|
+
const index = items.findIndex((item) => item.id === id);
|
|
39
|
+
const target = index + direction;
|
|
40
|
+
if (index < 0 || target < 0 || target >= items.length) return;
|
|
41
|
+
onItemsChange(reorder(items, index, target));
|
|
42
|
+
};
|
|
43
|
+
const add = (paletteItem) => {
|
|
44
|
+
const item = paletteItem.create();
|
|
45
|
+
onItemsChange([...items, item]);
|
|
46
|
+
select(item.id);
|
|
47
|
+
};
|
|
48
|
+
const remove = (id) => {
|
|
49
|
+
onItemsChange(items.filter((item) => item.id !== id));
|
|
50
|
+
if (selectedId === id) select(void 0, false);
|
|
51
|
+
};
|
|
52
|
+
const drop = (event, targetId) => {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
if (!draggedId || draggedId === targetId) return;
|
|
55
|
+
const from = items.findIndex((item) => item.id === draggedId);
|
|
56
|
+
const to = items.findIndex((item) => item.id === targetId);
|
|
57
|
+
if (from >= 0 && to >= 0) onItemsChange(reorder(items, from, to));
|
|
58
|
+
setDraggedId(void 0);
|
|
59
|
+
};
|
|
60
|
+
return /* @__PURE__ */ jsxs("section", { className: cx("uix-builder-canvas", className), "aria-label": "Builder canvas", children: [
|
|
61
|
+
/* @__PURE__ */ jsxs("aside", { className: "uix-builder-canvas__palette", "aria-label": "Item palette", children: [
|
|
62
|
+
/* @__PURE__ */ jsx("h3", { children: "Palette" }),
|
|
63
|
+
palette.length === 0 ? /* @__PURE__ */ jsx("p", { children: "No item types available." }) : /* @__PURE__ */ jsx("ul", { children: palette.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => add(item), children: [
|
|
64
|
+
/* @__PURE__ */ jsx("strong", { children: item.label }),
|
|
65
|
+
item.description && /* @__PURE__ */ jsx("span", { children: item.description }),
|
|
66
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "Add" })
|
|
67
|
+
] }) }, item.id)) })
|
|
68
|
+
] }),
|
|
69
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-builder-canvas__workspace", children: [
|
|
70
|
+
/* @__PURE__ */ jsx("h3", { children: "Canvas" }),
|
|
71
|
+
loading ? /* @__PURE__ */ jsx("div", { className: "uix-builder-canvas__state", role: "status", children: "Loading canvas\u2026" }) : error ? /* @__PURE__ */ jsxs("div", { className: "uix-builder-canvas__state", role: "alert", children: [
|
|
72
|
+
/* @__PURE__ */ jsx("p", { children: error }),
|
|
73
|
+
onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
|
|
74
|
+
] }) : items.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "uix-builder-canvas__state", children: [
|
|
75
|
+
/* @__PURE__ */ jsx("p", { children: emptyMessage }),
|
|
76
|
+
palette[0] && /* @__PURE__ */ jsxs("button", { type: "button", className: "uix-btn uix-btn--primary", onClick: () => add(palette[0]), children: [
|
|
77
|
+
"Add ",
|
|
78
|
+
palette[0].label
|
|
79
|
+
] })
|
|
80
|
+
] }) : /* @__PURE__ */ jsx("ol", { className: "uix-builder-canvas__items", children: items.map((item, index) => /* @__PURE__ */ jsxs(
|
|
81
|
+
"li",
|
|
82
|
+
{
|
|
83
|
+
draggable: true,
|
|
84
|
+
onDragStart: () => setDraggedId(item.id),
|
|
85
|
+
onDragEnd: () => setDraggedId(void 0),
|
|
86
|
+
onDragOver: (event) => event.preventDefault(),
|
|
87
|
+
onDrop: (event) => drop(event, item.id),
|
|
88
|
+
className: cx("uix-builder-canvas__item", selectedId === item.id && "uix-builder-canvas__item--selected"),
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ 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__ */ jsxs(Fragment, { children: [
|
|
91
|
+
/* @__PURE__ */ jsx("strong", { children: item.label }),
|
|
92
|
+
item.description && /* @__PURE__ */ jsx("span", { children: item.description })
|
|
93
|
+
] }) }),
|
|
94
|
+
/* @__PURE__ */ jsxs("span", { className: "uix-builder-canvas__item-actions", children: [
|
|
95
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => move(item.id, -1), disabled: index === 0, "aria-label": `Move ${item.label} up`, children: "Up" }),
|
|
96
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => move(item.id, 1), disabled: index === items.length - 1, "aria-label": `Move ${item.label} down`, children: "Down" }),
|
|
97
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => remove(item.id), "aria-label": `Remove ${item.label}`, children: "Remove" })
|
|
98
|
+
] })
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
item.id
|
|
102
|
+
)) })
|
|
103
|
+
] }),
|
|
104
|
+
/* @__PURE__ */ jsxs("aside", { className: "uix-builder-canvas__properties", "aria-label": "Item properties", children: [
|
|
105
|
+
/* @__PURE__ */ jsx("h3", { ref: propertyHeadingRef, tabIndex: -1, children: "Properties" }),
|
|
106
|
+
!selected ? /* @__PURE__ */ 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__ */ jsxs("dl", { children: [
|
|
107
|
+
/* @__PURE__ */ jsx("dt", { children: "Type" }),
|
|
108
|
+
/* @__PURE__ */ jsx("dd", { children: selected.type }),
|
|
109
|
+
/* @__PURE__ */ jsx("dt", { children: "Label" }),
|
|
110
|
+
/* @__PURE__ */ jsx("dd", { children: selected.label })
|
|
111
|
+
] })
|
|
112
|
+
] })
|
|
113
|
+
] });
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
BuilderCanvas
|
|
117
|
+
};
|
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { ChartProps } from './ChartCore.js';
|
|
3
|
+
export { ChartTableRow, uixChartPalette } from './ChartCore.js';
|
|
4
|
+
import 'echarts';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
[key: string]: string | number;
|
|
8
|
-
}
|
|
9
|
-
interface ChartProps {
|
|
10
|
-
option: EChartsOption;
|
|
11
|
-
title?: string;
|
|
12
|
-
subtitle?: string;
|
|
13
|
-
height?: string | number;
|
|
14
|
-
className?: string;
|
|
15
|
-
style?: CSSProperties;
|
|
16
|
-
ariaLabel?: string;
|
|
17
|
-
tableData?: ChartTableRow[];
|
|
18
|
-
tableHeaders?: string[];
|
|
19
|
-
onReady?: (chart: ECharts) => void;
|
|
20
|
-
header?: ReactNode;
|
|
21
|
-
}
|
|
22
|
-
declare function Chart({ option, title, subtitle, height, className, style, ariaLabel, tableData, tableHeaders, onReady, header, }: ChartProps): react.JSX.Element;
|
|
6
|
+
/** Full-compatibility adapter. Use `@tensor_1/react/chart/preset` for the lean preset. */
|
|
7
|
+
declare function Chart(props: ChartProps): react.JSX.Element;
|
|
23
8
|
|
|
24
|
-
export { Chart,
|
|
9
|
+
export { Chart, ChartProps };
|
package/dist/components/Chart.js
CHANGED
|
@@ -1,81 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx
|
|
3
|
-
import { useRef, useEffect, useId } from "react";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
3
|
import * as echarts from "echarts";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return
|
|
9
|
-
}
|
|
10
|
-
function uixChartPalette() {
|
|
11
|
-
return [1, 2, 3, 4, 5, 6, 7, 8].map((i) => readToken(`--uix-chart-${i}`));
|
|
12
|
-
}
|
|
13
|
-
function Chart({
|
|
14
|
-
option,
|
|
15
|
-
title,
|
|
16
|
-
subtitle,
|
|
17
|
-
height = 280,
|
|
18
|
-
className,
|
|
19
|
-
style,
|
|
20
|
-
ariaLabel,
|
|
21
|
-
tableData,
|
|
22
|
-
tableHeaders,
|
|
23
|
-
onReady,
|
|
24
|
-
header
|
|
25
|
-
}) {
|
|
26
|
-
const containerRef = useRef(null);
|
|
27
|
-
const chartRef = useRef(null);
|
|
28
|
-
const tableId = useId();
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
const el = containerRef.current;
|
|
31
|
-
if (!el) return;
|
|
32
|
-
const chart = echarts.init(el, null, { renderer: "svg" });
|
|
33
|
-
chartRef.current = chart;
|
|
34
|
-
chart.setOption(option);
|
|
35
|
-
onReady?.(chart);
|
|
36
|
-
const ro = new ResizeObserver(() => chart.resize());
|
|
37
|
-
ro.observe(el);
|
|
38
|
-
return () => {
|
|
39
|
-
ro.disconnect();
|
|
40
|
-
chart.dispose();
|
|
41
|
-
chartRef.current = null;
|
|
42
|
-
};
|
|
43
|
-
}, []);
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
chartRef.current?.setOption(option, { notMerge: false });
|
|
46
|
-
}, [option]);
|
|
47
|
-
const heightVal = typeof height === "number" ? `${height}px` : height;
|
|
48
|
-
const effectiveAriaLabel = ariaLabel ?? title ?? "Chart";
|
|
49
|
-
const hasTable = tableData && tableData.length > 0 && tableHeaders && tableHeaders.length > 0;
|
|
50
|
-
return /* @__PURE__ */ jsxs("div", { className: cx("uix-chart", className), style, children: [
|
|
51
|
-
(title != null || subtitle != null || header != null) && /* @__PURE__ */ jsxs("div", { children: [
|
|
52
|
-
header,
|
|
53
|
-
title && /* @__PURE__ */ jsx("div", { className: "uix-chart__title", children: title }),
|
|
54
|
-
subtitle && /* @__PURE__ */ jsx("div", { className: "uix-chart__subtitle", children: subtitle })
|
|
55
|
-
] }),
|
|
56
|
-
/* @__PURE__ */ jsx(
|
|
57
|
-
"div",
|
|
58
|
-
{
|
|
59
|
-
ref: containerRef,
|
|
60
|
-
style: { height: heightVal },
|
|
61
|
-
role: "img",
|
|
62
|
-
"aria-label": effectiveAriaLabel,
|
|
63
|
-
"aria-describedby": hasTable ? tableId : void 0
|
|
64
|
-
}
|
|
65
|
-
),
|
|
66
|
-
hasTable && /* @__PURE__ */ jsxs(
|
|
67
|
-
"table",
|
|
68
|
-
{
|
|
69
|
-
id: tableId,
|
|
70
|
-
className: "sr-only",
|
|
71
|
-
"aria-label": `${effectiveAriaLabel} \u2014 data table`,
|
|
72
|
-
children: [
|
|
73
|
-
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: tableHeaders.map((h) => /* @__PURE__ */ jsx("th", { scope: "col", children: h }, h)) }) }),
|
|
74
|
-
/* @__PURE__ */ jsx("tbody", { children: tableData.map((row, i) => /* @__PURE__ */ jsx("tr", { children: tableHeaders.map((h) => /* @__PURE__ */ jsx("td", { children: row[h] }, h)) }, i)) })
|
|
75
|
-
]
|
|
76
|
-
}
|
|
77
|
-
)
|
|
78
|
-
] });
|
|
4
|
+
import { ChartCore } from "./ChartCore.js";
|
|
5
|
+
import { uixChartPalette } from "./ChartCore.js";
|
|
6
|
+
function Chart(props) {
|
|
7
|
+
return /* @__PURE__ */ jsx(ChartCore, { ...props, engine: echarts });
|
|
79
8
|
}
|
|
80
9
|
export {
|
|
81
10
|
Chart,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
3
|
+
import { EChartsOption, ECharts } from 'echarts';
|
|
4
|
+
|
|
5
|
+
interface ChartEngine {
|
|
6
|
+
init(element: HTMLElement, theme?: string | null, options?: {
|
|
7
|
+
renderer?: 'svg' | 'canvas';
|
|
8
|
+
}): ECharts;
|
|
9
|
+
}
|
|
10
|
+
declare function uixChartPalette(): string[];
|
|
11
|
+
interface ChartTableRow {
|
|
12
|
+
[key: string]: string | number;
|
|
13
|
+
}
|
|
14
|
+
interface ChartProps {
|
|
15
|
+
option: EChartsOption;
|
|
16
|
+
title?: string;
|
|
17
|
+
subtitle?: string;
|
|
18
|
+
height?: string | number;
|
|
19
|
+
className?: string;
|
|
20
|
+
style?: CSSProperties;
|
|
21
|
+
ariaLabel?: string;
|
|
22
|
+
tableData?: ChartTableRow[];
|
|
23
|
+
tableHeaders?: string[];
|
|
24
|
+
onReady?: (chart: ECharts) => void;
|
|
25
|
+
header?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
interface ChartCoreProps extends ChartProps {
|
|
28
|
+
engine: ChartEngine;
|
|
29
|
+
}
|
|
30
|
+
/** Shared chart lifecycle. Runtime adapters only choose the ECharts build. */
|
|
31
|
+
declare function ChartCore({ engine, option, title, subtitle, height, className, style, ariaLabel, tableData, tableHeaders, onReady, header, }: ChartCoreProps): react.JSX.Element;
|
|
32
|
+
|
|
33
|
+
export { ChartCore, type ChartEngine, type ChartProps, type ChartTableRow, uixChartPalette };
|