@aurea-uds/react 0.28.0 → 0.30.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/graph.d.ts +98 -2
- package/dist/graph.js +394 -48
- package/dist/index.d.ts +1 -1
- package/dist/markup.d.ts +4 -2
- package/dist/markup.js +2 -2
- package/dist/pure.d.ts +8 -0
- package/dist/pure.js +2 -2
- package/dist/system.d.ts +11 -2
- package/dist/system.js +8 -1
- package/package.json +9 -1
package/dist/graph.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import React, { type ReactNode } from "react";
|
|
2
|
+
import { type AureaIcon } from "./system.js";
|
|
3
|
+
export type GraphOrientation = "horizontal" | "vertical";
|
|
4
|
+
export type GraphLayout = "simple" | "layered";
|
|
5
|
+
export type GraphEdgeShape = "curve" | "step";
|
|
6
|
+
export type GraphPortSide = "top" | "right" | "bottom" | "left";
|
|
7
|
+
/** Uma porta do nó: uma alça com nome e lado (MNT-14). A aresta liga nela por `fromPort`/`toPort`. */
|
|
8
|
+
export interface GraphPort {
|
|
9
|
+
id: string;
|
|
10
|
+
side: GraphPortSide;
|
|
11
|
+
label?: string;
|
|
12
|
+
}
|
|
13
|
+
export type GraphTone = "neutral" | "info" | "success" | "warning" | "danger";
|
|
2
14
|
export interface GraphNodeItem {
|
|
3
15
|
id: string;
|
|
4
16
|
label: string;
|
|
@@ -6,15 +18,57 @@ export interface GraphNodeItem {
|
|
|
6
18
|
detail?: ReactNode;
|
|
7
19
|
x?: number;
|
|
8
20
|
y?: number;
|
|
21
|
+
/** O ícone do tipo, JUNTO do texto do tipo, nunca no lugar dele (MNT-07). Nome do sprite ou desenho do app. */
|
|
22
|
+
icon?: AureaIcon;
|
|
23
|
+
/** O selo de quantidade no canto (MNT-09): "20". `countLabel` é o que o leitor de tela ouve ("20 estações"). */
|
|
24
|
+
count?: number;
|
|
25
|
+
countLabel?: string;
|
|
26
|
+
/** O selo de estado, no outro canto (MNT-16): a palavra, e o tom só reforça. */
|
|
27
|
+
status?: {
|
|
28
|
+
label: string;
|
|
29
|
+
tone?: GraphTone;
|
|
30
|
+
};
|
|
31
|
+
ports?: GraphPort[];
|
|
32
|
+
/** A camada sugerida pelo app, contada de 0 (só na arrumação `layered`). */
|
|
33
|
+
layer?: number;
|
|
9
34
|
}
|
|
35
|
+
export type GraphEdgePattern = "solid" | "dashed" | "dotted" | "double";
|
|
36
|
+
export type GraphEdgeWeight = "regular" | "thick" | "heavy";
|
|
37
|
+
export type GraphEdgeMark = "cross" | "dot" | "lock";
|
|
10
38
|
export interface GraphEdgeItem {
|
|
11
39
|
id?: string;
|
|
12
40
|
from: string;
|
|
13
41
|
to: string;
|
|
14
42
|
label?: string;
|
|
15
43
|
animated?: boolean;
|
|
44
|
+
/** A porta de cada ponta (MNT-14); sem ela, o lado de sempre. */
|
|
45
|
+
fromPort?: string;
|
|
46
|
+
toPort?: string;
|
|
47
|
+
/** O nome da porta escrito na PONTA da linha, perto do nó (MNT-14.3). */
|
|
48
|
+
sourceLabel?: string;
|
|
49
|
+
targetLabel?: string;
|
|
50
|
+
/** O desenho do traço, que diz o que a linha é SEM depender de cor (MNT-08 e MNT-15). */
|
|
51
|
+
pattern?: GraphEdgePattern;
|
|
52
|
+
weight?: GraphEdgeWeight;
|
|
53
|
+
mark?: GraphEdgeMark;
|
|
54
|
+
/** Um número no meio da linha: os membros de uma agregação (LAG). */
|
|
55
|
+
count?: number;
|
|
56
|
+
/** O nome desta espécie de linha na legenda automática (`legend`). */
|
|
57
|
+
legend?: string;
|
|
58
|
+
}
|
|
59
|
+
/** O que o app pede ao mapa pelo `apiRef` (MNT-18 e MNT-19). */
|
|
60
|
+
export interface DependencyGraphApi {
|
|
61
|
+
fitView(): void;
|
|
62
|
+
zoomIn(): void;
|
|
63
|
+
zoomOut(): void;
|
|
64
|
+
focus(id: string): void;
|
|
65
|
+
/** O mapa como arquivo do draw.io (`.drawio`), com as posições de agora. */
|
|
66
|
+
toDrawio(): string;
|
|
67
|
+
/** A foto do mapa inteiro, como `data:` URL. Pede o peer opcional `modern-screenshot`. */
|
|
68
|
+
toPng(): Promise<string>;
|
|
69
|
+
toSvg(): Promise<string>;
|
|
16
70
|
}
|
|
17
|
-
export
|
|
71
|
+
export interface DependencyGraphProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onSelect"> {
|
|
18
72
|
nodes: GraphNodeItem[];
|
|
19
73
|
edges: GraphEdgeItem[];
|
|
20
74
|
label?: string;
|
|
@@ -24,6 +78,8 @@ export declare function DependencyGraph({ nodes, edges, label, selectedId, onSel
|
|
|
24
78
|
onConnect?: (edge: {
|
|
25
79
|
from: string;
|
|
26
80
|
to: string;
|
|
81
|
+
fromPort?: string;
|
|
82
|
+
toPort?: string;
|
|
27
83
|
}) => void;
|
|
28
84
|
onNodeMove?: (id: string, pos: {
|
|
29
85
|
x: number;
|
|
@@ -31,4 +87,44 @@ export declare function DependencyGraph({ nodes, edges, label, selectedId, onSel
|
|
|
31
87
|
}) => void;
|
|
32
88
|
height?: string;
|
|
33
89
|
className?: string;
|
|
34
|
-
|
|
90
|
+
/** `vertical` desenha de cima para baixo: a linha entra por cima e sai por baixo (MNT-06). */
|
|
91
|
+
orientation?: GraphOrientation;
|
|
92
|
+
/** `layered` arruma em camadas com o `elkjs` (peer opcional): menos cruzamento, raiz no topo (MNT-11). */
|
|
93
|
+
layout?: GraphLayout;
|
|
94
|
+
/** O nó do topo, na arrumação `layered`. */
|
|
95
|
+
rootId?: string;
|
|
96
|
+
/** `step` desenha a linha em ângulo reto, com o canto arredondado (MNT-10). */
|
|
97
|
+
edgeShape?: GraphEdgeShape;
|
|
98
|
+
/** As linhas entre o mesmo par viram uma só, com o número; o número abre e junta (MNT-12.4). */
|
|
99
|
+
groupParallel?: boolean;
|
|
100
|
+
/** A visão geral no canto, com o retângulo da parte que está na tela (MNT-18.1). */
|
|
101
|
+
minimap?: boolean;
|
|
102
|
+
/** Os botões de aproximar, afastar e caber na tela (MNT-18.1). */
|
|
103
|
+
controls?: boolean;
|
|
104
|
+
/** A legenda das espécies de linha, montada sozinha pelo `legend` de cada aresta (MNT-15). */
|
|
105
|
+
legend?: boolean;
|
|
106
|
+
/** Centraliza e marca este nó — o "ir até" de uma busca (MNT-18.2). */
|
|
107
|
+
focusId?: string | null;
|
|
108
|
+
/** Destaca um caminho (nós e arestas) e apaga o resto (MNT-18.3). */
|
|
109
|
+
highlight?: {
|
|
110
|
+
nodes?: string[];
|
|
111
|
+
edges?: string[];
|
|
112
|
+
} | null;
|
|
113
|
+
/** Com um nó escolhido, destaca ele e os vizinhos e apaga o resto (MNT-18.6). */
|
|
114
|
+
highlightNeighbors?: boolean;
|
|
115
|
+
/** Some com estes nós SEM refazer a arrumação: o resto fica onde estava (MNT-18.5). */
|
|
116
|
+
hiddenIds?: string[];
|
|
117
|
+
onNodeHover?: (id: string | null) => void;
|
|
118
|
+
onNodeContextMenu?: (id: string, point: {
|
|
119
|
+
x: number;
|
|
120
|
+
y: number;
|
|
121
|
+
}) => void;
|
|
122
|
+
onEdgeSelect?: (id: string) => void;
|
|
123
|
+
/** O texto do nó, independente do zoom (MNT-19.3). */
|
|
124
|
+
textSize?: "md" | "lg";
|
|
125
|
+
/** Linhas e bordas no tom da letra, e mais grossas (MNT-19.3). */
|
|
126
|
+
highContrast?: boolean;
|
|
127
|
+
/** Os comandos do mapa para o app: caber, aproximar, ir até, e exportar (MNT-18 e MNT-19). */
|
|
128
|
+
apiRef?: React.Ref<DependencyGraphApi>;
|
|
129
|
+
}
|
|
130
|
+
export declare function DependencyGraph({ nodes, edges, label, selectedId, onSelect, connectable, onConnect, onNodeMove, height, className, orientation, layout, rootId, edgeShape, groupParallel, minimap, controls, legend, focusId, highlight, highlightNeighbors, hiddenIds, onNodeHover, onNodeContextMenu, onEdgeSelect, textSize, highContrast, apiRef, style, ...props }: DependencyGraphProps): React.JSX.Element;
|
package/dist/graph.js
CHANGED
|
@@ -39,8 +39,11 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
39
39
|
// Quem importa a folha é o CONSUMIDOR, uma vez, como já importa o `@aurea-uds/core/css`: um
|
|
40
40
|
// `import "…css"` dentro deste módulo quebraria em Node puro e o build daqui é `tsc` só.
|
|
41
41
|
import React from "react";
|
|
42
|
-
import { ReactFlow, ReactFlowProvider, Background, BaseEdge, Handle, Position, applyNodeChanges, getBezierPath } from "@xyflow/react";
|
|
42
|
+
import { ReactFlow, ReactFlowProvider, Background, BaseEdge, EdgeLabelRenderer, Handle, MiniMap, Panel, Position, applyNodeChanges, getBezierPath, getSmoothStepPath, useReactFlow } from "@xyflow/react";
|
|
43
43
|
import { cx, useAureaStrings } from "./internal.js";
|
|
44
|
+
import { Icon } from "./system.js";
|
|
45
|
+
import { Badge } from "./markup.js";
|
|
46
|
+
import { IconButton } from "./actions.js";
|
|
44
47
|
// ── O layout ─────────────────────────────────────────────────────────────────────────────
|
|
45
48
|
// O motor recebe `x`/`y` PRONTOS: ele não posiciona nada. Sem isto, um grafo sem coordenadas
|
|
46
49
|
// empilha tudo em (0,0) — que foi a medição que quase derrubou a escolha do motor.
|
|
@@ -64,7 +67,9 @@ const LARGURA = 180, ALTURA = 52, VAO_X = 90, VAO_Y = 24;
|
|
|
64
67
|
// `VAO_X`: grafo sem rótulo sai idêntico ao de antes.
|
|
65
68
|
const FONTE_DO_ROTULO = 13, GLIFO_DO_ROTULO = 0.6, FUNDO_X = 8, FOLGA_X = 32;
|
|
66
69
|
const vaoEntreColunas = (edges) => Math.max(VAO_X, ...edges.map(e => e.label ? Math.ceil(e.label.length * FONTE_DO_ROTULO * GLIFO_DO_ROTULO) + FUNDO_X + FOLGA_X : 0));
|
|
67
|
-
|
|
70
|
+
const medidasDoNo = (texto) => texto === "lg"
|
|
71
|
+
? { larg: Math.round(LARGURA * 16 / 14), alt: Math.round(ALTURA * 16 / 14) } : { larg: LARGURA, alt: ALTURA };
|
|
72
|
+
function dispor(nodes, edges, orientacao = "horizontal", m = { larg: LARGURA, alt: ALTURA }) {
|
|
68
73
|
const entram = new Map();
|
|
69
74
|
for (const n of nodes)
|
|
70
75
|
entram.set(n.id, []);
|
|
@@ -90,14 +95,33 @@ function dispor(nodes, edges) {
|
|
|
90
95
|
};
|
|
91
96
|
for (const n of nodes)
|
|
92
97
|
calcular(n.id);
|
|
93
|
-
const vaoX = vaoEntreColunas(edges);
|
|
94
98
|
const ocupacao = new Map();
|
|
95
99
|
const posicoes = new Map();
|
|
100
|
+
// DE CIMA PARA BAIXO (MNT-06): a profundidade vira a LINHA, e os irmãos ficam lado a lado. Cada
|
|
101
|
+
// camada se centra na mais larga — a raiz no meio e os filhos equilibrados, como pede a MNT-11.4.
|
|
102
|
+
// O vão entre camadas é o `VAO_X` de sempre: o rótulo da linha fica deitado no meio dela, e a
|
|
103
|
+
// largura dele não pede mais altura. Esquerda→direita continua EXATAMENTE como era.
|
|
104
|
+
if (orientacao === "vertical") {
|
|
105
|
+
const porCamada = new Map();
|
|
106
|
+
for (const n of nodes) {
|
|
107
|
+
const d = profundidade.get(n.id) ?? 0;
|
|
108
|
+
porCamada.set(d, [...(porCamada.get(d) ?? []), n.id]);
|
|
109
|
+
}
|
|
110
|
+
const maior = Math.max(...[...porCamada.values()].map(l => l.length), 1);
|
|
111
|
+
const passo = m.larg + VAO_Y;
|
|
112
|
+
for (const [d, ids] of porCamada) {
|
|
113
|
+
const recuo = (maior - ids.length) * passo / 2;
|
|
114
|
+
ids.forEach((id, i) => posicoes.set(id, { x: recuo + i * passo, y: d * (m.alt + VAO_X) }));
|
|
115
|
+
}
|
|
116
|
+
const camadas = Math.max(...[...porCamada.keys()], 0) + 1;
|
|
117
|
+
return { posicoes, extensao: { larg: maior * m.larg + (maior - 1) * VAO_Y, alt: camadas * m.alt + (camadas - 1) * VAO_X } };
|
|
118
|
+
}
|
|
119
|
+
const vaoX = vaoEntreColunas(edges);
|
|
96
120
|
for (const n of nodes) {
|
|
97
121
|
const d = profundidade.get(n.id) ?? 0;
|
|
98
122
|
const linha = ocupacao.get(d) ?? 0;
|
|
99
123
|
ocupacao.set(d, linha + 1);
|
|
100
|
-
posicoes.set(n.id, { x: d * (
|
|
124
|
+
posicoes.set(n.id, { x: d * (m.larg + vaoX), y: linha * (m.alt + VAO_Y) });
|
|
101
125
|
}
|
|
102
126
|
// A EXTENSÃO existe para o SERVIDOR: sem ela o `fitView` não tem viewport para caber, e o
|
|
103
127
|
// grafo renderizado fora do navegador sai ancorado no canto, com metade dos nós do lado de
|
|
@@ -109,22 +133,104 @@ function dispor(nodes, edges) {
|
|
|
109
133
|
// interno o acionava sem ser prop de ninguém.
|
|
110
134
|
const colunas = Math.max(...[...ocupacao.keys()], 0) + 1;
|
|
111
135
|
const linhas = Math.max(...[...ocupacao.values()], 1);
|
|
112
|
-
return { posicoes, extensao: { larg: colunas *
|
|
136
|
+
return { posicoes, extensao: { larg: colunas * m.larg + (colunas - 1) * vaoX, alt: linhas * m.alt + (linhas - 1) * VAO_Y } };
|
|
113
137
|
}
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
//
|
|
138
|
+
// ── A ARRUMAÇÃO EM CAMADAS (MNT-06 e MNT-11, ADR-0064) ──────────────────────────────────────
|
|
139
|
+
// `layout="layered"` chama o `elkjs` (peer OPCIONAL, EPL-2.0): camadas, MENOS cruzamento de linha,
|
|
140
|
+
// a raiz no topo (`rootId`, restrição FIRST), a camada que o app sugere (`layer`) e as portas no
|
|
141
|
+
// lado declarado. Ele entra por `import()` só quando pedido — quem não usa não baixa os ~465 KB — e
|
|
142
|
+
// roda depois do primeiro desenho: até a resposta, e no servidor, vale a arrumação simples acima.
|
|
143
|
+
// Sem o pacote instalado, o mapa avisa uma vez no console e fica na simples: não quebra a tela.
|
|
144
|
+
const LADO_ELK = { top: "NORTH", right: "EAST", bottom: "SOUTH", left: "WEST" };
|
|
145
|
+
// O índice da porta no arrumador anda no sentido do RELÓGIO, a partir do canto de cima à esquerda:
|
|
146
|
+
// em cima da esquerda para a direita, à direita de cima para baixo, embaixo da DIREITA para a
|
|
147
|
+
// esquerda, e à esquerda de BAIXO para cima. O desenho reparte cada lado na ordem declarada (da
|
|
148
|
+
// esquerda para a direita, de cima para baixo), então embaixo e à esquerda a ordem se inverte.
|
|
149
|
+
function portasEmOrdem(n) {
|
|
150
|
+
const do_ = (lado) => (n.ports ?? []).filter(p => p.side === lado);
|
|
151
|
+
const volta = [...do_("top"), ...do_("right"), ...do_("bottom").reverse(), ...do_("left").reverse()];
|
|
152
|
+
return volta.map((p, i) => [p, i]);
|
|
153
|
+
}
|
|
154
|
+
let avisouSemElk = false;
|
|
155
|
+
async function disporEmCamadas(nodes, edges, orientacao, m, rootId) {
|
|
156
|
+
let ELK;
|
|
157
|
+
try {
|
|
158
|
+
ELK = (await import("elkjs/lib/elk.bundled.js")).default;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
if (!avisouSemElk) {
|
|
162
|
+
avisouSemElk = true;
|
|
163
|
+
console.warn("DependencyGraph: layout=\"layered\" precisa do pacote `elkjs` (peer opcional). Ficou a arrumação simples.");
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const ids = new Set(nodes.map(n => n.id));
|
|
168
|
+
const portas = new Set(nodes.flatMap(n => (n.ports ?? []).map(p => `${n.id}::${p.id}`)));
|
|
169
|
+
const ponta = (no, porta) => porta && portas.has(`${no}::${porta}`) ? `${no}::${porta}` : no;
|
|
170
|
+
const resultado = await new ELK().layout({
|
|
171
|
+
id: "raiz",
|
|
172
|
+
layoutOptions: {
|
|
173
|
+
"elk.algorithm": "layered",
|
|
174
|
+
"elk.direction": orientacao === "vertical" ? "DOWN" : "RIGHT",
|
|
175
|
+
"elk.layered.spacing.nodeNodeBetweenLayers": String(VAO_X),
|
|
176
|
+
"elk.spacing.nodeNode": String(VAO_Y),
|
|
177
|
+
"elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
|
|
178
|
+
"elk.layered.nodePlacement.bk.fixedAlignment": "BALANCED",
|
|
179
|
+
},
|
|
180
|
+
children: nodes.map(n => ({ id: n.id, width: m.larg, height: m.alt,
|
|
181
|
+
// ORDEM FIXA, e não só o lado: o desenho põe as portas na ordem em que o app as declarou, e
|
|
182
|
+
// o arrumador livre as reordenava por dentro — a "porta 1" à esquerda ia ao vizinho da direita
|
|
183
|
+
// e as linhas se cruzavam (medido na bancada). Com a ordem fixa, quem se move são os nós.
|
|
184
|
+
layoutOptions: { ...(n.ports?.length ? { "elk.portConstraints": "FIXED_ORDER" } : {}),
|
|
185
|
+
...(n.id === rootId ? { "elk.layered.layering.layerConstraint": "FIRST" } : {}),
|
|
186
|
+
...(n.layer != null ? { "elk.layered.layering.layerChoiceConstraint": String(n.layer) } : {}) },
|
|
187
|
+
ports: portasEmOrdem(n).map(([p, i]) => ({ id: `${n.id}::${p.id}`, width: 0, height: 0,
|
|
188
|
+
layoutOptions: { "elk.port.side": LADO_ELK[p.side], "elk.port.index": String(i) } })) })),
|
|
189
|
+
edges: edges.filter(e => ids.has(e.from) && ids.has(e.to)).map((e, i) => ({ id: `e${i}`, sources: [ponta(e.from, e.fromPort)], targets: [ponta(e.to, e.toPort)] })),
|
|
190
|
+
});
|
|
191
|
+
return new Map((resultado.children ?? []).map(c => [c.id, { x: c.x ?? 0, y: c.y ?? 0 }]));
|
|
192
|
+
}
|
|
193
|
+
// ── AS ALÇAS: o lado de sempre, e as portas (MNT-06 e MNT-14) ─────────────────────────────────
|
|
194
|
+
// Sem porta, a linha entra por um lado e sai pelo oposto: esquerda→direita no horizontal, cima→
|
|
195
|
+
// baixo no vertical. Com porta, cada uma fica no lado dela, repartindo o lado em partes iguais. As
|
|
196
|
+
// alças entram também na lista do nó (`handles`) para a linha existir sem medir — no servidor e no
|
|
197
|
+
// modo de leitura, em que a alça não é desenhada (ver o nó, abaixo).
|
|
198
|
+
const LADO = { top: Position.Top, right: Position.Right, bottom: Position.Bottom, left: Position.Left };
|
|
199
|
+
function alcasDoNo(n, orientacao, m) {
|
|
200
|
+
const entra = orientacao === "vertical" ? Position.Top : Position.Left;
|
|
201
|
+
const sai = orientacao === "vertical" ? Position.Bottom : Position.Right;
|
|
202
|
+
const ponto = (pos, frac) => ({ frac, ...(pos === Position.Left ? { x: 0, y: m.alt * frac } : pos === Position.Right ? { x: m.larg, y: m.alt * frac }
|
|
203
|
+
: pos === Position.Top ? { x: m.larg * frac, y: 0 } : { x: m.larg * frac, y: m.alt }) });
|
|
204
|
+
const base = [{ type: "target", position: entra, ...ponto(entra, 1 / 2) }, { type: "source", position: sai, ...ponto(sai, 1 / 2) }];
|
|
205
|
+
const porLado = new Map();
|
|
206
|
+
for (const p of n.ports ?? [])
|
|
207
|
+
porLado.set(p.side, [...(porLado.get(p.side) ?? []), p]);
|
|
208
|
+
const portas = [];
|
|
209
|
+
for (const [lado, lista] of porLado)
|
|
210
|
+
lista.forEach((p, k) => {
|
|
211
|
+
const pos = LADO[lado], xy = ponto(pos, (k + 1) / (lista.length + 1));
|
|
212
|
+
// A porta é as duas coisas: o cabo de rede não tem sentido, e a linha pode sair ou chegar nela.
|
|
213
|
+
portas.push({ id: p.id, type: "source", position: pos, ...xy, rotulo: p.label }, { id: p.id, type: "target", position: pos, ...xy, rotulo: p.label });
|
|
214
|
+
});
|
|
215
|
+
return [...base, ...portas];
|
|
216
|
+
}
|
|
217
|
+
const SETAS = new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"]);
|
|
122
218
|
function NoAurea({ data }) {
|
|
123
219
|
const d = data;
|
|
124
|
-
const corpo = _jsxs(_Fragment, { children: [_jsx("span", { className: "graph-node-label", children: d.label }), d.kind && _jsx("span", { className: "graph-node-kind", children: d.kind }), d.detail && _jsx("span", { className: "hint", children: d.detail })] });
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
220
|
+
const corpo = _jsxs(_Fragment, { children: [d.icone && _jsx(Icon, { name: d.icone, size: "md", className: "graph-node-icon" }), _jsxs("span", { className: "graph-node-text", children: [_jsx("span", { className: "graph-node-label", children: d.label }), d.kind && _jsx("span", { className: "graph-node-kind", children: d.kind }), d.detail && _jsx("span", { className: "hint", children: d.detail })] })] });
|
|
221
|
+
// As SETAS andam de nó em nó (MNT-19.4) — o mais perto na direção da seta. Só no nó que é botão:
|
|
222
|
+
// nó de leitura não recebe foco, e dar foco a um `<span>` sem papel seria pior que não dar.
|
|
223
|
+
const teclar = (e) => { if (d.aoTeclar && SETAS.has(e.key)) {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
d.aoTeclar(e.key);
|
|
226
|
+
} };
|
|
227
|
+
return _jsxs("div", { className: "graph-node", "data-selected": d.selecionado || undefined, "data-focused": d.focado || undefined, "data-dimmed": d.apagado || undefined, children: [d.alcas.map(a => {
|
|
228
|
+
// A alça de porta fica no ponto dela ao longo do lado, em porcentagem: o nó pode crescer.
|
|
229
|
+
const lugar = a.position === Position.Left || a.position === Position.Right ? { top: `${a.frac * 100}%` } : { left: `${a.frac * 100}%` };
|
|
230
|
+
return _jsx(Handle, { id: a.id, type: a.type, position: a.position, isConnectable: !!d.conectavel, className: cx("graph-handle", !d.conectavel && "graph-handle-oculta"), style: a.id ? lugar : undefined, title: d.conectavel ? a.rotulo : undefined }, `${a.type}:${a.id ?? "lado"}`);
|
|
231
|
+
}), d.aoSelecionar
|
|
232
|
+
? _jsx("button", { type: "button", className: cx("graph-node-body", d.icone && "graph-node-has-icon"), "aria-pressed": !!d.selecionado, onClick: d.aoSelecionar, onKeyDown: teclar, children: corpo })
|
|
233
|
+
: _jsx("span", { className: cx("graph-node-body", d.icone && "graph-node-has-icon"), children: corpo }), d.contagem != null && _jsxs("span", { className: "graph-node-count", children: [_jsx(Badge, { size: "xs", variant: "neutral", emphasis: "solid", "aria-hidden": d.nomeDaContagem ? true : undefined, children: d.contagem }), d.nomeDaContagem && _jsx("span", { className: "sr-only", children: d.nomeDaContagem })] }), d.estado && _jsx("span", { className: "graph-node-status", children: _jsx(Badge, { size: "xs", variant: d.estado.tone ?? "neutral", children: d.estado.label }) })] });
|
|
128
234
|
}
|
|
129
235
|
const TIPOS = { aurea: NoAurea };
|
|
130
236
|
// ── A ARESTA, e por que ela também é nossa (A-09 e A-10, 23/09/2026) ─────────────────────────
|
|
@@ -138,16 +244,71 @@ const TIPOS = { aurea: NoAurea };
|
|
|
138
244
|
// MESMO caminho e os rótulos se sobrepõem. Cada uma sai de uma altura própria do nó: as `n`
|
|
139
245
|
// paralelas dividem a altura do nó em `n + 1` partes iguais. Uma aresta só fica no meio, que é o
|
|
140
246
|
// desenho de antes. O número vem de `ALTURA`, a do próprio nó — não há distância inventada aqui.
|
|
141
|
-
const ROTULO = { fill: "var(--xy-edge-label-color)", fontSize: "var(--text-xs)" };
|
|
247
|
+
const ROTULO = { fill: "var(--xy-edge-label-color)", fontSize: "var(--graph-edge-label-size,var(--text-xs))" };
|
|
142
248
|
const FUNDO_DO_ROTULO = { fill: "var(--xy-edge-label-background-color)" };
|
|
249
|
+
// O canto do DEGRAU (MNT-10) é o `--radius-sm` (8): o traçado em ângulo reto dobra arredondado,
|
|
250
|
+
// como o resto da casa, e não em quina viva.
|
|
251
|
+
const CANTO_DO_DEGRAU = 8;
|
|
252
|
+
// O rótulo da PONTA fica a um `--space-1` (4) da alça, do lado de fora da linha.
|
|
253
|
+
const FOLGA_DA_PONTA = 4;
|
|
254
|
+
const pontaDoRotulo = (x, y, pos) => {
|
|
255
|
+
const f = FOLGA_DA_PONTA;
|
|
256
|
+
if (pos === Position.Bottom)
|
|
257
|
+
return `translate(${x + f}px,${y + f}px)`;
|
|
258
|
+
if (pos === Position.Top)
|
|
259
|
+
return `translate(${x + f}px,${y - f}px) translateY(-100%)`;
|
|
260
|
+
if (pos === Position.Right)
|
|
261
|
+
return `translate(${x + f}px,${y - f}px) translateY(-100%)`;
|
|
262
|
+
return `translate(${x - f}px,${y - f}px) translate(-100%,-100%)`;
|
|
263
|
+
};
|
|
143
264
|
function ArestaAurea({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, label, data, markerEnd, style }) {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
265
|
+
const d = (data ?? { deslocamento: 0, vertical: false, forma: "curve" });
|
|
266
|
+
// A-10 · as paralelas se afastam na direção ATRAVESSADA à linha: na vertical, para os lados.
|
|
267
|
+
const dx = d.vertical ? d.deslocamento : 0, dy = d.vertical ? 0 : d.deslocamento;
|
|
268
|
+
const pontos = { sourceX: sourceX + dx, sourceY: sourceY + dy, sourcePosition, targetX: targetX + dx, targetY: targetY + dy, targetPosition };
|
|
269
|
+
const [caminho, lx, ly] = d.forma === "step" ? getSmoothStepPath({ ...pontos, borderRadius: CANTO_DO_DEGRAU }) : getBezierPath(pontos);
|
|
270
|
+
// O rótulo de texto é SVG (sai também no servidor). Quando a linha tem marca, número ou grupo, o
|
|
271
|
+
// rótulo vai para a pastilha de HTML do meio, junto deles — dois desenhos no mesmo ponto se cobririam.
|
|
272
|
+
// O traço DUPLO desenha o miolo por cima da linha — e por cima do rótulo SVG, que sairia riscado
|
|
273
|
+
// (medido na bancada). Com rótulo, ele também vai para a pastilha.
|
|
274
|
+
const pastilha = !!(d.marca || d.contagem || d.grupo || (d.padrao === "double" && label != null));
|
|
275
|
+
const cls = cx("graph-edge", d.padrao && d.padrao !== "solid" && `graph-edge-${d.padrao}`, d.peso && d.peso !== "regular" && `graph-edge-${d.peso}`, d.apagada && "is-dimmed");
|
|
276
|
+
return _jsxs(_Fragment, { children: [_jsx(BaseEdge, { id: id, path: caminho, className: cls, labelX: lx, labelY: ly, label: pastilha ? undefined : label, markerEnd: markerEnd, style: style, labelStyle: ROTULO, labelShowBg: true, labelBgStyle: FUNDO_DO_ROTULO }), d.padrao === "double" && _jsx("path", { d: caminho, fill: "none", className: cx("graph-edge-double-core", d.peso && d.peso !== "regular" && `graph-edge-${d.peso}`, d.apagada && "is-dimmed") }), (pastilha || d.origem || d.destino) && _jsxs(EdgeLabelRenderer, { children: [pastilha && _jsxs("div", { className: cx("graph-edge-chip nodrag nopan", d.apagada && "is-dimmed"), style: { transform: `translate(-50%,-50%) translate(${lx}px,${ly}px)` }, children: [d.marca && _jsx("span", { className: `graph-edge-mark graph-edge-mark-${d.marca}`, "aria-hidden": "true", children: d.marca === "lock" && _jsx(Icon, { name: "lock-key", size: "sm" }) }), label != null && _jsx("span", { children: label }), d.contagem != null && _jsxs("span", { className: "graph-edge-count", children: ["\u00D7", d.contagem] }), d.grupo && _jsxs("button", { type: "button", className: "graph-edge-group", "aria-expanded": d.grupo.aberto, "aria-label": `${d.grupo.aberto ? d.grupo.juntar : d.grupo.mostrar} ${d.grupo.n} ${d.grupo.conexoes}`, onClick: d.grupo.alternar, children: ["\u00D7", d.grupo.n] })] }), d.origem && _jsx("div", { className: cx("graph-edge-end", d.apagada && "is-dimmed"), style: { transform: pontaDoRotulo(sourceX + dx, sourceY + dy, sourcePosition) }, children: d.origem }), d.destino && _jsx("div", { className: cx("graph-edge-end", d.apagada && "is-dimmed"), style: { transform: pontaDoRotulo(targetX + dx, targetY + dy, targetPosition) }, children: d.destino })] })] });
|
|
147
277
|
}
|
|
148
278
|
const TIPOS_DE_ARESTA = { aurea: ArestaAurea };
|
|
279
|
+
/** A chave de um PAR sem sentido: o cabo entre A e B é o mesmo que entre B e A. */
|
|
280
|
+
const parSemSentido = (e) => e.from < e.to ? `${e.from}|${e.to}` : `${e.to}|${e.from}`;
|
|
281
|
+
/**
|
|
282
|
+
* O que se desenha com `groupParallel` (MNT-12.4): de cada par com duas ou mais linhas, fechado,
|
|
283
|
+
* só a PRIMEIRA, com o número do grupo; aberto, todas, e a primeira leva o botão de juntar.
|
|
284
|
+
*/
|
|
285
|
+
function agruparParalelas(edges, ligado, abertos) {
|
|
286
|
+
if (!ligado)
|
|
287
|
+
return edges.map(item => ({ item }));
|
|
288
|
+
const total = new Map();
|
|
289
|
+
for (const e of edges) {
|
|
290
|
+
const k = parSemSentido(e);
|
|
291
|
+
total.set(k, (total.get(k) ?? 0) + 1);
|
|
292
|
+
}
|
|
293
|
+
const visto = new Set();
|
|
294
|
+
const saida = [];
|
|
295
|
+
for (const e of edges) {
|
|
296
|
+
const k = parSemSentido(e), n = total.get(k);
|
|
297
|
+
if (n < 2) {
|
|
298
|
+
saida.push({ item: e });
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const aberto = abertos.has(k), primeira = !visto.has(k);
|
|
302
|
+
visto.add(k);
|
|
303
|
+
if (primeira)
|
|
304
|
+
saida.push({ item: e, grupo: { chave: k, n, aberto } });
|
|
305
|
+
else if (aberto)
|
|
306
|
+
saida.push({ item: e });
|
|
307
|
+
}
|
|
308
|
+
return saida;
|
|
309
|
+
}
|
|
149
310
|
/** Identidade e deslocamento de cada aresta — interno; o teste lê o resultado no DOM. */
|
|
150
|
-
function arestasParalelas(edges) {
|
|
311
|
+
function arestasParalelas(edges, alt = ALTURA) {
|
|
151
312
|
const total = new Map();
|
|
152
313
|
for (const e of edges) {
|
|
153
314
|
const k = `${e.from}->${e.to}`;
|
|
@@ -165,25 +326,99 @@ function arestasParalelas(edges) {
|
|
|
165
326
|
while (usados.has(id))
|
|
166
327
|
id = `${id}#`;
|
|
167
328
|
usados.add(id);
|
|
168
|
-
return { id, deslocamento: n > 1 ?
|
|
329
|
+
return { id, deslocamento: n > 1 ? alt * ((k + 1) / (n + 1) - 1 / 2) : 0 };
|
|
169
330
|
});
|
|
170
331
|
}
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
332
|
+
// ── AS ALÇAS DECLARADAS SÓ VALEM ATÉ A MEDIDA (achado na bancada da rodada 1, 10/10/2026) ──────
|
|
333
|
+
// O motor, quando o nó traz `handles`, usa SEMPRE a lista declarada — mesmo depois de medir a tela
|
|
334
|
+
// (`parseHandles`, no `@xyflow/system`). A lista é o que faz o grafo existir no servidor, mas ela
|
|
335
|
+
// foi calculada para o nó de 52 de altura: num nó com endereço, mais alto, a linha nascia DENTRO
|
|
336
|
+
// dele, e o nome da porta na ponta ficava escondido atrás do texto. Medido: os seis nomes de porta
|
|
337
|
+
// cobertos pelo endereço do nó. Depois da primeira medida, a lista sai e vale a borda real.
|
|
338
|
+
const semAlcasDeclaradas = (n) => { if (!n.measured?.width)
|
|
339
|
+
return n; const { handles: _, ...resto } = n; return resto; };
|
|
340
|
+
const EM_PAR = (a) => a ? new Set(a) : null;
|
|
341
|
+
/** O arquivo do draw.io, escrito à mão como o formato documenta (`mxfile > diagram > mxGraphModel`). */
|
|
342
|
+
// As aspas vão por código (\x22, \x27): o leitor de declarações do check 46 não casa aspa solta.
|
|
343
|
+
const XML = (t) => t.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\x22/g, """).replace(/\x27/g, "'");
|
|
344
|
+
const TRACO_DRAWIO = { solid: "", dashed: "dashed=1;dashPattern=8 4;", dotted: "dashed=1;dashPattern=1 4;", double: "shape=link;" };
|
|
345
|
+
const PESO_DRAWIO = { regular: 1, thick: 2, heavy: 3 };
|
|
346
|
+
function paraDrawio(nos, itens, forma, titulo) {
|
|
347
|
+
const visiveis = nos.filter(n => !n.hidden);
|
|
348
|
+
const ids = new Set(visiveis.map(n => n.id));
|
|
349
|
+
const celulas = ['<mxCell id="0"/>', '<mxCell id="1" parent="0"/>'];
|
|
350
|
+
for (const n of visiveis) {
|
|
351
|
+
const d = n.data;
|
|
352
|
+
const texto = [d.label, d.kind, typeof d.detail === "string" ? d.detail : undefined].filter(Boolean).join("\n");
|
|
353
|
+
const w = n.measured?.width ?? n.initialWidth ?? LARGURA, h = n.measured?.height ?? n.initialHeight ?? ALTURA;
|
|
354
|
+
celulas.push(`<mxCell id="${XML(`n-${n.id}`)}" value="${XML(texto)}" style="rounded=1;arcSize=20;whiteSpace=wrap;html=0;" vertex="1" parent="1"><mxGeometry x="${Math.round(n.position.x)}" y="${Math.round(n.position.y)}" width="${Math.round(w)}" height="${Math.round(h)}" as="geometry"/></mxCell>`);
|
|
355
|
+
}
|
|
356
|
+
itens.forEach((e, i) => {
|
|
357
|
+
if (!ids.has(e.from) || !ids.has(e.to))
|
|
358
|
+
return;
|
|
359
|
+
const id = `e-${i}`;
|
|
360
|
+
const estilo = `endArrow=none;html=0;${forma === "step" ? "edgeStyle=orthogonalEdgeStyle;rounded=1;" : "curved=1;"}${TRACO_DRAWIO[e.pattern ?? "solid"]}strokeWidth=${PESO_DRAWIO[e.weight ?? "regular"]};`;
|
|
361
|
+
celulas.push(`<mxCell id="${id}" value="${XML(e.label ?? "")}" style="${estilo}" edge="1" parent="1" source="${XML(`n-${e.from}`)}" target="${XML(`n-${e.to}`)}"><mxGeometry relative="1" as="geometry"/></mxCell>`);
|
|
362
|
+
// O nome da porta na ponta: o rótulo-filho da linha, a 80% do caminho para cada lado (o idioma do draw.io).
|
|
363
|
+
for (const [txt, x, suf] of [[e.sourceLabel, -0.8, "s"], [e.targetLabel, 0.8, "t"]])
|
|
364
|
+
if (txt)
|
|
365
|
+
celulas.push(`<mxCell id="${id}-${suf}" value="${XML(txt)}" style="edgeLabel;html=0;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="${id}"><mxGeometry x="${x}" relative="1" as="geometry"><mxPoint as="offset"/></mxGeometry></mxCell>`);
|
|
366
|
+
});
|
|
367
|
+
return `<mxfile host="Aurea"><diagram name="${XML(titulo)}" id="aurea"><mxGraphModel><root>${celulas.join("")}</root></mxGraphModel></diagram></mxfile>`;
|
|
368
|
+
}
|
|
369
|
+
export function DependencyGraph({ nodes, edges, label, selectedId, onSelect, connectable, onConnect, onNodeMove, height, className, orientation = "horizontal", layout = "simple", rootId, edgeShape = "curve", groupParallel, minimap, controls, legend, focusId, highlight, highlightNeighbors, hiddenIds, onNodeHover, onNodeContextMenu, onEdgeSelect, textSize = "md", highContrast, apiRef, style, ...props }) {
|
|
179
370
|
const s = useAureaStrings();
|
|
371
|
+
const m = React.useMemo(() => medidasDoNo(textSize), [textSize]);
|
|
372
|
+
const vertical = orientation === "vertical";
|
|
180
373
|
// O layout só roda para quem não trouxe coordenada. Quem traz manda — é o que deixa a Parte I
|
|
181
374
|
// guardar a posição que a pessoa arrastou sem brigar com a disposição automática.
|
|
182
|
-
const automatico = React.useMemo(() => dispor(nodes, edges), [nodes, edges]);
|
|
375
|
+
const automatico = React.useMemo(() => dispor(nodes, edges, orientation, m), [nodes, edges, orientation, m]);
|
|
376
|
+
// A arrumação em camadas chega DEPOIS (é assíncrona e o pacote é opcional); até lá, a simples.
|
|
377
|
+
const [emCamadas, setEmCamadas] = React.useState(null);
|
|
378
|
+
React.useEffect(() => {
|
|
379
|
+
if (layout !== "layered") {
|
|
380
|
+
setEmCamadas(null);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
let vivo = true;
|
|
384
|
+
disporEmCamadas(nodes, edges, orientation, m, rootId).then(r => { if (vivo)
|
|
385
|
+
setEmCamadas(r); });
|
|
386
|
+
return () => { vivo = false; };
|
|
387
|
+
}, [layout, nodes, edges, orientation, m, rootId]);
|
|
388
|
+
const posicao = React.useCallback((n) => ({
|
|
389
|
+
x: n.x ?? emCamadas?.get(n.id)?.x ?? automatico.posicoes.get(n.id)?.x ?? 0,
|
|
390
|
+
y: n.y ?? emCamadas?.get(n.id)?.y ?? automatico.posicoes.get(n.id)?.y ?? 0,
|
|
391
|
+
}), [emCamadas, automatico]);
|
|
392
|
+
// ── O que fica aceso: o caminho pedido, ou o nó escolhido e os vizinhos ────────────────────
|
|
393
|
+
const ocultos = React.useMemo(() => new Set(hiddenIds ?? []), [hiddenIds]);
|
|
394
|
+
const aceso = React.useMemo(() => {
|
|
395
|
+
if (highlight)
|
|
396
|
+
return { nos: EM_PAR(highlight.nodes), arestas: EM_PAR(highlight.edges) };
|
|
397
|
+
if (highlightNeighbors && selectedId) {
|
|
398
|
+
const nos = new Set([selectedId]), arestas = new Set();
|
|
399
|
+
const ident = arestasParalelas(edges, vertical ? m.larg : m.alt);
|
|
400
|
+
edges.forEach((e, i) => { if (e.from === selectedId || e.to === selectedId) {
|
|
401
|
+
nos.add(e.from);
|
|
402
|
+
nos.add(e.to);
|
|
403
|
+
arestas.add(ident[i].id);
|
|
404
|
+
} });
|
|
405
|
+
return { nos, arestas };
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}, [highlight, highlightNeighbors, selectedId, edges, vertical, m]);
|
|
409
|
+
// As setas e o "ir até" moram no lado de DENTRO do provedor (eles precisam do motor); o nó chama
|
|
410
|
+
// por esta referência estável, sem refazer a lista de nós a cada render.
|
|
411
|
+
const teclado = React.useRef(undefined);
|
|
183
412
|
const nosDoMotor = React.useMemo(() => nodes.map(n => ({
|
|
184
413
|
id: n.id, type: "aurea",
|
|
185
|
-
position:
|
|
414
|
+
position: posicao(n),
|
|
186
415
|
draggable: !!onNodeMove,
|
|
416
|
+
// `selectable:false` no PRÓPRIO nó, como na aresta: no servidor (a prévia do catálogo) o motor ainda
|
|
417
|
+
// não recebeu o `elementsSelectable={false}` do fluxo, e o nó saía com a classe `.selectable` e a
|
|
418
|
+
// mão de ponteiro — num mapa sem `onSelect`, uma promessa de clique sem teclado. O gate
|
|
419
|
+
// `alvo-clicavel` pegou na página do padrão de rede (10/10/2026).
|
|
420
|
+
selectable: false,
|
|
421
|
+
hidden: ocultos.has(n.id),
|
|
187
422
|
// ── O QUE FAZ ESTE GRAFO EXISTIR FORA DO NAVEGADOR ─────────────────────────────────
|
|
188
423
|
// O motor só desenha nó que tem tamanho, e no servidor não há o que medir. `initialWidth`
|
|
189
424
|
// e `initialHeight` valem SÓ até a primeira medição — no navegador o tamanho real assume
|
|
@@ -193,23 +428,38 @@ export function DependencyGraph({ nodes, edges, label, selectedId, onSelect, con
|
|
|
193
428
|
// Eu tinha concluído que grafo não cabia em HTML estático e escrevi isso como limite do
|
|
194
429
|
// componente. Era conclusão minha, não medição: o motor tem caminho de SSR documentado, e
|
|
195
430
|
// são estas três coisas. A prévia do catálogo existe por causa deste bloco.
|
|
196
|
-
initialWidth:
|
|
197
|
-
handles:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
431
|
+
initialWidth: m.larg, initialHeight: m.alt,
|
|
432
|
+
handles: alcasDoNo(n, orientation, m).map(({ rotulo: _r, frac: _f, ...a }) => a),
|
|
433
|
+
data: { label: n.label, kind: n.kind, detail: n.detail, icone: n.icon, contagem: n.count, nomeDaContagem: n.countLabel, estado: n.status,
|
|
434
|
+
alcas: alcasDoNo(n, orientation, m), selecionado: selectedId === n.id, focado: focusId === n.id,
|
|
435
|
+
apagado: aceso?.nos ? !aceso.nos.has(n.id) : false,
|
|
436
|
+
conectavel: !!connectable, aoSelecionar: onSelect ? () => onSelect(n.id) : undefined,
|
|
437
|
+
aoTeclar: onSelect ? (tecla) => teclado.current?.(n.id, tecla) : undefined },
|
|
438
|
+
})), [nodes, posicao, selectedId, focusId, onSelect, connectable, onNodeMove, ocultos, aceso, orientation, m]);
|
|
202
439
|
// `selectable:false` na PRÓPRIA aresta, e não só no `elementsSelectable` do `<ReactFlow>`:
|
|
203
440
|
// medido em 29/08/2026, a prop do fluxo não tira a classe `.selectable` da aresta, que é onde
|
|
204
441
|
// o `base.css` do motor põe `cursor:pointer`. A mão de ponteiro prometia um clique que a API
|
|
205
442
|
// da Aurea não atende — `onSelect` é do NÓ, e quem o atende é o <button> do corpo dele.
|
|
443
|
+
// (10/10/2026) Com `onEdgeSelect` a aresta passa a ser alvo de verdade, e aí a mão é honesta.
|
|
444
|
+
const [abertos, setAbertos] = React.useState(() => new Set());
|
|
445
|
+
const alternarGrupo = React.useCallback((k) => setAbertos(a => { const b = new Set(a); if (b.has(k))
|
|
446
|
+
b.delete(k);
|
|
447
|
+
else
|
|
448
|
+
b.add(k); return b; }), []);
|
|
449
|
+
const desenhadas = React.useMemo(() => agruparParalelas(edges, !!groupParallel, abertos), [edges, groupParallel, abertos]);
|
|
206
450
|
const arestasDoMotor = React.useMemo(() => {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
451
|
+
const itens = desenhadas.map(x => x.item);
|
|
452
|
+
const ident = arestasParalelas(itens, vertical ? m.larg : m.alt);
|
|
453
|
+
return desenhadas.map(({ item: e, grupo }, i) => ({
|
|
454
|
+
id: ident[i].id, type: "aurea", source: e.from, target: e.to, sourceHandle: e.fromPort, targetHandle: e.toPort,
|
|
455
|
+
// O grupo FECHADO é a linha de todos: o nome da primeira enganaria ("WAN 1" valendo pelas 10).
|
|
456
|
+
label: grupo && !grupo.aberto ? undefined : e.label, animated: e.animated, selectable: !!onEdgeSelect,
|
|
457
|
+
hidden: ocultos.has(e.from) || ocultos.has(e.to),
|
|
458
|
+
data: { deslocamento: ident[i].deslocamento, vertical, forma: edgeShape, padrao: e.pattern, peso: e.weight, marca: e.mark, contagem: e.count,
|
|
459
|
+
origem: e.sourceLabel, destino: e.targetLabel, apagada: aceso?.arestas ? !aceso.arestas.has(e.id ?? ident[i].id) : aceso?.nos ? !(aceso.nos.has(e.from) && aceso.nos.has(e.to)) : false,
|
|
460
|
+
grupo: grupo ? { n: grupo.n, aberto: grupo.aberto, alternar: () => alternarGrupo(grupo.chave), mostrar: s.graphShow, juntar: s.graphJoin, conexoes: s.graphConnections } : undefined },
|
|
211
461
|
}));
|
|
212
|
-
}, [
|
|
462
|
+
}, [desenhadas, vertical, m, edgeShape, onEdgeSelect, ocultos, aceso, alternarGrupo, s]);
|
|
213
463
|
// ── A MEDIDA TEM DE PODER VOLTAR, e isto foi achado NO NAVEGADOR ───────────────────────
|
|
214
464
|
// A primeira versão passava `nodes` e nenhum `onNodesChange`. O motor aceita calado e o
|
|
215
465
|
// resultado é um grafo INVISÍVEL: ele só tira o `visibility:hidden` do nó depois de gravar
|
|
@@ -224,16 +474,112 @@ export function DependencyGraph({ nodes, edges, label, selectedId, onSelect, con
|
|
|
224
474
|
React.useEffect(() => {
|
|
225
475
|
setNosVivos(anteriores => {
|
|
226
476
|
const medidos = new Map(anteriores.map(n => [n.id, n.measured]));
|
|
227
|
-
return nosDoMotor.map(n => ({ ...n, measured: medidos.get(n.id) }));
|
|
477
|
+
return nosDoMotor.map(n => semAlcasDeclaradas({ ...n, measured: medidos.get(n.id) }));
|
|
228
478
|
});
|
|
229
479
|
}, [nosDoMotor]);
|
|
230
480
|
const aoMudarNos = React.useCallback((mudancas) => {
|
|
231
|
-
setNosVivos(anteriores => applyNodeChanges(mudancas, anteriores));
|
|
481
|
+
setNosVivos(anteriores => applyNodeChanges(mudancas, anteriores).map(semAlcasDeclaradas));
|
|
232
482
|
}, []);
|
|
483
|
+
// A LEGENDA se monta com o que está na tela: uma linha por nome de `legend`, com o desenho dela.
|
|
484
|
+
const legenda = React.useMemo(() => {
|
|
485
|
+
const vistas = new Map();
|
|
486
|
+
for (const e of edges)
|
|
487
|
+
if (e.legend && !vistas.has(e.legend))
|
|
488
|
+
vistas.set(e.legend, e);
|
|
489
|
+
return [...vistas.entries()];
|
|
490
|
+
}, [edges]);
|
|
491
|
+
const raiz = React.useRef(null);
|
|
233
492
|
// `initialWidth`/`initialHeight` no provider é o par do de cima, um nível acima: é o viewport
|
|
234
493
|
// que o `fitView` usa ENQUANTO ninguém mediu. Com o prefixo `initial`, o navegador sobrescreve
|
|
235
494
|
// na primeira medição — passar `width`/`height` direto no ReactFlow fixaria o tamanho e mataria
|
|
236
495
|
// a fluidez. O valor é a extensão do próprio layout, então no servidor o grafo cabe exato.
|
|
237
|
-
|
|
238
|
-
|
|
496
|
+
const estilo = { ...style, ...(height ? { blockSize: height } : null), "--graph-node-w": `${m.larg}px` };
|
|
497
|
+
return _jsx("div", { ref: raiz, className: cx("dependency-graph", className), style: estilo, "data-text": textSize === "md" ? undefined : textSize, "data-contrast": highContrast ? "high" : undefined, role: "group", "aria-label": label ?? s.graphLabel, ...props, children: _jsx(ReactFlowProvider, { initialNodes: nosDoMotor, initialEdges: arestasDoMotor, fitView: true, initialWidth: automatico.extensao.larg, initialHeight: automatico.extensao.alt, children: _jsxs(ReactFlow, { nodes: nosVivos, onNodesChange: aoMudarNos, edges: arestasDoMotor, nodeTypes: TIPOS, edgeTypes: TIPOS_DE_ARESTA, fitView: true, proOptions: { hideAttribution: false }, nodesDraggable: !!onNodeMove, nodesConnectable: !!connectable, elementsSelectable: false, onNodeDragStop: onNodeMove ? (_, no) => onNodeMove(no.id, no.position) : undefined, onNodeMouseEnter: onNodeHover ? (_, no) => onNodeHover(no.id) : undefined, onNodeMouseLeave: onNodeHover ? () => onNodeHover(null) : undefined, onNodeContextMenu: onNodeContextMenu ? (ev, no) => { ev.preventDefault(); onNodeContextMenu(no.id, { x: ev.clientX, y: ev.clientY }); } : undefined, onEdgeClick: onEdgeSelect ? (_, a) => onEdgeSelect(a.id) : undefined, onConnect: onConnect ? c => { if (c.source && c.target)
|
|
498
|
+
onConnect({ from: c.source, to: c.target, fromPort: c.sourceHandle ?? undefined, toPort: c.targetHandle ?? undefined }); } : undefined, children: [_jsx(Background, {}), _jsx(Comandos, { raiz: raiz, teclado: teclado, focusId: focusId, posicoesProntas: emCamadas, controls: !!controls, apiRef: apiRef, itens: desenhadas.map(x => x.item), forma: edgeShape, titulo: label ?? s.graphLabel }), minimap && _jsx(MiniMap, { pannable: true, zoomable: true, ariaLabel: s.graphOverview, className: "graph-minimap" }), legend && legenda.length > 0 && _jsx(Panel, { position: "bottom-left", children: _jsx("ul", { className: "graph-legend", "aria-label": s.graphLegend, children: legenda.map(([nome, e]) => _jsxs("li", { children: [_jsxs("svg", { className: "graph-legend-sample", "aria-hidden": "true", viewBox: "0 0 32 8", children: [_jsx("path", { d: "M0 4H32", className: cx("graph-edge", e.pattern && e.pattern !== "solid" && `graph-edge-${e.pattern}`, e.weight && e.weight !== "regular" && `graph-edge-${e.weight}`) }), e.pattern === "double" && _jsx("path", { d: "M0 4H32", className: cx("graph-edge-double-core", e.weight && e.weight !== "regular" && `graph-edge-${e.weight}`) })] }), e.mark && _jsx("span", { className: `graph-edge-mark graph-edge-mark-${e.mark}`, "aria-hidden": "true", children: e.mark === "lock" && _jsx(Icon, { name: "lock-key", size: "sm" }) }), _jsx("span", { children: nome })] }, nome)) }) })] }) }) });
|
|
499
|
+
}
|
|
500
|
+
// `CSS.escape` existe no navegador; fora dele (o teste em jsdom), as aspas bastam.
|
|
501
|
+
const escapar = (id) => typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(id) : id.replace(/[\x22\\]/g, "\\$&");
|
|
502
|
+
/**
|
|
503
|
+
* O lado de DENTRO do provedor: o que precisa do motor — caber na tela depois da arrumação em
|
|
504
|
+
* camadas, o "ir até", as setas de nó em nó, os botões de zoom e o `apiRef`.
|
|
505
|
+
*/
|
|
506
|
+
function Comandos({ raiz, teclado, focusId, posicoesProntas, controls, apiRef, itens, forma, titulo }) {
|
|
507
|
+
const rf = useReactFlow();
|
|
508
|
+
const s = useAureaStrings();
|
|
509
|
+
// Quem pediu menos movimento não vê a tela deslizar: o "ir até" e o caber na tela pulam direto.
|
|
510
|
+
const duracao = () => typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches ? 0 : 300;
|
|
511
|
+
const centralizar = React.useCallback((id) => {
|
|
512
|
+
const no = rf.getNode(id);
|
|
513
|
+
if (!no)
|
|
514
|
+
return;
|
|
515
|
+
const w = no.measured?.width ?? no.initialWidth ?? LARGURA, h = no.measured?.height ?? no.initialHeight ?? ALTURA;
|
|
516
|
+
rf.setCenter(no.position.x + w / 2, no.position.y + h / 2, { zoom: Math.max(rf.getZoom(), 1), duration: duracao() });
|
|
517
|
+
}, [rf]);
|
|
518
|
+
// A arrumação em camadas chegou: o mapa cabe de novo na tela.
|
|
519
|
+
React.useEffect(() => { if (posicoesProntas)
|
|
520
|
+
requestAnimationFrame(() => rf.fitView({ duration: duracao() })); }, [posicoesProntas, rf]);
|
|
521
|
+
React.useEffect(() => { if (focusId)
|
|
522
|
+
centralizar(focusId); }, [focusId, centralizar]);
|
|
523
|
+
// AS SETAS (MNT-19.4): o nó mais perto na direção da seta, dentro de um cone de 45°. O foco vai
|
|
524
|
+
// para o botão dele; se ele estiver fora da tela, a tela vai até ele.
|
|
525
|
+
React.useEffect(() => {
|
|
526
|
+
teclado.current = (id, tecla) => {
|
|
527
|
+
const centro = (n) => ({ x: n.position.x + (n.measured?.width ?? LARGURA) / 2, y: n.position.y + (n.measured?.height ?? ALTURA) / 2 });
|
|
528
|
+
const atual = rf.getNode(id);
|
|
529
|
+
if (!atual)
|
|
530
|
+
return;
|
|
531
|
+
const a = centro(atual);
|
|
532
|
+
let melhor = null;
|
|
533
|
+
for (const n of rf.getNodes()) {
|
|
534
|
+
if (n.id === id || n.hidden)
|
|
535
|
+
continue;
|
|
536
|
+
const b = centro(n), dx = b.x - a.x, dy = b.y - a.y;
|
|
537
|
+
const naDirecao = tecla === "ArrowRight" ? dx > 0 && Math.abs(dy) <= dx : tecla === "ArrowLeft" ? dx < 0 && Math.abs(dy) <= -dx
|
|
538
|
+
: tecla === "ArrowDown" ? dy > 0 && Math.abs(dx) <= dy : dy < 0 && Math.abs(dx) <= -dy;
|
|
539
|
+
if (!naDirecao)
|
|
540
|
+
continue;
|
|
541
|
+
const d = dx * dx + dy * dy;
|
|
542
|
+
if (!melhor || d < melhor.d)
|
|
543
|
+
melhor = { id: n.id, d };
|
|
544
|
+
}
|
|
545
|
+
if (!melhor)
|
|
546
|
+
return;
|
|
547
|
+
const alvo = raiz.current?.querySelector(`.react-flow__node[data-id="${escapar(melhor.id)}"] button.graph-node-body`);
|
|
548
|
+
alvo?.focus({ preventScroll: true });
|
|
549
|
+
const caixa = raiz.current?.getBoundingClientRect(), noCaixa = alvo?.getBoundingClientRect();
|
|
550
|
+
if (caixa && noCaixa && (noCaixa.left < caixa.left || noCaixa.right > caixa.right || noCaixa.top < caixa.top || noCaixa.bottom > caixa.bottom))
|
|
551
|
+
centralizar(melhor.id);
|
|
552
|
+
};
|
|
553
|
+
}, [rf, raiz, teclado, centralizar]);
|
|
554
|
+
React.useImperativeHandle(apiRef, () => ({
|
|
555
|
+
fitView: () => { void rf.fitView({ duration: duracao() }); },
|
|
556
|
+
zoomIn: () => { void rf.zoomIn({ duration: duracao() }); },
|
|
557
|
+
zoomOut: () => { void rf.zoomOut({ duration: duracao() }); },
|
|
558
|
+
focus: centralizar,
|
|
559
|
+
toDrawio: () => paraDrawio(rf.getNodes(), itens, forma, titulo),
|
|
560
|
+
toPng: () => foto(raiz.current, "png"),
|
|
561
|
+
toSvg: () => foto(raiz.current, "svg"),
|
|
562
|
+
}), [rf, centralizar, itens, forma, titulo]);
|
|
563
|
+
if (!controls)
|
|
564
|
+
return null;
|
|
565
|
+
return _jsxs(Panel, { position: "top-right", className: "graph-controls", children: [_jsx(IconButton, { icon: "magnifying-glass-plus", label: s.graphZoomIn, onClick: () => void rf.zoomIn({ duration: duracao() }) }), _jsx(IconButton, { icon: "magnifying-glass-minus", label: s.graphZoomOut, onClick: () => void rf.zoomOut({ duration: duracao() }) }), _jsx(IconButton, { icon: "corners-out", label: s.graphFit, onClick: () => void rf.fitView({ duration: duracao() }) })] });
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* A FOTO do mapa (MNT-19.2), pelo `modern-screenshot` (peer OPCIONAL, MIT), carregado só aqui. Sai
|
|
569
|
+
* o desenho, sem os botões, o minimapa e a legenda flutuante — que são da tela, não do mapa —, no
|
|
570
|
+
* fundo do próprio cartão.
|
|
571
|
+
*/
|
|
572
|
+
async function foto(el, tipo) {
|
|
573
|
+
if (!el)
|
|
574
|
+
throw new Error("DependencyGraph: o mapa ainda não está na tela.");
|
|
575
|
+
let ms;
|
|
576
|
+
try {
|
|
577
|
+
ms = await import("modern-screenshot");
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
throw new Error("DependencyGraph: toPng/toSvg precisam do pacote `modern-screenshot` (peer opcional).");
|
|
581
|
+
}
|
|
582
|
+
const opcoes = { backgroundColor: getComputedStyle(el).backgroundColor, scale: tipo === "png" ? 2 : 1,
|
|
583
|
+
filter: (n) => !(n instanceof HTMLElement && n.classList.contains("react-flow__panel")) };
|
|
584
|
+
return tipo === "png" ? ms.domToPng(el, opcoes) : ms.domToSvg(el, opcoes);
|
|
239
585
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { cx, defaultStrings, ptBR, defaultSpriteUrl, gridStateToParams, gridStat
|
|
|
2
2
|
export { useAureaStrings, useSpriteUrl, useTheme, useDensity } from "./internal.js";
|
|
3
3
|
export { type Responsive, type Breakpoint, type ContainerBreakpoint, type Orientation, ESCALA, ESCALA_CONTAINER } from "./pure.js";
|
|
4
4
|
export { ContainerScope, useValorResponsivo } from "./responsivo-runtime.js";
|
|
5
|
-
export { AspectRatio, InputGroup, InputGroupAddon, Label, Card, Stack, Cluster, Grid, Container, Section, KPI, DataList, Timeline, Prose, Text, Heading, Paragraph, Code, Badge, Progress, Skeleton, AvatarGroup, LogStream, MediaPlayerShell, Header, Topbar, Kbd, Textarea, Checkbox, Radio, Switch, Range, formatBadgeCount, type BadgeVariant, type BadgeCategory, type BadgeEmphasis, type BadgeSize, type BadgePlacement, type BadgeProps, type KPIProps, type KPIDirection, type KPITone, type KPIVariant, type ProgressProps, type ProgressTone, type AvatarSize, type HeaderVariant, type HeaderProps, type TopbarVariant, type SectionVariant, type SectionFooterGroup, type TextProps, type HeadingProps, type ParagraphProps, type CodeProps, type TypographyType, type TypographyColor, type TypographyWeight, type TypographyAlign, type HeadingLevel, type ParagraphSize } from "./markup.js";
|
|
5
|
+
export { AspectRatio, InputGroup, InputGroupAddon, Label, Card, Stack, Cluster, Grid, Container, Section, KPI, DataList, Timeline, Prose, Text, Heading, Paragraph, Code, Badge, Progress, Skeleton, AvatarGroup, LogStream, MediaPlayerShell, Header, Topbar, Kbd, Textarea, Checkbox, Radio, Switch, Range, formatBadgeCount, type BadgeVariant, type BadgeCategory, type BadgeEmphasis, type BadgeSize, type BadgePlacement, type BadgeProps, type KPIProps, type KPIDirection, type KPITone, type KPIVariant, type ProgressProps, type ProgressTone, type AvatarSize, type HeaderVariant, type HeaderProps, type TopbarVariant, type SectionVariant, type SectionFooterGroup, type CardProps, type CardAccent, type TextProps, type HeadingProps, type ParagraphProps, type CodeProps, type TypographyType, type TypographyColor, type TypographyWeight, type TypographyAlign, type HeadingLevel, type ParagraphSize } from "./markup.js";
|
|
6
6
|
export * from "./system.js";
|
|
7
7
|
export * from "./actions.js";
|
|
8
8
|
export * from "./feedback.js";
|
package/dist/markup.d.ts
CHANGED
|
@@ -8,18 +8,20 @@ export type AvatarSize = "sm" | "md" | "lg";
|
|
|
8
8
|
export type HeaderVariant = "floating" | "flush" | "pill";
|
|
9
9
|
/** @deprecated `Topbar` é o nome antigo do `Header` (07/10/2026). O tipo continua valendo; use `HeaderVariant`. */
|
|
10
10
|
export type TopbarVariant = HeaderVariant;
|
|
11
|
+
export type CardAccent = "brand" | "success" | "info" | "warning" | "danger";
|
|
11
12
|
type CardBase = HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
12
13
|
padding?: "normal" | "none";
|
|
13
14
|
orientation?: "vertical" | "horizontal";
|
|
15
|
+
accent?: CardAccent;
|
|
14
16
|
};
|
|
15
17
|
export type CardProps = CardBase & ({
|
|
16
|
-
variant?: "base" | "raised" | "inset" | "selected" | "danger";
|
|
18
|
+
variant?: "base" | "raised" | "inset" | "selected" | "danger" | "contrast";
|
|
17
19
|
render?: ReactElement;
|
|
18
20
|
} | {
|
|
19
21
|
variant: "interactive";
|
|
20
22
|
render: ReactElement;
|
|
21
23
|
});
|
|
22
|
-
export declare function Card({ variant, padding, orientation, className, render, ...props }: CardProps): ReactElement<unknown, string | React.JSXElementConstructor<any>>;
|
|
24
|
+
export declare function Card({ variant, padding, orientation, accent, className, render, ...props }: CardProps): ReactElement<unknown, string | React.JSXElementConstructor<any>>;
|
|
23
25
|
export declare namespace Card {
|
|
24
26
|
export { CardMedia as Media };
|
|
25
27
|
}
|
package/dist/markup.js
CHANGED
|
@@ -37,8 +37,8 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
37
37
|
// tocar motor. Na dúvida, ele NÃO entra — cliente a mais é lento, servidor a mais é quebrado.
|
|
38
38
|
import React, { forwardRef } from "react";
|
|
39
39
|
import { cx, fundirRender, peleDoEixo } from "./pure.js";
|
|
40
|
-
export function Card({ variant = "base", padding = "normal", orientation = "vertical", className, render, ...props }) {
|
|
41
|
-
return fundirRender(render, { className: cx("card", variant !== "base" && `card-${variant}`, padding === "none" && "card-flush", orientation === "horizontal" && "card-horizontal", className), ...props });
|
|
40
|
+
export function Card({ variant = "base", padding = "normal", orientation = "vertical", accent, className, render, ...props }) {
|
|
41
|
+
return fundirRender(render, { className: cx("card", variant !== "base" && `card-${variant}`, accent && `card-accent card-accent-${accent}`, padding === "none" && "card-flush", orientation === "horizontal" && "card-horizontal", className), ...(variant === "contrast" ? { "data-theme": "dark" } : null), ...props });
|
|
42
42
|
}
|
|
43
43
|
/** A mídia do cartão — a primeira das partes da especificação do `Card` (A-14). No vertical,
|
|
44
44
|
* primeira filha, ela SANGRA até a borda de cima e dos lados; no horizontal, fica à esquerda com a
|
package/dist/pure.d.ts
CHANGED
|
@@ -162,6 +162,14 @@ export interface AureaStrings {
|
|
|
162
162
|
automationLastRun: string;
|
|
163
163
|
automationResult: Record<"success" | "failure", string>;
|
|
164
164
|
graphLabel: string;
|
|
165
|
+
graphZoomIn: string;
|
|
166
|
+
graphZoomOut: string;
|
|
167
|
+
graphFit: string;
|
|
168
|
+
graphLegend: string;
|
|
169
|
+
graphOverview: string;
|
|
170
|
+
graphConnections: string;
|
|
171
|
+
graphShow: string;
|
|
172
|
+
graphJoin: string;
|
|
165
173
|
agentState: Record<"idle" | "thinking" | "running" | "paused" | "error" | "completed", string>;
|
|
166
174
|
codeEditor: string;
|
|
167
175
|
chatLabel: string;
|
package/dist/pure.js
CHANGED
|
@@ -53,10 +53,10 @@ export const universalStates = ["waiting_user", "waiting_approval", "waiting_dep
|
|
|
53
53
|
// perdeu qualidade é avisado. Um `role="alert"` aqui interromperia o leitor de tela por uma
|
|
54
54
|
// condição que não pede ação imediata, que é o oposto do que o papel serve.
|
|
55
55
|
export const stateSeverity = (state) => state.startsWith("waiting_") ? "info" : "warning";
|
|
56
|
-
export const defaultStrings = { passwordShow: "Show password", passwordHide: "Hide password", skipToContent: "Skip to content", close: "Close", confirmCancel: "Cancel", dataError: "This could not be loaded.", dataEmpty: "Nothing here yet", confirmProceed: "Continue", paginationLabel: "Pagination", previous: "Previous", next: "Next", breadcrumbLabel: "Breadcrumb", tabsLabel: "Tabs", optionsLabel: "Options", tableLabel: "Table", commandLabel: "Command palette", commandPlaceholder: "Type a command…", dismissNotification: "Dismiss notification", toolbarLabel: "Toolbar", loading: "Loading", otpDigit: "Digit", stepperLabel: "Steps", increment: "Increase", decrement: "Decrease", comboboxEmpty: "No results", comboboxClear: "Clear selection", comboboxOpen: "Open list", comboboxRemove: "Remove", comboboxLoading: "Loading…", fileDropPrompt: "Drag files here or click to select", fileAdded: "File added", fileRemoved: "File removed", fileRemove: "Remove", fileTooLarge: "File exceeds the size limit", fileWrongType: "File type not allowed", treeLabel: "Tree", notificationsLabel: "Notifications", notificationMarkAll: "Mark all as read", notificationEmpty: "No notifications", notificationUnread: "Unread", notificationNew: "new notifications", dataGridFilter: "Filter", dataGridEmpty: "No results", dataGridSelectAll: "Select all rows", dataGridSelectRow: "Select row", dataGridSelected: "selected", dataGridClearSelection: "Clear selection", dataGridBulkLabel: "Bulk actions", dataGridColumns: "Columns", dataGridResize: "Resize column", dataGridStale: "Showing data that may be out of date.", dataGridPartial: "Some rows could not be loaded. What you see is incomplete.", dataGridError: "The rows could not be loaded.", dataGridDetails: "Details", dataGridDetailPanel: "Row detail", dataGridExport: "Export", dataGridExportRows: "rows", dataGridExportFiltered: "filtered rows", dataGridExportSelected: "selected rows", mediaPlayer: "Media player", mediaPlay: "Play", mediaPause: "Pause", mediaMute: "Mute", mediaUnmute: "Unmute", mediaSeek: "Seek", mediaVolume: "Volume", mediaCaptionsShow: "Show captions", mediaCaptionsHide: "Hide captions", mediaSkipBack: "Skip back 10 seconds", mediaSkipForward: "Skip forward 10 seconds", mediaFullscreenEnter: "Enter full screen", mediaFullscreenExit: "Exit full screen", mediaEmbedOpen: "Open the video on its own site", carouselLabel: "Carousel", carouselSlide: "Slide", positionOf: "of", carouselPrev: "Previous slide", carouselNext: "Next slide", galleryLabel: "Gallery", galleryVideo: "Video", sortableLabel: "Sortable list", sortableHandle: "Reorder", sortableHelp: "Press Space to pick this up, then Arrow Up and Arrow Down to move it. Space drops it, Escape puts it back.", sortableGrabbed: "Picked up", sortableDropped: "Dropped", sortableMoved: "Moved", sortableCanceled: "Put back", blockEditorLabel: "Content blocks", blockLabel: "Block", blockRemove: "Remove block", uploadSending: "Uploading", uploadCancel: "Cancel upload", uploadRetry: "Retry upload", uploadError: "Upload failed", uploadCanceled: "Upload canceled", uploadComplete: "Upload complete", uploadPause: "Pause upload", uploadResume: "Resume upload", uploadPaused: "Paused", uploadPending: "Waiting", uploadChecksum: "Checksum", uploadChecksumBad: "The server received different bytes than the ones sent.", fileConflict: "A file with this name is already in the queue", fileReplace: "Replace", fileKeepBoth: "Keep both", fileSkip: "Skip", uploadReceipt: "Receipt", uploadReceiptCopy: "Copy receipt", agentInspector: "Agent inspector", invocationLabel: "Invocation", invocationInput: "Input", invocationOutput: "Output", taskQueueLabel: "Task queue", taskState: { queued: "Queued", running: "Running", completed: "Completed", failed: "Failed", blocked: "Blocked", paused: "Paused" }, taskPriority: { low: "Low", medium: "Medium", high: "High" }, approvalLabel: "Approval", approvalApprove: "Approve", approvalDeny: "Deny", approvalApproved: "Approved", approvalDenied: "Denied", approvalDeadline: "Decide by", approvalRisk: { low: "Low risk", medium: "Medium risk", high: "High risk" }, permissionLabel: "Tool permissions", permissionAsk: "Ask", permissionAlways: "Always", permissionNever: "Never", eventStreamLabel: "Events", traceLabel: "Trace", healthLabel: "Health", healthState: { operational: "Operational", degraded: "Degraded", down: "Down", maintenance: "Maintenance", unknown: "Unknown" }, modelUsageLabel: "Model usage", usageMetric: { tokens: "Tokens", cost: "Cost", requests: "Requests" }, costMeterLabel: "Cost", costMeterSpent: "Spent", costMeterRemaining: "left", costMeterExceeded: "Over the limit", costMeterNear: "Spending is past the soft limit.", costMeterOver: "The limit has been reached. Further calls are blocked.", memoryLabel: "Memory ledger", memoryProvenance: "Where this came from", memoryOperation: { added: "Added", updated: "Updated", recalled: "Recalled", forgotten: "Forgotten" }, memoryScope: { episodic: "Episodic", semantic: "Semantic", procedural: "Procedural" }, agentMessageLabel: "Agent messages", agentMessageTo: "to", agentMessageBroadcast: "all agents", agentMessageReason: "Why it went this way", agentMessageKind: { request: "Request", response: "Response", handoff: "Handoff", broadcast: "Broadcast", error: "Error" }, automationWhen: "When", automationThen: "Then", automationEnabled: "Enabled", automationLastRun: "Last run", automationResult: { success: "Succeeded", failure: "Failed" }, graphLabel: "Dependency graph", agentState: { idle: "Idle", thinking: "Thinking", running: "Running", paused: "Paused", error: "Error", completed: "Completed" }, codeEditor: "Code editor", chatLabel: "Conversation", chatMessage: "Message", chatSend: "Send", chatAttach: "Attach file", chatAttachments: "Attachments", chatReplyTo: "Replying to", chatReplyCancel: "Cancel reply", chatEditing: "Editing message", chatEditCancel: "Cancel editing", chatSave: "Save", chatEdited: "edited", chatForwardedFrom: "Forwarded from", qrCode: "QR code", chartLabel: "Chart", copyCode: "Copy code", copied: "Copied", linkCopied: "Link copied", linkCopyFailed: "Could not copy. Copy the link below.", tocLabel: "On this page", navigationToggle: "Navigation", sidebarLabel: "Sidebar", sidebarCollapse: "Collapse sidebar", sidebarExpand: "Expand sidebar", bottomNavLabel: "Main", themeToDark: "Switch to dark theme", themeToLight: "Switch to light theme", universalState: { waiting_user: "Waiting for someone to act.", waiting_approval: "Waiting for approval.", waiting_dependency: "Waiting for something else to finish.", offline: "No connection. This was loaded earlier.", stale: "This may be out of date.", partial: "Some of this could not be loaded.", degraded: "Working with reduced capability." } };
|
|
56
|
+
export const defaultStrings = { passwordShow: "Show password", passwordHide: "Hide password", skipToContent: "Skip to content", close: "Close", confirmCancel: "Cancel", dataError: "This could not be loaded.", dataEmpty: "Nothing here yet", confirmProceed: "Continue", paginationLabel: "Pagination", previous: "Previous", next: "Next", breadcrumbLabel: "Breadcrumb", tabsLabel: "Tabs", optionsLabel: "Options", tableLabel: "Table", commandLabel: "Command palette", commandPlaceholder: "Type a command…", dismissNotification: "Dismiss notification", toolbarLabel: "Toolbar", loading: "Loading", otpDigit: "Digit", stepperLabel: "Steps", increment: "Increase", decrement: "Decrease", comboboxEmpty: "No results", comboboxClear: "Clear selection", comboboxOpen: "Open list", comboboxRemove: "Remove", comboboxLoading: "Loading…", fileDropPrompt: "Drag files here or click to select", fileAdded: "File added", fileRemoved: "File removed", fileRemove: "Remove", fileTooLarge: "File exceeds the size limit", fileWrongType: "File type not allowed", treeLabel: "Tree", notificationsLabel: "Notifications", notificationMarkAll: "Mark all as read", notificationEmpty: "No notifications", notificationUnread: "Unread", notificationNew: "new notifications", dataGridFilter: "Filter", dataGridEmpty: "No results", dataGridSelectAll: "Select all rows", dataGridSelectRow: "Select row", dataGridSelected: "selected", dataGridClearSelection: "Clear selection", dataGridBulkLabel: "Bulk actions", dataGridColumns: "Columns", dataGridResize: "Resize column", dataGridStale: "Showing data that may be out of date.", dataGridPartial: "Some rows could not be loaded. What you see is incomplete.", dataGridError: "The rows could not be loaded.", dataGridDetails: "Details", dataGridDetailPanel: "Row detail", dataGridExport: "Export", dataGridExportRows: "rows", dataGridExportFiltered: "filtered rows", dataGridExportSelected: "selected rows", mediaPlayer: "Media player", mediaPlay: "Play", mediaPause: "Pause", mediaMute: "Mute", mediaUnmute: "Unmute", mediaSeek: "Seek", mediaVolume: "Volume", mediaCaptionsShow: "Show captions", mediaCaptionsHide: "Hide captions", mediaSkipBack: "Skip back 10 seconds", mediaSkipForward: "Skip forward 10 seconds", mediaFullscreenEnter: "Enter full screen", mediaFullscreenExit: "Exit full screen", mediaEmbedOpen: "Open the video on its own site", carouselLabel: "Carousel", carouselSlide: "Slide", positionOf: "of", carouselPrev: "Previous slide", carouselNext: "Next slide", galleryLabel: "Gallery", galleryVideo: "Video", sortableLabel: "Sortable list", sortableHandle: "Reorder", sortableHelp: "Press Space to pick this up, then Arrow Up and Arrow Down to move it. Space drops it, Escape puts it back.", sortableGrabbed: "Picked up", sortableDropped: "Dropped", sortableMoved: "Moved", sortableCanceled: "Put back", blockEditorLabel: "Content blocks", blockLabel: "Block", blockRemove: "Remove block", uploadSending: "Uploading", uploadCancel: "Cancel upload", uploadRetry: "Retry upload", uploadError: "Upload failed", uploadCanceled: "Upload canceled", uploadComplete: "Upload complete", uploadPause: "Pause upload", uploadResume: "Resume upload", uploadPaused: "Paused", uploadPending: "Waiting", uploadChecksum: "Checksum", uploadChecksumBad: "The server received different bytes than the ones sent.", fileConflict: "A file with this name is already in the queue", fileReplace: "Replace", fileKeepBoth: "Keep both", fileSkip: "Skip", uploadReceipt: "Receipt", uploadReceiptCopy: "Copy receipt", agentInspector: "Agent inspector", invocationLabel: "Invocation", invocationInput: "Input", invocationOutput: "Output", taskQueueLabel: "Task queue", taskState: { queued: "Queued", running: "Running", completed: "Completed", failed: "Failed", blocked: "Blocked", paused: "Paused" }, taskPriority: { low: "Low", medium: "Medium", high: "High" }, approvalLabel: "Approval", approvalApprove: "Approve", approvalDeny: "Deny", approvalApproved: "Approved", approvalDenied: "Denied", approvalDeadline: "Decide by", approvalRisk: { low: "Low risk", medium: "Medium risk", high: "High risk" }, permissionLabel: "Tool permissions", permissionAsk: "Ask", permissionAlways: "Always", permissionNever: "Never", eventStreamLabel: "Events", traceLabel: "Trace", healthLabel: "Health", healthState: { operational: "Operational", degraded: "Degraded", down: "Down", maintenance: "Maintenance", unknown: "Unknown" }, modelUsageLabel: "Model usage", usageMetric: { tokens: "Tokens", cost: "Cost", requests: "Requests" }, costMeterLabel: "Cost", costMeterSpent: "Spent", costMeterRemaining: "left", costMeterExceeded: "Over the limit", costMeterNear: "Spending is past the soft limit.", costMeterOver: "The limit has been reached. Further calls are blocked.", memoryLabel: "Memory ledger", memoryProvenance: "Where this came from", memoryOperation: { added: "Added", updated: "Updated", recalled: "Recalled", forgotten: "Forgotten" }, memoryScope: { episodic: "Episodic", semantic: "Semantic", procedural: "Procedural" }, agentMessageLabel: "Agent messages", agentMessageTo: "to", agentMessageBroadcast: "all agents", agentMessageReason: "Why it went this way", agentMessageKind: { request: "Request", response: "Response", handoff: "Handoff", broadcast: "Broadcast", error: "Error" }, automationWhen: "When", automationThen: "Then", automationEnabled: "Enabled", automationLastRun: "Last run", automationResult: { success: "Succeeded", failure: "Failed" }, graphLabel: "Dependency graph", graphZoomIn: "Zoom in", graphZoomOut: "Zoom out", graphFit: "Fit to screen", graphLegend: "Legend", graphOverview: "Overview", graphConnections: "connections", graphShow: "Show", graphJoin: "Join", agentState: { idle: "Idle", thinking: "Thinking", running: "Running", paused: "Paused", error: "Error", completed: "Completed" }, codeEditor: "Code editor", chatLabel: "Conversation", chatMessage: "Message", chatSend: "Send", chatAttach: "Attach file", chatAttachments: "Attachments", chatReplyTo: "Replying to", chatReplyCancel: "Cancel reply", chatEditing: "Editing message", chatEditCancel: "Cancel editing", chatSave: "Save", chatEdited: "edited", chatForwardedFrom: "Forwarded from", qrCode: "QR code", chartLabel: "Chart", copyCode: "Copy code", copied: "Copied", linkCopied: "Link copied", linkCopyFailed: "Could not copy. Copy the link below.", tocLabel: "On this page", navigationToggle: "Navigation", sidebarLabel: "Sidebar", sidebarCollapse: "Collapse sidebar", sidebarExpand: "Expand sidebar", bottomNavLabel: "Main", themeToDark: "Switch to dark theme", themeToLight: "Switch to light theme", universalState: { waiting_user: "Waiting for someone to act.", waiting_approval: "Waiting for approval.", waiting_dependency: "Waiting for something else to finish.", offline: "No connection. This was loaded earlier.", stale: "This may be out of date.", partial: "Some of this could not be loaded.", degraded: "Working with reduced capability." } };
|
|
57
57
|
// pt-BR preservado como locale (mesmos valores que já eram o default): passe
|
|
58
58
|
// <AureaProvider strings={ptBR}> para restaurar português.
|
|
59
|
-
export const ptBR = { passwordShow: "Mostrar senha", passwordHide: "Ocultar senha", skipToContent: "Pular para o conteúdo", close: "Fechar", confirmCancel: "Cancelar", dataError: "Não foi possível carregar.", dataEmpty: "Ainda não há nada aqui", confirmProceed: "Continuar", paginationLabel: "Paginação", previous: "Anterior", next: "Próxima", breadcrumbLabel: "Breadcrumb", tabsLabel: "Abas", optionsLabel: "Opções", tableLabel: "Tabela", commandLabel: "Paleta de comandos", commandPlaceholder: "Digite um comando…", dismissNotification: "Dispensar notificação", toolbarLabel: "Barra de ferramentas", loading: "Carregando", otpDigit: "Dígito", stepperLabel: "Etapas", increment: "Aumentar", decrement: "Diminuir", comboboxEmpty: "Nenhum resultado", comboboxClear: "Limpar seleção", comboboxOpen: "Abrir lista", comboboxRemove: "Remover", comboboxLoading: "Carregando…", fileDropPrompt: "Arraste arquivos aqui ou clique para selecionar", fileAdded: "Arquivo adicionado", fileRemoved: "Arquivo removido", fileRemove: "Remover", fileTooLarge: "Arquivo maior que o limite", fileWrongType: "Tipo de arquivo não aceito", treeLabel: "Árvore", notificationsLabel: "Notificações", notificationMarkAll: "Marcar todas como lidas", notificationEmpty: "Nenhuma notificação", notificationUnread: "Não lida", notificationNew: "novas notificações", dataGridFilter: "Filtrar", dataGridEmpty: "Nenhum resultado", dataGridSelectAll: "Selecionar todas as linhas", dataGridSelectRow: "Selecionar linha", dataGridSelected: "selecionadas", dataGridClearSelection: "Limpar seleção", dataGridBulkLabel: "Ações em lote", dataGridColumns: "Colunas", dataGridResize: "Redimensionar coluna", dataGridStale: "Mostrando dados que podem estar desatualizados.", dataGridPartial: "Algumas linhas não puderam ser carregadas. O que aparece está incompleto.", dataGridError: "Não foi possível carregar as linhas.", dataGridDetails: "Detalhes", dataGridDetailPanel: "Detalhe da linha", dataGridExport: "Exportar", dataGridExportRows: "linhas", dataGridExportFiltered: "linhas filtradas", dataGridExportSelected: "linhas selecionadas", mediaPlayer: "Reprodutor de mídia", mediaPlay: "Reproduzir", mediaPause: "Pausar", mediaMute: "Silenciar", mediaUnmute: "Ativar som", mediaSeek: "Posição da reprodução", mediaVolume: "Volume", mediaCaptionsShow: "Ativar legendas", mediaCaptionsHide: "Desativar legendas", mediaSkipBack: "Voltar 10 segundos", mediaSkipForward: "Avançar 10 segundos", mediaFullscreenEnter: "Tela cheia", mediaFullscreenExit: "Sair da tela cheia", mediaEmbedOpen: "Abrir o vídeo no site de origem", carouselLabel: "Carrossel", carouselSlide: "Slide", positionOf: "de", carouselPrev: "Slide anterior", carouselNext: "Próximo slide", galleryLabel: "Galeria", galleryVideo: "Vídeo", sortableLabel: "Lista ordenável", sortableHandle: "Reordenar", sortableHelp: "Aperte Espaço para pegar, e as setas para cima e para baixo para mover. Espaço solta, Esc devolve ao lugar.", sortableGrabbed: "Pego", sortableDropped: "Solto", sortableMoved: "Movido", sortableCanceled: "Devolvido", blockEditorLabel: "Blocos de conteúdo", blockLabel: "Bloco", blockRemove: "Remover bloco", uploadSending: "Enviando", uploadCancel: "Cancelar envio", uploadRetry: "Tentar envio novamente", uploadError: "Falha no envio", uploadCanceled: "Envio cancelado", uploadComplete: "Envio concluído", uploadPause: "Pausar envio", uploadResume: "Retomar envio", uploadPaused: "Pausado", uploadPending: "Aguardando", uploadChecksum: "Soma de verificação", uploadChecksumBad: "O servidor recebeu bytes diferentes dos enviados.", fileConflict: "Já existe um arquivo com este nome na fila", fileReplace: "Substituir", fileKeepBoth: "Manter os dois", fileSkip: "Pular", uploadReceipt: "Recibo", uploadReceiptCopy: "Copiar recibo", agentInspector: "Inspetor do agente", invocationLabel: "Invocação", invocationInput: "Entrada", invocationOutput: "Saída", taskQueueLabel: "Fila de tarefas", taskState: { queued: "Na fila", running: "Executando", completed: "Concluída", failed: "Falhou", blocked: "Bloqueada", paused: "Pausada" }, taskPriority: { low: "Baixa", medium: "Média", high: "Alta" }, approvalLabel: "Aprovação", approvalApprove: "Aprovar", approvalDeny: "Negar", approvalApproved: "Aprovado", approvalDenied: "Negado", approvalDeadline: "Decidir até", approvalRisk: { low: "Risco baixo", medium: "Risco médio", high: "Risco alto" }, permissionLabel: "Permissões de ferramenta", permissionAsk: "Perguntar", permissionAlways: "Sempre", permissionNever: "Nunca", eventStreamLabel: "Eventos", traceLabel: "Rastro", healthLabel: "Saúde", healthState: { operational: "Operando", degraded: "Degradado", down: "Fora do ar", maintenance: "Em manutenção", unknown: "Desconhecido" }, modelUsageLabel: "Consumo por modelo", usageMetric: { tokens: "Tokens", cost: "Custo", requests: "Chamadas" }, costMeterLabel: "Custo", costMeterSpent: "Gasto", costMeterRemaining: "restantes", costMeterExceeded: "Acima do limite", costMeterNear: "O gasto passou do limite brando.", costMeterOver: "O limite foi atingido. As próximas chamadas estão bloqueadas.", memoryLabel: "Livro-razão de memória", memoryProvenance: "De onde isto veio", memoryOperation: { added: "Guardada", updated: "Atualizada", recalled: "Lembrada", forgotten: "Esquecida" }, memoryScope: { episodic: "Episódica", semantic: "Semântica", procedural: "Procedural" }, agentMessageLabel: "Recados entre agentes", agentMessageTo: "para", agentMessageBroadcast: "todos os agentes", agentMessageReason: "Por que foi por aqui", agentMessageKind: { request: "Pedido", response: "Resposta", handoff: "Passagem", broadcast: "Difusão", error: "Erro" }, automationWhen: "Quando", automationThen: "Então", automationEnabled: "Ativa", automationLastRun: "Última execução", automationResult: { success: "Concluiu", failure: "Falhou" }, graphLabel: "Grafo de dependências", agentState: { idle: "Ocioso", thinking: "Pensando", running: "Executando", paused: "Pausado", error: "Erro", completed: "Concluído" }, codeEditor: "Editor de código", chatLabel: "Conversa", chatMessage: "Mensagem", chatSend: "Enviar", chatAttach: "Anexar arquivo", chatAttachments: "Anexos", chatReplyTo: "Respondendo a", chatReplyCancel: "Cancelar resposta", chatEditing: "Editando mensagem", chatEditCancel: "Cancelar edição", chatSave: "Salvar", chatEdited: "editada", chatForwardedFrom: "Encaminhada de", qrCode: "Código QR", chartLabel: "Gráfico", copyCode: "Copiar código", copied: "Copiado", linkCopied: "Link copiado", linkCopyFailed: "Não deu para copiar. Copie o link abaixo.", tocLabel: "Nesta página", navigationToggle: "Navegação", sidebarLabel: "Lateral", sidebarCollapse: "Recolher o menu", sidebarExpand: "Abrir o menu", bottomNavLabel: "Principal", themeToDark: "Mudar para o tema escuro", themeToLight: "Mudar para o tema claro", universalState: { waiting_user: "Esperando alguém agir.", waiting_approval: "Esperando aprovação.", waiting_dependency: "Esperando outra coisa terminar.", offline: "Sem conexão. Isto foi carregado antes.", stale: "Isto pode estar desatualizado.", partial: "Parte disto não pôde ser carregada.", degraded: "Funcionando com capacidade reduzida." } };
|
|
59
|
+
export const ptBR = { passwordShow: "Mostrar senha", passwordHide: "Ocultar senha", skipToContent: "Pular para o conteúdo", close: "Fechar", confirmCancel: "Cancelar", dataError: "Não foi possível carregar.", dataEmpty: "Ainda não há nada aqui", confirmProceed: "Continuar", paginationLabel: "Paginação", previous: "Anterior", next: "Próxima", breadcrumbLabel: "Breadcrumb", tabsLabel: "Abas", optionsLabel: "Opções", tableLabel: "Tabela", commandLabel: "Paleta de comandos", commandPlaceholder: "Digite um comando…", dismissNotification: "Dispensar notificação", toolbarLabel: "Barra de ferramentas", loading: "Carregando", otpDigit: "Dígito", stepperLabel: "Etapas", increment: "Aumentar", decrement: "Diminuir", comboboxEmpty: "Nenhum resultado", comboboxClear: "Limpar seleção", comboboxOpen: "Abrir lista", comboboxRemove: "Remover", comboboxLoading: "Carregando…", fileDropPrompt: "Arraste arquivos aqui ou clique para selecionar", fileAdded: "Arquivo adicionado", fileRemoved: "Arquivo removido", fileRemove: "Remover", fileTooLarge: "Arquivo maior que o limite", fileWrongType: "Tipo de arquivo não aceito", treeLabel: "Árvore", notificationsLabel: "Notificações", notificationMarkAll: "Marcar todas como lidas", notificationEmpty: "Nenhuma notificação", notificationUnread: "Não lida", notificationNew: "novas notificações", dataGridFilter: "Filtrar", dataGridEmpty: "Nenhum resultado", dataGridSelectAll: "Selecionar todas as linhas", dataGridSelectRow: "Selecionar linha", dataGridSelected: "selecionadas", dataGridClearSelection: "Limpar seleção", dataGridBulkLabel: "Ações em lote", dataGridColumns: "Colunas", dataGridResize: "Redimensionar coluna", dataGridStale: "Mostrando dados que podem estar desatualizados.", dataGridPartial: "Algumas linhas não puderam ser carregadas. O que aparece está incompleto.", dataGridError: "Não foi possível carregar as linhas.", dataGridDetails: "Detalhes", dataGridDetailPanel: "Detalhe da linha", dataGridExport: "Exportar", dataGridExportRows: "linhas", dataGridExportFiltered: "linhas filtradas", dataGridExportSelected: "linhas selecionadas", mediaPlayer: "Reprodutor de mídia", mediaPlay: "Reproduzir", mediaPause: "Pausar", mediaMute: "Silenciar", mediaUnmute: "Ativar som", mediaSeek: "Posição da reprodução", mediaVolume: "Volume", mediaCaptionsShow: "Ativar legendas", mediaCaptionsHide: "Desativar legendas", mediaSkipBack: "Voltar 10 segundos", mediaSkipForward: "Avançar 10 segundos", mediaFullscreenEnter: "Tela cheia", mediaFullscreenExit: "Sair da tela cheia", mediaEmbedOpen: "Abrir o vídeo no site de origem", carouselLabel: "Carrossel", carouselSlide: "Slide", positionOf: "de", carouselPrev: "Slide anterior", carouselNext: "Próximo slide", galleryLabel: "Galeria", galleryVideo: "Vídeo", sortableLabel: "Lista ordenável", sortableHandle: "Reordenar", sortableHelp: "Aperte Espaço para pegar, e as setas para cima e para baixo para mover. Espaço solta, Esc devolve ao lugar.", sortableGrabbed: "Pego", sortableDropped: "Solto", sortableMoved: "Movido", sortableCanceled: "Devolvido", blockEditorLabel: "Blocos de conteúdo", blockLabel: "Bloco", blockRemove: "Remover bloco", uploadSending: "Enviando", uploadCancel: "Cancelar envio", uploadRetry: "Tentar envio novamente", uploadError: "Falha no envio", uploadCanceled: "Envio cancelado", uploadComplete: "Envio concluído", uploadPause: "Pausar envio", uploadResume: "Retomar envio", uploadPaused: "Pausado", uploadPending: "Aguardando", uploadChecksum: "Soma de verificação", uploadChecksumBad: "O servidor recebeu bytes diferentes dos enviados.", fileConflict: "Já existe um arquivo com este nome na fila", fileReplace: "Substituir", fileKeepBoth: "Manter os dois", fileSkip: "Pular", uploadReceipt: "Recibo", uploadReceiptCopy: "Copiar recibo", agentInspector: "Inspetor do agente", invocationLabel: "Invocação", invocationInput: "Entrada", invocationOutput: "Saída", taskQueueLabel: "Fila de tarefas", taskState: { queued: "Na fila", running: "Executando", completed: "Concluída", failed: "Falhou", blocked: "Bloqueada", paused: "Pausada" }, taskPriority: { low: "Baixa", medium: "Média", high: "Alta" }, approvalLabel: "Aprovação", approvalApprove: "Aprovar", approvalDeny: "Negar", approvalApproved: "Aprovado", approvalDenied: "Negado", approvalDeadline: "Decidir até", approvalRisk: { low: "Risco baixo", medium: "Risco médio", high: "Risco alto" }, permissionLabel: "Permissões de ferramenta", permissionAsk: "Perguntar", permissionAlways: "Sempre", permissionNever: "Nunca", eventStreamLabel: "Eventos", traceLabel: "Rastro", healthLabel: "Saúde", healthState: { operational: "Operando", degraded: "Degradado", down: "Fora do ar", maintenance: "Em manutenção", unknown: "Desconhecido" }, modelUsageLabel: "Consumo por modelo", usageMetric: { tokens: "Tokens", cost: "Custo", requests: "Chamadas" }, costMeterLabel: "Custo", costMeterSpent: "Gasto", costMeterRemaining: "restantes", costMeterExceeded: "Acima do limite", costMeterNear: "O gasto passou do limite brando.", costMeterOver: "O limite foi atingido. As próximas chamadas estão bloqueadas.", memoryLabel: "Livro-razão de memória", memoryProvenance: "De onde isto veio", memoryOperation: { added: "Guardada", updated: "Atualizada", recalled: "Lembrada", forgotten: "Esquecida" }, memoryScope: { episodic: "Episódica", semantic: "Semântica", procedural: "Procedural" }, agentMessageLabel: "Recados entre agentes", agentMessageTo: "para", agentMessageBroadcast: "todos os agentes", agentMessageReason: "Por que foi por aqui", agentMessageKind: { request: "Pedido", response: "Resposta", handoff: "Passagem", broadcast: "Difusão", error: "Erro" }, automationWhen: "Quando", automationThen: "Então", automationEnabled: "Ativa", automationLastRun: "Última execução", automationResult: { success: "Concluiu", failure: "Falhou" }, graphLabel: "Grafo de dependências", graphZoomIn: "Aproximar", graphZoomOut: "Afastar", graphFit: "Caber na tela", graphLegend: "Legenda", graphOverview: "Visão geral", graphConnections: "conexões", graphShow: "Mostrar", graphJoin: "Juntar", agentState: { idle: "Ocioso", thinking: "Pensando", running: "Executando", paused: "Pausado", error: "Erro", completed: "Concluído" }, codeEditor: "Editor de código", chatLabel: "Conversa", chatMessage: "Mensagem", chatSend: "Enviar", chatAttach: "Anexar arquivo", chatAttachments: "Anexos", chatReplyTo: "Respondendo a", chatReplyCancel: "Cancelar resposta", chatEditing: "Editando mensagem", chatEditCancel: "Cancelar edição", chatSave: "Salvar", chatEdited: "editada", chatForwardedFrom: "Encaminhada de", qrCode: "Código QR", chartLabel: "Gráfico", copyCode: "Copiar código", copied: "Copiado", linkCopied: "Link copiado", linkCopyFailed: "Não deu para copiar. Copie o link abaixo.", tocLabel: "Nesta página", navigationToggle: "Navegação", sidebarLabel: "Lateral", sidebarCollapse: "Recolher o menu", sidebarExpand: "Abrir o menu", bottomNavLabel: "Principal", themeToDark: "Mudar para o tema escuro", themeToLight: "Mudar para o tema claro", universalState: { waiting_user: "Esperando alguém agir.", waiting_approval: "Esperando aprovação.", waiting_dependency: "Esperando outra coisa terminar.", offline: "Sem conexão. Isto foi carregado antes.", stale: "Isto pode estar desatualizado.", partial: "Parte disto não pôde ser carregada.", degraded: "Funcionando com capacidade reduzida." } };
|
|
60
60
|
// Sprite de ícones: CONFIGURAÇÃO DE APLICAÇÃO, então entra pelo provider, uma vez.
|
|
61
61
|
// Antes o default absoluto "/aurea-icons.svg" vivia no Icon e 15 componentes declaravam
|
|
62
62
|
// e repassavam `spriteUrl` à mão — 52 sítios de código. Consequência: aplicação servida
|
package/dist/system.d.ts
CHANGED
|
@@ -28,8 +28,17 @@ export declare function useAureaTheme(): {
|
|
|
28
28
|
export type { IconName, PhosphorIconName, AureaIconNames, IconWeight } from "./icon-names.js";
|
|
29
29
|
import { type IconName, type IconWeight } from "./icon-names.js";
|
|
30
30
|
export type IconSize = "sm" | "md" | "lg" | "xl";
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
/**
|
|
32
|
+
* O DESENHO do próprio app, como no nativo (`AureaIcon`, R-11) — MNT-02, ADR-0064, 10/10/2026. Um
|
|
33
|
+
* componente que devolve o `<svg>` dele; recebe a classe e o tamanho da Aurea, e herda a cor
|
|
34
|
+
* (`currentColor`). Serve ao glifo que o Phosphor não tem (o roteador de um mapa de rede) e ao
|
|
35
|
+
* logotipo do app, sem sprite próprio nem `declare module`. Não tem forma cheia.
|
|
36
|
+
*/
|
|
37
|
+
export type AureaIconComponent = (props: React.SVGAttributes<SVGSVGElement>) => React.ReactElement;
|
|
38
|
+
/** Um ícone: o NOME de um glifo do sprite, ou o DESENHO do app. */
|
|
39
|
+
export type AureaIcon = IconName | AureaIconComponent;
|
|
40
|
+
export interface IconProps extends Omit<React.SVGAttributes<SVGSVGElement>, "name"> {
|
|
41
|
+
name: AureaIcon;
|
|
33
42
|
spriteUrl?: string;
|
|
34
43
|
size?: Responsive<IconSize>;
|
|
35
44
|
/** `fill` desenha a forma cheia — a do item escolhido (ADR-0053). Nome próprio do app fica no `regular`. */
|
package/dist/system.js
CHANGED
|
@@ -196,4 +196,11 @@ import { NOMES_PHOSPHOR } from "./icon-names.js";
|
|
|
196
196
|
// A forma cheia é o símbolo `i-<nome>-fill` do sprite. Só os nomes do Phosphor a têm: um nome
|
|
197
197
|
// próprio do app pedido em `fill` desenha o regular, em vez de um `<use>` vazio.
|
|
198
198
|
const idDoIcone = (name, weight = "regular") => weight === "fill" && NOMES_PHOSPHOR.has(name) ? `i-${name}-fill` : `i-${name}`;
|
|
199
|
-
export function Icon({ name, spriteUrl, size, weight, className, ...props }) {
|
|
199
|
+
export function Icon({ name, spriteUrl, size, weight, className, ...props }) {
|
|
200
|
+
const base = useSpriteUrl();
|
|
201
|
+
if (typeof name === "function") {
|
|
202
|
+
const Desenho = name;
|
|
203
|
+
return _jsx(Desenho, { "aria-hidden": "true", className: cx("icon", peleDoEixo("icon", size), className), ...props });
|
|
204
|
+
}
|
|
205
|
+
return _jsx("svg", { "aria-hidden": "true", className: cx("icon", peleDoEixo("icon", size), className), ...props, children: _jsx("use", { href: `${spriteUrl ?? base}#${idDoIcone(name, weight)}` }) });
|
|
206
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurea-uds/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Os componentes React da Aurea UDS. Importe @aurea-uds/fonts/css e @aurea-uds/core/css junto.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aurea",
|
|
@@ -127,6 +127,8 @@
|
|
|
127
127
|
"@tanstack/react-table": "^8.21.3",
|
|
128
128
|
"@xyflow/react": "^12.11.2",
|
|
129
129
|
"codemirror": "^6.0.2",
|
|
130
|
+
"elkjs": "^0.12.0",
|
|
131
|
+
"modern-screenshot": "^4.7.0",
|
|
130
132
|
"qr": "^0.6.0",
|
|
131
133
|
"react": ">=19",
|
|
132
134
|
"react-day-picker": "^10.0.1",
|
|
@@ -163,6 +165,12 @@
|
|
|
163
165
|
},
|
|
164
166
|
"@xyflow/react": {
|
|
165
167
|
"optional": true
|
|
168
|
+
},
|
|
169
|
+
"elkjs": {
|
|
170
|
+
"optional": true
|
|
171
|
+
},
|
|
172
|
+
"modern-screenshot": {
|
|
173
|
+
"optional": true
|
|
166
174
|
}
|
|
167
175
|
},
|
|
168
176
|
"devDependencies": {
|