@aurea-uds/react 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/actions.d.ts +33 -6
- package/dist/actions.js +38 -26
- package/dist/agents.js +1 -1
- package/dist/calendar.d.ts +38 -1
- package/dist/calendar.js +115 -2
- package/dist/code-client.d.ts +3 -1
- package/dist/code-client.js +23 -1
- package/dist/color.d.ts +92 -0
- package/dist/color.js +293 -0
- package/dist/disclosure.d.ts +19 -9
- package/dist/disclosure.js +8 -1
- package/dist/feedback-client.d.ts +11 -5
- package/dist/feedback-client.js +26 -15
- package/dist/graph-worker.d.ts +1 -0
- package/dist/graph-worker.js +17 -0
- package/dist/graph.d.ts +11 -1
- package/dist/graph.js +214 -19
- package/dist/icon-names.d.ts +7 -1
- package/dist/icon-names.js +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/inputs-client.d.ts +215 -2
- package/dist/inputs-client.js +504 -8
- package/dist/inputs.d.ts +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +4 -0
- package/dist/layout-client.d.ts +14 -2
- package/dist/layout-client.js +2 -2
- package/dist/markup.d.ts +89 -9
- package/dist/markup.js +48 -11
- package/dist/navigation-client.d.ts +31 -7
- package/dist/navigation-client.js +60 -16
- package/dist/overlays.d.ts +20 -6
- package/dist/overlays.js +65 -10
- package/dist/pure.d.ts +20 -0
- package/dist/pure.js +2 -2
- package/dist/system.d.ts +5 -2
- package/dist/system.js +17 -9
- package/package.json +8 -2
package/dist/inputs-client.js
CHANGED
|
@@ -11,16 +11,230 @@ import { Form as BaseForm } from "@base-ui/react/form";
|
|
|
11
11
|
import { Field as BaseField } from "@base-ui/react/field";
|
|
12
12
|
import { NumberField as BaseNumberField } from "@base-ui/react/number-field";
|
|
13
13
|
import { OTPField as BaseOTPField } from "@base-ui/react/otp-field";
|
|
14
|
+
import { Slider as BaseSlider } from "@base-ui/react/slider";
|
|
14
15
|
import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group";
|
|
15
16
|
import { Radio as BaseRadio } from "@base-ui/react/radio";
|
|
16
|
-
import {
|
|
17
|
+
import { useDirection } from "@base-ui/react/direction-provider";
|
|
18
|
+
import { cx, useAureaStrings, usePortalContainer, useReorder, controlesRotulaveis } from "./internal.js";
|
|
19
|
+
import { COLOR_CHANNELS, areaBackground, channelRange, colorName, corToHex, formatChannel, getChannel, hsbToRgb, parseColor, pureHue, setChannel, spaceOf, trackBackground } from "./color.js";
|
|
20
|
+
import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
17
21
|
// Os seis controles abaixo saíram deste arquivo no item O1 e moram no `markup.tsx`, sem a
|
|
18
22
|
// diretiva — são marcação, e quem escuta `onChange` é o consumidor. Voltam IMPORTADOS e não só
|
|
19
23
|
// reexportados porque o `ehControle` do `Field` compara por IDENTIDADE de referência: dois
|
|
20
24
|
// objetos diferentes com o mesmo nome quebrariam o rótulo do campo em silêncio.
|
|
21
|
-
import { Textarea, Checkbox, Radio, Switch, Range, InputGroup, InputGroupAddon } from "./markup.js";
|
|
25
|
+
import { Textarea, Checkbox, Radio, Switch, Range, InputGroup, InputGroupAddon, ColorSwatch } from "./markup.js";
|
|
22
26
|
import { Icon } from "./system.js";
|
|
23
27
|
import { IconButton } from "./actions.js";
|
|
28
|
+
// A FAMÍLIA DE COR (0.32.0, ADR-0069) — as peças INTERNAS moram aqui, no topo, antes do primeiro export (auxiliar
|
|
29
|
+
// entre exports entra na fatia de outro componente — regra do check 22). As públicas (`ColorArea`, `Slider` com
|
|
30
|
+
// `channel`, `ColorField`, `ColorPicker`) ficam com as irmãs delas, lá embaixo.
|
|
31
|
+
//
|
|
32
|
+
// A COR DE DENTRO é HSB com alfa (`Cor`, do `color.ts`), e não hex: o hex perde o matiz no cinza e no preto —
|
|
33
|
+
// puxar a saturação a 0 não pode trocar a cor do fundo da área. Os três espaços (RGB, HSL, HSB) e o alfa entram por
|
|
34
|
+
// ela, com as faixas de cada canal iguais às da biblioteca de acessibilidade em que a referência se apoia.
|
|
35
|
+
//
|
|
36
|
+
// A ÁREA (`AreaDeCor`) é o DESENHO acessível da referência, lido no código da biblioteca dela: um grupo com DOIS
|
|
37
|
+
// `input type="range"` escondidos, um por eixo, anunciados como controle 2D, e o valor dito com o NOME da cor. Só um
|
|
38
|
+
// dos dois fica na ordem do Tab (o do eixo que mexeu por último); o outro sai da árvore até a pessoa mexer nele pelo
|
|
39
|
+
// teclado. As teclas são as dela: setas (Shift: o salto do canal), Page Up/Down no eixo de cima, Home/End no de
|
|
40
|
+
// lado, invertidas na escrita da direita para a esquerda.
|
|
41
|
+
const no01 = (n) => Math.min(1, Math.max(0, n));
|
|
42
|
+
// COMO A PESSOA CHEGOU AQUI: a última interação foi teclado ou ponteiro? O anel de foco da área é só do teclado, e o
|
|
43
|
+
// foco que chega por código (a janela do `ColorPicker` o põe na área ao abrir; o arrasto o põe no `range`) não diz
|
|
44
|
+
// nada por si. É o jeito da biblioteca de acessibilidade em que a referência se apoia: ouvir o documento inteiro, uma
|
|
45
|
+
// vez, e lembrar a última modalidade. Medido em 13/10/2026: abrindo o seletor com o mouse, o anel acendia.
|
|
46
|
+
let modalidade = "teclado";
|
|
47
|
+
let ouvindoModalidade = false;
|
|
48
|
+
function ouvirModalidade() {
|
|
49
|
+
if (ouvindoModalidade || typeof document === "undefined")
|
|
50
|
+
return;
|
|
51
|
+
ouvindoModalidade = true;
|
|
52
|
+
document.addEventListener("keydown", (e) => { if (!e.metaKey && !e.ctrlKey && !e.altKey)
|
|
53
|
+
modalidade = "teclado"; }, true);
|
|
54
|
+
document.addEventListener("pointerdown", () => { modalidade = "ponteiro"; }, true);
|
|
55
|
+
}
|
|
56
|
+
/** O texto de entrada → a cor de dentro. Hex e `rgb()` não dizem o matiz do cinza: aí vale o matiz (e a saturação)
|
|
57
|
+
* da cor de antes. `hsl()`/`hsb()` dizem, e o que veio no texto vale. */
|
|
58
|
+
function corDoTexto(texto, anterior) {
|
|
59
|
+
const nova = texto ? parseColor(texto) : null;
|
|
60
|
+
if (!nova)
|
|
61
|
+
return anterior ?? { h: 0, s: 0, b: 1, a: 1 };
|
|
62
|
+
if (anterior && (nova.s === 0 || nova.b === 0) && !/^\s*hs[lb]/i.test(texto))
|
|
63
|
+
return { ...nova, h: anterior.h, s: nova.b === 0 ? anterior.s : nova.s };
|
|
64
|
+
return nova;
|
|
65
|
+
}
|
|
66
|
+
/** A cor que o `ColorPicker` reparte com as peças de dentro, quando o conteúdo da janela é montado por quem usa
|
|
67
|
+
* ("com campos", "com trilhas" — os exemplos da referência). */
|
|
68
|
+
const CorDoSeletor = React.createContext(null);
|
|
69
|
+
/** O estado de cor das peças: a cor de dentro por dentro, hex por fora. Controlado, se o hex que chega é o que a peça
|
|
70
|
+
* acabou de mandar, a cor de dentro vale (com o matiz); se é outro, vira cor guardando o matiz de antes no cinza.
|
|
71
|
+
* Dentro de um `ColorPicker`, a peça sem valor próprio usa a cor DELE. */
|
|
72
|
+
function useCor(value, defaultValue, onValueChange, onValueCommitted) {
|
|
73
|
+
const doSeletor = React.useContext(CorDoSeletor);
|
|
74
|
+
const [interno, setInterno] = React.useState(() => corDoTexto(defaultValue));
|
|
75
|
+
const ultimo = React.useRef(interno);
|
|
76
|
+
let cor = interno;
|
|
77
|
+
if (value !== undefined) {
|
|
78
|
+
const chegou = parseColor(value);
|
|
79
|
+
const mesmo = chegou !== null && corToHex(chegou) === corToHex(ultimo.current);
|
|
80
|
+
cor = mesmo ? ultimo.current : corDoTexto(value, ultimo.current);
|
|
81
|
+
}
|
|
82
|
+
ultimo.current = cor;
|
|
83
|
+
const avisar = (novo, fim) => { const h = corToHex(novo); onValueChange?.(h); if (fim)
|
|
84
|
+
onValueCommitted?.(h); };
|
|
85
|
+
if (doSeletor && value === undefined && defaultValue === undefined)
|
|
86
|
+
return [doSeletor.cor, (novo, fim) => { doSeletor.mudar(novo, fim); avisar(novo, fim); }];
|
|
87
|
+
const mudar = (novo, fim) => {
|
|
88
|
+
ultimo.current = novo;
|
|
89
|
+
// SEMPRE, e não só sem controle: no cinza o hex não muda com o matiz, o app devolve o MESMO valor e nada
|
|
90
|
+
// redesenharia — a trilha mexia e a área não (o teste do `ColorPicker` pegou). Este estado é o que redesenha;
|
|
91
|
+
// com controle, quem decide continua sendo o `value` (acima).
|
|
92
|
+
setInterno(novo);
|
|
93
|
+
avisar(novo, fim);
|
|
94
|
+
};
|
|
95
|
+
return [cor, mudar];
|
|
96
|
+
}
|
|
97
|
+
/** O espaço de uma peça de cor: o pedido, ou o que os canais exigem (vermelho/verde/azul no RGB, luminosidade no
|
|
98
|
+
* HSL, brilho no HSB). */
|
|
99
|
+
function espacoDosCanais(pedido, ...canais) {
|
|
100
|
+
if (pedido)
|
|
101
|
+
return pedido;
|
|
102
|
+
for (const ch of canais) {
|
|
103
|
+
const e = spaceOf(ch);
|
|
104
|
+
if (e !== "hsb" || ch === "brightness")
|
|
105
|
+
return e;
|
|
106
|
+
}
|
|
107
|
+
return "hsb";
|
|
108
|
+
}
|
|
109
|
+
function AreaDeCor({ cor, onCor, space, x: canalX, y: canalY, showDots, disabled, name, id, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby }) {
|
|
110
|
+
const s = useAureaStrings();
|
|
111
|
+
const rtl = useDirection() === "rtl";
|
|
112
|
+
const caixa = React.useRef(null);
|
|
113
|
+
const inX = React.useRef(null), inY = React.useRef(null);
|
|
114
|
+
const [arrastando, setArrastando] = React.useState(false);
|
|
115
|
+
const [focado, setFocado] = React.useState(null);
|
|
116
|
+
const [pelaTecla, setPelaTecla] = React.useState(false);
|
|
117
|
+
// O anel de foco é do TECLADO, como na referência: o foco que o arrasto põe no `range` não acende o anel.
|
|
118
|
+
const [peloPonteiro, setPeloPonteiro] = React.useState(false);
|
|
119
|
+
React.useEffect(ouvirModalidade, []);
|
|
120
|
+
const fx = channelRange(canalX), fy = channelRange(canalY);
|
|
121
|
+
const canalZ = COLOR_CHANNELS[space].find((ch) => ch !== canalX && ch !== canalY);
|
|
122
|
+
const vx = getChannel(cor, canalX, space), vy = getChannel(cor, canalY, space);
|
|
123
|
+
const px = (vx - fx.min) / (fx.max - fx.min), py = (vy - fy.min) / (fy.max - fy.min);
|
|
124
|
+
const hex = corToHex({ ...cor, a: 1 }), nome = colorName(hsbToRgb(cor), s);
|
|
125
|
+
// Antes de mexer, o leitor de tela ouve os três canais e o nome; depois de mexer pelo teclado, só o canal que
|
|
126
|
+
// mudou e o nome — como na referência (o resto ele já ouviu).
|
|
127
|
+
const fala = (ch, v) => `${s.colorChannel[ch]} ${formatChannel(ch, v)}`;
|
|
128
|
+
const textoDo = (eixo) => {
|
|
129
|
+
const a = fala(canalX, vx), b = fala(canalY, vy), c = fala(canalZ, getChannel(cor, canalZ, space));
|
|
130
|
+
const partes = pelaTecla ? [eixo === "x" ? a : b] : eixo === "x" ? [a, b, c] : [b, a, c];
|
|
131
|
+
return `${partes.join(", ")}, ${nome}`;
|
|
132
|
+
};
|
|
133
|
+
const rotulo = ariaLabel ? `${ariaLabel}, ${s.colorPicker}` : ariaLabelledby ? undefined : s.colorPicker;
|
|
134
|
+
// Na grade do passo do canal: somar passos em ponto flutuante acumula erro.
|
|
135
|
+
const naGrade = (v, f) => Math.round(Math.min(f.max, Math.max(f.min, v)) / f.step) * f.step;
|
|
136
|
+
const com = (nx, ny) => setChannel(setChannel(cor, canalX, nx, space), canalY, ny, space);
|
|
137
|
+
const pelaPosicao = (cx, cy, fim) => {
|
|
138
|
+
const r = caixa.current.getBoundingClientRect();
|
|
139
|
+
if (!r.width || !r.height)
|
|
140
|
+
return;
|
|
141
|
+
const x = no01((cx - r.left) / r.width), y = no01((cy - r.top) / r.height);
|
|
142
|
+
onCor(com(fx.min + (rtl ? 1 - x : x) * (fx.max - fx.min), fy.min + (1 - y) * (fy.max - fy.min)), fim);
|
|
143
|
+
};
|
|
144
|
+
const focar = (eixo) => { (eixo === "x" ? inX : inY).current?.focus({ preventScroll: true }); setFocado(eixo); };
|
|
145
|
+
const aoApertar = (e) => {
|
|
146
|
+
if (disabled || e.button !== 0)
|
|
147
|
+
return;
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
150
|
+
setArrastando(true);
|
|
151
|
+
setPelaTecla(false);
|
|
152
|
+
setPeloPonteiro(true);
|
|
153
|
+
focar("x");
|
|
154
|
+
pelaPosicao(e.clientX, e.clientY, false);
|
|
155
|
+
};
|
|
156
|
+
const aoMover = (e) => { if (arrastando)
|
|
157
|
+
pelaPosicao(e.clientX, e.clientY, false); };
|
|
158
|
+
const aoSoltar = (e) => { if (!arrastando)
|
|
159
|
+
return; setArrastando(false); pelaPosicao(e.clientX, e.clientY, true); };
|
|
160
|
+
const aoTeclar = (e) => {
|
|
161
|
+
if (disabled)
|
|
162
|
+
return;
|
|
163
|
+
const frente = rtl ? -1 : 1, sx = e.shiftKey ? fx.page : fx.step, sy = e.shiftKey ? fy.page : fy.step;
|
|
164
|
+
let nx = vx, ny = vy, eixo;
|
|
165
|
+
switch (e.key) {
|
|
166
|
+
case "ArrowRight":
|
|
167
|
+
nx += sx * frente;
|
|
168
|
+
eixo = "x";
|
|
169
|
+
break;
|
|
170
|
+
case "ArrowLeft":
|
|
171
|
+
nx -= sx * frente;
|
|
172
|
+
eixo = "x";
|
|
173
|
+
break;
|
|
174
|
+
case "ArrowUp":
|
|
175
|
+
ny += sy;
|
|
176
|
+
eixo = "y";
|
|
177
|
+
break;
|
|
178
|
+
case "ArrowDown":
|
|
179
|
+
ny -= sy;
|
|
180
|
+
eixo = "y";
|
|
181
|
+
break;
|
|
182
|
+
case "PageUp":
|
|
183
|
+
ny += fy.page;
|
|
184
|
+
eixo = "y";
|
|
185
|
+
break;
|
|
186
|
+
case "PageDown":
|
|
187
|
+
ny -= fy.page;
|
|
188
|
+
eixo = "y";
|
|
189
|
+
break;
|
|
190
|
+
case "Home":
|
|
191
|
+
nx -= fx.page * frente;
|
|
192
|
+
eixo = "x";
|
|
193
|
+
break;
|
|
194
|
+
case "End":
|
|
195
|
+
nx += fx.page * frente;
|
|
196
|
+
eixo = "x";
|
|
197
|
+
break;
|
|
198
|
+
default: return;
|
|
199
|
+
}
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
setPelaTecla(true);
|
|
202
|
+
setPeloPonteiro(false);
|
|
203
|
+
focar(eixo);
|
|
204
|
+
onCor(com(naGrade(nx, fx), naGrade(ny, fy)), true);
|
|
205
|
+
};
|
|
206
|
+
// O motor do navegador também muda o `range` (o leitor de tela pede "aumentar"): o valor chega pelo `onChange`.
|
|
207
|
+
const aoMudar = (eixo) => (e) => {
|
|
208
|
+
setPelaTecla(true);
|
|
209
|
+
onCor(setChannel(cor, eixo === "x" ? canalX : canalY, Number(e.target.value), space), true);
|
|
210
|
+
};
|
|
211
|
+
const escondido = (eixo) => focado === null ? eixo === "y" : focado !== eixo;
|
|
212
|
+
const fundo = areaBackground(cor, space, canalX, canalY, rtl);
|
|
213
|
+
return _jsxs("div", { ref: caixa, role: "group", id: id, "aria-label": ariaLabel ? rotulo : undefined, "aria-labelledby": ariaLabelledby, "aria-disabled": disabled || undefined, "data-disabled": disabled ? "" : undefined, "data-dragging": arrastando ? "" : undefined, className: cx("color-area", showDots && "color-area-dots", className), style: { "--color-area-fill": fundo.background, "--color-area-blend": fundo.blend ?? "normal", "--color-area-thumb": hex }, onPointerDown: aoApertar, onPointerMove: aoMover, onPointerUp: aoSoltar, onPointerCancel: aoSoltar, onKeyDown: aoTeclar, children: [_jsx("div", { className: "color-area-thumb", "data-focus-visible": focado && !peloPonteiro ? "" : undefined, style: { insetInlineStart: `${px * 100}%`, insetBlockStart: `${(1 - py) * 100}%` }, children: ["x", "y"].map((eixo) => {
|
|
214
|
+
const f = eixo === "x" ? fx : fy;
|
|
215
|
+
return _jsx("input", { ref: eixo === "x" ? inX : inY, type: "range", className: "color-area-input", min: f.min, max: f.max, step: f.step, value: naGrade(eixo === "x" ? vx : vy, f), disabled: disabled, "aria-label": rotulo, "aria-labelledby": rotulo ? undefined : ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-roledescription": s.colorArea2D, "aria-valuetext": textoDo(eixo), "aria-orientation": eixo === "x" ? "horizontal" : "vertical", tabIndex: escondido(eixo) ? -1 : undefined, "aria-hidden": escondido(eixo) && !pelaTecla ? true : undefined, onChange: aoMudar(eixo), onFocus: () => { setFocado(eixo); setPeloPonteiro(modalidade === "ponteiro"); }, onBlur: (e) => { if (!caixa.current?.contains(e.relatedTarget)) {
|
|
216
|
+
setFocado(null);
|
|
217
|
+
setPeloPonteiro(false);
|
|
218
|
+
} } }, eixo);
|
|
219
|
+
}) }), name && _jsx("input", { type: "hidden", name: name, value: corToHex(cor) })] });
|
|
220
|
+
}
|
|
221
|
+
function TrilhaDeCor({ cor, onCor, channel, colorSpace, label, labelHidden, showValue = true, orientation = "horizontal", disabled, name, className, id }) {
|
|
222
|
+
const s = useAureaStrings();
|
|
223
|
+
const rtl = useDirection() === "rtl";
|
|
224
|
+
const space = spaceOf(channel, colorSpace);
|
|
225
|
+
const { min, max, step, page } = channelRange(channel);
|
|
226
|
+
const v = getChannel(cor, channel, space);
|
|
227
|
+
const idDoRotulo = React.useId();
|
|
228
|
+
const fundo = trackBackground(cor, channel, space, orientation === "vertical" ? "to top" : rtl ? "to left" : "to right");
|
|
229
|
+
const pino = channel === "hue" ? pureHue(v) : channel === "alpha" ? corToHex(cor) : corToHex({ ...cor, a: 1 });
|
|
230
|
+
const falado = (n) => {
|
|
231
|
+
const base = `${s.colorChannel[channel]} ${formatChannel(channel, n)}`;
|
|
232
|
+
if (channel === "alpha")
|
|
233
|
+
return base;
|
|
234
|
+
return `${base}, ${colorName(hsbToRgb(channel === "hue" ? { h: n, s: 1, b: 1 } : setChannel(cor, channel, n, space)), s)}`;
|
|
235
|
+
};
|
|
236
|
+
return _jsxs(BaseSlider.Root, { id: id, "aria-labelledby": idDoRotulo, "aria-disabled": disabled || undefined, value: v, onValueChange: (n) => onCor(setChannel(cor, channel, n, space), false), onValueCommitted: (n) => onCor(setChannel(cor, channel, n, space), true), min: min, max: max, step: step, largeStep: page, orientation: orientation, disabled: disabled, className: cx("slider", "slider-color", channel === "alpha" && "slider-alpha", className), style: { "--slider-track-fill": fundo, "--slider-thumb-color": pino }, children: [_jsx("div", { id: idDoRotulo, className: labelHidden ? "sr-only" : "slider-label", children: label }), showValue && _jsx(BaseSlider.Value, { className: "slider-output", children: (_, valores) => formatChannel(channel, valores[0] ?? v) }), _jsx(BaseSlider.Control, { className: "slider-control", children: _jsx(BaseSlider.Track, { className: "slider-track", children: _jsx(BaseSlider.Thumb, { className: "slider-thumb", "aria-labelledby": idDoRotulo, getAriaValueText: (_f, n) => falado(n) }) }) }), name && _jsx("input", { type: "hidden", name: name, value: corToHex(cor) })] });
|
|
237
|
+
}
|
|
24
238
|
// FileInput mora em arquivo próprio (132 linhas — upload real, aborto, progresso) e é público
|
|
25
239
|
// por aqui: a categoria dele é Inputs.
|
|
26
240
|
export { FileInput, matchesAccept } from "./file-input.js";
|
|
@@ -77,9 +291,11 @@ function elementoRotulavel(tipo) {
|
|
|
77
291
|
// reprovava no axe com "Form elements must have labels". Fora desta lista o Field vira GRUPO e
|
|
78
292
|
// não pendura `htmlFor`, então o <input> de dentro ficava sem nome. Ele qualifica pela mesma
|
|
79
293
|
// regra que os outros: recebe `id` e espalha `...props` no `Input`, que é o nó focal.
|
|
80
|
-
return [Input, Select, Textarea, SearchField, PasswordField, Checkbox, Radio, Switch, Range, Combobox, MultiCombobox].includes(tipo)
|
|
294
|
+
return [Input, Select, Textarea, SearchField, PasswordField, Checkbox, Radio, Switch, Range, Combobox, MultiCombobox, ColorField].includes(tipo)
|
|
295
|
+
// os que moram fora do pacote principal e se registraram (o `DatePicker`, 0.32.0)
|
|
296
|
+
|| (typeof tipo === "function" && controlesRotulaveis.has(tipo));
|
|
81
297
|
}
|
|
82
|
-
export function Field({ label, hint, description, error, children, className, id, name, orientation, labelWidth, style, ...props }) {
|
|
298
|
+
export function Field({ label, hint, description, error, children, className, id, name, orientation, labelWidth, style, required, ...props }) {
|
|
83
299
|
const auto = React.useId();
|
|
84
300
|
// Fragment não cria nó no DOM. Um Fragment com um só controle é só sintaxe; com vários,
|
|
85
301
|
// Field precisa voltar para grupo em vez de pendurar `htmlFor` num id que nunca existirá.
|
|
@@ -116,6 +332,10 @@ export function Field({ label, hint, description, error, children, className, id
|
|
|
116
332
|
const invalidoFinal = error ? true : unico?.props["aria-invalid"];
|
|
117
333
|
if (invalidoFinal !== undefined)
|
|
118
334
|
injecao["aria-invalid"] = invalidoFinal;
|
|
335
|
+
// o `required` do controle é o que anuncia; o do filho, se ele já disse, vence
|
|
336
|
+
if (required && unico?.props.required === undefined)
|
|
337
|
+
injecao.required = true;
|
|
338
|
+
const asterisco = required ? _jsx("span", { className: "label-required", "aria-hidden": "true", children: "*" }) : null;
|
|
119
339
|
const controle = rotulavel ? React.cloneElement(unico, injecao) : children;
|
|
120
340
|
// a descrição mora no nó FOCAL quando existe um; sem filho único, no grupo — que é o que sobra
|
|
121
341
|
// para anunciar. Nos dois lugares seria anunciar duas vezes.
|
|
@@ -125,7 +345,7 @@ export function Field({ label, hint, description, error, children, className, id
|
|
|
125
345
|
// árvore de acessibilidade enquanto a frase de erro aparece na tela. Pior que não mostrar.
|
|
126
346
|
const controleFinal = name ? _jsx(BaseField.Control, { render: controle }) : controle;
|
|
127
347
|
const estilo = labelWidth ? { ...style, "--field-label-width": labelWidth === "auto" ? "max-content" : labelWidth } : style;
|
|
128
|
-
const corpo = _jsxs("div", { className: cx("field", peleDoEixo("field", orientation, "vertical", "field"), className), ...(nomeAqui ? {} : { role: "group", "aria-labelledby": idRotulo, ...(rotulavel ? {} : { "aria-describedby": descrito }) }), style: estilo, ...props, children: [_jsxs("span", { className: "label", children: [nomeAqui ?
|
|
348
|
+
const corpo = _jsxs("div", { className: cx("field", peleDoEixo("field", orientation, "vertical", "field"), className), ...(nomeAqui ? {} : { role: "group", "aria-labelledby": idRotulo, ...(rotulavel ? {} : { "aria-describedby": descrito }) }), style: estilo, ...props, children: [_jsxs("span", { className: "label", children: [nomeAqui ? _jsxs("label", { htmlFor: idControle, id: idRotulo, children: [label, asterisco] }) : _jsxs("span", { id: idRotulo, children: [label, asterisco] }), hint && _jsx("span", { className: "hint", id: idHint, children: hint })] }), controleFinal, description && _jsx("span", { className: "field-description", id: idDescricao, children: description }), error && _jsx("span", { className: "field-error", id: idErro, children: error }), name && _jsx(BaseField.Error, { className: "field-error" })] });
|
|
129
349
|
// `Field.Root` do motor entra SÓ quando há `name`: é ele que liga o campo à chave de `errors`
|
|
130
350
|
// do formulário. Ele não desenha nada — é contexto — então a pele não muda.
|
|
131
351
|
return name ? _jsx(BaseField.Root, { name: name, invalid: error ? true : undefined, render: corpo }) : corpo;
|
|
@@ -146,8 +366,8 @@ export function Field({ label, hint, description, error, children, className, id
|
|
|
146
366
|
export function Form({ errors, onSubmit, children, className, ...props }) {
|
|
147
367
|
return _jsx(BaseForm, { errors: errors, onFormSubmit: onSubmit ? (v) => onSubmit(v) : undefined, className: cx("form", className), ...props, children: children });
|
|
148
368
|
}
|
|
149
|
-
export const Input = forwardRef(function Input({ className, formatOnBlur, size, onBlur, onChange, ...props }, ref) {
|
|
150
|
-
return _jsx("input", { ref: ref, className: cx("input", peleDoEixo("input", size), className), onChange: onChange, onBlur: e => {
|
|
369
|
+
export const Input = forwardRef(function Input({ className, formatOnBlur, size, variant, onBlur, onChange, ...props }, ref) {
|
|
370
|
+
return _jsx("input", { ref: ref, className: cx("input", peleDoEixo("input", size), variant === "secondary" && "input-secondary", className), onChange: onChange, onBlur: e => {
|
|
151
371
|
if (formatOnBlur) {
|
|
152
372
|
const formatado = formatOnBlur(e.currentTarget.value);
|
|
153
373
|
if (formatado !== e.currentTarget.value) {
|
|
@@ -186,7 +406,283 @@ export function SearchField({ icon = "magnifying-glass", className, size, width,
|
|
|
186
406
|
// é a trava do item ("o valor mascarado continua no DOM") entregue pelo motor, e o teste a cobra.
|
|
187
407
|
export function NumberField({ value, defaultValue, onValueChange, min, max, step, disabled, readOnly, required, label, id, name, format, locale, className, scrubbable, scrubDirection }) {
|
|
188
408
|
const s = useAureaStrings();
|
|
189
|
-
return _jsx(BaseNumberField.Root, { id: id, name: name, value: value, defaultValue: defaultValue, onValueChange: soOValor(onValueChange), min: min, max: max, step: step, format: format, locale: locale, disabled: disabled, readOnly: readOnly, required: required, className: cx("number-field", className), children: _jsxs(BaseNumberField.Group, { className: "number-field-group", children: [scrubbable && _jsxs(BaseNumberField.ScrubArea, { className: cx("number-field-scrub", scrubDirection === "vertical" && "number-field-scrub-vertical"), direction: scrubDirection, "aria-hidden": "true", children: [_jsx(Icon, { name: "dots-six-vertical", "aria-hidden": true }), _jsx(BaseNumberField.ScrubAreaCursor, { className: "number-field-scrub-cursor", children: _jsx(Icon, { name: "dots-six-vertical", "aria-hidden": true }) })] }), _jsx(BaseNumberField.Decrement, { className: "
|
|
409
|
+
return _jsx(BaseNumberField.Root, { id: id, name: name, value: value, defaultValue: defaultValue, onValueChange: soOValor(onValueChange), min: min, max: max, step: step, format: format, locale: locale, disabled: disabled, readOnly: readOnly, required: required, className: cx("number-field", className), children: _jsxs(BaseNumberField.Group, { className: "number-field-group", children: [scrubbable && _jsxs(BaseNumberField.ScrubArea, { className: cx("number-field-scrub", scrubDirection === "vertical" && "number-field-scrub-vertical"), direction: scrubDirection, "aria-hidden": "true", children: [_jsx(Icon, { name: "dots-six-vertical", "aria-hidden": true }), _jsx(BaseNumberField.ScrubAreaCursor, { className: "number-field-scrub-cursor", children: _jsx(Icon, { name: "dots-six-vertical", "aria-hidden": true }) })] }), _jsx(BaseNumberField.Decrement, { className: "number-field-button number-field-decrement", "aria-label": s.decrement, children: _jsx(Icon, { name: "minus" }) }), _jsx(BaseNumberField.Input, { className: "input number-field-input", "aria-label": label }), _jsx(BaseNumberField.Increment, { className: "number-field-button number-field-increment", "aria-label": s.increment, children: _jsx(Icon, { name: "plus" }) })] }) });
|
|
410
|
+
}
|
|
411
|
+
export function CheckboxGroup({ label, description, error, value, defaultValue, onValueChange, name, disabled, required, children, className, ...props }) {
|
|
412
|
+
const auto = React.useId();
|
|
413
|
+
const [proprio, setProprio] = React.useState(defaultValue ?? []);
|
|
414
|
+
const marcados = value ?? proprio;
|
|
415
|
+
const trocar = (v, ligado) => {
|
|
416
|
+
const novo = ligado ? [...marcados.filter(x => x !== v), v] : marcados.filter(x => x !== v);
|
|
417
|
+
if (value === undefined)
|
|
418
|
+
setProprio(novo);
|
|
419
|
+
onValueChange?.(novo);
|
|
420
|
+
};
|
|
421
|
+
const idRotulo = `${auto}-label`, idDescricao = description ? `${auto}-description` : undefined, idErro = error ? `${auto}-error` : undefined;
|
|
422
|
+
const caixas = React.Children.map(children, filho => {
|
|
423
|
+
if (!React.isValidElement(filho))
|
|
424
|
+
return filho;
|
|
425
|
+
const p = filho.props;
|
|
426
|
+
if (typeof p.value !== "string")
|
|
427
|
+
return filho;
|
|
428
|
+
const v = p.value;
|
|
429
|
+
return React.cloneElement(filho, { checked: marcados.includes(v), name: name ?? filho.props.name,
|
|
430
|
+
disabled: p.disabled ?? disabled, ...(error ? { "aria-invalid": true } : null),
|
|
431
|
+
onChange: (e) => { p.onChange?.(e); trocar(v, e.currentTarget.checked); } });
|
|
432
|
+
});
|
|
433
|
+
return _jsxs("div", { role: "group", "aria-labelledby": idRotulo, "aria-describedby": [idDescricao, idErro].filter(Boolean).join(" ") || undefined, className: cx("checkbox-group", className), ...props, children: [_jsx("span", { className: "label", id: idRotulo, children: _jsxs("span", { children: [label, required ? _jsx("span", { className: "label-required", "aria-hidden": "true", children: "*" }) : null] }) }), description && _jsx("span", { className: "field-description", id: idDescricao, children: description }), caixas, error && _jsx("span", { className: "field-error", id: idErro, children: error })] });
|
|
434
|
+
}
|
|
435
|
+
const ehSecao = (x) => "items" in x;
|
|
436
|
+
const textoDoItem = (it) => it.textValue ?? (typeof it.label === "string" ? it.label : "");
|
|
437
|
+
export function ListBox({ label, items, selectionMode = "single", value, defaultValue, onValueChange, onAction, className, ...props }) {
|
|
438
|
+
const auto = React.useId();
|
|
439
|
+
const [proprio, setProprio] = React.useState(defaultValue ?? []);
|
|
440
|
+
const escolhidos = value ?? proprio;
|
|
441
|
+
const todos = React.useMemo(() => items.flatMap(x => ehSecao(x) ? x.items : [x]), [items]);
|
|
442
|
+
const habilitados = todos.filter(it => !it.disabled);
|
|
443
|
+
const [ativo, setAtivo] = React.useState(undefined);
|
|
444
|
+
// O item que recebe o Tab: o ativo; senão o primeiro escolhido; senão o primeiro habilitado.
|
|
445
|
+
const alvo = ativo ?? habilitados.find(it => escolhidos.includes(it.id))?.id ?? habilitados[0]?.id;
|
|
446
|
+
const refs = React.useRef(new Map());
|
|
447
|
+
const focar = (id) => { if (!id)
|
|
448
|
+
return; setAtivo(id); refs.current.get(id)?.focus(); };
|
|
449
|
+
const escolher = (it) => {
|
|
450
|
+
if (it.disabled)
|
|
451
|
+
return;
|
|
452
|
+
if (selectionMode === "none") {
|
|
453
|
+
onAction?.(it.id);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const ja = escolhidos.includes(it.id);
|
|
457
|
+
const novo = selectionMode === "multiple" ? (ja ? escolhidos.filter(x => x !== it.id) : [...escolhidos, it.id]) : [it.id];
|
|
458
|
+
if (value === undefined)
|
|
459
|
+
setProprio(novo);
|
|
460
|
+
onValueChange?.(novo);
|
|
461
|
+
onAction?.(it.id);
|
|
462
|
+
};
|
|
463
|
+
const aoTeclar = (e, it) => {
|
|
464
|
+
const i = habilitados.findIndex(x => x.id === it.id);
|
|
465
|
+
if (e.key === "ArrowDown") {
|
|
466
|
+
e.preventDefault();
|
|
467
|
+
focar(habilitados[Math.min(i + 1, habilitados.length - 1)]?.id);
|
|
468
|
+
}
|
|
469
|
+
else if (e.key === "ArrowUp") {
|
|
470
|
+
e.preventDefault();
|
|
471
|
+
focar(habilitados[Math.max(i - 1, 0)]?.id);
|
|
472
|
+
}
|
|
473
|
+
else if (e.key === "Home") {
|
|
474
|
+
e.preventDefault();
|
|
475
|
+
focar(habilitados[0]?.id);
|
|
476
|
+
}
|
|
477
|
+
else if (e.key === "End") {
|
|
478
|
+
e.preventDefault();
|
|
479
|
+
focar(habilitados[habilitados.length - 1]?.id);
|
|
480
|
+
}
|
|
481
|
+
else if (e.key === " " || e.key === "Enter") {
|
|
482
|
+
e.preventDefault();
|
|
483
|
+
escolher(it);
|
|
484
|
+
}
|
|
485
|
+
else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
486
|
+
// a primeira letra: o próximo item, depois do atual, que começa com ela (e dá a volta)
|
|
487
|
+
const letra = e.key.toLocaleLowerCase();
|
|
488
|
+
const ordem = [...habilitados.slice(i + 1), ...habilitados.slice(0, i + 1)];
|
|
489
|
+
const achou = ordem.find(x => textoDoItem(x).toLocaleLowerCase().startsWith(letra));
|
|
490
|
+
if (achou) {
|
|
491
|
+
e.preventDefault();
|
|
492
|
+
focar(achou.id);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
const item = (it) => {
|
|
497
|
+
const escolhido = selectionMode !== "none" && escolhidos.includes(it.id);
|
|
498
|
+
const idDesc = it.description ? `${auto}-${it.id}-d` : undefined;
|
|
499
|
+
return _jsxs("div", { ref: n => { if (n)
|
|
500
|
+
refs.current.set(it.id, n);
|
|
501
|
+
else
|
|
502
|
+
refs.current.delete(it.id); }, role: "option", "aria-selected": selectionMode === "none" ? undefined : escolhido, "aria-disabled": it.disabled || undefined, "aria-describedby": idDesc, tabIndex: it.id === alvo ? 0 : -1, className: cx("list-box-item", it.danger && "list-box-item-danger"), onClick: () => { if (!it.disabled) {
|
|
503
|
+
setAtivo(it.id);
|
|
504
|
+
escolher(it);
|
|
505
|
+
} }, onKeyDown: e => aoTeclar(e, it), onFocus: () => setAtivo(it.id), children: [it.icon && _jsx(Icon, { name: it.icon }), _jsxs("span", { className: "list-box-item-text", children: [_jsx("span", { className: "list-box-item-label", children: it.label }), it.description && _jsx("span", { className: "list-box-item-description", id: idDesc, children: it.description })] }), escolhido && _jsx(Icon, { name: "check", className: "list-box-item-indicator" })] }, it.id);
|
|
506
|
+
};
|
|
507
|
+
return _jsx("div", { role: "listbox", "aria-label": label, "aria-multiselectable": selectionMode === "multiple" || undefined, className: cx("list-box", className), ...props, children: items.map((x, i) => ehSecao(x)
|
|
508
|
+
? _jsxs("div", { role: "group", "aria-labelledby": `${auto}-s${i}`, className: "list-box-section", children: [_jsx("span", { className: "list-box-section-title", id: `${auto}-s${i}`, role: "presentation", children: x.title }), x.items.map(item)] }, `s${i}`)
|
|
509
|
+
: item(x)) });
|
|
510
|
+
}
|
|
511
|
+
const textoDaEtiqueta = (it) => it.textValue ?? (typeof it.label === "string" ? it.label : "");
|
|
512
|
+
export function TagGroup({ label, labelHidden, description, errorMessage, items, selectionMode = "none", selectedKeys, defaultSelectedKeys, onSelectionChange, onRemove, size = "md", variant = "default", className, id }) {
|
|
513
|
+
const s = useAureaStrings();
|
|
514
|
+
const auto = React.useId();
|
|
515
|
+
const [proprio, setProprio] = React.useState(defaultSelectedKeys ?? []);
|
|
516
|
+
const escolhidos = selectedKeys ?? proprio;
|
|
517
|
+
const escolhe = selectionMode !== "none";
|
|
518
|
+
const habilitadas = items.filter(it => !it.disabled);
|
|
519
|
+
const [ativa, setAtiva] = React.useState(undefined);
|
|
520
|
+
const alvo = (ativa && habilitadas.some(it => it.id === ativa) ? ativa : undefined) ?? habilitadas[0]?.id;
|
|
521
|
+
const refs = React.useRef(new Map());
|
|
522
|
+
const focar = (id) => { if (!id)
|
|
523
|
+
return; setAtiva(id); refs.current.get(id)?.focus(); };
|
|
524
|
+
const escolher = (it) => {
|
|
525
|
+
if (!escolhe || it.disabled)
|
|
526
|
+
return;
|
|
527
|
+
const ja = escolhidos.includes(it.id);
|
|
528
|
+
const novo = selectionMode === "multiple" ? (ja ? escolhidos.filter(x => x !== it.id) : [...escolhidos, it.id]) : (ja ? [] : [it.id]);
|
|
529
|
+
if (selectedKeys === undefined)
|
|
530
|
+
setProprio(novo);
|
|
531
|
+
onSelectionChange?.(novo);
|
|
532
|
+
};
|
|
533
|
+
const tirar = (it) => {
|
|
534
|
+
if (!onRemove || it.disabled)
|
|
535
|
+
return;
|
|
536
|
+
// o foco vai para a vizinha (a seguinte; na última, a anterior), e não se perde no documento
|
|
537
|
+
const i = habilitadas.findIndex(x => x.id === it.id);
|
|
538
|
+
const vizinha = habilitadas[i + 1] ?? habilitadas[i - 1];
|
|
539
|
+
onRemove([it.id]);
|
|
540
|
+
if (vizinha)
|
|
541
|
+
requestAnimationFrame(() => focar(vizinha.id));
|
|
542
|
+
};
|
|
543
|
+
const aoTeclar = (e, it) => {
|
|
544
|
+
const i = habilitadas.findIndex(x => x.id === it.id);
|
|
545
|
+
const rtl = getComputedStyle(e.currentTarget).direction === "rtl";
|
|
546
|
+
const frente = rtl ? "ArrowLeft" : "ArrowRight", tras = rtl ? "ArrowRight" : "ArrowLeft";
|
|
547
|
+
if (e.key === frente || e.key === "ArrowDown") {
|
|
548
|
+
e.preventDefault();
|
|
549
|
+
focar(habilitadas[Math.min(i + 1, habilitadas.length - 1)]?.id);
|
|
550
|
+
}
|
|
551
|
+
else if (e.key === tras || e.key === "ArrowUp") {
|
|
552
|
+
e.preventDefault();
|
|
553
|
+
focar(habilitadas[Math.max(i - 1, 0)]?.id);
|
|
554
|
+
}
|
|
555
|
+
else if (e.key === "Home") {
|
|
556
|
+
e.preventDefault();
|
|
557
|
+
focar(habilitadas[0]?.id);
|
|
558
|
+
}
|
|
559
|
+
else if (e.key === "End") {
|
|
560
|
+
e.preventDefault();
|
|
561
|
+
focar(habilitadas[habilitadas.length - 1]?.id);
|
|
562
|
+
}
|
|
563
|
+
else if ((e.key === "Delete" || e.key === "Backspace") && onRemove) {
|
|
564
|
+
e.preventDefault();
|
|
565
|
+
tirar(it);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
const guardar = (itId) => (n) => { if (n)
|
|
569
|
+
refs.current.set(itId, n);
|
|
570
|
+
else
|
|
571
|
+
refs.current.delete(itId); };
|
|
572
|
+
const idRotulo = `${auto}-l`, idDesc = description ? `${auto}-d` : undefined, idErro = errorMessage ? `${auto}-e` : undefined;
|
|
573
|
+
return _jsxs("div", { className: cx("tag-group", className), id: id, children: [_jsx("span", { className: labelHidden ? "sr-only" : "label", id: idRotulo, children: label }), _jsx("div", { role: escolhe ? "group" : "list", "aria-labelledby": idRotulo, "aria-describedby": [idDesc, idErro].filter(Boolean).join(" ") || undefined, className: "tag-group-list", children: items.map(it => {
|
|
574
|
+
const escolhida = escolhe && escolhidos.includes(it.id);
|
|
575
|
+
const conteudo = _jsxs(_Fragment, { children: [it.icon && _jsx(Icon, { name: it.icon }), _jsx("span", { className: "tag-label", children: it.label })] });
|
|
576
|
+
const x = onRemove && _jsx("button", { type: "button", className: "tag-remove", disabled: it.disabled, tabIndex: escolhe ? -1 : it.id === alvo ? 0 : -1, ref: escolhe ? undefined : guardar(it.id), "aria-label": `${s.comboboxRemove} ${textoDaEtiqueta(it)}`.trim(), onClick: () => tirar(it), onKeyDown: escolhe ? undefined : e => aoTeclar(e, it), onFocus: escolhe ? undefined : () => setAtiva(it.id), children: _jsx(Icon, { name: "x", weight: "bold" }) });
|
|
577
|
+
return _jsxs("span", { role: escolhe ? undefined : "listitem", className: cx("tag", size !== "md" && `tag-${size}`, variant !== "default" && `tag-${variant}`), "data-selected": escolhida || undefined, "data-disabled": it.disabled || undefined, children: [escolhe
|
|
578
|
+
? _jsx("button", { type: "button", className: "tag-main", ref: guardar(it.id), "aria-pressed": escolhida, disabled: it.disabled, tabIndex: it.id === alvo ? 0 : -1, onClick: () => { setAtiva(it.id); escolher(it); }, onKeyDown: e => aoTeclar(e, it), onFocus: () => setAtiva(it.id), children: conteudo })
|
|
579
|
+
: _jsx("span", { className: "tag-main", children: conteudo }), x] }, it.id);
|
|
580
|
+
}) }), description && _jsx("span", { className: "field-description", id: idDesc, children: description }), errorMessage && _jsx("span", { className: "field-error", id: idErro, children: errorMessage })] });
|
|
581
|
+
}
|
|
582
|
+
export function Slider(props) {
|
|
583
|
+
return props.channel ? _jsx(SliderDeCor, { ...props }) : _jsx(SliderDeNumero, { ...props });
|
|
584
|
+
}
|
|
585
|
+
function SliderDeCor({ value, defaultValue, onValueChange, onValueCommitted, ...resto }) {
|
|
586
|
+
const [cor, mudar] = useCor(value, defaultValue, onValueChange, onValueCommitted);
|
|
587
|
+
return _jsx(TrilhaDeCor, { ...resto, cor: cor, onCor: mudar });
|
|
588
|
+
}
|
|
589
|
+
function SliderDeNumero({ label, labelHidden, value, defaultValue, onValueChange, onValueCommitted, min, max, step, largeStep, orientation = "horizontal", disabled, name, format, locale, showValue = true, thumbLabels, className, id }) {
|
|
590
|
+
// Quantas alças: tantas quantos valores houver (a faixa é uma lista).
|
|
591
|
+
const atual = value ?? defaultValue;
|
|
592
|
+
const alcas = Array.isArray(atual) ? atual.length : 1;
|
|
593
|
+
// O NOME DA ALÇA SAI NA MARCAÇÃO. O motor liga o `<input>` de cada alça ao rótulo por um id que ele
|
|
594
|
+
// só conhece DEPOIS de montar — no HTML do servidor (e no catálogo, que é HTML parado) as alças saíam
|
|
595
|
+
// sem nome, e o axe do catálogo acusou as cinco. O `Slider.Label` do motor não aceita id, então o
|
|
596
|
+
// rótulo é um `<div>` nosso, com id, que nomeia o grupo e cada alça — o nome já vem no HTML. E o desligado vai também para o grupo (`aria-disabled`): o motor só marca o `<input>`, e a
|
|
597
|
+
// letra esmaecida do rótulo e do valor era lida como texto de contraste baixo, e não como peça desligada.
|
|
598
|
+
const idDoRotulo = React.useId();
|
|
599
|
+
return _jsxs(BaseSlider.Root, { id: id, name: name, "aria-labelledby": idDoRotulo, "aria-disabled": disabled || undefined, value: value, defaultValue: defaultValue, onValueChange: onValueChange ? (v) => onValueChange(v) : undefined, onValueCommitted: onValueCommitted ? (v) => onValueCommitted(v) : undefined, min: min, max: max, step: step, largeStep: largeStep, orientation: orientation, disabled: disabled, format: format, locale: locale, className: cx("slider", className), children: [_jsx("div", { id: idDoRotulo, className: labelHidden ? "sr-only" : "slider-label", children: label }), showValue && _jsx(BaseSlider.Value, { className: "slider-output" }), _jsx(BaseSlider.Control, { className: "slider-control", children: _jsxs(BaseSlider.Track, { className: "slider-track", children: [_jsx(BaseSlider.Indicator, { className: "slider-fill" }), Array.from({ length: alcas }, (_, i) => _jsx(BaseSlider.Thumb, { index: i, className: "slider-thumb", getAriaLabel: thumbLabels ? () => thumbLabels[i] ?? "" : undefined, "aria-labelledby": thumbLabels ? undefined : idDoRotulo }, i))] }) })] });
|
|
600
|
+
}
|
|
601
|
+
const EIXOS_PADRAO = { hsb: ["saturation", "brightness"], hsl: ["saturation", "lightness"], rgb: ["red", "green"] };
|
|
602
|
+
export function ColorArea({ value, defaultValue, onValueChange, onValueCommitted, colorSpace, xChannel, yChannel, ...resto }) {
|
|
603
|
+
const [cor, mudar] = useCor(value, defaultValue, onValueChange, onValueCommitted);
|
|
604
|
+
const space = espacoDosCanais(colorSpace, ...[xChannel, yChannel].filter((c) => !!c));
|
|
605
|
+
const [x, y] = [xChannel ?? EIXOS_PADRAO[space][0], yChannel ?? EIXOS_PADRAO[space][1]];
|
|
606
|
+
return _jsx(AreaDeCor, { ...resto, cor: cor, onCor: mudar, space: space, x: x, y: y });
|
|
607
|
+
}
|
|
608
|
+
export function ColorField({ value, defaultValue, onValueChange, onValueCommitted, channel, colorSpace, swatch, size, variant, width, disabled, readOnly, required, name, placeholder, id, className, ...aria }) {
|
|
609
|
+
const s = useAureaStrings();
|
|
610
|
+
const doSeletor = React.useContext(CorDoSeletor);
|
|
611
|
+
const [cor, mudar] = useCor(value === "" ? undefined : value, defaultValue, onValueChange, onValueCommitted);
|
|
612
|
+
// Vazio: o valor "" de fora, ou sem valor nenhum e ainda não escrito (o campo da referência começa vazio).
|
|
613
|
+
const [escrito, setEscrito] = React.useState(defaultValue !== undefined);
|
|
614
|
+
const vazio = !doSeletor && (value !== undefined ? value === "" : !escrito);
|
|
615
|
+
const [rascunho, setRascunho] = React.useState(null);
|
|
616
|
+
const canal = channel;
|
|
617
|
+
const space = canal ? spaceOf(canal, colorSpace) : "hsb";
|
|
618
|
+
const faixa = canal ? channelRange(canal) : null;
|
|
619
|
+
const v = canal ? getChannel(cor, canal, space) : 0;
|
|
620
|
+
const mostrado = vazio ? "" : canal ? formatChannel(canal, v) : corToHex(cor);
|
|
621
|
+
const confirmar = () => {
|
|
622
|
+
if (rascunho === null)
|
|
623
|
+
return;
|
|
624
|
+
const t = rascunho.trim();
|
|
625
|
+
setRascunho(null);
|
|
626
|
+
if (t === "") {
|
|
627
|
+
if (canal)
|
|
628
|
+
return;
|
|
629
|
+
setEscrito(false);
|
|
630
|
+
onValueChange?.("");
|
|
631
|
+
onValueCommitted?.("");
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (canal) {
|
|
635
|
+
const n = Number(t.replace(/[°%\s]/g, "").replace(",", "."));
|
|
636
|
+
if (!Number.isFinite(n))
|
|
637
|
+
return;
|
|
638
|
+
mudar(setChannel(cor, canal, canal === "alpha" && (n > 1 || /%/.test(t)) ? n / 100 : n, space), true);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const nova = parseColor(t);
|
|
642
|
+
if (!nova)
|
|
643
|
+
return;
|
|
644
|
+
setEscrito(true);
|
|
645
|
+
mudar(corDoTexto(t, cor), true);
|
|
646
|
+
};
|
|
647
|
+
const aoTeclar = (e) => {
|
|
648
|
+
if (e.key === "Enter") {
|
|
649
|
+
e.preventDefault();
|
|
650
|
+
confirmar();
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (!canal || !faixa || disabled || readOnly)
|
|
654
|
+
return;
|
|
655
|
+
const passo = e.key === "ArrowUp" ? faixa.step : e.key === "ArrowDown" ? -faixa.step : e.key === "PageUp" ? faixa.page : e.key === "PageDown" ? -faixa.page : 0;
|
|
656
|
+
const alvo = e.key === "Home" ? faixa.min : e.key === "End" ? faixa.max : passo ? v + passo : null;
|
|
657
|
+
if (alvo === null)
|
|
658
|
+
return;
|
|
659
|
+
e.preventDefault();
|
|
660
|
+
setRascunho(null);
|
|
661
|
+
mudar(setChannel(cor, canal, Math.round(alvo / faixa.step) * faixa.step, space), true);
|
|
662
|
+
};
|
|
663
|
+
const comAmostra = swatch ?? !canal;
|
|
664
|
+
return _jsxs(InputGroup, { className: className, width: width, children: [comAmostra && _jsx(InputGroupAddon, { children: _jsx(ColorSwatch, { color: vazio ? "transparent" : corToHex(cor), size: "sm", "aria-hidden": "true" }) }), _jsx(Input, { id: id, size: size, variant: variant, value: rascunho ?? mostrado, placeholder: placeholder, disabled: disabled, readOnly: readOnly, required: required, spellCheck: false, autoComplete: "off", inputMode: canal ? "decimal" : undefined, role: canal ? "spinbutton" : undefined, "aria-valuenow": canal ? v : undefined, "aria-valuemin": faixa?.min, "aria-valuemax": faixa?.max, "aria-valuetext": canal ? `${formatChannel(canal, v)}, ${colorName(hsbToRgb(cor), s)}` : undefined, ...aria, onChange: (e) => setRascunho(e.target.value), onBlur: confirmar, onKeyDown: aoTeclar }), name && _jsx("input", { type: "hidden", name: name, value: vazio ? "" : corToHex(cor) })] });
|
|
665
|
+
}
|
|
666
|
+
export function ColorPicker({ label, value, defaultValue, onValueChange, onValueCommitted, swatches, variant = "full", children, disabled, name, id, className }) {
|
|
667
|
+
const s = useAureaStrings();
|
|
668
|
+
const portal = usePortalContainer();
|
|
669
|
+
const idGrupo = React.useId();
|
|
670
|
+
const [cor, mudar] = useCor(value, defaultValue, onValueChange, onValueCommitted);
|
|
671
|
+
const hex = corToHex(cor), nome = colorName(hsbToRgb(cor), s);
|
|
672
|
+
const amostras = swatches?.map((o) => {
|
|
673
|
+
const op = typeof o === "string" ? { color: o } : o;
|
|
674
|
+
const c = parseColor(op.color);
|
|
675
|
+
return { color: op.color, label: op.label ?? (c ? colorName(hsbToRgb(c), s) : op.color) };
|
|
676
|
+
});
|
|
677
|
+
const escolhida = amostras?.find((o) => { const c = parseColor(o.color); return c !== null && corToHex(c) === hex; })?.color ?? "";
|
|
678
|
+
const contexto = React.useMemo(() => ({ cor, mudar }), [cor, mudar]);
|
|
679
|
+
// O seletor escuta o teclado e o ponteiro desde que MONTA: a área só monta ao abrir, DEPOIS do clique que abriu, e
|
|
680
|
+
// a primeira abertura com o mouse acendia o anel (medido na bancada em 14/10/2026; o teste abria pelo teclado antes).
|
|
681
|
+
React.useEffect(ouvirModalidade, []);
|
|
682
|
+
// O foco fica PRESO na janela (`trap-focus`), como na referência: com quatro peças dentro, o Tab que saía depois
|
|
683
|
+
// do código fechava a janela (medido no navegador). O motor pede um botão de fechar dentro, para o leitor de tela
|
|
684
|
+
// por toque sair; ele fica invisível e fora do Tab — quem usa teclado tem o Esc, e o foco nunca some na tela.
|
|
685
|
+
return _jsxs(BasePopover.Root, { modal: "trap-focus", children: [_jsxs(BasePopover.Trigger, { disabled: disabled, id: id, "aria-label": `${label}, ${nome}`, className: cx("color-picker-trigger", className), children: [_jsx(ColorSwatch, { color: hex, "aria-hidden": "true" }), _jsx("span", { children: label })] }), name && _jsx("input", { type: "hidden", name: name, value: hex }), _jsx(BasePopover.Portal, { container: portal, children: _jsx(BasePopover.Positioner, { side: "bottom", align: "start", sideOffset: 6, children: _jsxs(BasePopover.Popup, { className: "popover color-picker-popover", "aria-label": label, children: [_jsx(CorDoSeletor.Provider, { value: contexto, children: children ?? _jsxs(_Fragment, { children: [variant !== "swatches" && _jsxs(_Fragment, { children: [_jsx(AreaDeCor, { cor: cor, onCor: mudar, space: "hsb", x: "saturation", y: "brightness", "aria-label": label }), _jsx(TrilhaDeCor, { cor: cor, onCor: mudar, channel: "hue", label: s.colorChannel.hue, labelHidden: true, showValue: false })] }), amostras && _jsx(ColorSwatch, { options: amostras, name: `${idGrupo}-amostras`, label: s.colorSwatches, value: escolhida, onChange: (e) => mudar(corDoTexto(e.target.value, cor), true) }), _jsx(ColorField, { "aria-label": s.colorHex })] }) }), _jsx(BasePopover.Close, { className: "sr-only", tabIndex: -1, children: s.close })] }) }) })] });
|
|
190
686
|
}
|
|
191
687
|
// OTPField (Lote 1 do BUILDING.md). Um campo por dígito, com o comportamento que ninguém acerta
|
|
192
688
|
// à mão: colar o código inteiro distribui pelos campos, Backspace volta um, e o
|
package/dist/inputs.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * from "./inputs-client.js";
|
|
2
2
|
export { Textarea, Checkbox, Radio, Switch, Range } from "./markup.js";
|
|
3
|
-
export { Label, InputGroup, InputGroupAddon, type AddonSide, type AddonLayout, type FieldSize } from "./markup.js";
|
|
3
|
+
export { Label, InputGroup, InputGroupAddon, type AddonSide, type AddonLayout, type FieldSize, type FieldVariant } from "./markup.js";
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { type AureaStrings, type AureaTheme, type AureaDensity } from "./pure.js";
|
|
3
3
|
export { cx, fundirRender, defaultStrings, ptBR, defaultSpriteUrl, universalStates, stateSeverity, type AureaStrings, type UniversalState, type AureaTheme, type AureaDensity } from "./pure.js";
|
|
4
|
+
export declare const controlesRotulaveis: WeakSet<object>;
|
|
4
5
|
export declare const StringsContext: React.Context<AureaStrings>;
|
|
5
6
|
export declare const useAureaStrings: () => AureaStrings;
|
|
6
7
|
export declare const SpriteContext: React.Context<string>;
|
package/dist/internal.js
CHANGED
|
@@ -18,6 +18,10 @@ import { createContext, useContext, useEffect, 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";
|
|
21
|
+
// Os controles da Aurea que moram FORA do pacote principal (o `DatePicker`, em `./calendar`, 0.32.0) e
|
|
22
|
+
// entregam o `id` ao nó focal. O `Field` os nomeia com `<label for>`, como os da lista dele; eles se
|
|
23
|
+
// registram aqui ao carregar, porque importar cada um no `Field` puxaria o motor deles para todo app.
|
|
24
|
+
export const controlesRotulaveis = new WeakSet();
|
|
21
25
|
export const StringsContext = createContext(defaultStrings);
|
|
22
26
|
export const useAureaStrings = () => useContext(StringsContext);
|
|
23
27
|
export const SpriteContext = createContext(defaultSpriteUrl);
|
package/dist/layout-client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type HTMLAttributes, type RefAttributes, type ReactNode } from "react";
|
|
2
2
|
import { type Orientation, type Responsive } from "./pure.js";
|
|
3
|
-
import { type HeaderVariant, type SidebarVariant, type SidebarItem } from "./navigation.js";
|
|
4
|
-
export declare function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant, topbarDivider, sidebarVariant, sidebarCollapsed, sidebarCollapsible, defaultSidebarCollapsed, onSidebarCollapsedChange, contentVariant, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
3
|
+
import { type HeaderVariant, type SidebarVariant, type SidebarScrollbar, type SidebarItem } from "./navigation.js";
|
|
4
|
+
export declare function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant, topbarDivider, sidebarVariant, sidebarScrollbar, sidebarCollapsed, sidebarCollapsible, defaultSidebarCollapsed, onSidebarCollapsedChange, contentVariant, aside, asideLabel, asideVariant, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
|
|
5
5
|
brand: ReactNode;
|
|
6
6
|
navigation?: ReactNode;
|
|
7
7
|
navItems?: SidebarItem[];
|
|
@@ -11,6 +11,18 @@ export declare function AppShell({ brand, navigation, navItems, currentNavId, na
|
|
|
11
11
|
topbarVariant?: Exclude<HeaderVariant, "pill">;
|
|
12
12
|
topbarDivider?: boolean;
|
|
13
13
|
sidebarVariant?: SidebarVariant;
|
|
14
|
+
/** 0.32.0: a barra de rolagem da lateral da esquerda — o `scrollbar` da `Sidebar`. Padrão `hover` (clara, só com o
|
|
15
|
+
* ponteiro em cima); `visible` é a barra sempre à vista. */
|
|
16
|
+
sidebarScrollbar?: SidebarScrollbar;
|
|
17
|
+
/** 0.32.0 (DOC-01): a coluna da DIREITA — o "Nesta página" de uma documentação (um `TableOfContents`).
|
|
18
|
+
* Mora numa `Sidebar placement="right"` do mesmo desenho da da esquerda. De 1280 para cima é a
|
|
19
|
+
* terceira coluna; abaixo, desce para depois do conteúdo. */
|
|
20
|
+
aside?: ReactNode;
|
|
21
|
+
/** O nome da coluna da direita para o leitor de tela. Padrão: a frase `tocLabel` ("On this page"). */
|
|
22
|
+
asideLabel?: string;
|
|
23
|
+
/** 0.32.0: a variante da coluna da direita. Padrão: a mesma da esquerda (`sidebarVariant`). `plain`
|
|
24
|
+
* tira a caixa — os itens soltos na página, como no "On this page" da documentação da referência. */
|
|
25
|
+
asideVariant?: SidebarVariant;
|
|
14
26
|
/** A lateral em trilha de ícones. Controlado: quem passa decide (e o `onSidebarCollapsedChange` avisa). */
|
|
15
27
|
sidebarCollapsed?: boolean;
|
|
16
28
|
/** AN-01: o botão de recolher na junção do menu com o conteúdo, e a trilha sozinha entre 1024 e 1279 de largura. */
|