@aurea-uds/react 0.56.0 → 0.61.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/code-editor.js +1 -1
- 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.js +5 -3
- package/dist/markup.d.ts +2 -1
- package/dist/markup.js +14 -3
- package/dist/navigation-client.js +2 -2
- package/dist/overlays.js +1 -1
- package/dist/responsivo-runtime.js +2 -1
- 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;
|
package/dist/code-editor.js
CHANGED
|
@@ -38,7 +38,7 @@ import { cx, useAureaStrings } from "./internal.js";
|
|
|
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
40
|
"&": { backgroundColor: "var(--surface-inset)", color: "var(--foreground)", border: "var(--border-width) solid transparent", borderRadius: "var(--radius-lg)", fontSize: "var(--text-xs)" },
|
|
41
|
-
"&.cm-focused": { outline: "
|
|
41
|
+
"&.cm-focused": { outline: "var(--focus-width) solid var(--focus-strong)", outlineOffset: "calc(-1 * var(--focus-width))" },
|
|
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)" },
|
|
@@ -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.js
CHANGED
|
@@ -112,9 +112,9 @@ function useGavetaFechaNoDesktop(docs = false) {
|
|
|
112
112
|
}
|
|
113
113
|
catch { /* motor sem popover */ }
|
|
114
114
|
};
|
|
115
|
-
const pares = [[window.matchMedia(`(min-width: ${docs ? ESCALA.md : ESCALA.lg}
|
|
115
|
+
const pares = [[window.matchMedia(`(min-width: ${(docs ? ESCALA.md : ESCALA.lg) / 16}em)`), fechar(SHELL_NAV_ID)]];
|
|
116
116
|
if (docs)
|
|
117
|
-
pares.push([window.matchMedia(`(min-width: ${ESCALA.xl}
|
|
117
|
+
pares.push([window.matchMedia(`(min-width: ${ESCALA.xl / 16}em)`), fechar(SHELL_TOC_ID)]);
|
|
118
118
|
for (const [mq, f] of pares)
|
|
119
119
|
mq.addEventListener?.("change", f);
|
|
120
120
|
return () => { for (const [mq, f] of pares)
|
|
@@ -142,7 +142,9 @@ function useGavetaFechaNoDesktop(docs = false) {
|
|
|
142
142
|
//
|
|
143
143
|
// Tudo LIGADO por `sidebarCollapsible`: sem ele o shell é o de antes, e quem já usa
|
|
144
144
|
// `sidebarCollapsed` com o próprio botão não ganha um segundo.
|
|
145
|
-
|
|
145
|
+
// Em `em`, como o CSS (06/11/2026, o texto a 200%): `em` numa consulta de mídia é a fonte que a pessoa escolheu no
|
|
146
|
+
// navegador — com a fonte em dobro, a tela "encolhe" para a metade, e o JS tem de concordar com o CSS.
|
|
147
|
+
const MEDIA_MEDIA = `(min-width: ${ESCALA.lg / 16}em) and (max-width: ${(ESCALA.xl - 1) / 16}em)`;
|
|
146
148
|
const MEDIA_ESTREITA = `(max-width: ${ESCALA.lg - 1}px)`;
|
|
147
149
|
// A largura pela API do navegador, e não por `resize`: o `useSyncExternalStore` dá a resposta certa
|
|
148
150
|
// já no primeiro desenho do lado do cliente, e no servidor (sem janela) diz "não" — a folha cuida da
|
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] });
|
|
@@ -210,7 +219,9 @@ export function MediaPlayerShell({ children, className, ...props }) { return _js
|
|
|
210
219
|
// O item da barra e o do painel são o MESMO elemento com a mesma pele: o atual é o atual nos dois.
|
|
211
220
|
function linksDoHeader(items, current) {
|
|
212
221
|
return _jsx("ul", { className: "topbar-links", children: items.map(it => {
|
|
213
|
-
|
|
222
|
+
// O `onClick` do item vale aqui também (06/11/2026): a `Sidebar` o liga e o `Header` o jogava fora — o mesmo `SidebarItem`
|
|
223
|
+
// fazia coisas diferentes nas duas peças. Não é hook: é a prop do `<a>`, que a marcação pura pode passar.
|
|
224
|
+
const p = { className: "topbar-link", "aria-current": it.id === current ? "page" : undefined, onClick: it.onClick, children: it.label };
|
|
214
225
|
return _jsx("li", { children: it.render ? fundirRender(it.render, p, "a") : _jsx("a", { href: it.href, ...p }) }, it.id);
|
|
215
226
|
}) });
|
|
216
227
|
}
|
|
@@ -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.js
CHANGED
|
@@ -212,5 +212,5 @@ export function Menubar({ menus, label, modal, disabled, loopFocus, orientation,
|
|
|
212
212
|
const resolvida = useValorResponsivo(orientation, "horizontal", ancora);
|
|
213
213
|
const vertical = resolvida === "vertical";
|
|
214
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,
|
|
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)) });
|
|
216
216
|
}
|
|
@@ -58,7 +58,8 @@ export function ContainerScope({ children, className, ...props }) {
|
|
|
58
58
|
const temMatchMedia = () => typeof window !== "undefined" && typeof window.matchMedia === "function";
|
|
59
59
|
const mqls = new Map();
|
|
60
60
|
const mql = (px) => {
|
|
61
|
-
|
|
61
|
+
// em `em`, como o CSS gerado (06/11/2026): a fonte que a pessoa escolheu no navegador entra na conta
|
|
62
|
+
const q = `(min-width:${px / 16}em)`;
|
|
62
63
|
let m = mqls.get(q);
|
|
63
64
|
if (!m) {
|
|
64
65
|
m = window.matchMedia(q);
|