@aurea-uds/react 0.30.0 → 0.32.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/dist/color.js ADDED
@@ -0,0 +1,293 @@
1
+ /** Os três canais de cada espaço, na ordem dele. */
2
+ export const COLOR_CHANNELS = {
3
+ hsb: ["hue", "saturation", "brightness"], hsl: ["hue", "saturation", "lightness"], rgb: ["red", "green", "blue"],
4
+ };
5
+ const limitar = (n, min, max) => Math.min(max, Math.max(min, n));
6
+ // ── O texto ─────────────────────────────────────────────────────────────────────────────────────────────────
7
+ // Texto de cor é entrada do usuário: o tamanho se corta ANTES da expressão, para nenhum texto enorme custar caro
8
+ // (o defeito de uma biblioteca de cor do mercado, alerta de 08/09/2026, era exatamente esse).
9
+ const HEX = /^#?([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
10
+ /** `#rgb`, `#rgba`, `#rrggbb` ou `#rrggbbaa` (com ou sem `#`) → os canais e o alfa; o resto → `null`. */
11
+ export function parseHex(texto) {
12
+ if (texto.length > 16)
13
+ return null;
14
+ const m = HEX.exec(texto.trim());
15
+ if (!m)
16
+ return null;
17
+ const h = m[1].length <= 4 ? m[1].replace(/./g, (c) => c + c) : m[1];
18
+ return { r: parseInt(h.slice(0, 2), 16), g: parseInt(h.slice(2, 4), 16), b: parseInt(h.slice(4, 6), 16),
19
+ a: h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1 };
20
+ }
21
+ /** Os canais → `#rrggbb` (minúscula, como o `<input type="color">`); com alfa abaixo de 1, `#rrggbbaa`. */
22
+ export function toHex({ r, g, b, a }) {
23
+ const dois = (n) => Math.round(limitar(n, 0, 255)).toString(16).padStart(2, "0");
24
+ const alfa = a === undefined || a >= 1 ? "" : dois(a * 255);
25
+ return `#${dois(r)}${dois(g)}${dois(b)}${alfa}`;
26
+ }
27
+ /** Normaliza o texto hex; texto inválido → `null`. */
28
+ export const normalizeHex = (texto) => {
29
+ const rgb = parseHex(texto);
30
+ return rgb ? toHex(rgb) : null;
31
+ };
32
+ // As funções do CSS que a referência aceita na entrada: rgb/rgba, hsl/hsla, hsb/hsba — vírgula ou espaço, `%` onde
33
+ // o CSS põe, e o alfa por `,` ou `/`. Números sem expoente e sem sinal: é texto de pessoa, não de máquina.
34
+ const NUM = "(\\d{1,3}(?:\\.\\d+)?)";
35
+ const FUNCAO = new RegExp(`^(rgb|hsl|hsb)a?\\(\\s*${NUM}(deg|%)?\\s*[, ]\\s*${NUM}(%)?\\s*[, ]\\s*${NUM}(%)?\\s*(?:[,/]\\s*(\\d*(?:\\.\\d+)?)(%)?\\s*)?\\)$`, "i");
36
+ /** Texto de cor → a cor de dentro: hex (3, 4, 6 ou 8 dígitos), `rgb()`, `hsl()` ou `hsb()`, com alfa. O resto →
37
+ * `null`. O matiz de `hsl()`/`hsb()` cinza é guardado (o hex o perderia). */
38
+ export function parseColor(texto) {
39
+ if (texto.length > 64)
40
+ return null;
41
+ const t = texto.trim();
42
+ const hex = parseHex(t);
43
+ if (hex)
44
+ return { ...rgbToHsb(hex), a: hex.a };
45
+ const m = FUNCAO.exec(t);
46
+ if (!m)
47
+ return null;
48
+ const [, fn, v1, , v2, , v3, , va, pa] = m;
49
+ const n1 = Number(v1), n2 = Number(v2), n3 = Number(v3);
50
+ const a = va === undefined || va === "" ? 1 : limitar(pa ? Number(va) / 100 : Number(va), 0, 1);
51
+ const tipo = fn.toLowerCase();
52
+ if (tipo === "rgb") {
53
+ if (n1 > 255 || n2 > 255 || n3 > 255)
54
+ return null;
55
+ return { ...rgbToHsb({ r: n1, g: n2, b: n3 }), a };
56
+ }
57
+ if (n1 > 360 || n2 > 100 || n3 > 100)
58
+ return null;
59
+ if (tipo === "hsb")
60
+ return { h: n1 % 360, s: n2 / 100, b: n3 / 100, a };
61
+ return { ...hslParaHsb(n1 % 360, n2 / 100, n3 / 100), a };
62
+ }
63
+ /** A cor de dentro → hex (`#rrggbb`, ou `#rrggbbaa` com transparência). */
64
+ export const corToHex = (c) => toHex({ ...hsbToRgb(c), a: c.a });
65
+ // ── As conversões ───────────────────────────────────────────────────────────────────────────────────────────
66
+ // O matiz pela fórmula de seis setores (a mesma do HSV e do HSL).
67
+ function matiz(r, g, b, max, d) {
68
+ if (d === 0)
69
+ return 0;
70
+ const h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
71
+ return h * 60;
72
+ }
73
+ export function rgbToHsb({ r, g, b }) {
74
+ const [R, G, B] = [r / 255, g / 255, b / 255];
75
+ const max = Math.max(R, G, B), d = max - Math.min(R, G, B);
76
+ return { h: matiz(R, G, B, max, d), s: max === 0 ? 0 : d / max, b: max };
77
+ }
78
+ export function hsbToRgb({ h, s, b }) {
79
+ const f = (n) => {
80
+ const k = (n + h / 60) % 6;
81
+ return 255 * (b - b * s * Math.max(0, Math.min(k, 4 - k, 1)));
82
+ };
83
+ return { r: f(5), g: f(3), b: f(1) };
84
+ }
85
+ export function rgbToHsl({ r, g, b }) {
86
+ const [R, G, B] = [r / 255, g / 255, b / 255];
87
+ const max = Math.max(R, G, B), min = Math.min(R, G, B), d = max - min, l = (max + min) / 2;
88
+ return { h: matiz(R, G, B, max, d), s: d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1)), l };
89
+ }
90
+ export function hslToRgb({ h, s, l }) {
91
+ const a = s * Math.min(l, 1 - l);
92
+ const f = (n) => {
93
+ const k = (n + h / 30) % 12;
94
+ return 255 * (l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)));
95
+ };
96
+ return { r: f(0), g: f(8), b: f(4) };
97
+ }
98
+ // HSB ↔ HSL direto, com o matiz passando por fora (pelo RGB ele se perderia no cinza).
99
+ function hsbParaHsl(c) {
100
+ const l = c.b * (1 - c.s / 2);
101
+ return { l, s: l === 0 || l === 1 ? 0 : (c.b - l) / Math.min(l, 1 - l) };
102
+ }
103
+ function hslParaHsb(h, s, l, anterior) {
104
+ const b = l + s * Math.min(l, 1 - l);
105
+ return { h, s: b === 0 ? (anterior?.s ?? 0) : 2 * (1 - l / b), b };
106
+ }
107
+ // ── Os canais ───────────────────────────────────────────────────────────────────────────────────────────────
108
+ /** A faixa de cada canal, a mesma da referência: matiz 0–360 (passo 1, salto 15); saturação, brilho e luminosidade
109
+ * 0–100 (passo 1, salto 10); vermelho, verde e azul 0–255 (passo 1, salto 17); alfa 0–1 (passo 0,01, salto 0,1). */
110
+ export function channelRange(ch) {
111
+ if (ch === "hue")
112
+ return { min: 0, max: 360, step: 1, page: 15 };
113
+ if (ch === "red" || ch === "green" || ch === "blue")
114
+ return { min: 0, max: 255, step: 1, page: 17 };
115
+ if (ch === "alpha")
116
+ return { min: 0, max: 1, step: 0.01, page: 0.1 };
117
+ return { min: 0, max: 100, step: 1, page: 10 };
118
+ }
119
+ /** O espaço em que um canal vive: luminosidade só no HSL, brilho só no HSB, vermelho/verde/azul só no RGB; matiz,
120
+ * saturação e alfa no espaço pedido (sem pedido, HSB). */
121
+ export function spaceOf(ch, pedido) {
122
+ if (ch === "lightness")
123
+ return "hsl";
124
+ if (ch === "brightness")
125
+ return "hsb";
126
+ if (ch === "red" || ch === "green" || ch === "blue")
127
+ return "rgb";
128
+ return pedido === "rgb" && ch !== "alpha" ? "hsb" : pedido ?? "hsb";
129
+ }
130
+ /** O valor de um canal, na faixa dele. */
131
+ export function getChannel(c, ch, space = spaceOf(ch)) {
132
+ if (ch === "alpha")
133
+ return c.a;
134
+ if (ch === "hue")
135
+ return c.h;
136
+ if (space === "rgb") {
137
+ const rgb = hsbToRgb(c);
138
+ return ch === "red" ? rgb.r : ch === "green" ? rgb.g : rgb.b;
139
+ }
140
+ if (space === "hsl") {
141
+ const v = hsbParaHsl(c);
142
+ return (ch === "lightness" ? v.l : v.s) * 100;
143
+ }
144
+ return (ch === "brightness" ? c.b : c.s) * 100;
145
+ }
146
+ /** A cor com um canal trocado (o valor é limitado à faixa). O matiz e a saturação de antes ficam quando a cor nova
147
+ * vira cinza ou preta. */
148
+ export function setChannel(c, ch, v, space = spaceOf(ch)) {
149
+ const { min, max } = channelRange(ch);
150
+ const n = limitar(v, min, max);
151
+ if (ch === "alpha")
152
+ return { ...c, a: n };
153
+ if (ch === "hue")
154
+ return { ...c, h: n };
155
+ if (space === "rgb") {
156
+ const rgb = { ...hsbToRgb(c), [ch === "red" ? "r" : ch === "green" ? "g" : "b"]: n };
157
+ const novo = rgbToHsb(rgb);
158
+ return { h: novo.s === 0 || novo.b === 0 ? c.h : novo.h, s: novo.b === 0 ? c.s : novo.s, b: novo.b, a: c.a };
159
+ }
160
+ if (space === "hsl") {
161
+ const atual = hsbParaHsl(c);
162
+ const s = ch === "saturation" ? n / 100 : atual.s, l = ch === "lightness" ? n / 100 : atual.l;
163
+ return { ...hslParaHsb(c.h, s, l, c), a: c.a };
164
+ }
165
+ return { ...c, [ch === "brightness" ? "b" : "s"]: n / 100 };
166
+ }
167
+ // ── O contraste ─────────────────────────────────────────────────────────────────────────────────────────────
168
+ /** A luminância relativa da WCAG 2 (0 = preto, 1 = branco), a conta do contraste. */
169
+ export function relativeLuminance({ r, g, b }) {
170
+ const lin = (c) => { const s = c / 255; return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; };
171
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
172
+ }
173
+ /** A tinta que contrasta MAIS sobre a cor: `"dark"` (preto) ou `"light"` (branco), pela razão de contraste da WCAG
174
+ * — e não por um palpite de "cor clara". O amarelo da Aurea pede preto; um azul médio também. */
175
+ export function contrastingInk(rgb) {
176
+ const L = relativeLuminance(rgb);
177
+ return (L + 0.05) / 0.05 >= 1.05 / (L + 0.05) ? "dark" : "light";
178
+ }
179
+ const opaca = (c) => toHex(hsbToRgb(c));
180
+ // A cor pura de um matiz (saturação e brilho cheios).
181
+ const puro = (h) => opaca({ h, s: 1, b: 1, a: 1 });
182
+ /** A cor pura de um matiz, em hex — a cor do pino da trilha de matiz. */
183
+ export const pureHue = (h) => puro(h);
184
+ /** O FUNDO da trilha de um canal, para o `background` do trilho do `Slider`. O gradiente vai de ponta a ponta do
185
+ * trilho, mas as paradas começam e terminam na FOLGA (`--slider-cap`, o recuo que o pino respeita): antes da
186
+ * primeira e depois da última, a cor chapada da ponta. Assim o pino no mínimo fica sobre a cor do mínimo, em
187
+ * qualquer canal. O alfa vai do transparente à cor opaca (o xadrez embaixo é a pele, e não gradiente). */
188
+ export function trackBackground(c, ch, space = spaceOf(ch), direcao = "to right") {
189
+ const { min, max } = channelRange(ch);
190
+ let cores;
191
+ if (ch === "hue")
192
+ cores = [0, 60, 120, 180, 240, 300, 360].map(puro);
193
+ else if (ch === "alpha")
194
+ cores = [toHex({ ...hsbToRgb(c), a: 0 }), opaca(c)];
195
+ else if (ch === "lightness")
196
+ cores = [min, (max - min) / 2, max].map((v) => opaca(setChannel(c, ch, v, space)));
197
+ else
198
+ cores = [min, max].map((v) => opaca(setChannel(c, ch, v, space)));
199
+ const k = cores.length - 1;
200
+ const paradas = cores.map((cor, i) => `${cor} calc(var(--slider-cap, 0px) + (100% - 2 * var(--slider-cap, 0px)) * ${i / k})`);
201
+ return `linear-gradient(${direcao}, ${paradas.join(", ")})`;
202
+ }
203
+ /** O fundo da área de cor para um par de canais, com o terceiro fixo no valor da cor de agora. A técnica é a da
204
+ * biblioteca da referência (escrita aqui por nós): no RGB, uma camada por canal somadas pelo modo de mistura
205
+ * `screen`; no HSL e no HSB, uma camada por canal com a receita de paradas dele, e a cor pura embaixo quando o
206
+ * fixo é o matiz. `x` anda do começo ao fim da linha (na escrita da direita para a esquerda, o começo é a direita);
207
+ * `y`, de baixo para cima. */
208
+ export function areaBackground(c, space, x, y, rtl = false) {
209
+ const fim = rtl ? "to left" : "to right";
210
+ const z = COLOR_CHANNELS[space].find((ch) => ch !== x && ch !== y);
211
+ const zv = getChannel(c, z, space);
212
+ if (space === "rgb") {
213
+ const so = (ch, v) => {
214
+ const rgb = { r: 0, g: 0, b: 0 };
215
+ rgb[ch === "red" ? "r" : ch === "green" ? "g" : "b"] = v;
216
+ return toHex(rgb);
217
+ };
218
+ return { background: [`linear-gradient(${fim}, ${so(x, 0)}, ${so(x, 255)})`, `linear-gradient(to top, ${so(y, 0)}, ${so(y, 255)})`,
219
+ so(z, zv)].join(", "), blend: "screen" };
220
+ }
221
+ const base = setChannel({ h: 0, s: 1, b: 1, a: 1 }, z, zv, space);
222
+ const receita = (ch) => ch === "hue" ? [0, 60, 120, 180, 240, 300, 360].map((h) => opaca(setChannel(base, "hue", h, space))).join(", ")
223
+ : ch === "saturation" ? `${opaca(setChannel(base, "saturation", 0, space))}, transparent`
224
+ : ch === "lightness" ? "#000, transparent, #fff"
225
+ : "#000, transparent";
226
+ const camadas = COLOR_CHANNELS[space].filter((ch) => ch !== z)
227
+ .map((ch) => `linear-gradient(${ch === x ? fim : "to top"}, ${receita(ch)})`).reverse();
228
+ if (z === "hue")
229
+ camadas.push(opaca(base));
230
+ return { background: camadas.join(", ") };
231
+ }
232
+ // A CLARIDADE ("escuro", "claro") é pelo HSL: a luminosidade dele põe o amarelo puro e o azul puro no MESMO meio
233
+ // (0,5), então as palavras dizem a mistura com branco ou preto, e não a claridade própria de cada matiz. A FORÇA
234
+ // ("vivo", "pálido", "acinzentado") é pelo HSB: no HSL um rosa pastel tem saturação 1 e sairia "vivo".
235
+ function matizPeloNome(h, l) {
236
+ if (h >= 15 && h < 70 && l < 0.35)
237
+ return "brown";
238
+ if ((h >= 330 || h < 15) && l > 0.7)
239
+ return "pink";
240
+ if (h < 15 || h >= 345)
241
+ return "red";
242
+ if (h < 45)
243
+ return "orange";
244
+ if (h < 70)
245
+ return "yellow";
246
+ // O ciano é estreito (170–190): o azul-claro do CSS (`#add8e6`, 194,7°) e o azul-céu (197°) são azuis para quem vê.
247
+ if (h < 170)
248
+ return "green";
249
+ if (h < 190)
250
+ return "cyan";
251
+ if (h < 255)
252
+ return "blue";
253
+ if (h < 290)
254
+ return "purple";
255
+ return "magenta";
256
+ }
257
+ /** O nome da cor na língua das frases: "vivid blue", "dark red", "rosa pálido". */
258
+ export function colorName(rgb, s) {
259
+ const { h, l } = rgbToHsl(rgb);
260
+ const { s: forca, b: brilho } = rgbToHsb(rgb);
261
+ let hue;
262
+ let claridade;
263
+ let croma;
264
+ if (l >= 0.95)
265
+ hue = "white";
266
+ else if (l <= 0.08)
267
+ hue = "black";
268
+ else if (forca < 0.12)
269
+ hue = "gray";
270
+ else {
271
+ hue = matizPeloNome(h, l);
272
+ croma = forca >= 0.8 && brilho >= 0.8 ? "vivid" : forca < 0.4 ? (brilho >= 0.85 ? "pale" : "grayish") : undefined;
273
+ }
274
+ // "Pálido" já diz claro; o branco, o preto, o marrom e o rosa já dizem a claridade no próprio nome.
275
+ if (croma !== "pale" && hue !== "white" && hue !== "black" && hue !== "brown" && hue !== "pink") {
276
+ claridade = l < 0.2 ? "veryDark" : l < 0.35 ? "dark" : l > 0.85 ? "veryLight" : l > 0.7 ? "light" : undefined;
277
+ }
278
+ return s.colorNameFormat
279
+ .replace("{lightness}", claridade ? s.colorTone[claridade] : "")
280
+ .replace("{chroma}", croma ? s.colorTone[croma] : "")
281
+ .replace("{hue}", s.colorHue[hue])
282
+ .replace(/\s+/g, " ").trim();
283
+ }
284
+ /** O valor de um canal escrito para o leitor de tela e a saída: "210°", "80%", "128", alfa "50%". */
285
+ export function formatChannel(ch, v) {
286
+ if (ch === "hue")
287
+ return `${Math.round(v)}°`;
288
+ if (ch === "alpha")
289
+ return `${Math.round(v * 100)}%`;
290
+ if (ch === "red" || ch === "green" || ch === "blue")
291
+ return `${Math.round(v)}`;
292
+ return `${Math.round(v)}%`;
293
+ }
@@ -1,11 +1,21 @@
1
- import { type ReactNode } from "react";
2
- export declare function Accordion({ items }: {
3
- items: Array<{
4
- id: string;
5
- title: ReactNode;
6
- content: ReactNode;
7
- }>;
8
- }): import("react").JSX.Element;
1
+ import React, { type ReactNode } from "react";
2
+ export type AccordionVariant = "default" | "surface";
3
+ export interface AccordionItem {
4
+ id: string;
5
+ title: ReactNode;
6
+ content: ReactNode;
7
+ /** O item que não abre: anunciado como desligado e fora do Tab. */
8
+ disabled?: boolean;
9
+ }
10
+ export declare function Accordion({ items, variant, multiple, defaultValue, className }: {
11
+ items: AccordionItem[];
12
+ variant?: AccordionVariant;
13
+ /** Deixa vários itens abertos ao mesmo tempo. Sem isto, abrir um fecha o outro. */
14
+ multiple?: boolean;
15
+ /** Os itens que começam abertos (o `id` de cada um). */
16
+ defaultValue?: string[];
17
+ className?: string;
18
+ }): React.JSX.Element;
9
19
  export declare function Collapsible({ trigger, children, open, defaultOpen, onOpenChange, disabled, className }: {
10
20
  trigger: ReactNode;
11
21
  children: ReactNode;
@@ -14,4 +24,4 @@ export declare function Collapsible({ trigger, children, open, defaultOpen, onOp
14
24
  onOpenChange?: (open: boolean) => void;
15
25
  disabled?: boolean;
16
26
  className?: string;
17
- }): import("react").JSX.Element;
27
+ }): React.JSX.Element;
@@ -1,10 +1,17 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
4
+ // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
5
+ // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
6
+ import React from "react";
3
7
  import { Collapsible as BaseCollapsible } from "@base-ui/react/collapsible";
