@aurea-uds/react 0.33.0 → 0.35.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.
@@ -37,18 +37,18 @@ import { cx, useAureaStrings } from "./internal.js";
37
37
  // (auditoria 18/07/2026, ALTO 5). Com a anotação + sideEffects:false, o tema e o
38
38
  // CodeEditor (e o CodeMirror) somem quando não usados. Split por subpath = decisão do Victor.
39
39
  export const aureaEditorTheme = /*@__PURE__*/ EditorView.theme({
40
- "&": { backgroundColor: "var(--surface-inset)", color: "var(--foreground)", border: "var(--border-width) solid var(--border)", borderRadius: "var(--radius-lg)", fontSize: "var(--text-xs)" },
40
+ "&": { backgroundColor: "var(--surface-inset)", color: "var(--foreground)", border: "var(--border-width) solid transparent", borderRadius: "var(--radius-lg)", fontSize: "var(--text-xs)" },
41
41
  "&.cm-focused": { outline: "2px solid var(--focus-strong)", outlineOffset: "-2px" },
42
42
  ".cm-scroller": { fontFamily: "var(--font-code)", lineHeight: "var(--leading-relaxed)", borderRadius: "inherit" },
43
43
  ".cm-content": { caretColor: "var(--foreground)", padding: "var(--space-2) 0" },
44
44
  ".cm-line": { padding: "0 var(--space-3)" },
45
- ".cm-cursor, .cm-dropCursor": { borderLeftColor: "var(--foreground)" },
46
- "&.cm-focused .cm-cursor": { borderLeftColor: "var(--foreground)" },
45
+ ".cm-cursor, .cm-dropCursor": { borderLeftColor: "transparent", width: "calc(2 * var(--border-width))", marginLeft: "calc(-1 * var(--border-width))", backgroundColor: "var(--foreground)" },
46
+ "&.cm-focused .cm-cursor": { borderLeftColor: "transparent", backgroundColor: "var(--foreground)" },
47
47
  ".cm-selectionBackground, ::selection": { backgroundColor: "var(--selection)" },
48
48
  "&.cm-focused .cm-selectionBackground": { backgroundColor: "var(--selection)" },
49
49
  ".cm-selectionMatch": { backgroundColor: "var(--selection)" },
50
50
  ".cm-activeLine": { backgroundColor: "color-mix(in srgb,var(--foreground) 5%,transparent)" },
51
- ".cm-gutters": { backgroundColor: "var(--surface-inset)", color: "var(--muted-foreground)", border: "none", borderRight: "var(--border-width) solid var(--border)" },
51
+ ".cm-gutters": { backgroundColor: "var(--surface-inset)", color: "var(--muted-foreground)", border: "none", borderRight: "var(--border-width) solid transparent" },
52
52
  ".cm-gutterElement": { padding: "0 var(--space-2)" },
53
53
  ".cm-activeLineGutter": { backgroundColor: "color-mix(in srgb,var(--foreground) 5%,transparent)", color: "var(--foreground)" },
54
54
  ".cm-matchingBracket, &.cm-focused .cm-matchingBracket": { backgroundColor: "color-mix(in srgb,var(--foreground) 16%,transparent)", color: "var(--foreground)", outline: "none" },
@@ -56,9 +56,9 @@ export const aureaEditorTheme = /*@__PURE__*/ EditorView.theme({
56
56
  ".cm-searchMatch": { backgroundColor: "color-mix(in srgb,var(--warning-400) 30%,transparent)" },
57
57
  ".cm-searchMatch.cm-searchMatch-selected": { backgroundColor: "var(--selection)" },
58
58
  ".cm-panels": { backgroundColor: "var(--surface-2)", color: "var(--foreground)" },
59
- ".cm-panels.cm-panels-top": { borderBottom: "var(--border-width) solid var(--border)" },
60
- ".cm-panels.cm-panels-bottom": { borderTop: "var(--border-width) solid var(--border)" },
61
- ".cm-tooltip": { backgroundColor: "var(--popover)", color: "var(--popover-foreground)", border: "var(--border-width) solid var(--border)", borderRadius: "var(--radius-md)" },
59
+ ".cm-panels.cm-panels-top": { borderBottom: "var(--border-width) solid transparent" },
60
+ ".cm-panels.cm-panels-bottom": { borderTop: "var(--border-width) solid transparent" },
61
+ ".cm-tooltip": { backgroundColor: "var(--popover)", color: "var(--popover-foreground)", border: "var(--border-width) solid transparent", borderRadius: "var(--radius-md)" },
62
62
  ".cm-tooltip-autocomplete ul li[aria-selected]": { backgroundColor: "color-mix(in srgb,var(--primary) 14%,transparent)", color: "var(--foreground)" },
63
63
  ".cm-completionLabel": { color: "var(--foreground)" },
64
64
  });
@@ -39,6 +39,30 @@ export declare function remapear<T extends {
39
39
  para: string;
40
40
  n: number;
41
41
  }[];
42
+ export declare const naGrade: (v: number, passo: number) => number;
43
+ export declare function rotaNaGrade(rota: readonly Ponto[], dDe: Ponto, dPara: Ponto): Ponto[];
44
+ export type Guia = {
45
+ eixo: "x" | "y";
46
+ em: number;
47
+ de: number;
48
+ ate: number;
49
+ };
50
+ /**
51
+ * A ponta de uma linha ligada ao nó arrastado (o Victor, 17/10/2026: "todos consigo alinhar, menos esse — nunca fica
52
+ * reto"): a porta 1 fica a um terço do lado do Núcleo, e nem a borda nem o centro do outro nó caem embaixo dela. `meu` é
53
+ * onde a linha sai do nó arrastado, medido do canto dele; `outro`, onde ela chega no outro nó, no mapa. `eixo` é o que
54
+ * acerta para a linha ficar reta: `x` quando as duas pontas são de cima e de baixo; `y`, quando são dos lados.
55
+ */
56
+ export type PontaDoArraste = {
57
+ meu: Ponto;
58
+ outro: Ponto;
59
+ eixo: "x" | "y";
60
+ };
61
+ export declare function guiasDoArraste(eu: Caixa, outros: readonly Caixa[], tolerancia: number, pontas?: readonly PontaDoArraste[]): {
62
+ x: number;
63
+ y: number;
64
+ guias: Guia[];
65
+ };
42
66
  /** Tira os pontos repetidos e os do meio de um trecho reto: a ponte e o canto contam os trechos. */
43
67
  export declare function simplificar(pontos: readonly Ponto[]): Ponto[];
44
68
  /** O ponto no MEIO do comprimento — onde fica o rótulo e a pastilha da linha. */
@@ -204,6 +204,115 @@ export function remapear(arestas, rep) {
204
204
  }
205
205
  return saida;
206
206
  }
207
+ // ── NA GRADE (MNT-28, 17/10/2026) ─────────────────────────────────────────────────────────────
208
+ // O arranjo automático põe os nós onde o motor quer (0, 270, 540…), fora da grade de 16 do modo de
209
+ // arrumar: o nó arrastado só voltava a 528 ou 544, e a linha ganhava um degrau (o Victor, comparando com
210
+ // o draw.io). Com a grade ligada, cada nó vai para o ponto da grade mais perto, e a rota em ângulo reto
211
+ // acompanha: a ponta anda com o nó, e a primeira e a última dobra corrigem só o eixo do trecho delas,
212
+ // para o trecho continuar reto. A reta que sai do eixo vira um degrau no meio.
213
+ export const naGrade = (v, passo) => Math.round(v / passo) * passo;
214
+ export function rotaNaGrade(rota, dDe, dPara) {
215
+ if (rota.length < 2)
216
+ return rota.map(p => ({ x: p.x, y: p.y }));
217
+ const p = rota.map(q => ({ x: q.x, y: q.y })), n = p.length - 1;
218
+ const deitado = (a, b) => Math.abs(a.y - b.y) < 0.5;
219
+ const h0 = deitado(rota[0], rota[1]), hN = deitado(rota[n - 1], rota[n]);
220
+ p[0] = { x: p[0].x + dDe.x, y: p[0].y + dDe.y };
221
+ p[n] = { x: p[n].x + dPara.x, y: p[n].y + dPara.y };
222
+ if (n === 1) {
223
+ if (h0 && Math.abs(p[0].y - p[1].y) >= 0.5) {
224
+ const mx = (p[0].x + p[1].x) / 2;
225
+ return simplificar([p[0], { x: mx, y: p[0].y }, { x: mx, y: p[1].y }, p[1]]);
226
+ }
227
+ if (!h0 && Math.abs(p[0].x - p[1].x) >= 0.5) {
228
+ const my = (p[0].y + p[1].y) / 2;
229
+ return simplificar([p[0], { x: p[0].x, y: my }, { x: p[1].x, y: my }, p[1]]);
230
+ }
231
+ return p;
232
+ }
233
+ p[1] = h0 ? { x: p[1].x, y: p[0].y } : { x: p[0].x, y: p[1].y };
234
+ p[n - 1] = hN ? { x: p[n - 1].x, y: p[n].y } : { x: p[n].x, y: p[n - 1].y };
235
+ return simplificar(p);
236
+ }
237
+ export function guiasDoArraste(eu, outros, tolerancia, pontas = []) {
238
+ const marcasX = (c) => [c.x, c.x + c.larg / 2, c.x + c.larg], marcasY = (c) => [c.y, c.y + c.alt / 2, c.y + c.alt];
239
+ let dx = 0, dy = 0, melhorX = Infinity, melhorY = Infinity;
240
+ let retaX, retaY;
241
+ const mx = marcasX(eu), my = marcasY(eu);
242
+ for (const o of outros) {
243
+ const ox = marcasX(o), oy = marcasY(o);
244
+ for (let i = 0; i < 3; i++) {
245
+ const ex = ox[i] - mx[i], ey = oy[i] - my[i];
246
+ if (Math.abs(ex) <= tolerancia && Math.abs(ex) < melhorX) {
247
+ melhorX = Math.abs(ex);
248
+ dx = ex;
249
+ }
250
+ if (Math.abs(ey) <= tolerancia && Math.abs(ey) < melhorY) {
251
+ melhorY = Math.abs(ey);
252
+ dy = ey;
253
+ }
254
+ }
255
+ }
256
+ // A linha reta vem DEPOIS e ganha no empate: com a mesma distância, endireitar a linha vale mais que alinhar a caixa.
257
+ for (const p of pontas) {
258
+ if (p.eixo === "x") {
259
+ const e = p.outro.x - (eu.x + p.meu.x);
260
+ if (Math.abs(e) <= tolerancia && Math.abs(e) <= melhorX) {
261
+ melhorX = Math.abs(e);
262
+ dx = e;
263
+ retaX = p;
264
+ }
265
+ }
266
+ else {
267
+ const e = p.outro.y - (eu.y + p.meu.y);
268
+ if (Math.abs(e) <= tolerancia && Math.abs(e) <= melhorY) {
269
+ melhorY = Math.abs(e);
270
+ dy = e;
271
+ retaY = p;
272
+ }
273
+ }
274
+ }
275
+ const novo = { ...eu, x: eu.x + dx, y: eu.y + dy };
276
+ if (retaX || retaY) {
277
+ // A guia da linha reta corre por cima da própria linha, de uma ponta à outra.
278
+ const guias = [];
279
+ if (retaX) {
280
+ const y0 = novo.y + retaX.meu.y;
281
+ guias.push({ eixo: "x", em: retaX.outro.x, de: Math.min(y0, retaX.outro.y), ate: Math.max(y0, retaX.outro.y) });
282
+ }
283
+ if (retaY) {
284
+ const x0 = novo.x + retaY.meu.x;
285
+ guias.push({ eixo: "y", em: retaY.outro.y, de: Math.min(x0, retaY.outro.x), ate: Math.max(x0, retaY.outro.x) });
286
+ }
287
+ const resto = guiasDeAlinhamento(novo, outros, retaX ? Infinity : melhorX, retaY ? Infinity : melhorY);
288
+ return { x: novo.x, y: novo.y, guias: [...guias, ...resto] };
289
+ }
290
+ return { x: novo.x, y: novo.y, guias: guiasDeAlinhamento(novo, outros, melhorX, melhorY) };
291
+ }
292
+ function guiasDeAlinhamento(novo, outros, melhorX, melhorY) {
293
+ const marcasX = (c) => [c.x, c.x + c.larg / 2, c.x + c.larg], marcasY = (c) => [c.y, c.y + c.alt / 2, c.y + c.alt];
294
+ // UMA guia por eixo: com o mesmo tamanho, alinhar o topo alinha também o meio e a base, e três linhas juntas
295
+ // não dizem nada. Vale a do centro, se ele alinhou; senão, a da borda. Ela vai de ponta a ponta de quem alinhou.
296
+ const guias = [];
297
+ const umaGuia = (eixo, marcas) => {
298
+ const minhas = marcas(novo);
299
+ for (const i of [1, 0, 2]) {
300
+ const juntos = outros.filter(o => Math.abs(marcas(o)[i] - minhas[i]) < 0.5);
301
+ if (!juntos.length)
302
+ continue;
303
+ const todos = [novo, ...juntos];
304
+ guias.push(eixo === "x"
305
+ ? { eixo, em: minhas[i], de: Math.min(...todos.map(c => c.y)), ate: Math.max(...todos.map(c => c.y + c.alt)) }
306
+ : { eixo, em: minhas[i], de: Math.min(...todos.map(c => c.x)), ate: Math.max(...todos.map(c => c.x + c.larg)) });
307
+ return;
308
+ }
309
+ };
310
+ if (melhorX < Infinity)
311
+ umaGuia("x", marcasX);
312
+ if (melhorY < Infinity)
313
+ umaGuia("y", marcasY);
314
+ return guias;
315
+ }
207
316
  // ── O TRAÇADO (MNT-12.1 e 12.3) ───────────────────────────────────────────────────────────────
208
317
  /** Tira os pontos repetidos e os do meio de um trecho reto: a ponte e o canto contam os trechos. */
209
318
  export function simplificar(pontos) {
package/dist/graph.js CHANGED
@@ -39,12 +39,12 @@ 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, EdgeLabelRenderer, EdgeText, Handle, MiniMap, Panel, Position, SelectionMode, applyNodeChanges, getBezierPath, getSmoothStepPath, useNodesInitialized, useOnViewportChange, useReactFlow, useStore } from "@xyflow/react";
42
+ import { ReactFlow, ReactFlowProvider, Background, BaseEdge, EdgeLabelRenderer, EdgeText, Handle, MiniMap, Panel, Position, SelectionMode, ViewportPortal, applyNodeChanges, getBezierPath, getSmoothStepPath, useNodesInitialized, useOnViewportChange, useReactFlow, useStore } from "@xyflow/react";
43
43
  import { cx, useAureaStrings } from "./internal.js";
44
44
  import { Icon } from "./system.js";
45
45
  import { Badge } from "./markup.js";
46
46
  import { IconButton, Toolbar, ToolbarButton, ToolbarSeparator } from "./actions.js";
47
- import { alinhar, distribuir, subarvores, recolher, remapear, simplificar, pontes, tracado, meioDoTracado, ladoDoTrecho, rotaCruza, bordaNaDirecao, rotulosQueSomem, contornoDeNuvem } from "./graph-geometria.js";
47
+ import { alinhar, distribuir, subarvores, recolher, remapear, simplificar, pontes, tracado, meioDoTracado, ladoDoTrecho, rotaCruza, bordaNaDirecao, rotulosQueSomem, contornoDeNuvem, naGrade, rotaNaGrade, guiasDoArraste } from "./graph-geometria.js";
48
48
  // ── O layout ─────────────────────────────────────────────────────────────────────────────
49
49
  // O motor recebe `x`/`y` PRONTOS: ele não posiciona nada. Sem isto, um grafo sem coordenadas
50
50
  // empilha tudo em (0,0) — que foi a medição que quase derrubou a escolha do motor.
@@ -63,6 +63,8 @@ const LARGURA = 180, ALTURA = 52, VAO_X = 90, VAO_Y = 24;
63
63
  const CABECA_DO_GRUPO = 40, RECHEIO_DO_GRUPO = 16;
64
64
  // A grade do modo de arrumar: `--space-4` (16). As setas andam um passo dela; com Shift, quatro.
65
65
  const GRADE = 16;
66
+ // As guias do arraste (MNT-28): o nó encaixa no vizinho a até `--space-2` (8) dele, em unidade do mapa.
67
+ const TOLERANCIA_DA_GUIA = 8;
66
68
  // A ponte no cruzamento: meia elipse de raio `--radius-xs` (6) — o tamanho do salto do draw.io.
67
69
  const RAIO_DA_PONTE = 6;
68
70
  // O desvio de linhas (`@tisoap/react-flow-smart-edge`): folga em volta do nó e malha da busca, as duas
@@ -694,7 +696,33 @@ export function DependencyGraph({ nodes, edges, label, selectedId, onSelect, con
694
696
  // `entradaElk` muda junto com a chave; a chave é o que diz se mudou DE VERDADE.
695
697
  // eslint-disable-next-line react-hooks/exhaustive-deps
696
698
  }, [chaveElk]);
697
- const autoDe = React.useCallback((id) => elk?.posicoes.get(id) ?? automatico.posicoes.get(id) ?? { x: 0, y: 0 }, [elk, automatico]);
699
+ // MNT-28: com a grade ligada, o arranjo automático JÁ na grade — o motor e a arrumação simples —, e a rota do
700
+ // motor acompanha o nó (`rotaNaGrade`). Assim o nó arrastado volta exatamente à linha dos outros.
701
+ const naGradeLigada = editavel && grid;
702
+ const elkVisto = React.useMemo(() => {
703
+ if (!elk || !naGradeLigada)
704
+ return elk;
705
+ const posicoes = new Map(), delta = new Map();
706
+ for (const [id, p] of elk.posicoes) {
707
+ const q = { x: naGrade(p.x, GRADE), y: naGrade(p.y, GRADE) };
708
+ posicoes.set(id, q);
709
+ delta.set(id, { x: q.x - p.x, y: q.y - p.y });
710
+ }
711
+ const pontas = new Map((entradaElk?.arestas ?? []).map(a => [a.id, a])), zero = { x: 0, y: 0 };
712
+ const rotas = new Map();
713
+ for (const [id, r] of elk.rotas) {
714
+ const a = pontas.get(id);
715
+ rotas.set(id, a ? rotaNaGrade(r, delta.get(a.de) ?? zero, delta.get(a.para) ?? zero) : r);
716
+ }
717
+ return { posicoes, rotas };
718
+ }, [elk, naGradeLigada, entradaElk]);
719
+ const autoDe = React.useCallback((id) => {
720
+ const doMotor = elkVisto?.posicoes.get(id);
721
+ if (doMotor)
722
+ return doMotor;
723
+ const a = automatico.posicoes.get(id) ?? { x: 0, y: 0 };
724
+ return naGradeLigada ? { x: naGrade(a.x, GRADE), y: naGrade(a.y, GRADE) } : a;
725
+ }, [elkVisto, automatico, naGradeLigada]);
698
726
  // ── ONDE CADA UM ESTÁ (coordenada ABSOLUTA) ──────────────────────────────────────────────────
699
727
  const posFinal = React.useMemo(() => {
700
728
  const p = new Map();
@@ -821,7 +849,72 @@ export function DependencyGraph({ nodes, edges, label, selectedId, onSelect, con
821
849
  const confirmar = React.useRef(() => { });
822
850
  const abertosAgora = React.useRef(abertos);
823
851
  abertosAgora.current = abertos;
824
- const aoMudarNos = React.useCallback((mudancas) => {
852
+ // ── AS GUIAS DO ARRASTE (MNT-28) ───────────────────────────────────────────────────────────────
853
+ // Um nó só, arrastado no modo de arrumar: ele encaixa na borda ou no centro do vizinho mais perto
854
+ // (`guiasDoArraste`), e a guia aparece. A posição encaixada vale também ao soltar — no fim do arraste o
855
+ // motor manda a posição DELE, e a que fica é a nossa.
856
+ const [guias, setGuias] = React.useState([]);
857
+ const agora = React.useRef({ caixas, paiAberto, ocultos, editavel });
858
+ agora.current = { caixas, paiAberto, ocultos, editavel };
859
+ const encaixado = React.useRef(new Map());
860
+ // As pontas das linhas do nó arrastado, para ele encaixar onde a linha fica RETA (o Victor, 17/10/2026: a linha da
861
+ // porta 1 do Núcleo "nunca fica reta"). A mesma conta do traçado: a alça, a porta e o afastamento das paralelas.
862
+ const pontasDe = React.useRef(() => []);
863
+ pontasDe.current = id => {
864
+ const alca = (no, porta, tipo) => {
865
+ const n = porId.get(no), c = caixas.get(no);
866
+ if (!n || !c)
867
+ return undefined;
868
+ const lista = alcasDoNo(abertos.has(no) || contemFilhos.has(no) ? { ...n, ports: undefined } : n, orientation, { larg: c.larg, alt: c.alt });
869
+ return (porta && lista.find(a => a.id === porta && a.type === tipo)) || lista.find(a => !a.id && a.type === tipo);
870
+ };
871
+ const deitada = (a) => a.position === Position.Left || a.position === Position.Right;
872
+ const saida = [];
873
+ desenhadas.forEach(({ item: e }, i) => {
874
+ if ((e.from !== id && e.to !== id) || e.from === e.to || ocultos.has(e.from) || ocultos.has(e.to))
875
+ return;
876
+ const sou = e.from === id, outro = sou ? e.to : e.from, cm = caixas.get(id), co = caixas.get(outro);
877
+ const am = alca(id, sou ? e.fromPort : e.toPort, sou ? "source" : "target"), ao = alca(outro, sou ? e.toPort : e.fromPort, sou ? "target" : "source");
878
+ if (!cm || !co || !am || !ao || deitada(am) !== deitada(ao))
879
+ return;
880
+ const desl = ident[i].deslocamento, ddx = vertical ? desl : 0, ddy = vertical ? 0 : desl;
881
+ const pm = pontoDaAlca(cm, am), po = pontoDaAlca(co, ao);
882
+ saida.push({ meu: { x: pm.x - cm.x + ddx, y: pm.y - cm.y + ddy }, outro: { x: po.x + ddx, y: po.y + ddy }, eixo: deitada(am) ? "y" : "x" });
883
+ });
884
+ return saida;
885
+ };
886
+ const comGuias = React.useCallback((mudancas) => {
887
+ const { caixas: cx, paiAberto: paiDe, ocultos: oc, editavel: ed } = agora.current, grupos = abertosAgora.current;
888
+ const arrastes = mudancas.filter((c) => c.type === "position" && !!c.dragging && !!c.position);
889
+ let saida = mudancas;
890
+ const c = arrastes.length === 1 ? arrastes[0] : undefined, eu = c ? cx.get(c.id) : undefined;
891
+ if (ed && c && eu && !grupos.has(c.id)) {
892
+ const pai = paiDe(c.id), cp = pai ? cx.get(pai) : undefined, ox = cp?.x ?? 0, oy = cp?.y ?? 0;
893
+ const outros = [...cx].filter(([id]) => id !== c.id && !oc.has(id) && !grupos.has(id)).map(([, x]) => x);
894
+ const r = guiasDoArraste({ ...eu, x: c.position.x + ox, y: c.position.y + oy }, outros, TOLERANCIA_DA_GUIA, pontasDe.current(c.id));
895
+ const rel = { x: r.x - ox, y: r.y - oy };
896
+ encaixado.current.set(c.id, rel);
897
+ saida = saida.map(m => m === c ? { ...c, position: rel } : m);
898
+ setGuias(g => g.length === r.guias.length && g.every((x, i) => x.eixo === r.guias[i].eixo && x.em === r.guias[i].em && x.de === r.guias[i].de && x.ate === r.guias[i].ate) ? g : r.guias);
899
+ }
900
+ else if (arrastes.length)
901
+ setGuias(g => g.length ? [] : g);
902
+ if (mudancas.some(m => m.type === "position" && !m.dragging)) {
903
+ saida = saida.map(m => {
904
+ if (m.type !== "position" || m.dragging)
905
+ return m;
906
+ const e = encaixado.current.get(m.id);
907
+ if (!e)
908
+ return m;
909
+ encaixado.current.delete(m.id);
910
+ return { ...m, position: e };
911
+ });
912
+ setGuias(g => g.length ? [] : g);
913
+ }
914
+ return saida;
915
+ }, []);
916
+ const aoMudarNos = React.useCallback((mudancasDoMotor) => {
917
+ const mudancas = comGuias(mudancasDoMotor);
825
918
  setNosVivos(anteriores => applyNodeChanges(mudancas, anteriores).map(semAlcasDeclaradas));
826
919
  const dims = mudancas.filter(c => c.type === "dimensions" && !!c.dimensions && !abertosAgora.current.has(c.id));
827
920
  if (dims.length)
@@ -841,7 +934,7 @@ export function DependencyGraph({ nodes, edges, label, selectedId, onSelect, con
841
934
  const soltos = mudancas.filter((c) => c.type === "position" && !c.dragging && !!c.position);
842
935
  if (soltos.length)
843
936
  confirmar.current(soltos);
844
- }, []);
937
+ }, [comGuias]);
845
938
  // ── O DESFAZER (MNT-13.5) E O AVISO PARA O APP GUARDAR (MNT-13.6) ─────────────────────────────
846
939
  const pilha = React.useRef({ lista: [], ponteiro: 0 });
847
940
  const [, setVersaoDaPilha] = React.useState(0);
@@ -1008,8 +1101,8 @@ export function DependencyGraph({ nodes, edges, label, selectedId, onSelect, con
1008
1101
  }
1009
1102
  if (edgeShape !== "step")
1010
1103
  return;
1011
- const doElk = elk?.rotas.get(id);
1012
- const noLugar = (no) => { const a = elk?.posicoes.get(no), c = caixas.get(no); return !!a && !!c && Math.abs(a.x - c.x) < 0.5 && Math.abs(a.y - c.y) < 0.5; };
1104
+ const doElk = elkVisto?.rotas.get(id);
1105
+ const noLugar = (no) => { const a = elkVisto?.posicoes.get(no), c = caixas.get(no); return !!a && !!c && Math.abs(a.x - c.x) < 0.5 && Math.abs(a.y - c.y) < 0.5; };
1013
1106
  if (doElk && noLugar(e.from) && noLugar(e.to) && naBorda(doElk[0], cs) && naBorda(doElk[doElk.length - 1], ct) && !rotaCruza(doElk, menos(e.from, e.to))) {
1014
1107
  mapa.set(id, doElk);
1015
1108
  return;
@@ -1035,7 +1128,7 @@ export function DependencyGraph({ nodes, edges, label, selectedId, onSelect, con
1035
1128
  pendentes.push({ id, de: e.from, para: e.to, s: sp, t: tp, ls: as.position, lt: at.position, chave });
1036
1129
  });
1037
1130
  return { mapa, pendentes, obstaculos };
1038
- }, [desenhadas, ident, caixas, abertos, ocultos, movendo, porId, contemFilhos, orientation, layout, edgeShape, elk, desvios, vertical]);
1131
+ }, [desenhadas, ident, caixas, abertos, ocultos, movendo, porId, contemFilhos, orientation, layout, edgeShape, elkVisto, desvios, vertical]);
1039
1132
  const chavePendentes = tracados.pendentes.map(p => `${p.id}@${p.chave}`).join("|");
1040
1133
  React.useEffect(() => {
1041
1134
  if (!tracados.pendentes.length || movendo.size)
@@ -1257,7 +1350,7 @@ export function DependencyGraph({ nodes, edges, label, selectedId, onSelect, con
1257
1350
  // a fluidez. O valor é a extensão do próprio layout, então no servidor o grafo cabe exato.
1258
1351
  const estilo = { ...style, ...(height ? { blockSize: height } : null), "--graph-node-w": `${m.larg}px` };
1259
1352
  return _jsx("div", { ref: raiz, className: cx("dependency-graph", className), style: estilo, "data-text": textSize === "md" ? undefined : textSize, "data-contrast": highContrast ? "high" : undefined, "data-editable": editavel || 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 }, minZoom: ESCALA_MINIMA, nodesDraggable: editavel || !!onNodeMove, nodesConnectable: !!connectable, elementsSelectable: editavel, nodesFocusable: editavel, edgesFocusable: !!onEdgeSelect, disableKeyboardA11y: !editavel, selectionOnDrag: editavel, panOnDrag: editavel ? BOTOES_DE_ARRASTAR : true, selectionMode: SelectionMode.Partial, multiSelectionKeyCode: TECLAS_DE_SOMAR, deleteKeyCode: null, snapToGrid: editavel && grid, snapGrid: [GRADE, GRADE], onlyRenderVisibleElements: !!visibleOnly, ariaLabelConfig: rotulosDoMotor, onSelectionChange: editavel ? aoMudarEscolha : 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)
1260
- onConnect({ from: c.source, to: c.target, fromPort: c.sourceHandle ?? undefined, toPort: c.targetHandle ?? undefined }); } : undefined, children: [_jsx(Background, { gap: editavel && grid ? GRADE : undefined }), _jsx(Comandos, { raiz: raiz, teclado: teclado, focusId: focusId, pedidoDeCaber: pedidoDeCaber, controls: !!controls, apiRef: apiRef, linhas: linhas, forma: edgeShape, titulo: label ?? s.graphLabel, desenho: tracados, editavel: editavel, barra: barra, acoes: acoes }), minimap && _jsx(MiniMap, { pannable: true, zoomable: true, ariaLabel: s.graphOverview, className: "graph-minimap" }), legend && (legenda.tipos.length > 0 || legenda.estados.length > 0) && _jsx(Panel, { position: "bottom-left", children: _jsxs("div", { className: "graph-legend", role: "group", "aria-label": s.graphLegend, children: [legenda.tipos.length > 0 && _jsxs(_Fragment, { children: [legenda.estados.length > 0 && _jsx("p", { className: "graph-legend-title", id: `${idDaLegenda}-tipo`, children: s.graphLegendKind }), _jsx("ul", { "aria-labelledby": legenda.estados.length > 0 ? `${idDaLegenda}-tipo` : undefined, children: legenda.tipos.map(t => _jsxs("li", { children: [_jsx(AmostraDaLinha, { padrao: t.padrao, peso: t.peso, marca: t.marca }), _jsx("span", { children: t.nome })] }, t.nome)) })] }), legenda.estados.length > 0 && _jsxs(_Fragment, { children: [legenda.tipos.length > 0 && _jsx("p", { className: "graph-legend-title", id: `${idDaLegenda}-estado`, children: s.graphLegendStatus }), _jsx("ul", { "aria-labelledby": legenda.tipos.length > 0 ? `${idDaLegenda}-estado` : undefined, children: legenda.estados.map(e => _jsxs("li", { children: [_jsx(AmostraDaLinha, { tom: e.tom, marca: e.marca }), _jsx("span", { children: e.nome })] }, e.nome)) })] })] }) })] }) }) });
1353
+ onConnect({ from: c.source, to: c.target, fromPort: c.sourceHandle ?? undefined, toPort: c.targetHandle ?? undefined }); } : undefined, children: [_jsx(Background, { gap: editavel && grid ? GRADE : undefined }), guias.length > 0 && _jsx(ViewportPortal, { children: guias.map(g => _jsx("div", { className: g.eixo === "x" ? "graph-guide graph-guide-x" : "graph-guide graph-guide-y", "aria-hidden": "true", style: g.eixo === "x" ? { transform: `translate(${g.em}px, ${g.de}px)`, blockSize: g.ate - g.de } : { transform: `translate(${g.de}px, ${g.em}px)`, inlineSize: g.ate - g.de } }, `${g.eixo}${g.em}`)) }), _jsx(Comandos, { raiz: raiz, teclado: teclado, focusId: focusId, pedidoDeCaber: pedidoDeCaber, controls: !!controls, apiRef: apiRef, linhas: linhas, forma: edgeShape, titulo: label ?? s.graphLabel, desenho: tracados, editavel: editavel, barra: barra, acoes: acoes }), minimap && _jsx(MiniMap, { pannable: true, zoomable: true, ariaLabel: s.graphOverview, className: "graph-minimap" }), legend && (legenda.tipos.length > 0 || legenda.estados.length > 0) && _jsx(Panel, { position: "bottom-left", children: _jsxs("div", { className: "graph-legend", role: "group", "aria-label": s.graphLegend, children: [legenda.tipos.length > 0 && _jsxs(_Fragment, { children: [legenda.estados.length > 0 && _jsx("p", { className: "graph-legend-title", id: `${idDaLegenda}-tipo`, children: s.graphLegendKind }), _jsx("ul", { "aria-labelledby": legenda.estados.length > 0 ? `${idDaLegenda}-tipo` : undefined, children: legenda.tipos.map(t => _jsxs("li", { children: [_jsx(AmostraDaLinha, { padrao: t.padrao, peso: t.peso, marca: t.marca }), _jsx("span", { children: t.nome })] }, t.nome)) })] }), legenda.estados.length > 0 && _jsxs(_Fragment, { children: [legenda.tipos.length > 0 && _jsx("p", { className: "graph-legend-title", id: `${idDaLegenda}-estado`, children: s.graphLegendStatus }), _jsx("ul", { "aria-labelledby": legenda.tipos.length > 0 ? `${idDaLegenda}-estado` : undefined, children: legenda.estados.map(e => _jsxs("li", { children: [_jsx(AmostraDaLinha, { tom: e.tom, marca: e.marca }), _jsx("span", { children: e.nome })] }, e.nome)) })] })] }) })] }) }) });
1261
1354
  }
