@aurea-uds/react 0.2.0 → 0.4.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.
Files changed (49) hide show
  1. package/README.md +34 -2
  2. package/dist/actions.d.ts +22 -3
  3. package/dist/actions.js +62 -6
  4. package/dist/code-client.d.ts +6 -0
  5. package/dist/code-client.js +24 -0
  6. package/dist/code.d.ts +2 -13
  7. package/dist/code.js +5 -34
  8. package/dist/data-display-client.d.ts +14 -0
  9. package/dist/data-display-client.js +22 -0
  10. package/dist/data-display.d.ts +2 -23
  11. package/dist/data-display.js +5 -14
  12. package/dist/feedback-client.d.ts +64 -0
  13. package/dist/feedback-client.js +110 -0
  14. package/dist/feedback.d.ts +2 -63
  15. package/dist/feedback.js +5 -101
  16. package/dist/file-input.js +57 -9
  17. package/dist/identity-client.d.ts +9 -0
  18. package/dist/identity-client.js +12 -0
  19. package/dist/identity.d.ts +2 -17
  20. package/dist/identity.js +5 -25
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.js +19 -3
  23. package/dist/inputs-client.d.ts +105 -0
  24. package/dist/inputs-client.js +264 -0
  25. package/dist/inputs.d.ts +2 -91
  26. package/dist/inputs.js +9 -126
  27. package/dist/internal.d.ts +20 -5
  28. package/dist/internal.js +91 -10
  29. package/dist/layout-client.d.ts +10 -0
  30. package/dist/layout-client.js +77 -0
  31. package/dist/layout.d.ts +2 -15
  32. package/dist/layout.js +5 -76
  33. package/dist/markup.d.ts +103 -0
  34. package/dist/markup.js +124 -0
  35. package/dist/media-client.d.ts +36 -0
  36. package/dist/media-client.js +239 -0
  37. package/dist/media.d.ts +2 -9
  38. package/dist/media.js +5 -99
  39. package/dist/navigation-client.d.ts +120 -0
  40. package/dist/navigation-client.js +196 -0
  41. package/dist/navigation.d.ts +2 -85
  42. package/dist/navigation.js +5 -138
  43. package/dist/overlays.d.ts +23 -0
  44. package/dist/overlays.js +46 -2
  45. package/dist/pure.d.ts +31 -0
  46. package/dist/pure.js +26 -2
  47. package/dist/system.d.ts +9 -0
  48. package/dist/system.js +46 -0
  49. package/package.json +180 -176