4
8
  import { cx } from "./internal.js";
5
9
  import { soOValor } from "./pure.js";
6
10
  import { Icon } from "./system.js";
7
- export function Accordion({ items }) { return _jsx("div", { className: "accordion", children: items.map(i => _jsxs("details", { children: [_jsx("summary", { children: i.title }), _jsx("div", { className: "accordion-content", children: i.content })] }, i.id)) }); }
11
+ export function Accordion({ items, variant = "default", multiple = false, defaultValue, className }) {
12
+ const grupo = React.useId();
13
+ return _jsx("div", { className: cx("accordion", variant === "surface" && "accordion-surface", className), children: items.map(i => _jsxs("details", { className: "accordion-item", name: multiple ? undefined : grupo, open: defaultValue?.includes(i.id) || undefined, children: [_jsxs("summary", { "aria-disabled": i.disabled || undefined, tabIndex: i.disabled ? -1 : undefined, onClick: i.disabled ? (e) => e.preventDefault() : undefined, children: [_jsx("span", { className: "accordion-title", children: i.title }), _jsx("span", { className: "accordion-chevron", "aria-hidden": "true" })] }), _jsx("div", { className: "accordion-content", children: i.content })] }, i.id)) });
14
+ }
8
15
  // COLLAPSIBLE — uma seção que abre e fecha, com o estado do lado de quem usa.