1262
1355
  /** O primeiro alvo de Tab de um nó no mapa de leitura: o botão dele; sem ele, o de fechar. */
1263
1356
  function ancoraDoNo(raiz, id) {
@@ -1,7 +1,7 @@
1
1
  import { type HTMLAttributes, type RefAttributes, type ReactNode } from "react";
2
2
  import { type Orientation, type Responsive } from "./pure.js";
3
- import { type HeaderVariant, type SidebarVariant, type SidebarScrollbar, type SidebarItem } from "./navigation.js";
4
- export declare function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant, topbarDivider, sidebarVariant, sidebarScrollbar, sidebarCollapsed, sidebarCollapsible, defaultSidebarCollapsed, onSidebarCollapsedChange, contentVariant, aside, asideLabel, asideVariant, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
3
+ import { type HeaderVariant, type HeaderTab, type SidebarVariant, type SidebarScrollbar, type SidebarItem } from "./navigation.js";
4
+ export declare function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant, topbarDivider, topbarTabs, topbarCurrentTab, topbarTabsLabel, layout, sidebarVariant, sidebarScrollbar, sidebarCollapsed, sidebarCollapsible, defaultSidebarCollapsed, onSidebarCollapsedChange, contentVariant, aside, asideLabel, asideVariant, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
5
5
  brand: ReactNode;
6
6
  navigation?: ReactNode;
7
7
  navItems?: SidebarItem[];
@@ -11,6 +11,16 @@ export declare function AppShell({ brand, navigation, navItems, currentNavId, na
11
11
  topbarVariant?: Exclude<HeaderVariant, "pill">;
12
12
  topbarDivider?: boolean;
13
13
  sidebarVariant?: SidebarVariant;
14
+ /** O arranjo (lote T, 17/10/2026): `"app"` (o padrão, o de sempre) ou `"docs"`, o da documentação da referência — um
15
+ * bloco CENTRALIZADO de 1400 com a lateral de 220, o texto e o índice de 268 (de 1280 para cima), a barra de cima de 56
16
+ * com a linha das áreas (`topbarTabs`) embaixo. A lateral fica onde a variante dela mandar (`plain`: sem caixa). */
17
+ layout?: "app" | "docs";
18
+ /** A segunda linha da barra de cima: as áreas do site, com ícone (o `tabs` do `Header`). */
19
+ topbarTabs?: HeaderTab[];
20
+ /** O `id` da área atual da segunda linha. */
21
+ topbarCurrentTab?: string;
22
+ /** O nome da segunda linha para o leitor de tela. Padrão: `"Sections"`. */
23
+ topbarTabsLabel?: string;
14
24
  /** 0.32.0: a barra de rolagem da lateral da esquerda — o `scrollbar` da `Sidebar`. Padrão `hover` (clara, só com o
15
25
  * ponteiro em cima); `visible` é a barra sempre à vista. */
16
26
  sidebarScrollbar?: SidebarScrollbar;
@@ -146,7 +146,7 @@ function useLargura(query) {
146
146
  return () => mq.removeEventListener?.("change", avisar);
147
147
  }, () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(query).matches, () => false);
148
148
  }
149
- export function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant = "floating", topbarDivider, sidebarVariant, sidebarScrollbar, sidebarCollapsed, sidebarCollapsible, defaultSidebarCollapsed, onSidebarCollapsedChange, contentVariant = "surface", aside, asideLabel, asideVariant, children, className, ...props }) {
149
+ export function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant = "floating", topbarDivider, topbarTabs, topbarCurrentTab, topbarTabsLabel, layout = "app", sidebarVariant, sidebarScrollbar, sidebarCollapsed, sidebarCollapsible, defaultSidebarCollapsed, onSidebarCollapsedChange, contentVariant = "surface", aside, asideLabel, asideVariant, children, className, ...props }) {
150
150
  const s = useAureaStrings();
151
151
  useGavetaFechaNoDesktop();
152
152
  const controlado = sidebarCollapsed !== undefined;
@@ -180,7 +180,7 @@ export function AppShell({ brand, navigation, navItems, currentNavId, navLabel,
180
180
  // A condição vai escrita no `icon=` do botão, e não numa variável: é ali que o check 46 lê os
181
181
  // ícones que o shell desenha.
182
182
  const paraOComeco = !efetiva, direita = useDirection() === "rtl";
183
- return _jsxs("div", { className: cx("app-shell", topbarVariant === "flush" && "app-shell-flush", sidebarCollapsible && "app-shell-collapsible", aside != null && "app-shell-with-aside", className), ...props, children: [_jsx("a", { className: "skip-link", href: `#${SHELL_MAIN_ID}`, children: s.skipToContent }), _jsx(Header, { variant: topbarVariant, divider: topbarDivider, brand: _jsxs(_Fragment, { children: [_jsx(IconButton, { className: "nav-toggle", icon: "list", label: s.navigationToggle, popoverTarget: SHELL_NAV_ID }), brand] }), children: topbar }), sidebarCollapsible && _jsx("div", { className: "sidebar-toggle-slot", children: _jsx(Tooltip, { content: rotulo, side: "right", children: _jsx(IconButton, { className: "sidebar-toggle", size: "xs", icon: paraOComeco === direita ? "caret-right" : "caret-left", label: rotulo, "aria-expanded": !efetiva, "aria-controls": SHELL_NAV_ID, onClick: alternar }) }) }), _jsx(Sidebar, { id: SHELL_NAV_ID, popover: "auto", variant: sidebarVariant, scrollbar: sidebarScrollbar, collapsed: efetiva, items: navItems, current: currentNavId, label: navLabel, onToggle: focoDaGaveta, children: navigation }), _jsx("main", { id: SHELL_MAIN_ID, tabIndex: -1, className: cx("content", contentVariant === "plain" && "content-plain"), children: children }), aside != null && _jsx(Sidebar, { placement: "right", variant: asideVariant ?? sidebarVariant, className: "app-shell-aside", "aria-label": asideLabel ?? s.tocLabel, children: aside })] });
183
+ return _jsxs("div", { className: cx("app-shell", layout === "docs" && "app-shell-docs", topbarVariant === "flush" && "app-shell-flush", sidebarCollapsible && "app-shell-collapsible", aside != null && "app-shell-with-aside", className), ...props, children: [_jsx("a", { className: "skip-link", href: `#${SHELL_MAIN_ID}`, children: s.skipToContent }), _jsx(Header, { variant: topbarVariant, divider: topbarDivider, tabs: topbarTabs, currentTab: topbarCurrentTab, tabsLabel: topbarTabsLabel, ...(layout === "docs" ? { maxWidth: "docs", size: "sm" } : {}), brand: _jsxs(_Fragment, { children: [_jsx(IconButton, { className: "nav-toggle", icon: "list", label: s.navigationToggle, popoverTarget: SHELL_NAV_ID }), brand] }), children: topbar }), sidebarCollapsible && _jsx("div", { className: "sidebar-toggle-slot", children: _jsx(Tooltip, { content: rotulo, side: "right", children: _jsx(IconButton, { className: "sidebar-toggle", size: "xs", icon: paraOComeco === direita ? "caret-right" : "caret-left", label: rotulo, "aria-expanded": !efetiva, "aria-controls": SHELL_NAV_ID, onClick: alternar }) }) }), _jsx(Sidebar, { id: SHELL_NAV_ID, popover: "auto", variant: sidebarVariant, scrollbar: sidebarScrollbar, collapsed: efetiva, items: navItems, current: currentNavId, label: navLabel, onToggle: focoDaGaveta, children: navigation }), _jsx("main", { id: SHELL_MAIN_ID, tabIndex: -1, className: cx("content", contentVariant === "plain" && "content-plain"), children: children }), aside != null && _jsx(Sidebar, { placement: "right", variant: asideVariant ?? sidebarVariant, className: "app-shell-aside", "aria-label": asideLabel ?? s.tocLabel, children: aside })] });
184
184
  }
185
185
  export function Separator({ orientation, spacing = "none", className, ...props }) {
186
186
  const ancora = useRef(null);
package/dist/markup.d.ts CHANGED
@@ -101,7 +101,7 @@ export interface GridItemProps extends DivProps {
101
101
  /** O item da grade que sabe quantas colunas ocupa (GAR-03). Mora em `Grid.Item`, como `Card.Media`. */
102
102
  declare function GridItem({ span, className, ...props }: GridItemProps): React.JSX.Element;
103
103
  /** A largura máxima do `Container`, pelos nomes da escala de pontos. `xl` (1280) é o padrão. */
104
- export type ContainerSize = "sm" | "md" | "lg" | "xl" | "2xl" | "full";
104
+ export type ContainerSize = "sm" | "md" | "lg" | "xl" | "2xl" | "docs" | "full";
105
105
  export interface ContainerProps extends HTMLAttributes<HTMLDivElement>, RefAttributes<HTMLDivElement> {
106
106
  /** Até onde o conteúdo cresce: `sm` 640 · `md` 768 · `lg` 1024 · `xl` 1280 (padrão) · `2xl` 1536 · `full` sem teto. */
107
107
  size?: ContainerSize;
@@ -348,8 +348,25 @@ export interface HeaderProps extends HTMLAttributes<HTMLElement>, RefAttributes<
348
348
  menuLabel?: string;
349
349
  /** O `id` do painel do celular. Padrão: `"aurea-header-menu"`. Troque só se a página tiver dois cabeçalhos de site. */
350
350
  menuId?: string;
351
+ /**
352
+ * A SEGUNDA LINHA (lote T, 17/10/2026): as áreas do site, cada uma com o ícone, como a linha das áreas da documentação da
353
+ * referência. A atual (`currentTab`) ganha `aria-current="page"` e o traço embaixo. Fica por último na ordem do Tab.
354
+ */
355
+ tabs?: HeaderTab[];
356
+ /** O `id` da área atual da segunda linha. */
357
+ currentTab?: string;
358
+ /** O nome da segunda linha para o leitor de tela. Padrão: `"Sections"`. */
359
+ tabsLabel?: string;
360
+ }
361
+ /** Uma área da segunda linha do `Header`: o nome, o endereço e, se quiser, o ícone (o desenho que o app passa). */
362
+ export interface HeaderTab {
363
+ id: string;
364
+ label: ReactNode;
365
+ href?: string;
366
+ icon?: ReactNode;
367
+ render?: SidebarItem["render"];
351
368
  }
352
- export declare function Header({ variant, divider, brand, items, current, actions, maxWidth, itemsJustify, size, blurred, menuActions, navLabel, menuLabel, menuId, children, className, ...props }: HeaderProps): React.JSX.Element;
369
+ export declare function Header({ variant, divider, brand, items, current, actions, maxWidth, itemsJustify, size, blurred, menuActions, navLabel, menuLabel, menuId, tabs, currentTab, tabsLabel, children, className, ...props }: HeaderProps): React.JSX.Element;
353
370
  /** @deprecated `Topbar` é o nome antigo do `Header` (07/10/2026). Continua funcionando, igual; use `Header`. */
354
371
  export declare function Topbar(props: HeaderProps): React.JSX.Element;
355
372
  export declare function Kbd({ children, className, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement>): React.JSX.Element;
package/dist/markup.js CHANGED
@@ -196,10 +196,14 @@ function linksDoHeader(items, current) {
196
196
  return _jsx("li", { children: it.render ? fundirRender(it.render, p, "a") : _jsx("a", { href: it.href, ...p }) }, it.id);
197
197
  }) });
198
198
  }
199
- export function Header({ variant = "floating", divider, brand, items, current, actions, maxWidth, itemsJustify = "end", size = "md", blurred, menuActions, navLabel = "Main", menuLabel = "Menu", menuId = "aurea-header-menu", children, className, ...props }) {
200
- const site = !!items && items.length > 0;
201
- const miolo = _jsxs(_Fragment, { children: [brand != null && _jsx("div", { className: "brand", children: brand }), site && _jsx("nav", { className: "topbar-nav", "aria-label": navLabel, children: linksDoHeader(items, current) }), children, actions != null && _jsx("div", { className: "topbar-actions", children: actions }), site && _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "btn btn-ghost btn-icon topbar-menu-toggle", popoverTarget: menuId, "aria-label": menuLabel, children: _jsx("span", { className: "topbar-menu-icon", "aria-hidden": "true" }) }), _jsxs("nav", { id: menuId, popover: "auto", className: "topbar-menu-panel", "aria-label": navLabel, children: [linksDoHeader(items, current), menuActions != null && _jsx("div", { className: "topbar-menu-actions", children: menuActions })] })] })] });
202
- return _jsx("header", { className: cx("topbar", `topbar-${variant}`, divider && variant === "flush" && "topbar-divider", site && "topbar-site", site && itemsJustify === "start" && "topbar-items-start", size === "sm" && "topbar-sm", blurred && variant === "flush" && "topbar-blurred", maxWidth && "topbar-contained", className), ...props, children: maxWidth ? _jsx("div", { className: cx("container", maxWidth !== "xl" && `container-${maxWidth}`, "topbar-inner"), children: miolo }) : miolo });
199
+ export function Header({ variant = "floating", divider, brand, items, current, actions, maxWidth, itemsJustify = "end", size = "md", blurred, menuActions, navLabel = "Main", menuLabel = "Menu", menuId = "aurea-header-menu", tabs, currentTab, tabsLabel = "Sections", children, className, ...props }) {
200
+ const site = !!items && items.length > 0, comAbas = !!tabs && tabs.length > 0;
201
+ const miolo = _jsxs(_Fragment, { children: [brand != null && _jsx("div", { className: "brand", children: brand }), site && _jsx("nav", { className: "topbar-nav", "aria-label": navLabel, children: linksDoHeader(items, current) }), children, actions != null && _jsx("div", { className: "topbar-actions", children: actions }), site && _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "btn btn-ghost btn-icon topbar-menu-toggle", popoverTarget: menuId, "aria-label": menuLabel, children: _jsx("span", { className: "topbar-menu-icon", "aria-hidden": "true" }) }), _jsxs("nav", { id: menuId, popover: "auto", className: "topbar-menu-panel", "aria-label": navLabel, children: [linksDoHeader(items, current), menuActions != null && _jsx("div", { className: "topbar-menu-actions", children: menuActions })] })] }), comAbas && _jsx("nav", { className: "topbar-tabs", "aria-label": tabsLabel, children: tabs.map(t => {
202
+ const p = { className: "topbar-tab", "aria-current": t.id === currentTab ? "page" : undefined,
203
+ children: _jsxs(_Fragment, { children: [t.icon != null && _jsx("span", { className: "topbar-tab-icon", "aria-hidden": "true", children: t.icon }), _jsx("span", { children: t.label })] }) };
204
+ return _jsx(React.Fragment, { children: t.render ? fundirRender(t.render, p, "a") : _jsx("a", { href: t.href, ...p }) }, t.id);
205
+ }) })] });
206
+ return _jsx("header", { className: cx("topbar", `topbar-${variant}`, divider && variant === "flush" && "topbar-divider", site && "topbar-site", site && itemsJustify === "start" && "topbar-items-start", size === "sm" && "topbar-sm", blurred && variant === "flush" && "topbar-blurred", maxWidth && "topbar-contained", comAbas && "topbar-with-tabs", className), ...props, children: maxWidth ? _jsx("div", { className: cx("container", maxWidth !== "xl" && `container-${maxWidth}`, "topbar-inner"), children: miolo }) : miolo });
203
207
  }
204
208
  /** @deprecated `Topbar` é o nome antigo do `Header` (07/10/2026). Continua funcionando, igual; use `Header`. */
205
209
  export function Topbar(props) { return _jsx(Header, { ...props }); }
@@ -1,2 +1,2 @@
1
1
  export * from "./navigation-client.js";
2
- export { Header, Topbar, type HeaderVariant, type HeaderProps, type TopbarVariant } from "./markup.js";
2
+ export { Header, Topbar, type HeaderVariant, type HeaderProps, type HeaderTab, type TopbarVariant } from "./markup.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurea-uds/react",
3
- "version": "0.33.0",
3
+ "version": "0.35.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",