@@ -0,0 +1,264 @@
1
+ "use client";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
4
+ // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
5
+ // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
6
+ import React, { forwardRef } from "react";
7
+ import { Combobox as BaseCombobox } from "@base-ui/react/combobox";
8
+ import { Form as BaseForm } from "@base-ui/react/form";
9
+ import { Field as BaseField } from "@base-ui/react/field";
10
+ import { NumberField as BaseNumberField } from "@base-ui/react/number-field";
11
+ import { OTPField as BaseOTPField } from "@base-ui/react/otp-field";
12
+ import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group";
13
+ import { Radio as BaseRadio } from "@base-ui/react/radio";
14
+ import { cx, useAureaStrings, usePortalContainer, useReorder } from "./internal.js";
15
+ // Os seis controles abaixo saíram deste arquivo no item O1 e moram no `markup.tsx`, sem a
16
+ // diretiva — são marcação, e quem escuta `onChange` é o consumidor. Voltam IMPORTADOS e não só
17
+ // reexportados porque o `ehControle` do `Field` compara por IDENTIDADE de referência: dois
18
+ // objetos diferentes com o mesmo nome quebrariam o rótulo do campo em silêncio.
19
+ import { Select, Textarea, Checkbox, Radio, Switch, Range } from "./markup.js";
20
+ import { Icon } from "./system.js";
21
+ import { IconButton } from "./actions.js";
22
+ // FileInput mora em arquivo próprio (132 linhas — upload real, aborto, progresso) e é público
23
+ // por aqui: a categoria dele é Inputs.
24
+ export { FileInput, matchesAccept } from "./file-input.js";
25
+ // hint e error são DESCRIÇÃO (aria-describedby), nunca parte do NOME. Nome é o que o campo É;
26
+ // hint e erro são sobre o VALOR.
27
+ //
28
+ // ISTO JÁ ESTAVA ESCRITO AQUI E ERA MENTIRA — reaberto como AUD-0001 em 12/08/2026, e o achado
29
+ // A12 volta com ele. O comentário anterior dizia "medido em 30/07/2026, corrigido", e a marcação
30
+ // logo abaixo mantinha `hint` e `error` DENTRO do `<label>` externo. Nome acessível medido:
31
+ // "E-mail Usamos para entrar Endereço inválido" — os três grudados —, e a descrição repetindo o
32
+ // que já estava no nome. Quinta vez neste repositório que comentário passou por código; a
33
+ // diferença é que aqui ele passou por CORREÇÃO. O teste que devia pegar aceitava `/E-mail/`, que
34
+ // casa com a frase inteira: regex frouxa é gate que dorme.
35
+ //
36
+ // O contêiner é `<div>` e não `<label>`, e isso resolve três coisas de uma vez: hint e erro saem
37
+ // da árvore de nome; `Checkbox`/`Radio`/`Switch`, que trazem `<label>` próprio, param de produzir
38
+ // `<label>` dentro de `<label>` (HTML inválido); e a ligação passa a ser explícita.
39
+ //
40
+ // DUAS FORMAS, e a regra que escolhe entre elas é UMA: o Field só é dono do NOME quando há um
41
+ // filho único que não se nomeia sozinho.
42
+ // • dono do nome → `<label htmlFor>`, e o filho recebe `id`/`aria-describedby`/`aria-invalid`;
43
+ // • não dono → `role="group"` + `aria-labelledby`, sem `htmlFor`.
44
+ // Os dois casos de "não dono" foram medidos, não imaginados:
45
+ // 1. VÁRIOS FILHOS — o catálogo já faz isso (um grupo de rádios sob um rótulo). Aqui `htmlFor`
46
+ // apontaria para um id que não existe, o que é pior que não ligar.
47
+ // 2. FILHO COM RÓTULO PRÓPRIO — `Switch`, `Checkbox`, `Radio` trazem `<label>` deles. Somar um
48
+ // `<label for>` daria DOIS rótulos ao mesmo controle, e o nome acessível vira a concatenação
49
+ // dos dois ("Notificações Ativo"). A detecção é a prop `label` do filho: é prop NOSSA, não
50
+ // é farejar tipo — todo controle que se nomeia declara ela.
51
+ // O `role="group"` não é invenção: é o que a ficha do Field sempre declarou.
52
+ //
53
+ // LIMITE DECLARADO, e é o que faz o clone falhar em silêncio: componente que destrincha uma
54
+ // lista fixa de props DESCARTA o que for injetado. `Input`, `Select`, `Textarea`, `Range` e
55
+ // `Switch` espalham `...props` e recebem tudo. `Combobox` e `MultiCombobox` não espalhavam, e
56
+ // por isso ganharam `aria-describedby`/`aria-invalid` explícitos abaixo. Os outros
57
+ // (`NumberField`, `OTPField`, `SegmentedControl`, `Toggle`) têm rótulo próprio e não são filhos
58
+ // de `Field` — a ficha diz quais são.
59
+ // As tags que o HTML permite associar a um `<label>`. Fora desta lista (e fora dos nossos
60
+ // componentes), o filho é layout e o Field vira grupo — ver o comentário dentro da função.
61
+ const TAGS_ROTULAVEIS = new Set(["input", "select", "textarea", "button", "meter", "output", "progress"]);
62
+ function filhoUnico(children) {
63
+ const filhos = React.Children.toArray(children);
64
+ if (filhos.length !== 1 || !React.isValidElement(filhos[0]))
65
+ return null;
66
+ const filho = filhos[0];
67
+ return filho.type === React.Fragment ? filhoUnico(filho.props.children) : filho;
68
+ }
69
+ function elementoRotulavel(tipo) {
70
+ if (typeof tipo === "string")
71
+ return TAGS_ROTULAVEIS.has(tipo);
72
+ // Componente React arbitrário pode consumir as props no wrapper e nunca entregá-las ao nó
73
+ // focal. Só os controles Aurea cujo encaminhamento é parte do contrato entram aqui.
74
+ return [Input, Select, Textarea, SearchField, Checkbox, Radio, Switch, Range, Combobox, MultiCombobox].includes(tipo);
75
+ }
76
+ export function Field({ label, hint, error, children, className, id, name, ...props }) {
77
+ const auto = React.useId();
78
+ // Fragment não cria nó no DOM. Um Fragment com um só controle é só sintaxe; com vários,
79
+ // Field precisa voltar para grupo em vez de pendurar `htmlFor` num id que nunca existirá.
80
+ const unico = filhoUnico(children);
81
+ // e o TERCEIRO caso de "não dono", que o gate achou e eu não tinha previsto: o filho único pode
82
+ // ser um `<div>` de LAYOUT com os controles dentro — o catálogo faz isso no `Range` ("With the
83
+ // number": um slider e um campo numérico lado a lado). `htmlFor` apontando para um `<div>` não
84
+ // nomeia nada, e o `catalog-sweep` reprovou `range.html` com duas violações de `label` no axe.
85
+ // A leitura é do TIPO do elemento JSX, que é API estável do React. Para tag nativa vale a lista
86
+ // do HTML; para componente vale a lista explícita dos controles Aurea que repassam a ligação ao
87
+ // nó focal. Componente arbitrário vira grupo — presumir forwarding recria o id sem alvo.
88
+ const rotulavel = !!unico && elementoRotulavel(unico.type);
89
+ // `label` é a API dos controles compostos; aria-label/labelledby são a API universal. Se uma
90
+ // delas já nomeia o filho, acrescentar `<label for>` daria dois donos para o mesmo nome.
91
+ const nomeProprio = rotulavel && (unico.props.label !== undefined || [unico.props["aria-label"], unico.props["aria-labelledby"]].some(v => typeof v === "string" && v.trim().length > 0));
92
+ const nomeAqui = rotulavel && !nomeProprio;
93
+ // o id que o filho já declara VENCE: sobrescrevê-lo quebraria uma ligação que o consumidor fez.
94
+ const idControle = unico?.props.id ?? id ?? `${auto}-control`;
95
+ const idRotulo = `${auto}-label`;
96
+ const idHint = hint ? `${auto}-hint` : undefined;
97
+ const idErro = error ? `${auto}-error` : undefined;
98
+ const descrito = [idHint, idErro].filter(Boolean).join(" ") || undefined;
99
+ // As chaves de aria só entram quando têm VALOR. Passar `undefined` explícito não é o mesmo que
100
+ // não passar: quando o filho vira `Field.Control` do motor (ramo do `name`), a prop explícita
101
+ // do elemento vence a mesclagem, e o `aria-invalid` que o motor calculou a partir do erro
102
+ // externo era apagado por um `undefined` nosso. Medido em 13/08/2026: o input saía com
103
+ // `data-invalid` e SEM `aria-invalid`, ou seja, vermelho na tela e válido para o leitor de
104
+ // tela — que é o pior dos dois mundos.
105
+ const injecao = { id: idControle };
106
+ const descritoFinal = [unico?.props["aria-describedby"], descrito].filter(Boolean).join(" ");
107
+ if (descritoFinal)
108
+ injecao["aria-describedby"] = descritoFinal;
109
+ const invalidoFinal = error ? true : unico?.props["aria-invalid"];
110
+ if (invalidoFinal !== undefined)
111
+ injecao["aria-invalid"] = invalidoFinal;
112
+ const controle = rotulavel ? React.cloneElement(unico, injecao) : children;
113
+ // a descrição mora no nó FOCAL quando existe um; sem filho único, no grupo — que é o que sobra
114
+ // para anunciar. Nos dois lugares seria anunciar duas vezes.
115
+ // Com `name`, o filho passa a ser o CONTROLE do motor: é isso que faz o `aria-invalid` e o
116
+ // `aria-describedby` do erro externo chegarem ao nó focal. Medido em 13/08/2026 — sem este
117
+ // embrulho o `Field.Root` recebe o erro e não tem a quem entregá-lo, e o campo fica válido na
118
+ // árvore de acessibilidade enquanto a frase de erro aparece na tela. Pior que não mostrar.
119
+ const controleFinal = name ? _jsx(BaseField.Control, { render: controle }) : controle;
120
+ const corpo = _jsxs("div", { className: cx("field", className), ...(nomeAqui ? {} : { role: "group", "aria-labelledby": idRotulo, ...(rotulavel ? {} : { "aria-describedby": descrito }) }), ...props, children: [_jsxs("span", { className: "label", children: [nomeAqui ? _jsx("label", { htmlFor: idControle, children: label }) : _jsx("span", { id: idRotulo, children: label }), hint && _jsx("span", { className: "hint", id: idHint, children: hint })] }), controleFinal, error && _jsx("span", { className: "field-error", id: idErro, children: error }), name && _jsx(BaseField.Error, { className: "field-error" })] });
121
+ // `Field.Root` do motor entra SÓ quando há `name`: é ele que liga o campo à chave de `errors`
122
+ // do formulário. Ele não desenha nada — é contexto — então a pele não muda.
123
+ return name ? _jsx(BaseField.Root, { name: name, invalid: error ? true : undefined, render: corpo }) : corpo;
124
+ }
125
+ // Form (M1): o formulário que EXIBE erro, e não o que decide o que é erro.
126
+ //
127
+ // A divisão saiu de pesquisa e da medição dos consumidores (13/08/2026). O padrão de mercado é
128
+ // `react-hook-form` + `zod`, e é o que o shadcn embrulha — mas embrulhar isso aqui obrigaria TODO
129
+ // projeto que usa a Aurea a usar react-hook-form, inclusive os que já validam de outro jeito (um
130
+ // consumidor real valida com zod em server action, sem biblioteca de formulário no cliente).
131
+ // Dependência nova também é decisão do Victor pelo BUILDING.md §3.3, e ela não foi necessária:
132
+ // o `Form` do Base UI — motor que a Aurea JÁ paga desde a Fase 2 — declara no próprio tipo que
133
+ // `errors` são "validation errors returned externally, typically after submission by a server or
134
+ // a form action". Ou seja, a divisão que eu ia propor já é a do motor.
135
+ //
136
+ // O que a Aurea entrega: a pele, o `name` ligando campo a erro, e o envio com os valores já
137
+ // coletados. O que ela NÃO entrega, e não vai: schema, regra, resolver, estado de campo.
138
+ export function Form({ errors, onSubmit, children, className, ...props }) {
139
+ return _jsx(BaseForm, { errors: errors, onFormSubmit: onSubmit ? (v) => onSubmit(v) : undefined, className: cx("form", className), ...props, children: children });
140
+ }
141
+ export const Input = forwardRef(function Input({ className, formatOnBlur, onBlur, onChange, ...props }, ref) {
142
+ return _jsx("input", { ref: ref, className: cx("input", className), onChange: onChange, onBlur: e => {
143
+ if (formatOnBlur) {
144
+ const formatado = formatOnBlur(e.currentTarget.value);
145
+ if (formatado !== e.currentTarget.value) {
146
+ e.currentTarget.value = formatado;
147
+ onChange?.(e);
148
+ }
149
+ }
150
+ onBlur?.(e);
151
+ }, ...props });
152
+ });
153
+ export function SearchField({ icon = "search", className, ...props }) { return _jsxs("div", { className: cx("input-wrap", className), children: [_jsx(Icon, { name: icon }), _jsx(Input, { type: "search", ...props })] }); }
154
+ // labelHidden: coluna de seleção de tabela precisa do rótulo POR LINHA para o leitor de
155
+ // tela ("Select Analyst"), mas mostrá-lo engorda a coluna. O rótulo continua no DOM, só
156
+ // sai da tela (.sr-only) — nunca trocar por aria-label solto num <label> visível vazio.
157
+ // NumberField (Lote 1 do BUILDING.md). Um `<input type="number">` cru tem três problemas que
158
+ // este resolve, e todos vieram do motor, não de nós: as setas nativas são alvos minúsculos e
159
+ // somem no Safari; a roda do mouse altera o valor por acidente sobre o campo focado; e o valor
160
+ // digitado não é formatado por locale. O Base UI trata os três.
161
+ // A anatomia (menos · campo · mais numa peça só) é a que as referências convergem.
162
+ // Fora de propósito: o ScrubArea do Base UI — arrastar o rótulo para variar o número. É gesto
163
+ // que ninguém descobre sozinho e que não tem equivalente por teclado.
164
+ // `format`/`locale`/`name` são a metade de MOEDA do item L6, e não custaram componente nenhum: o
165
+ // comentário acima já dizia que o motor formata por locale, e a nossa casca simplesmente não
166
+ // repassava a opção. Medido no `@base-ui/react@1.6.0`: `NumberFieldRoot` aceita
167
+ // `format?: Intl.NumberFormatOptions` e `locale?`, e formata no BLUR — que é exatamente o momento
168
+ // que a pesquisa de acessibilidade recomenda, e o oposto da máscara ao vivo.
169
+ // Com isso, `<NumberField format={{style:"currency",currency:"BRL"}} locale="pt-BR"/>` substitui a
170
+ // biblioteca de moeda que um consumidor carrega só para isso. Quem formata é o `Intl` da
171
+ // plataforma; a Aurea não escreve formatador de dinheiro, e nem deveria.
172
+ // `name` entra junto porque é ele que faz o motor renderizar o input escondido com o valor CRU —
173
+ // é a trava do item ("o valor mascarado continua no DOM") entregue pelo motor, e o teste a cobra.
174
+ export function NumberField({ value, defaultValue, onValueChange, min, max, step, disabled, readOnly, required, label, id, name, format, locale, className }) {
175
+ const s = useAureaStrings();
176
+ return _jsx(BaseNumberField.Root, { id: id, name: name, value: value, defaultValue: defaultValue, onValueChange: 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: [_jsx(BaseNumberField.Decrement, { className: "btn btn-ghost btn-icon", "aria-label": s.decrement, children: _jsx(Icon, { name: "subtract" }) }), _jsx(BaseNumberField.Input, { className: "input number-field-input", "aria-label": label }), _jsx(BaseNumberField.Increment, { className: "btn btn-ghost btn-icon", "aria-label": s.increment, children: _jsx(Icon, { name: "add" }) })] }) });
177
+ }
178
+ // OTPField (Lote 1 do BUILDING.md). Um campo por dígito, com o comportamento que ninguém acerta
179
+ // à mão: colar o código inteiro distribui pelos campos, Backspace volta um, e o
180
+ // `autocomplete="one-time-code"` deixa o iOS oferecer o código do SMS. Tudo do motor.
181
+ // Uma referência resolve isto com um pacote npm separado; aqui não entra dependência nova —
182
+ // o `@base-ui/react`, que já é a única dependência de runtime da biblioteca, tem `otp-field`.
183
+ // `length` é obrigatório de propósito: sem ele o campo não sabe quando está completo.
184
+ // CADA caixa leva nome próprio, e não só o grupo. O `catalog-sweep` pegou isto no primeiro
185
+ // lote: com `aria-label` só na raiz, o axe acusou `label` em todas as seis — seis campos de
186
+ // formulário anônimos. Um leitor de tela anunciaria "editar texto" seis vezes seguidas sem
187
+ // dizer qual é qual. O grupo continua nomeado (`role="group"`), que é o que diz para que serve
188
+ // o código; o nome de cada caixa é o que diz onde você está.
189
+ export function OTPField({ length, value, defaultValue, onValueChange, mask, disabled, required, label, id, className }) {
190
+ const s = useAureaStrings();
191
+ return _jsx(BaseOTPField.Root, { id: id, length: length, value: value, defaultValue: defaultValue, onValueChange: onValueChange, mask: mask, disabled: disabled, required: required, role: "group", "aria-label": label, className: cx("otp-field", className), children: Array.from({ length }, (_, i) => _jsx(BaseOTPField.Input, { render: _jsx("input", { "aria-label": `${s.otpDigit} ${i + 1}` }), className: "input otp-slot" }, i)) });
192
+ }
193
+ // SegmentedControl — achado M20, fechado na Parte C do PLANO-1.0 em 07/08/2026.
194
+ //
195
+ // O DEFEITO: era `role="group"` com N botões de `aria-pressed`. Isso descreve N alternâncias
196
+ // INDEPENDENTES — cada botão anuncia "pressionado/não pressionado", nada diz que só um pode
197
+ // valer, e nunca se ouve "1 de 2". Para escolha única entre poucas opções o padrão APG é
198
+ // `radiogroup`, e a diferença é o que o leitor de tela consegue prometer.
199
+ // A troca mudou a semântica de um componente publicado, então foi registrada: ADR-0016.
200
+ //
201
+ // O MOTOR ENTREGA, ENTÃO NÃO ESCREVEMOS (BUILDING.md §1). O `RadioGroup` do Base UI 1.6 traz o
202
+ // padrão inteiro: roving tabindex, setas que movem E selecionam, Home/End, e `aria-checked`.
203
+ // Escrever isso à mão seria reimplementar um composite que já está testado.
204
+ //
205
+ // `render={<button type="button"/>}` não é enfeite, e a medição de 07/08 é que disse: o
206
+ // `Radio.Root` renderiza um `<span>` por padrão, e a pele da Aurea é `.segmented button`. Sem
207
+ // isso o componente perderia a pele inteira — e o check 18 não veria, porque ele olha CLASSE.
208
+ // O motor também emite um `<input type="radio">` escondido por item (para envio de formulário);
209
+ // ele é `position:fixed` e `aria-hidden`, então não entra no flex nem na árvore de acessibilidade.
210
+ //
211
+ // E `nativeButton` é o par obrigatório do `render` acima — medido em 11/08/2026. O `Radio.Root`
212
+ // declara `nativeButton = false` por padrão (é o valor certo para o `<span>` que ele renderiza
213
+ // sozinho), e o `useButton` do motor CONFERE isso no DOM montado, dentro de um efeito: sem esta
214
+ // palavra sai um `console.error` do Base UI a cada montagem de CLIENTE. Não no catálogo, que é
215
+ // HTML estático — no console de quem INSTALA.
216
+ // O DOM foi medido nos dois lados antes da troca. O `role="button"` que a mensagem ameaça NÃO
217
+ // aparecia (o `role="radio"` do motor já vencia a fusão); o que muda de fato é o `<input>`
218
+ // escondido perder o `id`, que migra para este botão — e migra para o lado certo, porque o botão
219
+ // é o rádio e o input é `aria-hidden`. Comportamento idêntico: Enter não seleciona, Espaço
220
+ // seleciona, a seta move E seleciona. O que sai é a camada sintética que o motor punha por cima
221
+ // da ativação nativa do `<button>`.
222
+ export function SegmentedControl({ items, value, onChange, label }) {
223
+ const s = useAureaStrings();
224
+ return _jsx(BaseRadioGroup, { className: "segmented", "aria-label": label ?? s.optionsLabel, 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: i.label }, i.value)) });
225
+ }
226
+ // `aria-describedby` e `aria-invalid` são explícitos e não caem em `...props` por acidente: este
227
+ // componente destrincha uma lista fixa, então o que não está aqui é DESCARTADO em silêncio — e era
228
+ // exatamente isso que fazia o `Field` não alcançar o input do Combobox (AUD-0001, 12/08/2026).
229
+ // Vão para o `Input`, que é o nó focal: no contêiner eles não seriam anunciados.
230
+ // `disabled` vai na ROOT, não no Input: `Input`, `Trigger`, `Clear` e `ChipRemove` leem todos o
231
+ // mesmo `selectors.disabled` do store do Base UI (@base-ui/react 1.6.0). Desabilitar o Input
232
+ // deixaria os dois botões vivos — abrir a lista de um campo desabilitado é o defeito, não o texto.
233
+ export function Combobox({ items, value, onValueChange, placeholder, label, disabled, id, className, "aria-describedby": descrito, "aria-invalid": invalido }) {
234
+ const s = useAureaStrings();
235
+ const portal = usePortalContainer();
236
+ const autoId = React.useId();
237
+ const inputId = id ?? autoId;
238
+ return _jsxs(BaseCombobox.Root, { items: items, value: value, onValueChange: onValueChange, disabled: disabled, itemToStringLabel: (i) => i.label, children: [_jsxs("div", { className: cx("field", className), children: [label && _jsx("label", { className: "label", htmlFor: inputId, children: label }), _jsxs(BaseCombobox.InputGroup, { className: "combobox-group", children: [_jsx(BaseCombobox.Input, { id: inputId, placeholder: placeholder, className: "input", "aria-describedby": descrito, "aria-invalid": invalido }), _jsxs("span", { className: "combobox-actions", children: [_jsx(BaseCombobox.Clear, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.comboboxClear }) }), _jsx(BaseCombobox.Trigger, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "chevron--down", label: s.comboboxOpen }) })] })] })] }), _jsx(BaseCombobox.Portal, { container: portal, children: _jsx(BaseCombobox.Positioner, { sideOffset: 6, children: _jsxs(BaseCombobox.Popup, { className: "menu combobox-popup", children: [_jsx(BaseCombobox.Empty, { className: "combobox-empty", children: s.comboboxEmpty }), _jsx(BaseCombobox.List, { children: (item) => _jsxs(BaseCombobox.Item, { value: item, className: "menu-item combobox-item", children: [_jsx(BaseCombobox.ItemIndicator, { className: "combobox-check", children: _jsx(Icon, { name: "checkmark", size: "sm" }) }), _jsx("span", { children: item.label })] }, item.value) })] }) }) })] });
239
+ }
240
+ const isGrouped = (items) => items.length > 0 && "items" in items[0];
241
+ export function MultiCombobox({ items, value, onValueChange, onInputChange, loading, placeholder, label, disabled, id, className, "aria-describedby": descrito, "aria-invalid": invalido }) {
242
+ const s = useAureaStrings();
243
+ const portal = usePortalContainer();
244
+ const autoId = React.useId();
245
+ const inputId = id ?? autoId;
246
+ const renderItem = (item) => _jsxs(BaseCombobox.Item, { value: item, className: "menu-item combobox-item", children: [_jsx(BaseCombobox.ItemIndicator, { className: "combobox-check", children: _jsx(Icon, { name: "checkmark", size: "sm" }) }), _jsx("span", { children: item.label })] }, item.value);
247
+ return _jsxs(BaseCombobox.Root, { multiple: true, items: items, value: value, onValueChange: onValueChange, disabled: disabled, itemToStringLabel: (i) => i.label, filter: onInputChange ? null : undefined, onInputValueChange: onInputChange ? ((v, d) => { if (d.reason !== "item-press")
248
+ onInputChange(v); }) : undefined, children: [_jsxs("div", { className: cx("field", className), children: [label && _jsx("label", { className: "label", htmlFor: inputId, children: label }), _jsxs(BaseCombobox.InputGroup, { className: "combobox-multi", children: [_jsx(BaseCombobox.Chips, { className: "combobox-chips", children: _jsx(BaseCombobox.Value, { children: (selected) => _jsxs(_Fragment, { children: [selected.map((item) => _jsxs(BaseCombobox.Chip, { className: "combobox-chip", children: [item.label, _jsx(BaseCombobox.ChipRemove, { className: "combobox-chip-remove", "aria-label": `${s.comboboxRemove} ${item.label}`, children: _jsx(Icon, { name: "close", size: "sm" }) })] }, item.value)), _jsx(BaseCombobox.Input, { id: inputId, placeholder: selected.length ? undefined : placeholder, className: "combobox-chip-input", "aria-describedby": descrito, "aria-invalid": invalido })] }) }) }), _jsxs("span", { className: "combobox-actions", children: [_jsx(BaseCombobox.Clear, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.comboboxClear }) }), _jsx(BaseCombobox.Trigger, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "chevron--down", label: s.comboboxOpen }) })] })] })] }), _jsx(BaseCombobox.Portal, { container: portal, children: _jsx(BaseCombobox.Positioner, { sideOffset: 6, children: _jsxs(BaseCombobox.Popup, { className: "menu combobox-popup", children: [_jsx(BaseCombobox.Empty, { className: "combobox-empty", children: loading ? s.comboboxLoading : s.comboboxEmpty }), _jsx(BaseCombobox.List, { children: isGrouped(items)
249
+ ? (group) => _jsxs(BaseCombobox.Group, { items: group.items, className: "combobox-section", children: [_jsx(BaseCombobox.GroupLabel, { className: "combobox-group-label", children: group.label }), _jsx(BaseCombobox.Collection, { children: renderItem })] }, group.label)
250
+ : renderItem })] }) }) })] });
251
+ }
252
+ export function BlockEditor({ blocks, onReorder, onRemove, label, className, ...props }) {
253
+ const s = useAureaStrings();
254
+ const bid = React.useId();
255
+ const n = blocks.length;
256
+ // O protocolo de arrasto acessível é o MESMO da `SortableList` — extraído para o `internal` em
257
+ // vez de copiado, porque duas cópias garantem que a próxima correção entre em uma só.
258
+ const r = useReorder({ count: n, order: blocks, onReorder,
259
+ rowSelector: ".block-item", handleSelector: ".block-handle" });
260
+ return _jsxs(_Fragment, { children: [_jsx("ol", { ref: r.ref, className: cx("block-editor", className), "aria-label": label ?? s.blockEditorLabel, ...props, children: blocks.map((b, i) => {
261
+ const kid = `${bid}k${i}`, hid = `${bid}h${i}`, rid = `${bid}r${i}`;
262
+ return _jsxs("li", { className: "block-item", "data-grabbed": r.pego === i || undefined, children: [_jsxs("div", { className: "block-rail", children: [_jsxs("button", { type: "button", id: hid, className: "block-handle", "aria-labelledby": `${hid} ${kid}`, "aria-describedby": `${bid}ajuda`, "aria-pressed": r.pego === i, onKeyDown: e => r.teclado(e, i), onPointerDown: e => r.ponteiroBaixo(e, i), onPointerMove: r.ponteiroMove, onPointerUp: r.ponteiroSolta, onPointerCancel: r.ponteiroSolta, children: [_jsx(Icon, { name: "drag--horizontal" }), _jsx("span", { className: "sr-only", children: s.sortableHandle })] }), onRemove && _jsxs("button", { type: "button", id: rid, className: "block-remove", "aria-labelledby": `${rid} ${kid}`, onClick: () => onRemove(i), children: [_jsx(Icon, { name: "trash-can" }), _jsx("span", { className: "sr-only", children: s.blockRemove })] })] }), _jsx("div", { className: "block-body", children: b.children }), _jsxs("span", { id: kid, className: "sr-only", children: [b.kind ?? s.blockLabel, " ", i + 1, " ", s.positionOf, " ", n] })] }, b.id);
263
+ }) }), _jsx("span", { id: `${bid}ajuda`, className: "sr-only", children: s.sortableHelp }), _jsx("div", { role: "status", "aria-live": "polite", className: "sr-only", children: r.aviso })] });
264
+ }
package/dist/inputs.d.ts CHANGED
@@ -1,91 +1,2 @@
1
- import React, { type HTMLAttributes, type InputHTMLAttributes, type SelectHTMLAttributes, type TextareaHTMLAttributes, type RefAttributes, type ReactNode } from "react";
2
- import { type IconName } from "./system.js";
3
- export { FileInput, matchesAccept, type FileRejection, type UploadContext, type UploadFn } from "./file-input.js";
4
- export interface FieldProps extends HTMLAttributes<HTMLLabelElement>, RefAttributes<HTMLLabelElement> {
5
- label: string;
6
- hint?: ReactNode;
7
- error?: ReactNode;
8
- htmlFor?: string;
9
- }
10
- export declare function Field({ label, hint, error, children, className, id, ...props }: FieldProps): React.JSX.Element;
11
- export declare const Input: React.ForwardRefExoticComponent<Omit<InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;
12
- export declare const Select: React.ForwardRefExoticComponent<Omit<SelectHTMLAttributes<HTMLSelectElement> & RefAttributes<HTMLSelectElement>, "ref"> & RefAttributes<HTMLSelectElement>>;
13
- export declare const Textarea: React.ForwardRefExoticComponent<Omit<TextareaHTMLAttributes<HTMLTextAreaElement> & RefAttributes<HTMLTextAreaElement>, "ref"> & RefAttributes<HTMLTextAreaElement>>;
14
- export declare function SearchField({ icon, className, ...props }: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement> & {
15
- icon?: IconName;
16
- }): React.JSX.Element;
17
- export declare function Checkbox({ label, labelHidden, description, className, ...props }: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement> & {
18
- label: ReactNode;
19
- labelHidden?: boolean;
20
- description?: ReactNode;
21
- }): React.JSX.Element;
22
- export declare function Radio({ label, className, ...props }: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement> & {
23
- label: ReactNode;
24
- }): React.JSX.Element;
25
- export declare function Switch({ label, className, ...props }: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement> & {
26
- label: ReactNode;
27
- }): React.JSX.Element;
28
- export declare function Range(props: InputHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement>): React.JSX.Element;
29
- export declare function NumberField({ value, defaultValue, onValueChange, min, max, step, disabled, readOnly, required, label, id, className }: {
30
- value?: number | null;
31
- defaultValue?: number;
32
- onValueChange?: (v: number | null) => void;
33
- min?: number;
34
- max?: number;
35
- step?: number;
36
- disabled?: boolean;
37
- readOnly?: boolean;
38
- required?: boolean;
39
- label?: string;
40
- id?: string;
41
- className?: string;
42
- }): React.JSX.Element;
43
- export declare function OTPField({ length, value, defaultValue, onValueChange, mask, disabled, required, label, id, className }: {
44
- length: number;
45
- value?: string;
46
- defaultValue?: string;
47
- onValueChange?: (v: string) => void;
48
- mask?: boolean;
49
- disabled?: boolean;
50
- required?: boolean;
51
- label?: string;
52
- id?: string;
53
- className?: string;
54
- }): React.JSX.Element;
55
- export declare function SegmentedControl({ items, value, onChange, label }: {
56
- items: Array<{
57
- value: string;
58
- label: ReactNode;
59
- }>;
60
- value: string;
61
- onChange: (v: string) => void;
62
- label?: string;
63
- }): React.JSX.Element;
64
- export interface ComboboxOption {
65
- value: string;
66
- label: string;
67
- }
68
- export declare function Combobox({ items, value, onValueChange, placeholder, label, id, className }: {
69
- items: ComboboxOption[];
70
- value?: ComboboxOption | null;
71
- onValueChange?: (v: ComboboxOption | null) => void;
72
- placeholder?: string;
73
- label?: ReactNode;
74
- id?: string;
75
- className?: string;
76
- }): React.JSX.Element;
77
- export interface ComboboxOptGroup {
78
- label: string;
79
- items: ComboboxOption[];
80
- }
81
- export declare function MultiCombobox({ items, value, onValueChange, onInputChange, loading, placeholder, label, id, className }: {
82
- items: ComboboxOption[] | ComboboxOptGroup[];
83
- value?: ComboboxOption[];
84
- onValueChange?: (v: ComboboxOption[]) => void;
85
- onInputChange?: (query: string) => void;
86
- loading?: boolean;
87
- placeholder?: string;
88
- label?: ReactNode;
89
- id?: string;
90
- className?: string;
91
- }): React.JSX.Element;
1
+ export * from "./inputs-client.js";
2
+ export { Select, Textarea, Checkbox, Radio, Switch, Range } from "./markup.js";
package/dist/inputs.js CHANGED
@@ -1,126 +1,9 @@
1
- "use client";
2
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
4
- // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
5
- // internal system actions feedback inputs navigation layout data-display → resto.
6
- import React, { forwardRef } from "react";
7
- import { Combobox as BaseCombobox } from "@base-ui/react/combobox";
8
- import { NumberField as BaseNumberField } from "@base-ui/react/number-field";
9
- import { OTPField as BaseOTPField } from "@base-ui/react/otp-field";
10
- import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group";
11
- import { Radio as BaseRadio } from "@base-ui/react/radio";
12
- import { cx, useAureaStrings, usePortalContainer } from "./internal.js";
13
- import { Icon } from "./system.js";
14
- import { IconButton } from "./actions.js";
15
- // FileInput mora em arquivo próprio (132 linhas — upload real, aborto, progresso) e é público
16
- // por aqui: a categoria dele é Inputs.
17
- export { FileInput, matchesAccept } from "./file-input.js";
18
- // hint e error entram como DESCRIÇÃO (aria-describedby) e não como parte do nome. Medido em
19
- // 30/07/2026: por estarem dentro do <label>, o nome acessível do campo virava "E-mail Usamos
20
- // para entrar Endereço inválido" — tudo grudado — e nada dizia ao leitor de tela que o valor
21
- // era inválido. Nome é o que o campo É; hint e erro são sobre o valor.
22
- // O controle vem como children, então a ligação é feita clonando-o: é a única forma de pôr
23
- // aria-describedby/aria-invalid no elemento certo sem obrigar o consumidor a repetir ids.
24
- export function Field({ label, hint, error, children, className, id, ...props }) {
25
- const auto = React.useId();
26
- const base = id ?? auto;
27
- const idHint = hint ? `${base}-hint` : undefined;
28
- const idErro = error ? `${base}-error` : undefined;
29
- const descrito = [idHint, idErro].filter(Boolean).join(" ") || undefined;
30
- const controle = React.isValidElement(children)
31
- ? React.cloneElement(children, {
32
- "aria-describedby": [children.props["aria-describedby"], descrito].filter(Boolean).join(" ") || undefined,
33
- "aria-invalid": error ? true : children.props["aria-invalid"],
34
- })
35
- : children;
36
- return _jsxs("label", { className: cx("field", className), id: id, ...props, children: [_jsxs("span", { className: "label", children: [label, hint && _jsx("span", { className: "hint", id: idHint, children: hint })] }), controle, error && _jsx("span", { className: "field-error", id: idErro, children: error })] });
37
- }
38
- export const Input = forwardRef(function Input({ className, ...props }, ref) { return _jsx("input", { ref: ref, className: cx("input", className), ...props }); });
39
- export const Select = forwardRef(function Select({ className, ...props }, ref) { return _jsx("select", { ref: ref, className: cx("select", className), ...props }); });
40
- export const Textarea = forwardRef(function Textarea({ className, ...props }, ref) { return _jsx("textarea", { ref: ref, className: cx("textarea", className), ...props }); });
41
- export function SearchField({ icon = "search", className, ...props }) { return _jsxs("div", { className: cx("input-wrap", className), children: [_jsx(Icon, { name: icon }), _jsx(Input, { type: "search", ...props })] }); }
42
- // labelHidden: coluna de seleção de tabela precisa do rótulo POR LINHA para o leitor de
43
- // tela ("Select Analyst"), mas mostrá-lo engorda a coluna. O rótulo continua no DOM, só
44
- // sai da tela (.sr-only) — nunca trocar por aria-label solto num <label> visível vazio.
45
- export function Checkbox({ label, labelHidden, description, className, ...props }) { return _jsxs("label", { className: cx("checkbox", labelHidden && "checkbox-bare", className), children: [_jsx("input", { type: "checkbox", ...props }), _jsx("span", { className: "control-mark" }), _jsxs("span", { className: labelHidden ? "sr-only" : undefined, children: [_jsx("strong", { children: label }), description && _jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "muted", children: description })] })] })] }); }
46
- export function Radio({ label, className, ...props }) { return _jsxs("label", { className: cx("radio", className), children: [_jsx("input", { type: "radio", ...props }), _jsx("span", { className: "control-mark" }), label] }); }
47
- export function Switch({ label, className, ...props }) { return _jsxs("label", { className: cx("switch", className), children: [_jsx("input", { type: "checkbox", role: "switch", ...props }), _jsx("span", { className: "switch-track" }), _jsx("span", { children: label })] }); }
48
- export function Range(props) { return _jsx("input", { className: cx("range", props.className), type: "range", ...props }); }
49
- // NumberField (Lote 1 do BUILDING.md). Um `<input type="number">` cru tem três problemas que
50
- // este resolve, e todos vieram do motor, não de nós: as setas nativas são alvos minúsculos e
51
- // somem no Safari; a roda do mouse altera o valor por acidente sobre o campo focado; e o valor
52
- // digitado não é formatado por locale. O Base UI trata os três.
53
- // A anatomia (menos · campo · mais numa peça só) é a que as referências convergem.
54
- // Fora de propósito: o ScrubArea do Base UI — arrastar o rótulo para variar o número. É gesto
55
- // que ninguém descobre sozinho e que não tem equivalente por teclado.
56
- export function NumberField({ value, defaultValue, onValueChange, min, max, step, disabled, readOnly, required, label, id, className }) {
57
- const s = useAureaStrings();
58
- return _jsx(BaseNumberField.Root, { id: id, value: value, defaultValue: defaultValue, onValueChange: onValueChange, min: min, max: max, step: step, disabled: disabled, readOnly: readOnly, required: required, className: cx("number-field", className), children: _jsxs(BaseNumberField.Group, { className: "number-field-group", children: [_jsx(BaseNumberField.Decrement, { className: "btn btn-ghost btn-icon", "aria-label": s.decrement, children: _jsx(Icon, { name: "subtract" }) }), _jsx(BaseNumberField.Input, { className: "input number-field-input", "aria-label": label }), _jsx(BaseNumberField.Increment, { className: "btn btn-ghost btn-icon", "aria-label": s.increment, children: _jsx(Icon, { name: "add" }) })] }) });
59
- }
60
- // OTPField (Lote 1 do BUILDING.md). Um campo por dígito, com o comportamento que ninguém acerta
61
- // à mão: colar o código inteiro distribui pelos campos, Backspace volta um, e o
62
- // `autocomplete="one-time-code"` deixa o iOS oferecer o código do SMS. Tudo do motor.
63
- // Uma referência resolve isto com um pacote npm separado; aqui não entra dependência nova —
64
- // o `@base-ui/react`, que já é a única dependência de runtime da biblioteca, tem `otp-field`.
65
- // `length` é obrigatório de propósito: sem ele o campo não sabe quando está completo.
66
- // CADA caixa leva nome próprio, e não só o grupo. O `catalog-sweep` pegou isto no primeiro
67
- // lote: com `aria-label` só na raiz, o axe acusou `label` em todas as seis — seis campos de
68
- // formulário anônimos. Um leitor de tela anunciaria "editar texto" seis vezes seguidas sem
69
- // dizer qual é qual. O grupo continua nomeado (`role="group"`), que é o que diz para que serve
70
- // o código; o nome de cada caixa é o que diz onde você está.
71
- export function OTPField({ length, value, defaultValue, onValueChange, mask, disabled, required, label, id, className }) {
72
- const s = useAureaStrings();
73
- return _jsx(BaseOTPField.Root, { id: id, length: length, value: value, defaultValue: defaultValue, onValueChange: onValueChange, mask: mask, disabled: disabled, required: required, role: "group", "aria-label": label, className: cx("otp-field", className), children: Array.from({ length }, (_, i) => _jsx(BaseOTPField.Input, { render: _jsx("input", { "aria-label": `${s.otpDigit} ${i + 1}` }), className: "input otp-slot" }, i)) });
74
- }
75
- // SegmentedControl — achado M20, fechado na Parte C do PLANO-1.0 em 07/08/2026.
76
- //
77
- // O DEFEITO: era `role="group"` com N botões de `aria-pressed`. Isso descreve N alternâncias
78
- // INDEPENDENTES — cada botão anuncia "pressionado/não pressionado", nada diz que só um pode
79
- // valer, e nunca se ouve "1 de 2". Para escolha única entre poucas opções o padrão APG é
80
- // `radiogroup`, e a diferença é o que o leitor de tela consegue prometer.
81
- // A troca mudou a semântica de um componente publicado, então foi registrada: ADR-0016.
82
- //
83
- // O MOTOR ENTREGA, ENTÃO NÃO ESCREVEMOS (BUILDING.md §1). O `RadioGroup` do Base UI 1.6 traz o
84
- // padrão inteiro: roving tabindex, setas que movem E selecionam, Home/End, e `aria-checked`.
85
- // Escrever isso à mão seria reimplementar um composite que já está testado.
86
- //
87
- // `render={<button type="button"/>}` não é enfeite, e a medição de 07/08 é que disse: o
88
- // `Radio.Root` renderiza um `<span>` por padrão, e a pele da Aurea é `.segmented button`. Sem
89
- // isso o componente perderia a pele inteira — e o check 18 não veria, porque ele olha CLASSE.
90
- // O motor também emite um `<input type="radio">` escondido por item (para envio de formulário);
91
- // ele é `position:fixed` e `aria-hidden`, então não entra no flex nem na árvore de acessibilidade.
92
- //
93
- // E `nativeButton` é o par obrigatório do `render` acima — medido em 11/08/2026. O `Radio.Root`
94
- // declara `nativeButton = false` por padrão (é o valor certo para o `<span>` que ele renderiza
95
- // sozinho), e o `useButton` do motor CONFERE isso no DOM montado, dentro de um efeito: sem esta
96
- // palavra sai um `console.error` do Base UI a cada montagem de CLIENTE. Não no catálogo, que é
97
- // HTML estático — no console de quem INSTALA.
98
- // O DOM foi medido nos dois lados antes da troca. O `role="button"` que a mensagem ameaça NÃO
99
- // aparecia (o `role="radio"` do motor já vencia a fusão); o que muda de fato é o `<input>`
100
- // escondido perder o `id`, que migra para este botão — e migra para o lado certo, porque o botão
101
- // é o rádio e o input é `aria-hidden`. Comportamento idêntico: Enter não seleciona, Espaço
102
- // seleciona, a seta move E seleciona. O que sai é a camada sintética que o motor punha por cima
103
- // da ativação nativa do `<button>`.
104
- export function SegmentedControl({ items, value, onChange, label }) {
105
- const s = useAureaStrings();
106
- return _jsx(BaseRadioGroup, { className: "segmented", "aria-label": label ?? s.optionsLabel, 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: i.label }, i.value)) });
107
- }
108
- export function Combobox({ items, value, onValueChange, placeholder, label, id, className }) {
109
- const s = useAureaStrings();
110
- const portal = usePortalContainer();
111
- const autoId = React.useId();
112
- const inputId = id ?? autoId;
113
- return _jsxs(BaseCombobox.Root, { items: items, value: value, onValueChange: onValueChange, itemToStringLabel: (i) => i.label, children: [_jsxs("div", { className: cx("field", className), children: [label && _jsx("label", { className: "label", htmlFor: inputId, children: label }), _jsxs(BaseCombobox.InputGroup, { className: "combobox-group", children: [_jsx(BaseCombobox.Input, { id: inputId, placeholder: placeholder, className: "input" }), _jsxs("span", { className: "combobox-actions", children: [_jsx(BaseCombobox.Clear, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.comboboxClear }) }), _jsx(BaseCombobox.Trigger, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "chevron--down", label: s.comboboxOpen }) })] })] })] }), _jsx(BaseCombobox.Portal, { container: portal, children: _jsx(BaseCombobox.Positioner, { sideOffset: 6, children: _jsxs(BaseCombobox.Popup, { className: "menu combobox-popup", children: [_jsx(BaseCombobox.Empty, { className: "combobox-empty", children: s.comboboxEmpty }), _jsx(BaseCombobox.List, { children: (item) => _jsxs(BaseCombobox.Item, { value: item, className: "menu-item combobox-item", children: [_jsx(BaseCombobox.ItemIndicator, { className: "combobox-check", children: _jsx(Icon, { name: "checkmark", size: "sm" }) }), _jsx("span", { children: item.label })] }, item.value) })] }) }) })] });
114
- }
115
- const isGrouped = (items) => items.length > 0 && "items" in items[0];
116
- export function MultiCombobox({ items, value, onValueChange, onInputChange, loading, placeholder, label, id, className }) {
117
- const s = useAureaStrings();
118
- const portal = usePortalContainer();
119
- const autoId = React.useId();
120
- const inputId = id ?? autoId;
121
- const renderItem = (item) => _jsxs(BaseCombobox.Item, { value: item, className: "menu-item combobox-item", children: [_jsx(BaseCombobox.ItemIndicator, { className: "combobox-check", children: _jsx(Icon, { name: "checkmark", size: "sm" }) }), _jsx("span", { children: item.label })] }, item.value);
122
- return _jsxs(BaseCombobox.Root, { multiple: true, items: items, value: value, onValueChange: onValueChange, itemToStringLabel: (i) => i.label, filter: onInputChange ? null : undefined, onInputValueChange: onInputChange ? ((v, d) => { if (d.reason !== "item-press")
123
- onInputChange(v); }) : undefined, children: [_jsxs("div", { className: cx("field", className), children: [label && _jsx("label", { className: "label", htmlFor: inputId, children: label }), _jsxs(BaseCombobox.InputGroup, { className: "combobox-multi", children: [_jsx(BaseCombobox.Chips, { className: "combobox-chips", children: _jsx(BaseCombobox.Value, { children: (selected) => _jsxs(_Fragment, { children: [selected.map((item) => _jsxs(BaseCombobox.Chip, { className: "combobox-chip", children: [item.label, _jsx(BaseCombobox.ChipRemove, { className: "combobox-chip-remove", "aria-label": `${s.comboboxRemove} ${item.label}`, children: _jsx(Icon, { name: "close", size: "sm" }) })] }, item.value)), _jsx(BaseCombobox.Input, { id: inputId, placeholder: selected.length ? undefined : placeholder, className: "combobox-chip-input" })] }) }) }), _jsxs("span", { className: "combobox-actions", children: [_jsx(BaseCombobox.Clear, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "close", label: s.comboboxClear }) }), _jsx(BaseCombobox.Trigger, { render: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "chevron--down", label: s.comboboxOpen }) })] })] })] }), _jsx(BaseCombobox.Portal, { container: portal, children: _jsx(BaseCombobox.Positioner, { sideOffset: 6, children: _jsxs(BaseCombobox.Popup, { className: "menu combobox-popup", children: [_jsx(BaseCombobox.Empty, { className: "combobox-empty", children: loading ? s.comboboxLoading : s.comboboxEmpty }), _jsx(BaseCombobox.List, { children: isGrouped(items)
124
- ? (group) => _jsxs(BaseCombobox.Group, { items: group.items, className: "combobox-section", children: [_jsx(BaseCombobox.GroupLabel, { className: "combobox-group-label", children: group.label }), _jsx(BaseCombobox.Collection, { children: renderItem })] }, group.label)
125
- : renderItem })] }) }) })] });
126
- }
1
+ // VITRINE da categoria — é este arquivo que `@aurea-uds/react/inputs` resolve. Sem `"use client"`
2
+ // de propósito: a diretiva contamina o módulo inteiro e faria a marcação pura chegar como cliente
3
+ // por vizinhança (ADR-0026; o check 26b reprova quem a puser de volta aqui).
4
+ //
5
+ // Os seis controles continuam IMPORTADOS pelo irmão de cliente, e não reexportados aqui: o
6
+ // `ehControle` do `Field` compara por IDENTIDADE de referência, e dois objetos com o mesmo nome
7
+ // quebrariam o rótulo do campo em silêncio. Um caminho de import, um objeto.
8
+ export * from "./inputs-client.js";
9
+ export { Select, Textarea, Checkbox, Radio, Switch, Range } from "./markup.js";
@@ -1,10 +1,25 @@
1
- import { type HTMLAttributes, type RefAttributes } from "react";
1
+ import React from "react";
2
2
  import { type AureaStrings } from "./pure.js";