9
16
  //
10
17
  // Não é um Accordion de um item só, e a diferença é o motivo de existir: o `Accordion` é um
@@ -8,7 +8,7 @@ export declare function Status({ variant, state, children, className, ...props }
8
8
  variant?: StatusVariant;
9
9
  state?: UniversalState;
10
10
  }): React.JSX.Element;
11
- export type AlertVariant = "info" | "success" | "warning" | "danger";
11
+ export type AlertVariant = "info" | "success" | "warning" | "danger" | "accent";
12
12
  export declare function Alert({ variant, state, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
13
13
  variant?: AlertVariant;
14
14
  state?: UniversalState;
@@ -16,7 +16,7 @@ export declare function Alert({ variant, state, title, icon, onDismiss, children
16
16
  icon?: IconName;
17
17
  onDismiss?: () => void;
18
18
  }): React.JSX.Element;
19
- export type BannerVariant = AlertVariant;
19
+ export type BannerVariant = "info" | "success" | "warning" | "danger";
20
20
  export declare function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
21
21
  variant?: BannerVariant;
22
22
  state?: UniversalState;
@@ -24,13 +24,14 @@ export declare function Banner({ variant, state, title, icon, onDismiss, childre
24
24
  icon?: IconName;
25
25
  onDismiss?: () => void;
26
26
  }): React.JSX.Element;
27
+ export type SpinnerSize = "sm" | "md" | "lg" | "xl";
27
28
  export declare function Spinner({ size, label, decorative, className, ...props }: HTMLAttributes<HTMLSpanElement> & RefAttributes<HTMLSpanElement> & {
28
- size?: Responsive<"sm" | "md" | "lg">;
29
+ size?: Responsive<SpinnerSize>;
29
30
  label?: string;
30
31
  decorative?: boolean;
31
32
  }): React.JSX.Element;
32
33
  export type DataStateValue = "loading" | "error" | "empty" | UniversalState;
33
- export declare function DataState({ state, message, skeleton, emptyTitle, emptyIcon, action, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
34
+ export type DataStateProps = HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
34
35
  state?: DataStateValue;
35
36
  message?: ReactNode;
36
37
  skeleton?: ReactNode;
@@ -38,7 +39,12 @@ export declare function DataState({ state, message, skeleton, emptyTitle, emptyI
38
39
  emptyIcon?: IconName;
39
40
  action?: ReactNode;
40
41
  children: ReactNode | (() => ReactNode);
41
- }): React.JSX.Element;
42
+ /** A barreira de erro (MNT-25): o que quebrar ao desenhar o conteúdo vira a cara de erro, com "Tentar de novo". */
43
+ catchErrors?: boolean;
44
+ /** Recebe o erro que a barreira pegou — para o app registrar. */
45
+ onCatch?: (error: unknown, info: React.ErrorInfo) => void;
46
+ };
47
+ export declare function DataState({ catchErrors, onCatch, ...props }: DataStateProps): React.JSX.Element;
42
48
  export declare function EmptyState({ icon, title, titleAs: TitleTag, description, action, state }: {
43
49
  icon?: IconName;
44
50
  title: ReactNode;
@@ -8,7 +8,7 @@ import React from "react";
8
8
  import { Popover as BasePopover } from "@base-ui/react/popover";
9
9
  import { cx, useAureaStrings, usePortalContainer, stateSeverity } from "./internal.js";
10
10
  import { Icon } from "./system.js";
11
- import { Button, IconButton } from "./actions.js";
11
+ import { Button, IconButton, CloseButton } from "./actions.js";
12
12
  // `oracle` saiu daqui (achado M9, 26/07/2026): estava no tipo e no CSS, e NÃO estava na
13
13
  // ficha — ou seja, era superfície pública que o contrato não declarava. E é vocabulário do
14
14
  // app de origem (papel de agente), não do sistema, como as classes de domínio que o achado
@@ -36,7 +36,7 @@ export function Status({ variant, state, children, className, ...props }) { cons
36
36
  // referência e o aviso de largura de página nem sempre quer glifo — a assimetria é decidida,
37
37
  // não herdada.
38
38
  const ICONE_DA_VARIANTE = { info: "info",
39
- success: "check-circle", warning: "warning", danger: "x-circle" };
39
+ success: "check-circle", warning: "warning", danger: "x-circle", accent: "info" };
40
40
  // A grade do `.alert` tem TRÊS trilhas (auto 1fr auto): ícone, corpo, ação. Uma versão anterior
41
41
  // emitia o título solto na trilha 1 — a do ícone — e o corpo como item anônimo. Medido no
42
42
  // navegador em 21/08/2026: o título ocupava a coluna do ícone e crescia com o próprio texto,
@@ -51,23 +51,34 @@ const ICONE_DA_VARIANTE = { info: "info",
51
51
  // universal. Quem passa só `state` recebe a cor derivada da gravidade e o rótulo universal
52
52
  // quando não há filho; quem passa os dois manda, porque só o consumidor sabe se aquele
53
53
  // "esperando" dele é grave.
54
- export function Alert({ variant, state, title, icon, onDismiss, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("alert", `alert-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [_jsx(Icon, { name: icon ?? ICONE_DA_VARIANTE[v], weight: icon ? undefined : "fill" }), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }), onDismiss ? _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.close, onClick: onDismiss }) : _jsx("span", {})] }); }
54
+ export function Alert({ variant, state, title, icon, onDismiss, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("alert", `alert-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [_jsx(Icon, { name: icon ?? ICONE_DA_VARIANTE[v], weight: icon ? undefined : "fill" }), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }), onDismiss ? _jsx(CloseButton, { onClick: onDismiss }) : _jsx("span", {})] }); }
55
55
  export function Banner({ variant, state, title, icon, onDismiss, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("banner", `banner-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [icon ? _jsx(Icon, { name: icon }) : _jsx("span", {}), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? s.universalState[state] : null)] }), onDismiss ? _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.close, onClick: onDismiss }) : _jsx("span", {})] }); }
56
- // A barra grampeava 0..100 e o `aria-valuenow` NÃO — medido ao publicar o contrato de API na
57
- // Parte E: com value=150 o desenho parava em 100% e o leitor de tela anunciava "150 de 100".
58
- // A causa é a de sempre: o grampo existia num lugar só. Agora é UMA expressão que serve os dois,
59
- // então não há como divergirem de novo.
60
- // SÓ DETERMINADA, de propósito: não há modo indeterminado nem `.progress` para ele no core. A
61
- // ficha dizia que havia e era falso — corrigido junto, porque contrato que promete o que não
62
- // existe custa mais que ausência.
63
- // O DEGRAU RESPONSIVO volta (merge de 28/08/2026): a ficha declara `responsive.size` e o
64
- // componente tinha perdido a capacidade quando este arquivo entrou da `main`. Mesma perda
65
- // silenciosa do `Input`, do `Tabs` e do `NumberField` — a ficha documentava, o código não fazia.
66
- export function Spinner({ size, label, decorative, className, ...props }) { const s = useAureaStrings(); return _jsx("span", { className: cx("spinner", peleDoEixo("spinner", size, "sm", "spinner"), className), ...(decorative ? { "aria-hidden": true } : { role: "status", "aria-label": label ?? s.loading }), ...props }); }
56
+ export function Spinner({ size = "md", label, decorative, className, ...props }) { const s = useAureaStrings(); return _jsx("span", { className: cx("spinner", peleDoEixo("spinner", size, "sm", "spinner"), className), ...(decorative ? { "aria-hidden": true } : { role: "status", "aria-label": label ?? s.loading }), ...props }); }
67
57
  // `children` como FUNÇÃO é de propósito para o caso `loading`: assim o consumidor não paga o
68
58
  // render do conteúdo enquanto ele não existe. Aceita nó também, porque a maioria das telas já
69
59
  // tem o conteúdo pronto e obrigar função seria cerimônia.
70
- export function DataState({ state, message, skeleton, emptyTitle, emptyIcon, action, children, className, ...props }) {
60
+ // MNT-25 (passo 10, 0.32.0): A BARREIRA DE ERRO, como variação do `DataState` (escolha do Victor,
61
+ // 12/10/2026: escrita na Aurea, sem dependência nova; variação, e não peça nova). Um erro de desenho no app
62
+ // de rede deixou a tela inteira em branco. Com `catchErrors`, o que quebra DENTRO do `DataState` vira a
63
+ // cara de erro dele (a mensagem e "Tentar de novo"), e o resto da tela fica. O React só pega erro de
64
+ // desenho com um componente de CLASSE (não há gancho para isso) — é o jeito que ele manda, e o único.
65
+ // `onCatch`, e não `onError`: `onError` já é o evento do HTML que o `DataState` repassa ao `<div>`.
66
+ // O que a barreira NÃO pega (é do React, não daqui): erro em evento de clique, em código assíncrono e no
67
+ // desenho no servidor.
68
+ class BarreiraDeErro extends React.Component {
69
+ state = { quebrou: false };
70
+ static getDerivedStateFromError() { return { quebrou: true }; }
71
+ componentDidCatch(erro, info) { this.props.onCatch?.(erro, info); }
72
+ tentar = () => this.setState({ quebrou: false });
73
+ render() { return this.state.quebrou ? this.props.falha(this.tentar) : this.props.children; }
74
+ }
75
+ export function DataState({ catchErrors, onCatch, ...props }) {
76
+ const s = useAureaStrings();
77
+ if (!catchErrors)
78
+ return _jsx(CorpoDoDataState, { ...props });
79
+ return _jsx(BarreiraDeErro, { onCatch: onCatch, falha: tentar => _jsxs("div", { className: cx("data-state", props.className), "data-state": "error", children: [_jsx(Alert, { variant: "danger", children: props.message ?? s.dataCrashed }), _jsx("div", { children: _jsx(Button, { size: "sm", onClick: tentar, children: s.dataRetry }) })] }), children: _jsx(CorpoDoDataState, { ...props }) });
80
+ }
81
+ function CorpoDoDataState({ state, message, skeleton, emptyTitle, emptyIcon, action, children, className, ...props }) {
71
82
  const s = useAureaStrings();
72
83
  const conteudo = () => typeof children === "function" ? children() : children;
73
84
  const caixa = (inner, ocupado) => _jsx("div", { className: cx("data-state", className), "aria-busy": ocupado || undefined, "data-state": state, ...props, children: inner });
@@ -0,0 +1,84 @@
1
+ export type Ponto = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ /** Retângulo no plano do mapa. `larg`/`alt`, e não `width`/`height`: o check 23 lê o fonte inteiro. */
6
+ export type Caixa = {
7
+ x: number;
8
+ y: number;
9
+ larg: number;
10
+ alt: number;
11
+ };
12
+ export type Borda = "left" | "center" | "right" | "top" | "middle" | "bottom";
13
+ export declare function alinhar(caixas: ReadonlyMap<string, Caixa>, borda: Borda): Map<string, Ponto>;
14
+ /**
15
+ * Distribuir mantém as DUAS PONTAS paradas e deixa os vãos iguais entre os do meio. Se os nós já
16
+ * se sobrepõem (o vão daria negativo), o passo igual vai para os CENTROS, como no draw.io.
17
+ */
18
+ export declare function distribuir(caixas: ReadonlyMap<string, Caixa>, eixo: "horizontal" | "vertical"): Map<string, Ponto>;
19
+ export declare function subarvores(ids: readonly string[], arestas: readonly {
20
+ from: string;
21
+ to: string;
22
+ }[], raiz?: string): Map<string, string[]>;
23
+ export declare function recolher(ids: readonly string[], pais: ReadonlyMap<string, string>, dominados: ReadonlyMap<string, readonly string[]>, recolhidos: ReadonlySet<string>, contemFilhos: ReadonlySet<string>): {
24
+ rep: Map<string, string>;
25
+ guardados: Map<string, number>;
26
+ };
27
+ /**
28
+ * As linhas depois do fechamento. A que fica inteira dentro de um fechado some; a que saía de um
29
+ * escondido passa a sair do representante; e as que ficaram entre o MESMO par viram UMA linha com o
30
+ * número — a "aresta agregada" do mercado (o padrão aberto de quem fecha grupo). As inteiras ficam
31
+ * como estavam.
32
+ */
33
+ export declare function remapear<T extends {
34
+ from: string;
35
+ to: string;
36
+ }>(arestas: readonly T[], rep: ReadonlyMap<string, string>): {
37
+ item: T;
38
+ de: string;
39
+ para: string;
40
+ n: number;
41
+ }[];
42
+ /** Tira os pontos repetidos e os do meio de um trecho reto: a ponte e o canto contam os trechos. */
43
+ export declare function simplificar(pontos: readonly Ponto[]): Ponto[];
44
+ /** O ponto no MEIO do comprimento — onde fica o rótulo e a pastilha da linha. */
45
+ export declare function meioDoTracado(pontos: readonly Ponto[]): Ponto;
46
+ /** Uma ponte: o intervalo do trecho, medido no eixo dele, em que a linha salta. */
47
+ export type Ponte = {
48
+ de: number;
49
+ ate: number;
50
+ };
51
+ /**
52
+ * AS PONTES (MNT-12.3), na técnica do draw.io: a linha desenhada DEPOIS salta sobre as de antes,
53
+ * só onde um trecho horizontal cruza um vertical — dois trechos no mesmo eixo não se cruzam. O
54
+ * cruzamento perto demais de uma dobra não ganha ponte (o arco bateria no canto). Pontes que se
55
+ * encostam viram uma só, mais larga. A caixa de cada linha filtra os pares antes da conta.
56
+ */
57
+ export declare function pontes(linhas: readonly {
58
+ id: string;
59
+ pontos: readonly Ponto[];
60
+ }[], raio: number, canto: number): Map<string, Map<number, Ponte[]>>;
61
+ /**
62
+ * O `d` do SVG: a linha pelos pontos, com o canto arredondado (o `--radius-sm` da casa, ou menos se
63
+ * o trecho for curto) e o salto de cada ponte — meia elipse, por cima no trecho deitado e à direita
64
+ * no de pé, seja qual for o sentido do trecho.
65
+ */
66
+ export declare function tracado(pontos: readonly Ponto[], canto: number, saltos?: ReadonlyMap<number, readonly Ponte[]>, raio?: number): string;
67
+ /** De que lado do nó a linha sai (ou entra), lido do primeiro (ou último) trecho. */
68
+ export declare function ladoDoTrecho(de: Ponto, para: Ponto, saindo: boolean): "top" | "right" | "bottom" | "left";
69
+ /** O trecho atravessa o MIOLO da caixa (encostar na borda não conta — é assim que a linha chega). */
70
+ export declare function trechoCruza(a: Ponto, b: Ponto, c: Caixa): boolean;
71
+ export declare const rotaCruza: (pontos: readonly Ponto[], caixas: Iterable<Caixa>) => boolean;
72
+ /** Onde a reta entre os dois centros sai da caixa: a ponta da linha "flutuante" (arrumação radial). */
73
+ export declare function bordaNaDirecao(c: Caixa, para: Ponto): Ponto;
74
+ /**
75
+ * Nenhum rótulo cobre outro rótulo nem um nó. Não há biblioteca livre que POSICIONE sem sobrepor;
76
+ * o que as bibliotecas abertas fazem é ESCONDER por prioridade: o de prioridade maior fica, e no empate fica
77
+ * o que veio antes na lista do app. Devolve os que somem.
78
+ */
79
+ export declare function rotulosQueSomem(rotulos: readonly {
80
+ id: string;
81
+ caixa: Caixa;
82
+ prioridade: number;
83
+ }[], obstaculos: readonly Caixa[]): Set<string>;
84
+ export declare function contornoDeNuvem(larg: number, alt: number): string;