@aurea-uds/react 0.54.1 → 0.59.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/code-client.js +5 -2
- package/dist/data-display-client.d.ts +4 -1
- package/dist/data-display-client.js +1 -1
- package/dist/data-grid.d.ts +4 -2
- package/dist/data-grid.js +67 -2
- package/dist/feedback-client.js +3 -1
- package/dist/inputs-client.js +6 -2
- package/dist/internal.d.ts +6 -0
- package/dist/internal.js +192 -7
- package/dist/layout-client.d.ts +8 -1
- package/dist/layout-client.js +20 -3
- package/dist/markup.d.ts +2 -1
- package/dist/markup.js +11 -2
- package/dist/navigation-client.js +2 -2
- package/dist/overlays.d.ts +9 -1
- package/dist/overlays.js +17 -11
- package/package.json +1 -1
package/dist/code-client.js
CHANGED
|
@@ -70,13 +70,17 @@ function linhasDoCodigo(codigo, language, highlight) {
|
|
|
70
70
|
// arquivo à esquerda e, com `copyable`, o "Copy" ESCRITO (o botão secundário, com o ícone antes do nome; vira "Copied").
|
|
71
71
|
// O bloco vira o cartão (canto 22, a cor do cartão) e a linha longa DESCE em vez de rolar. Sem `filename`, o de sempre.
|
|
72
72
|
export function CodeBlock({ children, language = "text", copyable, preview, previewLayout = "tabs", lineNumbers, highlight, tabs, filename, className }) {
|
|
73
|
+
// Os hooks ANTES de todo `return` (auditoria externa, 05/11/2026): moravam depois dos três retornos da caixa e dos dois do
|
|
74
|
+
// bloco sem cópia, e trocar `copyable` entre renderizações mudava a ordem dos hooks — o React avisava e o "copiado" se
|
|
75
|
+
// perdia. Regra dos hooks do React: mesma ordem, em toda renderização.
|
|
76
|
+
const s = useAureaStrings();
|
|
77
|
+
const [copiado, setCopiado] = React.useState(false);
|
|
73
78
|
if (tabs && tabs.length > 0)
|
|
74
79
|
return _jsx(CodigoEmAbas, { tabs: tabs, language: language, copyable: copyable, highlight: highlight, className: className });
|
|
75
80
|
if (preview !== undefined && previewLayout === "stacked")
|
|
76
81
|
return _jsx(CaixaEmpilhada, { preview: preview, className: className, linhas: (children ?? "").split("\n").length, codigo: _jsx(CodeBlock, { language: language, copyable: copyable, lineNumbers: lineNumbers, highlight: highlight, children: children }) });
|
|
77
82
|
if (preview !== undefined)
|
|
78
83
|
return _jsx(CaixaDeExemplo, { preview: preview, className: className, codigo: _jsx(CodeBlock, { language: language, copyable: copyable, lineNumbers: lineNumbers, highlight: highlight, children: children }) });
|
|
79
|
-
const s = useAureaStrings();
|
|
80
84
|
// tabIndex no <pre>: `.code-block` é `overflow:auto` por construção, então é REGIÃO ROLÁVEL
|
|
81
85
|
// e não tem nada focável dentro — quem navega por teclado não alcança o código que passa da
|
|
82
86
|
// largura. É a regra `scrollable-region-focusable` do axe, e o mesmo defeito que o painel de
|
|
@@ -97,7 +101,6 @@ export function CodeBlock({ children, language = "text", copyable, preview, prev
|
|
|
97
101
|
return _jsxs("div", { className: cx("code-block-wrap", "code-block-file", className), children: [_jsx("div", { className: "code-block-head", children: _jsx("span", { className: "code-block-title", children: filename }) }), pre] });
|
|
98
102
|
// 0.28.0: o "copiado" é dito ao leitor de tela. Com o `aurea.js` na página, é ele quem diz (pelo
|
|
99
103
|
// texto do `data-aurea-copy-done`, na língua do app); sem ele, a região de status daqui.
|
|
100
|
-
const [copiado, setCopiado] = React.useState(false);
|
|
101
104
|
const onCopy = (e) => {
|
|
102
105
|
e.stopPropagation();
|
|
103
106
|
const vanilla = window.Aurea;
|
|
@@ -17,8 +17,11 @@ export interface TableProps extends HTMLAttributes<HTMLTableElement>, RefAttribu
|
|
|
17
17
|
/** As linhas ímpares do corpo com fundo (o site B, 02/11/2026, a "B" na imagem): a 1ª, a 3ª… — o olho segue a linha
|
|
18
18
|
* numa tabela larga. Nos dois arranjos (`default` e `rows`). */
|
|
19
19
|
striped?: boolean;
|
|
20
|
+
/** MNT-38: uma linha clara entre as fileiras do corpo, menos depois da última (a forma "A", aprovada em 05/11/2026). Uma
|
|
21
|
+
* exceção nomeada ao sem linha (ADR-0072): só aparece quando o app escolhe. Sem efeito no arranjo `rows` e na Ember. */
|
|
22
|
+
dividers?: boolean;
|
|
20
23
|
}
|
|
21
|
-
export declare function Table({ caption, stickyHeader, stickyFirstColumn, fit, differencesOnly, striped, variant, children, className, ...props }: TableProps): React.JSX.Element;
|
|
24
|
+
export declare function Table({ caption, stickyHeader, stickyFirstColumn, fit, differencesOnly, striped, dividers, variant, children, className, ...props }: TableProps): React.JSX.Element;
|
|
22
25
|
export interface SortableItem {
|
|
23
26
|
id: string;
|
|
24
27
|
label: ReactNode;
|
|
@@ -6,7 +6,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { cx, useAureaStrings, useReorder } from "./internal.js";
|
|
8
8
|
import { Icon } from "./system.js";
|
|
9
|
-
export function Table({ caption, stickyHeader, stickyFirstColumn, fit, differencesOnly, striped, variant = "default", children, className, ...props }) { const s = useAureaStrings(); return _jsx("div", { className: cx("table-region", stickyHeader && "table-sticky-header", stickyFirstColumn && "table-sticky-first", variant === "rows" && "table-region-rows"), role: "region", "aria-label": typeof caption === "string" ? caption : s.tableLabel, tabIndex: 0, children: _jsxs("table", { className: cx("table", fit && "table-fit", differencesOnly && "table-differences-only", variant === "rows" && "table-rows", striped && "table-striped", className), ...props, children: [caption && _jsx("caption", { children: caption }), children] }) }); }
|
|
9
|
+
export function Table({ caption, stickyHeader, stickyFirstColumn, fit, differencesOnly, striped, dividers, variant = "default", children, className, ...props }) { const s = useAureaStrings(); return _jsx("div", { className: cx("table-region", stickyHeader && "table-sticky-header", stickyFirstColumn && "table-sticky-first", variant === "rows" && "table-region-rows"), role: "region", "aria-label": typeof caption === "string" ? caption : s.tableLabel, tabIndex: 0, children: _jsxs("table", { className: cx("table", fit && "table-fit", differencesOnly && "table-differences-only", variant === "rows" && "table-rows", striped && "table-striped", dividers && "table-dividers", className), ...props, children: [caption && _jsx("caption", { children: caption }), children] }) }); }
|
|
10
10
|
// O protocolo de teclado e de ponteiro saiu daqui para o `useReorder` do `internal` no item N1,
|
|
11
11
|
// quando o `BlockEditor` virou o segundo dono dele. A DOM abaixo não mudou uma vírgula na
|
|
12
12
|
// extração — é o que o gate de pixel e o `skin.spec` continuam medindo.
|
package/dist/data-grid.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface GridFilterSpec {
|
|
|
12
12
|
label: string;
|
|
13
13
|
}>;
|
|
14
14
|
}
|
|
15
|
-
export declare function DataGrid<T>({ data, columns, label, filterable, pageSize, selectable, onSelectionChange, getRowId, className, sorting: sortingProp, onSortingChange, globalFilter: globalFilterProp, onGlobalFilterChange, page, onPageChange, rowSelection: rowSelectionProp, onRowSelectionChange, manualSorting, manualFiltering, manualPagination, rowCount, filters, columnFilters: columnFiltersProp, onColumnFiltersChange, bulkActions, stickyHeader, hideableColumns, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, resizableColumns, columnSizing: columnSizingProp, onColumnSizingChange, state, stateMessage, renderDetail, detailRowId: detailRowIdProp, onDetailRowIdChange, onExport }: {
|
|
15
|
+
export declare function DataGrid<T>({ data, columns, label, filterable, pageSize, selectable, onSelectionChange, getRowId, className, sorting: sortingProp, onSortingChange, globalFilter: globalFilterProp, onGlobalFilterChange, page, onPageChange, rowSelection: rowSelectionProp, onRowSelectionChange, manualSorting, manualFiltering, manualPagination, rowCount, filters, columnFilters: columnFiltersProp, onColumnFiltersChange, bulkActions, stickyHeader, dividers, fit, hideableColumns, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, resizableColumns, columnSizing: columnSizingProp, onColumnSizingChange, state, stateMessage, renderDetail, detailRowId: detailRowIdProp, onDetailRowIdChange, onExport }: {
|
|
16
16
|
data: T[];
|
|
17
17
|
columns: Array<ColumnDef<T, any>>;
|
|
18
18
|
label?: string;
|
|
@@ -43,7 +43,9 @@ export declare function DataGrid<T>({ data, columns, label, filterable, pageSize
|
|
|
43
43
|
icon?: IconName;
|
|
44
44
|
onAction: (rows: T[], clear: () => void) => void;
|
|
45
45
|
}>;
|
|
46
|
-
stickyHeader?: boolean;
|
|
46
|
+
stickyHeader?: boolean | "page";
|
|
47
|
+
dividers?: boolean;
|
|
48
|
+
fit?: boolean;
|
|
47
49
|
hideableColumns?: boolean;
|
|
48
50
|
columnVisibility?: VisibilityState;
|
|
49
51
|
onColumnVisibilityChange?: (v: VisibilityState) => void;
|
package/dist/data-grid.js
CHANGED
|
@@ -95,7 +95,67 @@ function useMaybe(outer, onChange, initial) {
|
|
|
95
95
|
return [value, u => { const next = typeof u === "function" ? u(value) : u; if (!controlled)
|
|
96
96
|
setInner(next); onChange?.(next); }];
|
|
97
97
|
}
|
|
98
|
-
|
|
98
|
+
/** O que fica parado no topo da página na coluna da grade: o fundo da pilha de elementos no alto da tela, subindo até o
|
|
99
|
+
* primeiro ancestral `fixed` ou `sticky` que não é da própria grade. Devolve onde ele termina (0 se não houver). */
|
|
100
|
+
function topoParado(caixa) {
|
|
101
|
+
const r = caixa.getBoundingClientRect();
|
|
102
|
+
const x = Math.min(Math.max(r.left + r.width / 2, 0), innerWidth - 1);
|
|
103
|
+
let fundo = 0;
|
|
104
|
+
// Sem `elementsFromPoint` (um ambiente sem desenho), não há o que medir: o cabeçalho fica onde está.
|
|
105
|
+
for (const el of document.elementsFromPoint?.(x, 0) ?? []) {
|
|
106
|
+
if (caixa.contains(el))
|
|
107
|
+
continue;
|
|
108
|
+
for (let a = el; a && a !== document.documentElement; a = a.parentElement) {
|
|
109
|
+
const pos = getComputedStyle(a).position;
|
|
110
|
+
if (pos === "fixed" || pos === "sticky") {
|
|
111
|
+
fundo = Math.max(fundo, a.getBoundingClientRect().bottom);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return fundo;
|
|
117
|
+
}
|
|
118
|
+
/** S-10: o cabeçalho da grade anda com a página. Cada `th` do `thead` ganha um `translate` — a caixa continua rolando de
|
|
119
|
+
* lado, e o cabeçalho, que está dentro dela, vai junto. Para no fim da caixa, com a última fileira ainda à vista. */
|
|
120
|
+
function usePresoNaPagina(ligado, grade, montagem) {
|
|
121
|
+
React.useLayoutEffect(() => {
|
|
122
|
+
const tabela = grade.current;
|
|
123
|
+
if (!ligado || !tabela)
|
|
124
|
+
return;
|
|
125
|
+
const caixa = tabela.closest(".table-wrap");
|
|
126
|
+
if (!caixa)
|
|
127
|
+
return;
|
|
128
|
+
let quadro = 0;
|
|
129
|
+
const medir = () => {
|
|
130
|
+
quadro = 0;
|
|
131
|
+
const cab = tabela.tHead;
|
|
132
|
+
if (!cab)
|
|
133
|
+
return;
|
|
134
|
+
const r = caixa.getBoundingClientRect();
|
|
135
|
+
const ultima = tabela.tBodies[0]?.rows[tabela.tBodies[0].rows.length - 1]?.offsetHeight ?? 0;
|
|
136
|
+
const y = Math.round(Math.max(0, Math.min(topoParado(caixa) - r.top, r.height - cab.offsetHeight - ultima)));
|
|
137
|
+
for (const th of cab.querySelectorAll(":scope > tr > th"))
|
|
138
|
+
th.style.translate = y ? `0 ${y}px` : "";
|
|
139
|
+
};
|
|
140
|
+
const pedir = () => { if (!quadro)
|
|
141
|
+
quadro = requestAnimationFrame(medir); };
|
|
142
|
+
medir();
|
|
143
|
+
addEventListener("scroll", pedir, { passive: true, capture: true });
|
|
144
|
+
addEventListener("resize", pedir);
|
|
145
|
+
const vigia = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(pedir);
|
|
146
|
+
vigia?.observe(tabela);
|
|
147
|
+
return () => {
|
|
148
|
+
cancelAnimationFrame(quadro);
|
|
149
|
+
removeEventListener("scroll", pedir, { capture: true });
|
|
150
|
+
removeEventListener("resize", pedir);
|
|
151
|
+
vigia?.disconnect();
|
|
152
|
+
for (const th of tabela.querySelectorAll("thead th"))
|
|
153
|
+
th.style.translate = "";
|
|
154
|
+
};
|
|
155
|
+
// `montagem`: a linha aberta do painel de detalhe muda o lugar da tabela na árvore, e a tabela nasce de novo.
|
|
156
|
+
}, [ligado, grade, montagem]);
|
|
157
|
+
}
|
|
158
|
+
export function DataGrid({ data, columns, label, filterable, pageSize, selectable, onSelectionChange, getRowId, className, sorting: sortingProp, onSortingChange, globalFilter: globalFilterProp, onGlobalFilterChange, page, onPageChange, rowSelection: rowSelectionProp, onRowSelectionChange, manualSorting, manualFiltering, manualPagination, rowCount, filters, columnFilters: columnFiltersProp, onColumnFiltersChange, bulkActions, stickyHeader, dividers, fit, hideableColumns, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, resizableColumns, columnSizing: columnSizingProp, onColumnSizingChange, state, stateMessage, renderDetail, detailRowId: detailRowIdProp, onDetailRowIdChange, onExport }) {
|
|
99
159
|
const s = useAureaStrings();
|
|
100
160
|
const [sorting, setSorting] = useMaybe(sortingProp, onSortingChange, []);
|
|
101
161
|
const [globalFilter, setGlobalFilter] = useMaybe(globalFilterProp, onGlobalFilterChange, "");
|
|
@@ -400,7 +460,12 @@ export function DataGrid({ data, columns, label, filterable, pageSize, selectabl
|
|
|
400
460
|
// O marcador vai na GRADE, não no recado: quem está obsoleto é a tabela, e o `Alert` é só
|
|
401
461
|
// como ela conta isso. Achado pelo check 30 na primeira execução dele — eu tinha marcado o
|
|
402
462
|
// recado, e com `state="loading"` não há recado nenhum, então o estado não chegava ao DOM.
|
|
403
|
-
|
|
463
|
+
// S-10 (o MNT-40, aprovado em 05/11/2026 como valor novo): com `stickyHeader="page"` a caixa não ganha teto nem rolagem
|
|
464
|
+
// de cima para baixo, e o cabeçalho anda com a PÁGINA até encostar embaixo do que fica parado no topo dela (a barra do
|
|
465
|
+
// `AppShell`, o cabeçalho do site). Só com CSS não dá: a caixa rola de lado, e um elemento grudento gruda no contêiner de
|
|
466
|
+
// rolagem mais próximo — a caixa, não a página. Então o deslocamento é medido aqui, a cada rolagem.
|
|
467
|
+
usePresoNaPagina(stickyHeader === "page", grade, detailRowId);
|
|
468
|
+
return _jsxs("div", { className: cx("datagrid", stickyHeader === true && "datagrid-sticky", stickyHeader === "page" && "datagrid-sticky-page", dividers && "datagrid-dividers", fit && "datagrid-fit", className), "data-state": state, children: [emLote.length > 0 && _jsxs(Toolbar, { label: s.dataGridBulkLabel, children: [_jsxs("span", { className: "hint", children: [emLote.length, " ", s.dataGridSelected] }), _jsx(ToolbarSeparator, {}), bulkActions?.map(a => _jsx(ToolbarButton, { size: "sm", leadingIcon: a.icon, onClick: () => a.onAction(emLote, limparSelecao), children: a.label }, a.id)), _jsx(ToolbarButton, { size: "sm", onClick: limparSelecao, children: s.dataGridClearSelection })] }), controles.length > 1 ? _jsx(Cluster, { children: controles }) : controles[0] ?? null, recado && _jsx(Alert, { variant: state === "error" ? "danger" : universal ? stateSeverity(universal) : "warning", children: recado }), (() => {
|
|
404
469
|
// F9: o embrulho de duas colunas só existe COM o painel aberto — fechado, a
|
|
405
470
|
// marcação é a de sempre. É a lição do F7: não mudar a estrutura de quem não
|
|
406
471
|
// pediu nada. A largura do painel é `--datagrid-detail-w`, e quem decide se
|
package/dist/feedback-client.js
CHANGED
|
@@ -59,10 +59,12 @@ const ICONE_DA_VARIANTE = { info: "info",
|
|
|
59
59
|
// na cor do texto. Sem ele (`"card"`), o cartão de sempre.
|
|
60
60
|
export function Alert({ variant, state, title, icon, onDismiss, action, appearance, children, className, ...props }) { const s = useAureaStrings(); const v = variant ?? (state ? stateSeverity(state) : "info"); return _jsxs("div", { className: cx("alert", `alert-${v}`, appearance === "soft" && "alert-soft", action != null && "alert-has-action", className), role: v === "danger" ? "alert" : "status", "data-state": state, ...props, children: [_jsx(Icon, { name: icon ?? proprio(ICONE_DA_VARIANTE, v) ?? "info", weight: icon ? undefined : "fill" }), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? proprio(s.universalState, state) : null)] }), action != null && _jsx("div", { className: "alert-action", children: action }), onDismiss ? _jsx(CloseButton, { onClick: onDismiss }) : _jsx("span", {})] }); }
|
|
61
61
|
export function Banner(props) {
|
|
62
|
+
// O hook ANTES do `return` do `consent` (auditoria externa, 05/11/2026): trocar a variante entre renderizações mudava a
|
|
63
|
+
// ordem dos hooks.
|
|
64
|
+
const s = useAureaStrings();
|
|
62
65
|
if (props.variant === "consent")
|
|
63
66
|
return _jsx(BannerConsent, { ...props });
|
|
64
67
|
const { variant, state, title, icon, onDismiss, children, className, categories: _c, value: _v, onConsent: _o, ...rest } = props;
|
|
65
|
-
const s = useAureaStrings();
|
|
66
68
|
const v = variant ?? (state ? stateSeverity(state) : "info");
|
|
67
69
|
return _jsxs("div", { className: cx("banner", `banner-${v}`, className), role: v === "danger" ? "alert" : "status", "data-state": state, ...rest, children: [icon ? _jsx(Icon, { name: icon }) : _jsx("span", {}), _jsxs("div", { children: [title && _jsx("strong", { children: title }), children ?? (state ? proprio(s.universalState, state) : null)] }), onDismiss ? _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.close, onClick: onDismiss }) : _jsx("span", {})] });
|
|
68
70
|
}
|
package/dist/inputs-client.js
CHANGED
|
@@ -15,7 +15,7 @@ import { Slider as BaseSlider } from "@base-ui/react/slider";
|
|
|
15
15
|
import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group";
|
|
16
16
|
import { Radio as BaseRadio } from "@base-ui/react/radio";
|
|
17
17
|
import { useDirection } from "@base-ui/react/direction-provider";
|
|
18
|
-
import { cx, useAureaStrings, usePortalContainer, useReorder, controlesRotulaveis } from "./internal.js";
|
|
18
|
+
import { cx, useAureaStrings, usePortalContainer, useReorder, useDeslize, controlesRotulaveis } from "./internal.js";
|
|
19
19
|
import { COLOR_CHANNELS, areaBackground, channelRange, colorName, corToHex, formatChannel, getChannel, hsbToRgb, parseColor, pureHue, setChannel, spaceOf, trackBackground } from "./color.js";
|
|
20
20
|
import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
21
21
|
// Os seis controles abaixo saíram deste arquivo no item O1 e moram no `markup.tsx`, sem a
|
|
@@ -734,11 +734,15 @@ export function OTPField({ length, value, defaultValue, onValueChange, mask, dis
|
|
|
734
734
|
// cheia), `size="2xl"` (segmento de 48), `wrap` (quebra linha, o trilho com canto 22) e `orientation="vertical"` (em pé).
|
|
735
735
|
export function SegmentedControl({ items, value, onChange, label, fullWidth, size = "md", selectedStyle, wrap, orientation = "horizontal" }) {
|
|
736
736
|
const s = useAureaStrings();
|
|
737
|
+
// S-04 (05/11/2026): o escolhido DESLIZA de um segmento ao outro, em vez de pular (`useDeslize`, no `internal`). Só na
|
|
738
|
+
// escolha: com `href` a troca é de página, e a página nova já nasce com o escolhido no lugar.
|
|
739
|
+
const fila = React.useRef(null);
|
|
740
|
+
useDeslize(fila, ".active", value);
|
|
737
741
|
const classe = cx("segmented", fullWidth && "segmented-full", size === "sm" && "segmented-sm", size === "lg" && "segmented-lg", size === "xl" && "segmented-xl", size === "2xl" && "segmented-2xl", wrap && orientation !== "vertical" && "segmented-wrap", orientation === "vertical" && "segmented-vertical");
|
|
738
742
|
const miolo = (i) => i.icon ? _jsxs(_Fragment, { children: [_jsx(Icon, { name: i.icon, weight: i.value === value ? "fill" : "regular" }), i.label] }) : i.label;
|
|
739
743
|
if (items.length > 0 && items.every(i => i.href != null))
|
|
740
744
|
return _jsx("nav", { className: classe, "data-segment-style": selectedStyle, "aria-label": label ?? s.optionsLabel, children: items.map(i => _jsx("a", { href: i.href, className: i.value === value ? "active" : undefined, "aria-current": i.value === value ? "page" : undefined, children: miolo(i) }, i.value)) });
|
|
741
|
-
return _jsx(BaseRadioGroup, { className: classe, "data-segment-style": selectedStyle, "aria-label": label ?? s.optionsLabel, "aria-orientation": orientation === "vertical" ? "vertical" : undefined, value: value, onValueChange: v => onChange?.(String(v)), children: items.map(i => _jsx(BaseRadio.Root, { value: i.value, className: i.value === value ? "active" : undefined, nativeButton: true, render: _jsx("button", { type: "button" }), children: miolo(i) }, i.value)) });
|
|
745
|
+
return _jsx(BaseRadioGroup, { ref: fila, className: classe, "data-segment-style": selectedStyle, "aria-label": label ?? s.optionsLabel, "aria-orientation": orientation === "vertical" ? "vertical" : undefined, value: value, onValueChange: v => onChange?.(String(v)), children: items.map(i => _jsx(BaseRadio.Root, { value: i.value, className: i.value === value ? "active" : undefined, nativeButton: true, render: _jsx("button", { type: "button" }), children: miolo(i) }, i.value)) });
|
|
742
746
|
}
|
|
743
747
|
const ehAgrupado = (items) => items != null && items.length > 0 && typeof items[0] === "object" && items[0] != null && "items" in items[0];
|
|
744
748
|
// UM renderizador e UMA lista para os dois combobox: duas cópias da mesma árvore é como a
|
package/dist/internal.d.ts
CHANGED
|
@@ -33,6 +33,12 @@ export declare const DentroDoProviderContext: React.Context<boolean>;
|
|
|
33
33
|
export declare const avisosAncorados: import("@base-ui/react").ToastManager<any>;
|
|
34
34
|
export declare const PortalContext: React.Context<HTMLElement | null | undefined>;
|
|
35
35
|
export declare const usePortalContainer: () => HTMLElement | null | undefined;
|
|
36
|
+
export declare const reduzMovimento: () => boolean;
|
|
37
|
+
export declare function tempoDoToken(el: Element, duracao: string, curva: string): {
|
|
38
|
+
duration: number;
|
|
39
|
+
easing: string;
|
|
40
|
+
} | null;
|
|
41
|
+
export declare function useDeslize(ref: React.RefObject<HTMLElement | null>, seletor: string, chave: unknown): void;
|
|
36
42
|
export interface Reordenavel<E extends HTMLElement> {
|
|
37
43
|
ref: React.RefObject<E | null>;
|
|
38
44
|
pego: number | null;
|
package/dist/internal.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// NÃO é subpath — `@aurea-uds/react/internal` não existe, de propósito.
|
|
15
15
|
// Kbd mora aqui por dependência, não por categoria: o Button precisa dele e ele não precisa de
|
|
16
16
|
// ninguém. A casa pública dele continua sendo `/data-display`, que o reexporta.
|
|
17
|
-
import { createContext, useContext, useEffect, useRef, useState } from "react";
|
|
17
|
+
import React, { createContext, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
18
18
|
import { Toast as BaseToast } from "@base-ui/react/toast";
|
|
19
19
|
import { defaultStrings, defaultSpriteUrl } from "./pure.js";
|
|
20
20
|
export { cx, fundirRender, defaultStrings, ptBR, defaultSpriteUrl, universalStates, stateSeverity } from "./pure.js";
|
|
@@ -71,6 +71,111 @@ export const DentroDoProviderContext = createContext(false);
|
|
|
71
71
|
export const avisosAncorados = BaseToast.createToastManager();
|
|
72
72
|
export const PortalContext = createContext(undefined);
|
|
73
73
|
export const usePortalContainer = () => useContext(PortalContext);
|
|
74
|
+
// direction informa o Base UI (lado dos popovers, setas do teclado). O CSS
|
|
75
|
+
// espelha sozinho por propriedade lógica, mas depende do atributo dir no DOM:
|
|
76
|
+
// quem consome precisa pôr dir="rtl" no <html>. Este provider não mexe no DOM.
|
|
77
|
+
// ── O MOVIMENTO (S-04 e S-05, 05/11/2026) ─────────────────────────────────────────────────────
|
|
78
|
+
// O Victor achou a Aurea "estática": o escolhido do `SegmentedControl` e do `Tabs` PULAVA, e o arrasto do
|
|
79
|
+
// `SortableList` e do `BlockEditor` ia de casa em casa. Decidido por ele (05/11/2026, "Construir nós"): sem biblioteca
|
|
80
|
+
// nova, CSS e a animação do próprio navegador (`Element.animate`). O tempo e a curva NÃO se escrevem aqui: saem dos
|
|
81
|
+
// tokens da folha (`--duration-*`, `--ease-*`), lidos no elemento — o mesmo número que o CSS usa. Sem a folha, não há
|
|
82
|
+
// token, e não há animação: a troca acontece de uma vez, como antes. E quem pediu "reduzir movimento" também vê de
|
|
83
|
+
// uma vez — a regra global do CSS não alcança a animação feita por código, então quem pergunta é este arquivo.
|
|
84
|
+
export const reduzMovimento = () => typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
85
|
+
export function tempoDoToken(el, duracao, curva) {
|
|
86
|
+
const cs = getComputedStyle(el);
|
|
87
|
+
const d = cs.getPropertyValue(duracao).trim(), easing = cs.getPropertyValue(curva).trim();
|
|
88
|
+
const duration = d.endsWith("ms") ? parseFloat(d) : d.endsWith("s") ? parseFloat(d) * 1000 : NaN;
|
|
89
|
+
return Number.isFinite(duration) && duration > 0 && easing ? { duration, easing } : null;
|
|
90
|
+
}
|
|
91
|
+
// O nome das animações deste arquivo: para cancelar só as nossas, e não a transição de CSS da mesma peça.
|
|
92
|
+
const ANIMACAO = "aurea-movimento";
|
|
93
|
+
// ── useDeslize (S-04) ─────────────────────────────────────────────────────────────────────────
|
|
94
|
+
// O ESCOLHIDO DESLIZA. A referência tem uma peça separada atrás dos itens. Aqui o escolhido continua se pintando — as
|
|
95
|
+
// sete cores do segmento, as treze marcas e os dois temas são regras de CSS que este gancho NÃO repete —, e só na troca
|
|
96
|
+
// entra uma peça de passagem: ela COPIA a pintura do novo escolhido (o fundo, a sombra, o aro e o fio de baixo, que na
|
|
97
|
+
// aba secundária é o único sinal), esconde a do item e anda da posição de antes até a nova. No fim, sai, e o item volta
|
|
98
|
+
// a se pintar. Copiar o que o navegador calculou, em vez de reescrever as cores, é o que garante que o escolhido em
|
|
99
|
+
// repouso fique IDÊNTICO ao de hoje. Troca no meio do caminho parte de onde a peça está, e não do item de antes.
|
|
100
|
+
// `seletor` acha o escolhido dentro de `ref`; `chave` é o valor escolhido.
|
|
101
|
+
export function useDeslize(ref, seletor, chave) {
|
|
102
|
+
const anterior = useRef(null);
|
|
103
|
+
const emCurso = useRef(null);
|
|
104
|
+
React.useLayoutEffect(() => () => { emCurso.current?.(); }, []);
|
|
105
|
+
React.useLayoutEffect(() => {
|
|
106
|
+
const raiz = ref.current;
|
|
107
|
+
if (!raiz)
|
|
108
|
+
return;
|
|
109
|
+
const novo = raiz.querySelector(seletor);
|
|
110
|
+
const velho = anterior.current;
|
|
111
|
+
anterior.current = novo;
|
|
112
|
+
const corrente = emCurso.current;
|
|
113
|
+
emCurso.current = null;
|
|
114
|
+
const de = corrente ? corrente() : velho && velho !== novo && velho.isConnected ? velho.getBoundingClientRect() : null;
|
|
115
|
+
if (!novo || !de || !de.width || reduzMovimento() || typeof novo.animate !== "function")
|
|
116
|
+
return;
|
|
117
|
+
const tempo = tempoDoToken(raiz, "--duration-slow", "--ease-emphasized");
|
|
118
|
+
if (!tempo)
|
|
119
|
+
return;
|
|
120
|
+
const para = novo.getBoundingClientRect();
|
|
121
|
+
if (!para.width)
|
|
122
|
+
return;
|
|
123
|
+
const cs = getComputedStyle(novo), fio = getComputedStyle(novo, "::after");
|
|
124
|
+
const passagem = document.createElement("span");
|
|
125
|
+
passagem.className = "selection-indicator";
|
|
126
|
+
passagem.setAttribute("aria-hidden", "true");
|
|
127
|
+
Object.assign(passagem.style, { left: "0px", top: "0px", borderRadius: cs.borderRadius, backgroundColor: cs.backgroundColor,
|
|
128
|
+
backgroundImage: cs.backgroundImage, boxShadow: cs.boxShadow });
|
|
129
|
+
if (fio.content && fio.content !== "none" && fio.content !== "normal" && fio.display !== "none") {
|
|
130
|
+
const linha = document.createElement("span");
|
|
131
|
+
Object.assign(linha.style, { left: fio.left, right: fio.right, bottom: fio.bottom, height: fio.height,
|
|
132
|
+
borderRadius: fio.borderRadius, backgroundColor: fio.backgroundColor });
|
|
133
|
+
passagem.append(linha);
|
|
134
|
+
}
|
|
135
|
+
// A peça mora DENTRO da fila, atrás dos itens (`z-index:-1` num contexto de empilhamento só dela), e a fila só
|
|
136
|
+
// ganha posição e contexto durante a passagem — em repouso, nada nela muda.
|
|
137
|
+
const antes = { position: raiz.style.position, isolation: raiz.style.isolation };
|
|
138
|
+
if (getComputedStyle(raiz).position === "static")
|
|
139
|
+
raiz.style.position = "relative";
|
|
140
|
+
raiz.style.isolation = "isolate";
|
|
141
|
+
const rr = raiz.getBoundingClientRect();
|
|
142
|
+
const quadro = (r) => ({ transform: `translate(${r.left - rr.left - raiz.clientLeft + raiz.scrollLeft}px,${r.top - rr.top - raiz.clientTop + raiz.scrollTop}px)`,
|
|
143
|
+
width: `${r.width}px`, height: `${r.height}px` });
|
|
144
|
+
const pintura = ["background-color", "background-image", "box-shadow"].map(p => [p, novo.style.getPropertyValue(p), novo.style.getPropertyPriority(p)]);
|
|
145
|
+
for (const [p] of pintura)
|
|
146
|
+
novo.style.setProperty(p, p === "background-color" ? "transparent" : "none", "important");
|
|
147
|
+
novo.setAttribute("data-sliding", "");
|
|
148
|
+
raiz.prepend(passagem);
|
|
149
|
+
const a = passagem.animate([quadro(de), quadro(para)], { ...tempo, fill: "forwards", id: ANIMACAO });
|
|
150
|
+
let feito = false;
|
|
151
|
+
const encerra = () => {
|
|
152
|
+
const onde = passagem.getBoundingClientRect();
|
|
153
|
+
if (feito)
|
|
154
|
+
return onde;
|
|
155
|
+
feito = true;
|
|
156
|
+
a.cancel();
|
|
157
|
+
passagem.remove();
|
|
158
|
+
for (const [p, v, pr] of pintura) {
|
|
159
|
+
if (v)
|
|
160
|
+
novo.style.setProperty(p, v, pr);
|
|
161
|
+
else
|
|
162
|
+
novo.style.removeProperty(p);
|
|
163
|
+
}
|
|
164
|
+
if (!novo.getAttribute("style"))
|
|
165
|
+
novo.removeAttribute("style");
|
|
166
|
+
novo.removeAttribute("data-sliding");
|
|
167
|
+
raiz.style.position = antes.position;
|
|
168
|
+
raiz.style.isolation = antes.isolation;
|
|
169
|
+
if (!raiz.getAttribute("style"))
|
|
170
|
+
raiz.removeAttribute("style");
|
|
171
|
+
if (emCurso.current === encerra)
|
|
172
|
+
emCurso.current = null;
|
|
173
|
+
return onde;
|
|
174
|
+
};
|
|
175
|
+
emCurso.current = encerra;
|
|
176
|
+
a.onfinish = () => { encerra(); };
|
|
177
|
+
}, [chave]);
|
|
178
|
+
}
|
|
74
179
|
export function useReorder({ count, order, onReorder, rowSelector, handleSelector }) {
|
|
75
180
|
const s = useAureaStrings();
|
|
76
181
|
const ref = useRef(null);
|
|
@@ -79,16 +184,55 @@ export function useReorder({ count, order, onReorder, rowSelector, handleSelecto
|
|
|
79
184
|
const [aviso, setAviso] = useState("");
|
|
80
185
|
const origem = useRef(null);
|
|
81
186
|
const arrasto = useRef(null);
|
|
187
|
+
// S-05: a MÃO — a linha que o ponteiro pegou, onde o ponteiro estava ao pegar e agora, e o topo dela ao pegar. Com isso a
|
|
188
|
+
// linha anda com o ponteiro (`translate`), e não só pula de casa em casa.
|
|
189
|
+
const mao = useRef(null);
|
|
190
|
+
// S-05: onde cada linha estava NA TELA antes da troca — a conta do "First, Last, Invert, Play": depois de a ordem nova
|
|
191
|
+
// entrar, cada vizinho parte de onde estava e desliza até o lugar novo.
|
|
192
|
+
const antes = useRef(null);
|
|
82
193
|
const anuncia = (verbo, i) => setAviso(`${verbo}: ${i + 1} ${s.positionOf} ${count}`);
|
|
83
194
|
const alcas = () => [...(ref.current?.querySelectorAll(handleSelector) ?? [])];
|
|
195
|
+
const linhas = () => [...(ref.current?.querySelectorAll(rowSelector) ?? [])];
|
|
196
|
+
// `offsetTop` não conta o `translate` nem a animação: é o lugar da linha na lista, e não onde ela aparece agora.
|
|
197
|
+
const seguir = (m) => { m.linha.style.translate = `0 ${m.y - m.y0 - (m.linha.offsetTop - m.topo0)}px`; };
|
|
84
198
|
// Vai por efeito e não por `requestAnimationFrame`: o React precisa ter commitado a ordem nova
|
|
85
199
|
// antes de procurar a alça.
|
|
86
200
|
useEffect(() => { if (foco !== null) {
|
|
87
201
|
alcas()[foco]?.focus();
|
|
88
202
|
setFoco(null);
|
|
89
203
|
} }, [foco, order]);
|
|
90
|
-
|
|
91
|
-
|
|
204
|
+
// A ordem nova entrou: a linha pega volta para baixo do ponteiro, e os vizinhos deslizam de onde estavam.
|
|
205
|
+
useLayoutEffect(() => {
|
|
206
|
+
const m = mao.current;
|
|
207
|
+
if (m)
|
|
208
|
+
seguir(m);
|
|
209
|
+
const mapa = antes.current;
|
|
210
|
+
antes.current = null;
|
|
211
|
+
if (!mapa || !ref.current || reduzMovimento())
|
|
212
|
+
return;
|
|
213
|
+
const tempo = tempoDoToken(ref.current, "--duration-base", "--ease-emphasized");
|
|
214
|
+
if (!tempo)
|
|
215
|
+
return;
|
|
216
|
+
for (const l of linhas()) {
|
|
217
|
+
if (m && l === m.linha)
|
|
218
|
+
continue;
|
|
219
|
+
const de = mapa.get(l);
|
|
220
|
+
if (de == null || typeof l.animate !== "function")
|
|
221
|
+
continue;
|
|
222
|
+
for (const a of l.getAnimations?.() ?? [])
|
|
223
|
+
if (a.id === ANIMACAO)
|
|
224
|
+
a.cancel();
|
|
225
|
+
const d = de - l.getBoundingClientRect().top;
|
|
226
|
+
if (Math.abs(d) >= 1)
|
|
227
|
+
l.animate([{ transform: `translateY(${d}px)` }, { transform: "none" }], { ...tempo, id: ANIMACAO });
|
|
228
|
+
}
|
|
229
|
+
}, [order]);
|
|
230
|
+
const mover = (de, para) => {
|
|
231
|
+
if (para >= 0 && para < count && de !== para) {
|
|
232
|
+
antes.current = new Map(linhas().map(l => [l, l.getBoundingClientRect().top]));
|
|
233
|
+
onReorder(de, para);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
92
236
|
const teclado = (e, i) => {
|
|
93
237
|
if (e.key === " " || e.key === "Enter") {
|
|
94
238
|
e.preventDefault();
|
|
@@ -130,23 +274,56 @@ export function useReorder({ count, order, onReorder, rowSelector, handleSelecto
|
|
|
130
274
|
}
|
|
131
275
|
};
|
|
132
276
|
// O alvo é a primeira linha cujo MEIO ainda está abaixo do ponteiro — a conta que faz o item
|
|
133
|
-
// trocar de lugar ao passar da metade da linha vizinha, e não ao encostar nela.
|
|
277
|
+
// trocar de lugar ao passar da metade da linha vizinha, e não ao encostar nela. O meio é o do LUGAR da linha
|
|
278
|
+
// (`offsetTop`), e não o de onde ela aparece: a linha pega anda com o ponteiro e os vizinhos estão deslizando, e medir a
|
|
279
|
+
// caixa na tela faria o alvo tremer. A origem dos `offsetTop` na tela sai da própria lista.
|
|
134
280
|
const ponteiroMove = (e) => {
|
|
135
281
|
const de = arrasto.current;
|
|
136
282
|
if (de === null)
|
|
137
283
|
return;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
284
|
+
const m = mao.current;
|
|
285
|
+
if (m) {
|
|
286
|
+
m.y = e.clientY;
|
|
287
|
+
seguir(m);
|
|
288
|
+
}
|
|
289
|
+
const lista = ref.current;
|
|
290
|
+
if (!lista)
|
|
291
|
+
return;
|
|
292
|
+
const ls = linhas();
|
|
293
|
+
const caixa = lista.getBoundingClientRect();
|
|
294
|
+
const zero = ls[0]?.offsetParent === lista ? caixa.top + lista.clientTop - lista.scrollTop : caixa.top - lista.offsetTop;
|
|
295
|
+
const achado = ls.findIndex(l => e.clientY < zero + l.offsetTop + l.offsetHeight / 2);
|
|
296
|
+
const para = achado === -1 ? ls.length - 1 : achado;
|
|
141
297
|
if (para !== de) {
|
|
142
298
|
mover(de, para);
|
|
143
299
|
arrasto.current = para;
|
|
144
300
|
setPego(para);
|
|
145
301
|
}
|
|
146
302
|
};
|
|
303
|
+
// Ao soltar, a linha desliza do ponteiro até o lugar dela, e só então desce de volta para o nível das outras.
|
|
304
|
+
const pousa = () => {
|
|
305
|
+
const m = mao.current;
|
|
306
|
+
mao.current = null;
|
|
307
|
+
if (!m)
|
|
308
|
+
return;
|
|
309
|
+
const d = m.y - m.y0 - (m.linha.offsetTop - m.topo0);
|
|
310
|
+
m.linha.style.translate = "";
|
|
311
|
+
if (!m.linha.getAttribute("style"))
|
|
312
|
+
m.linha.removeAttribute("style");
|
|
313
|
+
const fim = () => m.linha.removeAttribute("data-dragging");
|
|
314
|
+
const tempo = ref.current && !reduzMovimento() && typeof m.linha.animate === "function" ? tempoDoToken(ref.current, "--duration-base", "--ease-emphasized") : null;
|
|
315
|
+
if (!tempo || Math.abs(d) < 1) {
|
|
316
|
+
fim();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const a = m.linha.animate([{ transform: `translateY(${d}px)` }, { transform: "none" }], { ...tempo, id: ANIMACAO });
|
|
320
|
+
a.onfinish = fim;
|
|
321
|
+
a.oncancel = fim;
|
|
322
|
+
};
|
|
147
323
|
const ponteiroSolta = () => {
|
|
148
324
|
if (arrasto.current === null)
|
|
149
325
|
return;
|
|
326
|
+
pousa();
|
|
150
327
|
anuncia(s.sortableDropped, arrasto.current);
|
|
151
328
|
arrasto.current = null;
|
|
152
329
|
setPego(null);
|
|
@@ -157,6 +334,14 @@ export function useReorder({ count, order, onReorder, rowSelector, handleSelecto
|
|
|
157
334
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
158
335
|
arrasto.current = i;
|
|
159
336
|
setPego(i);
|
|
337
|
+
const linha = e.currentTarget.closest(rowSelector);
|
|
338
|
+
if (linha) {
|
|
339
|
+
for (const a of linha.getAnimations?.() ?? [])
|
|
340
|
+
if (a.id === ANIMACAO)
|
|
341
|
+
a.finish();
|
|
342
|
+
linha.setAttribute("data-dragging", "");
|
|
343
|
+
mao.current = { linha, y0: e.clientY, topo0: linha.offsetTop, y: e.clientY };
|
|
344
|
+
}
|
|
160
345
|
};
|
|
161
346
|
return { ref, pego, aviso, teclado, ponteiroBaixo, ponteiroMove, ponteiroSolta };
|
|
162
347
|
}
|
package/dist/layout-client.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type Orientation, type Responsive } from "./pure.js";
|
|
|
3
3
|
import { type IconButtonProps } from "./actions.js";
|
|
4
4
|
import { type HeaderVariant, type HeaderTab, type SidebarVariant, type SidebarScrollbar, type SidebarEntry, type SidebarCollapsible, type SidebarIndicator } from "./navigation.js";
|
|
5
5
|
declare function AppShellSidebarToggle(props: Omit<IconButtonProps, "icon" | "label" | "onClick" | "variant">): React.JSX.Element;
|
|
6
|
-
export declare function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant, topbarDivider, topbarTabs, topbarCurrentTab, topbarTabsLabel, topbarTabsEnd, topbarActions, topbarBlurred, layout, sidebarVariant, sidebarScrollbar, sidebarFooter, sidebarCollapsed, sidebarCollapsible, sidebarToggle, sidebarShortcut, sidebarIndicator, sidebarPanel, sidebarContrast, defaultSidebarCollapsed, onSidebarCollapsedChange, contentVariant, aside, asideLabel, asideVariant, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
6
|
+
export declare function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant, topbarDivider, topbarTabs, topbarCurrentTab, topbarTabsLabel, topbarTabsEnd, topbarActions, topbarBlurred, layout, sidebarVariant, sidebarScrollbar, sidebarFooter, sidebarCollapsed, sidebarCollapsible, sidebarToggle, sidebarShortcut, sidebarIndicator, sidebarPanel, sidebarContrast, defaultSidebarCollapsed, onSidebarCollapsedChange, contentVariant, highContrast, textSize, aside, asideLabel, asideVariant, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
7
7
|
brand: ReactNode;
|
|
8
8
|
navigation?: ReactNode;
|
|
9
9
|
navItems?: SidebarEntry[];
|
|
@@ -74,6 +74,13 @@ export declare function AppShell({ brand, navigation, navItems, currentNavId, na
|
|
|
74
74
|
/** AN-01: avisa cada troca — pelo botão, ou pela largura cruzando 1280. Guardar a preferência é do app. */
|
|
75
75
|
onSidebarCollapsedChange?: (collapsed: boolean) => void;
|
|
76
76
|
contentVariant?: "surface" | "plain";
|
|
77
|
+
/** O alto contraste no app inteiro (o MNT-30, ADR-0090): contorno nos campos, nos cartões e nos botões, o texto de apoio
|
|
78
|
+
* mais perto do texto e o link sempre sublinhado. Marca o `AppShell` e a raiz da página (`data-contrast="high"`), para
|
|
79
|
+
* os menus e as janelas, que abrem fora dele, saírem iguais. */
|
|
80
|
+
highContrast?: boolean;
|
|
81
|
+
/** O texto grande no app inteiro (o MNT-30, ADR-0090): cada letra 2 acima e o controle médio a 44. `data-text="lg"` no
|
|
82
|
+
* `AppShell` e na raiz da página, pela mesma razão do `highContrast`. */
|
|
83
|
+
textSize?: "md" | "lg";
|
|
77
84
|
}): React.JSX.Element;
|
|
78
85
|
export declare namespace AppShell {
|
|
79
86
|
export { AppShellSidebarToggle as SidebarToggle };
|
package/dist/layout-client.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
// Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
|
|
4
4
|
// do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
|
|
5
5
|
// internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
|
|
6
|
-
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
6
|
+
import React, { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
7
7
|
import { Separator as BaseSeparator } from "@base-ui/react/separator";
|
|
8
8
|
import { useDirection } from "@base-ui/react/direction-provider";
|
|
9
9
|
import { cx, useAureaStrings } from "./internal.js";
|
|
@@ -167,8 +167,25 @@ function ProvedorDaLateral({ ligado, children, ...p }) {
|
|
|
167
167
|
return ligado ? _jsx(Sidebar.Provider, { id: SHELL_NAV_ID, ...p, children: children }) : _jsx(_Fragment, { children: children });
|
|
168
168
|
}
|
|
169
169
|
function AppShellSidebarToggle(props) { return _jsx(Sidebar.Toggle, { ...props }); }
|
|
170
|
-
|
|
170
|
+
// O modo de leitura (o alto contraste, o texto grande) também na RAIZ da página (ADR-0090): menus, janelas e avisos abrem
|
|
171
|
+
// fora do `AppShell`, presos ao fim do documento, e sem isto sairiam sem o modo. Devolve a raiz como estava ao sair.
|
|
172
|
+
function useModoNaRaiz(atributo, valor) {
|
|
173
|
+
React.useEffect(() => {
|
|
174
|
+
if (valor === undefined || typeof document === "undefined")
|
|
175
|
+
return;
|
|
176
|
+
const raiz = document.documentElement;
|
|
177
|
+
const antes = raiz.getAttribute(atributo);
|
|
178
|
+
raiz.setAttribute(atributo, valor);
|
|
179
|
+
return () => { if (antes === null)
|
|
180
|
+
raiz.removeAttribute(atributo);
|
|
181
|
+
else
|
|
182
|
+
raiz.setAttribute(atributo, antes); };
|
|
183
|
+
}, [atributo, valor]);
|
|
184
|
+
}
|
|
185
|
+
export function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant = "floating", topbarDivider, topbarTabs, topbarCurrentTab, topbarTabsLabel, topbarTabsEnd, topbarActions, topbarBlurred, layout = "app", sidebarVariant, sidebarScrollbar, sidebarFooter, sidebarCollapsed, sidebarCollapsible, sidebarToggle = "edge", sidebarShortcut = true, sidebarIndicator, sidebarPanel, sidebarContrast, defaultSidebarCollapsed, onSidebarCollapsedChange, contentVariant = "surface", highContrast, textSize, aside, asideLabel, asideVariant, children, className, ...props }) {
|
|
171
186
|
const s = useAureaStrings();
|
|
187
|
+
useModoNaRaiz("data-contrast", highContrast ? "high" : undefined);
|
|
188
|
+
useModoNaRaiz("data-text", textSize === "lg" ? "lg" : undefined);
|
|
172
189
|
const docs = layout === "docs", lateral = layout === "sidebar";
|
|
173
190
|
useGavetaFechaNoDesktop(docs);
|
|
174
191
|
const controlado = sidebarCollapsed !== undefined;
|
|
@@ -224,7 +241,7 @@ export function AppShell({ brand, navigation, navItems, currentNavId, navLabel,
|
|
|
224
241
|
// No arranjo, o topo é o pequeno (`size="sm"`), e o botão também: com o tamanho de sempre ele ficava 4 maior que os
|
|
225
242
|
// vizinhos e fora da linha deles (medido a 375, sem toque; com toque, todos sobem para 44).
|
|
226
243
|
const botaoDaGaveta = _jsx(IconButton, { className: "nav-toggle", icon: "list", label: s.navigationToggle, popoverTarget: SHELL_NAV_ID, ...(docs ? { size: "sm" } : {}) });
|
|
227
|
-
return _jsx("div", { className: cx("app-shell", layout === "docs" && "app-shell-docs", lateral && "app-shell-lateral", !lateral && topbarVariant === "flush" && "app-shell-flush", !lateral && topbarVariant === "pill" && "app-shell-pill", recolhe && "app-shell-collapsible", aside != null && "app-shell-with-aside", className), ...props, children: _jsxs(ProvedorDaLateral, { ligado: recolhe, collapsed: efetiva, onCollapsedChange: definir, collapsible: forma, shortcut: sidebarShortcut, children: [_jsx("a", { className: "skip-link", href: `#${SHELL_MAIN_ID}`, children: s.skipToContent }), !lateral && _jsxs(Header, { variant: topbarVariant, divider: topbarDivider, tabs: topbarTabs, currentTab: topbarCurrentTab, tabsLabel: topbarTabsLabel, tabsEnd: topbarTabsEnd, ...(docs ? { maxWidth: "docs", ...(topbarVariant === "pill" ? {} : { size: "sm" }) } : topbarVariant === "pill" ? { maxWidth: "full" } : {}), blurred: topbarBlurred, brand: docs ? brand : _jsxs(_Fragment, { children: [botaoDaGaveta, brand] }), actions: topbarActions != null ? _jsxs(_Fragment, { children: [topbarActions, docs && botaoDaGaveta] }) : undefined, children: [topbar, docs && topbarActions == null && botaoDaGaveta] }), recolhe && !lateral && sidebarToggle !== "none" && _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, collapsible: forma, indicator: sidebarIndicator, panel: sidebarPanel, contrast: sidebarContrast, items: navItems, current: currentNavId, label: navLabel, onToggle: focoDaGaveta, header: lateral ? brand : undefined, footer: sidebarFooter, children: navigation }), docs && aside != null && _jsx("div", { className: "docs-toc-bar", children: _jsxs("button", { type: "button", className: "docs-toc-toggle", popoverTarget: SHELL_TOC_ID, children: [_jsx(Icon, { name: "list-bullets" }), _jsx("span", { className: "docs-toc-current", "data-toc-current": "", children: asideLabel ?? s.tocLabel }), _jsx(Icon, { name: "caret-down" })] }) }), lateral
|
|
244
|
+
return _jsx("div", { "data-contrast": highContrast ? "high" : undefined, "data-text": textSize === "lg" ? "lg" : undefined, className: cx("app-shell", layout === "docs" && "app-shell-docs", lateral && "app-shell-lateral", !lateral && topbarVariant === "flush" && "app-shell-flush", !lateral && topbarVariant === "pill" && "app-shell-pill", recolhe && "app-shell-collapsible", aside != null && "app-shell-with-aside", className), ...props, children: _jsxs(ProvedorDaLateral, { ligado: recolhe, collapsed: efetiva, onCollapsedChange: definir, collapsible: forma, shortcut: sidebarShortcut, children: [_jsx("a", { className: "skip-link", href: `#${SHELL_MAIN_ID}`, children: s.skipToContent }), !lateral && _jsxs(Header, { variant: topbarVariant, divider: topbarDivider, tabs: topbarTabs, currentTab: topbarCurrentTab, tabsLabel: topbarTabsLabel, tabsEnd: topbarTabsEnd, ...(docs ? { maxWidth: "docs", ...(topbarVariant === "pill" ? {} : { size: "sm" }) } : topbarVariant === "pill" ? { maxWidth: "full" } : {}), blurred: topbarBlurred, brand: docs ? brand : _jsxs(_Fragment, { children: [botaoDaGaveta, brand] }), actions: topbarActions != null ? _jsxs(_Fragment, { children: [topbarActions, docs && botaoDaGaveta] }) : undefined, children: [topbar, docs && topbarActions == null && botaoDaGaveta] }), recolhe && !lateral && sidebarToggle !== "none" && _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, collapsible: forma, indicator: sidebarIndicator, panel: sidebarPanel, contrast: sidebarContrast, items: navItems, current: currentNavId, label: navLabel, onToggle: focoDaGaveta, header: lateral ? brand : undefined, footer: sidebarFooter, children: navigation }), docs && aside != null && _jsx("div", { className: "docs-toc-bar", children: _jsxs("button", { type: "button", className: "docs-toc-toggle", popoverTarget: SHELL_TOC_ID, children: [_jsx(Icon, { name: "list-bullets" }), _jsx("span", { className: "docs-toc-current", "data-toc-current": "", children: asideLabel ?? s.tocLabel }), _jsx(Icon, { name: "caret-down" })] }) }), lateral
|
|
228
245
|
? _jsxs("div", { className: "app-shell-column", children: [topbar != null || topbarActions != null
|
|
229
246
|
? _jsx(Header, { variant: "flush", size: "sm", divider: topbarDivider, brand: _jsxs(_Fragment, { children: [botaoDaGaveta, sidebarToggle !== "none" && _jsx(AppShellSidebarToggle, {})] }), actions: topbarActions, children: topbar })
|
|
230
247
|
: _jsxs("div", { className: "app-shell-narrow-bar", children: [botaoDaGaveta, brand] }), _jsx("main", { id: SHELL_MAIN_ID, tabIndex: -1, className: cx("content", contentVariant === "plain" && "content-plain"), children: children })] })
|
package/dist/markup.d.ts
CHANGED
|
@@ -200,7 +200,8 @@ export interface KPIProps extends HTMLAttributes<HTMLDivElement>, RefAttributes<
|
|
|
200
200
|
/** As barras embaixo do número (o site B, 0.54.0): cada uma é um botão de opção nativo; a escolhida fica branca no
|
|
201
201
|
* `brand` (na cor do texto, fora dele) e a leitura diz o nome e o valor dela. */
|
|
202
202
|
bars?: KPIBar[];
|
|
203
|
-
/** A barra que começa escolhida (o índice). Padrão: a última.
|
|
203
|
+
/** A barra que começa escolhida (o índice). Padrão: a última. Dois `KPI` com o mesmo título E as mesmas barras na
|
|
204
|
+
* mesma página precisam de `id` diferentes, para cada um guardar a sua barra. */
|
|
204
205
|
defaultBar?: number;
|
|
205
206
|
}
|
|
206
207
|
export declare function KPI({ label, value, trend, direction, tone, directionLabel, variant, bars, defaultBar, className, ...props }: KPIProps): React.JSX.Element;
|
package/dist/markup.js
CHANGED
|
@@ -119,6 +119,10 @@ export function Section({ surface = "background", spacing = "md", theme, variant
|
|
|
119
119
|
}
|
|
120
120
|
const TOM_DA_DIRECAO = { up: "success", down: "danger", flat: "neutral" };
|
|
121
121
|
const PALAVRA_DA_DIRECAO = { up: "Up", down: "Down", flat: "No change" };
|
|
122
|
+
// Uma assinatura curta e determinística de um texto (djb2, em base 36): o mesmo texto dá o mesmo nome no servidor e no
|
|
123
|
+
// navegador, sem hook.
|
|
124
|
+
const assinatura = (texto) => { let h = 5381; for (let i = 0; i < texto.length; i++)
|
|
125
|
+
h = ((h << 5) + h + texto.charCodeAt(i)) | 0; return (h >>> 0).toString(36); };
|
|
122
126
|
export function KPI({ label, value, trend, direction, tone, directionLabel, variant = "card", bars, defaultBar, className, ...props }) {
|
|
123
127
|
const tom = direction ? (tone ?? TOM_DA_DIRECAO[direction]) : undefined;
|
|
124
128
|
const miolo = _jsxs(_Fragment, { children: [_jsx("span", { className: "muted", children: label }), _jsx("strong", { children: value }), (trend || trend === 0 || direction) && _jsxs("small", { className: direction ? cx("kpi-trend", `kpi-trend-${direction}`, `kpi-tone-${tom}`) : undefined, children: [direction && _jsx("span", { className: "kpi-trend-icon", "aria-hidden": "true" }), direction && _jsx("span", { className: "sr-only", children: directionLabel ?? PALAVRA_DA_DIRECAO[direction] }), trend] })] });
|
|
@@ -127,8 +131,13 @@ export function KPI({ label, value, trend, direction, tone, directionLabel, vari
|
|
|
127
131
|
const leituraDe = (b) => `${b.label} · ${typeof b.value === "string" || typeof b.value === "number" ? b.value : ""}`;
|
|
128
132
|
// As barras são botões de opção NATIVOS (o KPI é marcação pura, roda no servidor): o grupo é um `fieldset` com o rótulo
|
|
129
133
|
// do KPI, cada barra o `label` de um rádio com a leitura dela; o CSS mostra a leitura da escolhida e a da que está sob o
|
|
130
|
-
// mouse.
|
|
131
|
-
|
|
134
|
+
// mouse.
|
|
135
|
+
// O `name` do grupo (auditoria externa de 05/11/2026): vinha SÓ do rótulo, e dois KPI com o mesmo título — ou com o
|
|
136
|
+
// título em JSX, que virava "x" — caíam no mesmo grupo de rádios; medido no Chromium e no Firefox, o primeiro já
|
|
137
|
+
// perdia a barra escolhida ao carregar. Marcação pura não chama `useId` (a mesma regra do `menuId` do `Header`), então
|
|
138
|
+
// o nome junta o `id` do KPI, quando o app passa um, o rótulo e uma assinatura das próprias barras: dois KPI com dados
|
|
139
|
+
// diferentes nunca colidem, e dois idênticos se separam pelo `id`.
|
|
140
|
+
const nomeDoGrupo = bars && bars.length > 0 ? `kpi-bars-${props.id ? `${props.id}-` : ""}${typeof label === "string" ? label.toLowerCase().replace(/[^a-z0-9]+/g, "-") : "x"}-${assinatura(bars.map(b => `${b.label}:${b.amount}`).join("|"))}` : "";
|
|
132
141
|
const barras = bars && bars.length > 0 && _jsxs("fieldset", { className: "kpi-bars-wrap", children: [typeof label === "string" && _jsx("legend", { className: "sr-only", children: label }), _jsx("div", { className: "kpi-bars", style: { "--kpi-bars": bars.length }, children: bars.map((b, i) => _jsxs("label", { className: "kpi-bar", children: [_jsx("input", { type: "radio", className: "kpi-bar-input", name: nomeDoGrupo, value: String(i), defaultChecked: i === escolhida, "aria-label": leituraDe(b) }), _jsx("span", { className: "kpi-bar-fill", style: { "--kpi-bar-h": `${Math.round(b.amount / maior * 100)}%` } }), _jsx("span", { className: "kpi-bar-reading", "aria-hidden": "true", children: leituraDe(b) })] }, i)) }), _jsx("div", { className: "kpi-bars-x", "aria-hidden": "true", style: { "--kpi-bars": bars.length }, children: bars.map((b, i) => _jsx("span", { children: b.label }, i)) })] });
|
|
133
142
|
return variant === "plain" ? _jsxs("div", { className: cx("kpi", className), ...props, children: [miolo, barras] })
|
|
134
143
|
: _jsxs(Card, { className: cx("kpi", variant === "brand" && "kpi-brand", className), ...props, children: [miolo, barras] });
|
|
@@ -10,7 +10,7 @@ import { Autocomplete as BaseAutocomplete } from "@base-ui/react/autocomplete";
|
|
|
10
10
|
import { Dialog as BaseDialog } from "@base-ui/react/dialog";
|
|
11
11
|
import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
12
12
|
import { useValorResponsivo } from "./responsivo-runtime.js";
|
|
13
|
-
import { cx, fundirRender, useAureaStrings, usePortalContainer } from "./internal.js";
|
|
13
|
+
import { cx, fundirRender, useAureaStrings, usePortalContainer, useDeslize } from "./internal.js";
|
|
14
14
|
import { Kbd, ProgressCircle } from "./markup.js";
|
|
15
15
|
import { Icon } from "./system.js";
|
|
16
16
|
import { Button, IconButton } from "./actions.js";
|
|
@@ -37,7 +37,7 @@ export function Stepper({ items, label, variant = "default", startContent, endCo
|
|
|
37
37
|
return lista;
|
|
38
38
|
}
|
|
39
39
|
export function Breadcrumb({ items, label }) { const s = useAureaStrings(); return _jsx("nav", { className: "breadcrumb", "aria-label": label ?? s.breadcrumbLabel, children: items.map((i, n) => _jsxs(React.Fragment, { children: [n > 0 && _jsx(Icon, { name: "caret-right", size: "sm" }), " ", i.render ? fundirRender(i.render, { children: i.label }, "a") : i.href ? _jsx("a", { href: i.href, children: i.label }) : _jsx("strong", { "aria-current": "page", children: i.label })] }, n)) }); }
|
|
40
|
-
export function Tabs({ tabs, value, onChange, label, orientation, activateOnFocus = true, loopFocus, panel = "card", variant = "primary" }) { const s = useAureaStrings(); const ancora = useRef(null); const resolvida = useValorResponsivo(orientation, "horizontal", ancora); return _jsxs(BaseTabs.Root, { ref: ancora, value: value, onValueChange: v => onChange(String(v)), orientation: resolvida, className: "tabs-root", children: [_jsx(BaseTabs.List, { className: cx("tabs", variant === "secondary" && "tabs-secondary"), "aria-label": label ?? s.tabsLabel, activateOnFocus: activateOnFocus, loopFocus: loopFocus, children: tabs.map(t => _jsx(BaseTabs.Tab, { value: t.id, className: "tab", children: t.label }, t.id)) }), tabs.map(t => _jsx(BaseTabs.Panel, { value: t.id, className: panel === "plain" ? "tabs-panel" : "card card-inset", tabIndex: 0, children: t.content }, t.id))] }); }
|
|
40
|
+
export function Tabs({ tabs, value, onChange, label, orientation, activateOnFocus = true, loopFocus, panel = "card", variant = "primary" }) { const s = useAureaStrings(); const ancora = useRef(null); const resolvida = useValorResponsivo(orientation, "horizontal", ancora); const lista = useRef(null); useDeslize(lista, ".tab[data-active]", value); return _jsxs(BaseTabs.Root, { ref: ancora, value: value, onValueChange: v => onChange(String(v)), orientation: resolvida, className: "tabs-root", children: [_jsx(BaseTabs.List, { ref: lista, className: cx("tabs", variant === "secondary" && "tabs-secondary"), "aria-label": label ?? s.tabsLabel, activateOnFocus: activateOnFocus, loopFocus: loopFocus, children: tabs.map(t => _jsx(BaseTabs.Tab, { value: t.id, className: "tab", children: t.label }, t.id)) }), tabs.map(t => _jsx(BaseTabs.Panel, { value: t.id, className: panel === "plain" ? "tabs-panel" : "card card-inset", tabIndex: 0, children: t.content }, t.id))] }); }
|
|
41
41
|
function paginasVisiveis(page, total, siblings) {
|
|
42
42
|
if (total <= 5 + siblings * 2)
|
|
43
43
|
return Array.from({ length: total }, (_, i) => i + 1);
|
package/dist/overlays.d.ts
CHANGED
|
@@ -76,6 +76,10 @@ interface MenuItemBase {
|
|
|
76
76
|
label: ReactNode;
|
|
77
77
|
disabled?: boolean;
|
|
78
78
|
leadingIcon?: IconName;
|
|
79
|
+
/** 0.55.0: a explicação embaixo do nome, na letra pequena (a anatomia do item da referência). */
|
|
80
|
+
description?: ReactNode;
|
|
81
|
+
/** 0.55.0: o ícone na ponta do item — por exemplo, o "abre fora" de um link que abre outro site. */
|
|
82
|
+
trailingIcon?: IconName;
|
|
79
83
|
}
|
|
80
84
|
/** Item de ação — a forma de sempre. `kind` é opcional para não quebrar quem já usa. */
|
|
81
85
|
export interface MenuActionDef extends MenuItemBase {
|
|
@@ -93,6 +97,8 @@ export interface MenuLinkDef {
|
|
|
93
97
|
kind: "link";
|
|
94
98
|
label: ReactNode;
|
|
95
99
|
leadingIcon?: IconName;
|
|
100
|
+
description?: ReactNode;
|
|
101
|
+
trailingIcon?: IconName;
|
|
96
102
|
href: string;
|
|
97
103
|
target?: string;
|
|
98
104
|
rel?: string;
|
|
@@ -137,10 +143,12 @@ export type MenuEntry = MenuActionDef | MenuLinkDef | MenuCheckboxDef | MenuRadi
|
|
|
137
143
|
/** @deprecated desde 28/08/2026 — use `MenuEntry`. Mantido porque `MenuActionDef` É a forma
|
|
138
144
|
* antiga: quem tipava com `MenuItemDef` continua compilando. */
|
|
139
145
|
export type MenuItemDef = MenuActionDef;
|
|
140
|
-
export declare function DropdownMenu({ trigger, items, side, label }: {
|
|
146
|
+
export declare function DropdownMenu({ trigger, items, side, align, label }: {
|
|
141
147
|
trigger: ReactElement;
|
|
142
148
|
items: MenuEntry[];
|
|
143
149
|
side?: OverlaySide;
|
|
150
|
+
/** 0.55.0: por onde o menu se alinha ao gatilho — `"end"` encosta a ponta dele na ponta do botão (a referência, no botão dividido). */
|
|
151
|
+
align?: "start" | "center" | "end";
|
|
144
152
|
label?: string;
|
|
145
153
|
}): React.JSX.Element;
|
|
146
154
|
export declare function ContextMenu({ children, items, label, className }: {
|
package/dist/overlays.js
CHANGED
|
@@ -150,26 +150,31 @@ const marca = (children) => _jsx("span", { className: "menu-mark", "aria-hidden"
|
|
|
150
150
|
// container configurado pelo provider e o submenu escaparia para o `document.body`. Duas
|
|
151
151
|
// camadas do mesmo menu em containers diferentes é o tipo de divergência que só aparece quando
|
|
152
152
|
// alguém monta a Aurea dentro de um shadow root ou de um diálogo nativo.
|
|
153
|
-
|
|
153
|
+
// O item com `description` (0.55.0) se anuncia como o da referência: o NOME é só o rótulo e a explicação é a DESCRIÇÃO
|
|
154
|
+
// (`aria-labelledby` + `aria-describedby`). Sem isso o leitor de tela lia os dois colados ("View as MarkdownView page as…").
|
|
155
|
+
// `ids` é o `useId` de quem monta o menu, mais a posição do item: estável no servidor e no navegador.
|
|
156
|
+
const nomeDoItem = (x, id) => x.description != null ? { "aria-labelledby": `${id}-l`, "aria-describedby": `${id}-d` } : {};
|
|
157
|
+
function renderMenuItems(items, portal, ids = "") {
|
|
154
158
|
return items.map((it, i) => {
|
|
155
159
|
if (it === "separator")
|
|
156
160
|
return _jsx(BaseMenu.Separator, { className: "menu-sep" }, i);
|
|
157
161
|
const kind = it.kind ?? "item";
|
|
158
|
-
const
|
|
162
|
+
const id = `${ids}-${i}`;
|
|
163
|
+
const dentro = (x, xid = id) => _jsxs(_Fragment, { children: [x.leadingIcon && _jsx(Icon, { name: x.leadingIcon }), x.description != null ? _jsxs("span", { className: "menu-item-text", children: [_jsx("span", { id: `${xid}-l`, children: x.label }), _jsx("span", { id: `${xid}-d`, className: "menu-item-description", children: x.description })] }) : x.label, x.trailingIcon && _jsx(Icon, { name: x.trailingIcon, className: "menu-item-trailing" })] });
|
|
159
164
|
switch (kind) {
|
|
160
165
|
case "group": {
|
|
161
166
|
const g = it;
|
|
162
|
-
return _jsxs(BaseMenu.Group, { children: [g.label && _jsx(BaseMenu.GroupLabel, { className: "menu-label", children: g.label }), renderMenuItems(g.items, portal)] }, i);
|
|
167
|
+
return _jsxs(BaseMenu.Group, { children: [g.label && _jsx(BaseMenu.GroupLabel, { className: "menu-label", children: g.label }), renderMenuItems(g.items, portal, id)] }, i);
|
|
163
168
|
}
|
|
164
169
|
case "radiogroup": {
|
|
165
170
|
const g = it;
|
|
166
|
-
return _jsxs(BaseMenu.RadioGroup, { value: g.value, defaultValue: g.defaultValue, onValueChange: v => g.onValueChange?.(String(v)), children: [g.label && _jsx(BaseMenu.GroupLabel, { className: "menu-label", children: g.label }), g.items.map((r, j) => _jsxs(BaseMenu.RadioItem, { className: "menu-item", value: r.value, disabled: r.disabled, children: [marca(_jsx(BaseMenu.RadioItemIndicator, { className: "menu-ponto" })), dentro(r)] }, j))] }, i);
|
|
171
|
+
return _jsxs(BaseMenu.RadioGroup, { value: g.value, defaultValue: g.defaultValue, onValueChange: v => g.onValueChange?.(String(v)), children: [g.label && _jsx(BaseMenu.GroupLabel, { className: "menu-label", children: g.label }), g.items.map((r, j) => _jsxs(BaseMenu.RadioItem, { className: "menu-item", value: r.value, disabled: r.disabled, ...nomeDoItem(r, `${id}-${j}`), children: [marca(_jsx(BaseMenu.RadioItemIndicator, { className: "menu-ponto" })), dentro(r, `${id}-${j}`)] }, j))] }, i);
|
|
167
172
|
}
|
|
168
173
|
case "submenu": {
|
|
169
174
|
const sm = it;
|
|
170
175
|
// O submenu é uma RAIZ própria com gatilho próprio — não é um item que "abre outro menu".
|
|
171
176
|
// É o que dá `aria-haspopup`, a seta para a direita e o fechamento em cascata de graça.
|
|
172
|
-
return _jsxs(BaseMenu.SubmenuRoot, { children: [_jsxs(BaseMenu.SubmenuTrigger, { className: "menu-item", disabled: sm.disabled, children: [dentro(sm), _jsx(Icon, { name: "caret-right", size: "sm", className: "menu-seta" })] }), _jsx(BaseMenu.Portal, { container: portal, children: _jsx(BaseMenu.Positioner, { className: "positioner", side: "inline-end", sideOffset: 4, children: _jsx(BaseMenu.Popup, { className: "menu", children: renderMenuItems(sm.items, portal) }) }) })] }, i);
|
|
177
|
+
return _jsxs(BaseMenu.SubmenuRoot, { children: [_jsxs(BaseMenu.SubmenuTrigger, { className: "menu-item", disabled: sm.disabled, ...nomeDoItem(sm, id), children: [dentro(sm), _jsx(Icon, { name: "caret-right", size: "sm", className: "menu-seta" })] }), _jsx(BaseMenu.Portal, { container: portal, children: _jsx(BaseMenu.Positioner, { className: "positioner", side: "inline-end", sideOffset: 4, children: _jsx(BaseMenu.Popup, { className: "menu", children: renderMenuItems(sm.items, portal, id) }) }) })] }, i);
|
|
173
178
|
}
|
|
174
179
|
case "checkbox": {
|
|
175
180
|
const c = it;
|
|
@@ -178,26 +183,26 @@ function renderMenuItems(items, portal) {
|
|
|
178
183
|
// assinatura pública da Aurea mudar junto com uma versão do motor. Mesma decisão do
|
|
179
184
|
// `onValueChange` do grupo de rádio. O teste pegou: sem o embrulho, o `vi.fn()` recebia um
|
|
180
185
|
// `PointerEvent` de brinde.
|
|
181
|
-
return _jsxs(BaseMenu.CheckboxItem, { className: "menu-item", disabled: c.disabled, checked: c.checked, defaultChecked: c.defaultChecked, onCheckedChange: v => c.onCheckedChange?.(v), children: [marca(_jsx(BaseMenu.CheckboxItemIndicator, { children: _jsx(Icon, { name: "check", size: "sm" }) })), dentro(c)] }, i);
|
|
186
|
+
return _jsxs(BaseMenu.CheckboxItem, { className: "menu-item", disabled: c.disabled, checked: c.checked, defaultChecked: c.defaultChecked, onCheckedChange: v => c.onCheckedChange?.(v), ...nomeDoItem(c, id), children: [marca(_jsx(BaseMenu.CheckboxItemIndicator, { children: _jsx(Icon, { name: "check", size: "sm" }) })), dentro(c)] }, i);
|
|
182
187
|
}
|
|
183
188
|
case "link": {
|
|
184
189
|
const l = it;
|
|
185
|
-
return _jsx(BaseMenu.LinkItem, { className: "menu-item", href: l.href, target: l.target, rel: l.rel, closeOnClick: l.closeOnClick ?? true, children: dentro(l) }, i);
|
|
190
|
+
return _jsx(BaseMenu.LinkItem, { className: "menu-item", href: l.href, target: l.target, rel: l.rel, closeOnClick: l.closeOnClick ?? true, ...nomeDoItem(l, id), children: dentro(l) }, i);
|
|
186
191
|
}
|
|
187
192
|
default: {
|
|
188
193
|
const a = it;
|
|
189
|
-
return _jsx(BaseMenu.Item, { className: "menu-item", disabled: a.disabled, onClick: a.onClick, render: a.render, children: dentro(a) }, i);
|
|
194
|
+
return _jsx(BaseMenu.Item, { className: "menu-item", disabled: a.disabled, onClick: a.onClick, render: a.render, ...nomeDoItem(a, id), children: dentro(a) }, i);
|
|
190
195
|
}
|
|
191
196
|
}
|
|
192
197
|
});
|
|
193
198
|
}
|
|
194
|
-
export function DropdownMenu({ trigger, items, side = "bottom", label }) { const portal = usePortalContainer(); return _jsxs(BaseMenu.Root, { children: [_jsx(BaseMenu.Trigger, { render: trigger }), _jsx(BaseMenu.Portal, { container: portal, children: _jsx(BaseMenu.Positioner, { className: "positioner", side: side, sideOffset: 6, children: _jsx(BaseMenu.Popup, { className: "menu", "aria-label": label, children: renderMenuItems(items, portal) }) }) })] }); }
|
|
199
|
+
export function DropdownMenu({ trigger, items, side = "bottom", align = "center", label }) { const portal = usePortalContainer(); const ids = React.useId(); return _jsxs(BaseMenu.Root, { children: [_jsx(BaseMenu.Trigger, { render: trigger }), _jsx(BaseMenu.Portal, { container: portal, children: _jsx(BaseMenu.Positioner, { className: "positioner", side: side, align: align, sideOffset: 6, children: _jsx(BaseMenu.Popup, { className: "menu", "aria-label": label, children: renderMenuItems(items, portal, ids) }) }) })] }); }
|
|
195
200
|
// ContextMenu: abre no botão direito e — por teclado — em Shift+F10 / tecla Menu,
|
|
196
201
|
// que o browser só dispara (como evento contextmenu) sobre um elemento FOCADO. Por
|
|
197
202
|
// isso o gatilho é focável (tabIndex 0) e tem nome acessível (auditoria 18/07/2026,
|
|
198
203
|
// MÉDIO 3); sem isso o teclado não alcança o menu. Consumidor pode sobrescrever
|
|
199
204
|
// tabIndex via children se o próprio já for focável.
|
|
200
|
-
export function ContextMenu({ children, items, label, className }) { const portal = usePortalContainer(); return _jsxs(BaseContextMenu.Root, { children: [_jsx(BaseContextMenu.Trigger, { className: className, tabIndex: 0, "aria-label": label, "aria-haspopup": "menu", children: children }), _jsx(BaseContextMenu.Portal, { container: portal, children: _jsx(BaseContextMenu.Positioner, { className: "positioner", children: _jsx(BaseContextMenu.Popup, { className: "menu", "aria-label": label, children: renderMenuItems(items, portal) }) }) })] }); }
|
|
205
|
+
export function ContextMenu({ children, items, label, className }) { const portal = usePortalContainer(); const ids = React.useId(); return _jsxs(BaseContextMenu.Root, { children: [_jsx(BaseContextMenu.Trigger, { className: className, tabIndex: 0, "aria-label": label, "aria-haspopup": "menu", children: children }), _jsx(BaseContextMenu.Portal, { container: portal, children: _jsx(BaseContextMenu.Positioner, { className: "positioner", children: _jsx(BaseContextMenu.Popup, { className: "menu", "aria-label": label, children: renderMenuItems(items, portal, ids) }) }) })] }); }
|
|
201
206
|
// G-AXIS-06 — o valor responsivo RESOLVIDO é a única fonte de verdade: ele vai para o motor, o
|
|
202
207
|
// motor publica `data-orientation`/`aria-orientation` e ajusta o teclado, e a pele reage ao
|
|
203
208
|
// atributo publicado. Ver `decisions/0047-css-first-para-apresentacao-runtime-para-semantica.md`.
|
|
@@ -206,5 +211,6 @@ export function Menubar({ menus, label, modal, disabled, loopFocus, orientation,
|
|
|
206
211
|
const ancora = useRef(null);
|
|
207
212
|
const resolvida = useValorResponsivo(orientation, "horizontal", ancora);
|
|
208
213
|
const vertical = resolvida === "vertical";
|
|
209
|
-
|
|
214
|
+
const ids = React.useId();
|
|
215
|
+
return _jsx(BaseMenubar, { ref: ancora, modal: modal, disabled: disabled, loopFocus: loopFocus, orientation: resolvida, "aria-label": label, className: cx("menubar", className), children: menus.map((m, i) => _jsxs(BaseMenu.Root, { disabled: m.disabled, children: [_jsx(BaseMenu.Trigger, { className: "menubar-trigger", children: m.label }), _jsx(BaseMenu.Portal, { container: portal, children: _jsx(BaseMenu.Positioner, { className: "positioner", side: vertical ? "inline-end" : "bottom", align: "start", sideOffset: 4, children: _jsx(BaseMenu.Popup, { className: "menu", children: renderMenuItems(m.items, portal, `${ids}-${i}`) }) }) })] }, i)) });
|
|
210
216
|
}
|