3
3
  export { cx, defaultStrings, ptBR, defaultSpriteUrl, universalStates, stateSeverity, type AureaStrings, type UniversalState } from "./pure.js";
4
- export declare const StringsContext: import("react").Context<AureaStrings>;
4
+ export declare const StringsContext: React.Context<AureaStrings>;
5
5
  export declare const useAureaStrings: () => AureaStrings;
6
- export declare const SpriteContext: import("react").Context<string>;
6
+ export declare const SpriteContext: React.Context<string>;
7
7
  export declare const useSpriteUrl: () => string;
8
- export declare const PortalContext: import("react").Context<HTMLElement | null | undefined>;
8
+ export declare const PortalContext: React.Context<HTMLElement | null | undefined>;
9
9
  export declare const usePortalContainer: () => HTMLElement | null | undefined;
10
- export declare function Kbd({ children, className, ...props }: HTMLAttributes<HTMLElement> & RefAttributes<HTMLElement>): import("react").JSX.Element;
10
+ export interface Reordenavel<E extends HTMLElement> {
11
+ ref: React.RefObject<E | null>;
12
+ pego: number | null;
13
+ aviso: string;
14
+ teclado: (e: React.KeyboardEvent<HTMLButtonElement>, i: number) => void;
15
+ ponteiroBaixo: (e: React.PointerEvent<HTMLButtonElement>, i: number) => void;
16
+ ponteiroMove: (e: React.PointerEvent) => void;
17
+ ponteiroSolta: () => void;
18
+ }
19
+ export declare function useReorder<E extends HTMLElement>({ count, order, onReorder, rowSelector, handleSelector }: {
20
+ count: number;
21
+ order: unknown;
22
+ onReorder: (from: number, to: number) => void;
23
+ rowSelector: string;
24
+ handleSelector: string;
25
+ }): Reordenavel<E>;