@aurea-uds/native 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/busca.d.ts CHANGED
@@ -46,6 +46,13 @@ export interface ComboboxProps {
46
46
  empty?: React.ReactNode;
47
47
  /** Mostra o botão de limpar no gatilho quando há escolha. Padrão **true**. */
48
48
  clearable?: boolean;
49
+ /**
50
+ * Arrastar o cabeçalho da folha para baixo fecha. Ligado.
51
+ *
52
+ * ⚠ **É a ÚNICA saída por gesto no iOS** — lá não há botão VOLTAR, então sem isto restaria só
53
+ * tocar no fundo. Mesmo gesto, mesmo limiar e mesma curva do `BottomSheet` do Lote 5.
54
+ */
55
+ draggable?: boolean;
49
56
  disabled?: boolean;
50
57
  size?: AureaFieldSize;
51
58
  /** O glifo da seta. Registre-o, ou passe `false`. */
@@ -84,7 +91,7 @@ export interface ComboboxProps {
84
91
  * na web e pela mesma razão do achado I1 da auditoria: um componente com a sua própria cópia do
85
92
  * estado é a segunda fonte de verdade que ninguém sabe que existe.
86
93
  */
87
- export declare function Combobox({ items, value, onValueChange, onSearchChange, searchDelay, loading, onEndReached, placeholder, searchPlaceholder, empty, clearable, disabled, size, chevron, searchIcon, style, testID, }: ComboboxProps): React.JSX.Element;
94
+ export declare function Combobox({ items, value, onValueChange, onSearchChange, searchDelay, loading, onEndReached, placeholder, searchPlaceholder, empty, clearable, draggable, disabled, size, chevron, searchIcon, style, testID, }: ComboboxProps): React.JSX.Element;
88
95
  export interface SearchFieldProps {
89
96
  value?: string;
90
97
  onChangeText?: (v: string) => void;
package/dist/busca.js CHANGED
@@ -67,12 +67,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
67
67
  // aceita digitação, então `button` é o que ele É nos dois sistemas. Quem digita é o campo da
68
68
  // folha, e ele leva `search` — que mapeia nos dois.
69
69
  import * as React from "react";
70
- import { FlatList, Modal, Pressable, TextInput, View, } from "react-native";
70
+ import { Animated, Easing, FlatList, Modal, PanResponder, Pressable, TextInput, View, } from "react-native";
71
71
  import { IconButton } from "./actions.js";
72
72
  import { criarFolha } from "./estilos.js";
73
73
  import { Spinner } from "./feedback.js";
74
74
  import { Icon } from "./icon.js";
75
- import { useCampo } from "./inputs.js";
75
+ import { KeyboardAvoiding, useCampo } from "./inputs.js";
76
+ import { useReduceMotion } from "./movimento.js";
76
77
  import { Text } from "./text.js";
77
78
  import { useAureaStrings, useAureaTokens } from "./theme.js";
78
79
  // As mesmas contas do `inputs.tsx`, e elas são repetidas AQUI de propósito: exportá-las de lá
@@ -80,7 +81,23 @@ import { useAureaStrings, useAureaTokens } from "./theme.js";
80
81
  // módulos irmãos é o começo de um acoplamento que ninguém declara. São três linhas.
81
82
  const alturaDoTamanho = (t, s) => s === "sm" ? t.size.controlHSm : s === "lg" ? t.size.controlHLg : t.size.controlHMd;
82
83
  const fonteDoTamanho = (t, s) => s === "sm" ? t.size.textXs : s === "lg" ? t.size.textBase : t.size.textMd;
84
+ // ⚠ O `13` do tamanho `md` NÃO é número inventado: é o `padding-inline` literal do `.input`
85
+ // (`aurea.css:685`), e é o mesmo valor que o `inputs.tsx:60` usa. Não há token de 13 — a escala
86
+ // tem `space3`=12 e `space4`=16 —, e é por isso que o CSS o escreve cru dos dois lados.
83
87
  const respiroDoTamanho = (t, s) => s === "sm" ? t.size.space3 : s === "lg" ? t.size.space4 : 13;
88
+ // ── AS DUAS CONSTANTES DE INTERAÇÃO, e elas NÃO são geometria ────────────────────────────────
89
+ // Ficam nomeadas e no topo porque número solto no meio do componente é o que o `CLAUDE.md`
90
+ // passou a proibir em 12/09/2026. **Nenhuma das duas tem linha de `aurea.css` atrás, e não
91
+ // deveria ter:** uma é tempo de digitação, a outra é fração de viewport do motor de lista.
92
+ //
93
+ // 250 ms é o intervalo entre teclas de quem digita corrido. Abaixo disso a espera não junta
94
+ // letra nenhuma e cada tecla vira uma ida ao servidor; muito acima, a lista demora a reagir e a
95
+ // pessoa acha que o campo travou. `searchDelay` existe para o app discordar.
96
+ const ESPERA_PADRAO = 250;
97
+ // Fração da altura visível que sobra abaixo antes de pedir a próxima página. Meia tela de
98
+ // antecedência: menos que isso e a lista chega ao fim antes da resposta; mais, e o app pagina
99
+ // páginas que ninguém vai ver. É prop do `FlatList`, não medida de desenho.
100
+ const ANTECEDENCIA_DE_PAGINA = 0.5;
84
101
  const folha = criarFolha((t) => ({
85
102
  // O gatilho é o `.input` da web (`aurea.css:685`), igual ao do `Select` — mesma altura, mesma
86
103
  // borda, o mesmo raio de controle.
@@ -94,20 +111,28 @@ const folha = criarFolha((t) => ({
94
111
  desabilitado: { opacity: 0.5 },
95
112
  // As ações do gatilho — limpar e a seta. O `.combobox-actions` da web (`aurea.css:1673`) é
96
113
  // absoluto sobre o campo; aqui é um irmão na linha, porque não há campo por baixo.
97
- acoes: { flexDirection: "row", alignItems: "center", gap: 2 },
114
+ // `space05` mede 2 — o mesmo valor, agora com nome. O `.combobox-actions` da web
115
+ // (`aurea.css:1673`) usa `gap:2px` literal; aqui o token existe, então é ele que entra.
116
+ acoes: { flexDirection: "row", alignItems: "center", gap: t.size.space05 },
98
117
  // A folha, com a mesma anatomia da do `Select` (`inputs.tsx:98-113`) — e é de propósito que
99
118
  // sejam iguais: são o mesmo gesto, e duas folhas diferentes para o mesmo gesto é como uma
100
119
  // biblioteca deixa de ter linguagem.
101
120
  fundoDaLista: { flex: 1, justifyContent: "flex-end", backgroundColor: "#00000080" },
102
121
  fundoDeToque: { position: "absolute", top: 0, right: 0, bottom: 0, left: 0 },
122
+ // 🔴 OS 85% E OS 45% ERAM INVENTADOS, e saíram em 12/09/2026. **A casa já tinha a medida:**
123
+ // o `folhaBaixo` do `BottomSheet` do Lote 5 (`overlays.tsx:104`) usa `maxHeight: "90%"`, e
124
+ // duas folhas que sobem do rodapé com alturas diferentes é como uma biblioteca deixa de ter
125
+ // linguagem. O `minHeight` sumiu: folha abraça o conteúdo, e uma altura mínima só servia para
126
+ // desenhar vazio.
103
127
  lista: {
104
- // `maxHeight` MAIOR que a do `Select` (60%), e a razão é o teclado: aqui ele sobe junto, e
105
- // uma folha de 60% com teclado aberto deixa três linhas visíveis. O `KeyboardAvoidingView`
106
- // não entra — dentro de um `Modal` do RN ele não recebe as métricas do teclado no Android.
107
- maxHeight: "85%", minHeight: "45%",
128
+ maxHeight: "90%",
108
129
  borderTopLeftRadius: t.size.radiusCard, borderTopRightRadius: t.size.radiusCard,
109
130
  backgroundColor: t.color.popover, borderWidth: t.size.borderWidth, borderColor: t.color.border,
110
131
  },
132
+ // O puxador, copiado do `BottomSheet` (`overlays.tsx:111-112`) — mesma medida, mesmo token.
133
+ puxadorArea: { alignItems: "center", paddingVertical: t.size.space2 },
134
+ puxador: { width: 40, height: 4, borderRadius: t.size.radiusFull,
135
+ backgroundColor: t.color.borderStrong },
111
136
  // O campo de digitar, no topo da folha. É o `.input-group` da web (`aurea.css:741`): a borda
112
137
  // mora no GRUPO e o campo dentro dele é transparente e sem borda.
113
138
  grupo: {
@@ -160,7 +185,7 @@ const folha = criarFolha((t) => ({
160
185
  * na web e pela mesma razão do achado I1 da auditoria: um componente com a sua própria cópia do
161
186
  * estado é a segunda fonte de verdade que ninguém sabe que existe.
162
187
  */
163
- export function Combobox({ items, value, onValueChange, onSearchChange, searchDelay = 250, loading, onEndReached, placeholder, searchPlaceholder, empty, clearable = true, disabled, size, chevron = "chevron--down", searchIcon = "search", style, testID, }) {
188
+ export function Combobox({ items, value, onValueChange, onSearchChange, searchDelay = ESPERA_PADRAO, loading, onEndReached, placeholder, searchPlaceholder, empty, clearable = true, draggable = true, disabled, size, chevron = "chevron--down", searchIcon = "search", style, testID, }) {
164
189
  const t = useAureaTokens();
165
190
  const s = folha(t);
166
191
  const strings = useAureaStrings();
@@ -169,6 +194,7 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
169
194
  const inativo = disabled ?? campo?.disabled;
170
195
  const [aberto, setAberto] = React.useState(false);
171
196
  const [texto, setTexto] = React.useState("");
197
+ const reduzir = useReduceMotion();
172
198
  // ── A ESPERA ────────────────────────────────────────────────────────────────────────────────
173
199
  // O timer vive num `ref` e é limpo em três lugares: à digitação seguinte, ao fechar a folha e
174
200
  // ao desmontar. O terceiro é o que importa e o que mais se esquece — sem ele, sair da tela com
@@ -206,6 +232,35 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
206
232
  setAberto(false);
207
233
  setTexto("");
208
234
  }, [cancelar]);
235
+ // ── O ARRASTO, copiado do `BottomSheet` do Lote 5 (`overlays.tsx:427-451`) ──────────────────
236
+ // Os números são os DE LÁ, não novos: 6dp para reivindicar, limiar de 80dp ou um terço da
237
+ // altura, velocidade 1.2. Copiar em vez de reinventar é o que mantém as duas folhas com o
238
+ // mesmo tato — e é a regra do `BUILDING.md` sobre não criar escala nova no meio de um
239
+ // componente.
240
+ const arrasto = React.useRef(new Animated.Value(0)).current;
241
+ const altura = React.useRef(0);
242
+ React.useEffect(() => { if (!aberto)
243
+ arrasto.setValue(0); }, [aberto, arrasto]);
244
+ const gestos = React.useMemo(() => PanResponder.create({
245
+ onMoveShouldSetPanResponder: (_e, g) => draggable && g.dy > 6,
246
+ onPanResponderMove: (_e, g) => { if (g.dy > 0)
247
+ arrasto.setValue(g.dy); },
248
+ onPanResponderRelease: (_e, g) => {
249
+ const limiar = Math.max(80, altura.current / 3);
250
+ if (g.dy > limiar || g.vy > 1.2) {
251
+ fechar();
252
+ return;
253
+ }
254
+ if (reduzir !== false) {
255
+ arrasto.setValue(0);
256
+ return;
257
+ }
258
+ Animated.timing(arrasto, {
259
+ toValue: 0, duration: t.size.durationSlow, useNativeDriver: true,
260
+ easing: Easing.bezier(...t.easing.easeEmphasized),
261
+ }).start();
262
+ },
263
+ }), [draggable, fechar, arrasto, reduzir, t.size.durationSlow, t.easing.easeEmphasized]);
209
264
  // ── O FILTRO, QUANDO ELE É NOSSO ────────────────────────────────────────────────────────────
210
265
  const filtrados = React.useMemo(() => {
211
266
  if (onSearchChange)
@@ -222,7 +277,7 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
222
277
  campo?.invalido && s.invalido,
223
278
  inativo && s.desabilitado,
224
279
  style,
225
- ], children: [_jsx(Text, { size: tam === "sm" ? "xs" : tam === "lg" ? "base" : "md", tone: value ? "default" : "subtle", numberOfLines: 1, style: { flex: 1, minWidth: 0 }, children: value?.label ?? placeholder ?? "" }), _jsxs(View, { style: s.acoes, children: [clearable && value != null && !inativo && (_jsx(IconButton, { name: "close", label: strings.comboboxClear, appearance: "ghost", size: "sm", onPress: () => onValueChange?.(null), testID: testID ? `${testID}-limpar` : undefined })), chevron && _jsx(Icon, { name: chevron, size: "sm", color: t.color.subtleForeground })] })] }), _jsx(Modal, { visible: aberto, transparent: true, animationType: "slide", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: fechar, children: _jsxs(View, { style: s.fundoDaLista, children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: fechar, accessible: false, testID: testID ? `${testID}-fundo` : undefined }), _jsxs(View, { style: s.lista, onStartShouldSetResponder: () => true, children: [_jsxs(View, { style: [s.grupo, { height: alturaDoTamanho(t, tam) }], children: [searchIcon && _jsx(Icon, { name: searchIcon, size: "sm", color: t.color.subtleForeground }), _jsx(TextInput, { testID: testID ? `${testID}-busca` : undefined, value: texto, onChangeText: digitar, placeholder: searchPlaceholder ?? strings.comboboxSearch, placeholderTextColor: t.color.subtleForeground,
280
+ ], children: [_jsx(Text, { size: tam === "sm" ? "xs" : tam === "lg" ? "base" : "md", tone: value ? "default" : "subtle", numberOfLines: 1, style: { flex: 1, minWidth: 0 }, children: value?.label ?? placeholder ?? "" }), _jsxs(View, { style: s.acoes, children: [clearable && value != null && !inativo && (_jsx(IconButton, { name: "close", label: strings.comboboxClear, appearance: "ghost", size: "sm", onPress: () => onValueChange?.(null), testID: testID ? `${testID}-limpar` : undefined })), chevron && _jsx(Icon, { name: chevron, size: "sm", color: t.color.subtleForeground })] })] }), _jsx(Modal, { visible: aberto, transparent: true, animationType: reduzir !== false ? "none" : "slide", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: fechar, children: _jsxs(KeyboardAvoiding, { style: s.fundoDaLista, children: [_jsx(Pressable, { style: s.fundoDeToque, onPress: fechar, accessible: false, testID: testID ? `${testID}-fundo` : undefined }), _jsxs(Animated.View, { style: [s.lista, { transform: [{ translateY: arrasto }] }], onLayout: (e) => { altura.current = e.nativeEvent.layout.height; }, onStartShouldSetResponder: () => true, children: [_jsx(View, { style: s.puxadorArea, ...(draggable ? gestos.panHandlers : null), accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", children: _jsx(View, { style: s.puxador }) }), _jsxs(View, { style: [s.grupo, { height: alturaDoTamanho(t, tam) }], ...(draggable ? gestos.panHandlers : null), children: [searchIcon && _jsx(Icon, { name: searchIcon, size: "sm", color: t.color.subtleForeground }), _jsx(TextInput, { testID: testID ? `${testID}-busca` : undefined, value: texto, onChangeText: digitar, placeholder: searchPlaceholder ?? strings.comboboxSearch, placeholderTextColor: t.color.subtleForeground,
226
281
  // ⚠ `autoFocus` é o que faz a folha valer a pena: abrir um campo de busca e
227
282
  // exigir um segundo toque para o teclado subir é um toque a mais em cada
228
283
  // cadastro. O foco entra com a folha; o teclado vem junto.
@@ -238,7 +293,7 @@ export function Combobox({ items, value, onValueChange, onSearchChange, searchDe
238
293
  color: t.color.foreground },
239
294
  ] }), texto.length > 0 && (_jsx(IconButton, { name: "close", label: strings.searchClear, appearance: "ghost", size: "sm", onPress: () => digitar(""), testID: testID ? `${testID}-busca-limpar` : undefined }))] }), loading && (_jsx(View, { style: s.carregando, children: _jsx(Spinner, { label: strings.comboboxLoading }) })), nada && (_jsx(View, { style: s.vazio, children: typeof empty === "string" || empty == null
240
295
  ? _jsx(Text, { size: "sm", tone: "muted", children: empty ?? strings.comboboxEmpty })
241
- : empty })), _jsx(FlatList, { data: filtrados, keyExtractor: (i) => i.value, keyboardShouldPersistTaps: "handled", onEndReached: onEndReached, onEndReachedThreshold: 0.5, renderItem: ({ item }) => (_jsx(Pressable, { disabled: item.disabled, onPress: () => { onValueChange?.(item); fechar(); }, accessibilityRole: "menuitem", accessibilityState: {
296
+ : empty })), _jsx(FlatList, { data: filtrados, keyExtractor: (i) => i.value, keyboardShouldPersistTaps: "handled", onEndReached: onEndReached, onEndReachedThreshold: ANTECEDENCIA_DE_PAGINA, renderItem: ({ item }) => (_jsx(Pressable, { disabled: item.disabled, onPress: () => { onValueChange?.(item); fechar(); }, accessibilityRole: "menuitem", accessibilityState: {
242
297
  selected: item.value === value?.value, disabled: !!item.disabled,
243
298
  }, style: [
244
299
  s.opcao,
@@ -283,7 +338,7 @@ const dobrar = (v) => {
283
338
  * ⚠ **A borda mora no GRUPO, não no campo** — é o `.input-group` da web (`aurea.css:741`), e é o
284
339
  * que deixa lupa, campo e botão de limpar dentro de uma caixa só em vez de três.
285
340
  */
286
- export function SearchField({ value, onChangeText, onSearchChange, searchDelay = 250, placeholder, disabled, size, icon = "search", clearable = true, onSubmit, style, testID, }) {
341
+ export function SearchField({ value, onChangeText, onSearchChange, searchDelay = ESPERA_PADRAO, placeholder, disabled, size, icon = "search", clearable = true, onSubmit, style, testID, }) {
287
342
  const t = useAureaTokens();
288
343
  const s = folha(t);
289
344
  const strings = useAureaStrings();
package/dist/numero.d.ts CHANGED
@@ -52,6 +52,14 @@ export interface NumberFieldProps {
52
52
  /** Mostra o valor e não deixa editar — os botões também somem. */
53
53
  readOnly?: boolean;
54
54
  size?: AureaFieldSize;
55
+ /**
56
+ * Ocupa a largura disponível em vez de abraçar o conteúdo. Padrão **false**.
57
+ *
58
+ * O padrão segue o `.number-field` da web, que é `inline-flex` (`aurea.css:704`) — e o mesmo
59
+ * eixo existe na HeroUI (`fullWidth`, medido no inventário). **Ligue para moeda:** o campo em
60
+ * repouso tem `--space-16` (64dp), que cabe um contador e não cabe `R$ 1.234,50`.
61
+ */
62
+ fullWidth?: boolean;
55
63
  /** O nome para o leitor de tela quando não há `Field` em volta. */
56
64
  label?: string;
57
65
  placeholder?: string;
@@ -96,5 +104,5 @@ type IconNameLocal = string;
96
104
  * ⚠ **Formata no blur, e só no blur.** Enquanto o campo tem foco, ele mostra exatamente o que foi
97
105
  * digitado — essa é a decisão inteira da ADR-0024, e o teste que a cobra está no lote.
98
106
  */
99
- export declare function NumberField({ value, defaultValue, onValueChange, min, max, step, format, locale, disabled, readOnly, size, label, placeholder, keyboardType, icons, style, testID, }: NumberFieldProps): React.JSX.Element;
107
+ export declare function NumberField({ value, defaultValue, onValueChange, min, max, step, format, locale, disabled, readOnly, size, fullWidth, label, placeholder, keyboardType, icons, style, testID, }: NumberFieldProps): React.JSX.Element;
100
108
  export {};
package/dist/numero.js CHANGED
@@ -53,18 +53,32 @@ import { useAureaStrings, useAureaTokens } from "./theme.js";
53
53
  const alturaDoTamanho = (t, s) => s === "sm" ? t.size.controlHSm : s === "lg" ? t.size.controlHLg : t.size.controlHMd;
54
54
  const fonteDoTamanho = (t, s) => s === "sm" ? t.size.textXs : s === "lg" ? t.size.textBase : t.size.textMd;
55
55
  const folha = criarFolha((t) => ({
56
- // `.number-field-group` da web (`aurea.css:705`): linha, centro, `--space-1` entre as peças.
57
- grupo: { flexDirection: "row", alignItems: "center", gap: t.size.space1 },
58
- // O `.number-field-input` (`aurea.css:706`) é `--space-16` de largura e CENTRALIZADO. A largura
59
- // fixa atravessa como mínimo e não como fixa: num telefone de 360dp, com dois botões ao lado,
60
- // um campo rígido estoura a linha em `spacious`.
56
+ // 🔴 ELE ESTICAVA, E NÃO DEVIA — achado em 12/09/2026, olhando a primeira imagem da vitrine.
57
+ //
58
+ // O `.number-field` da web é `inline-flex` (`aurea.css:704`) e o `.number-field-group` também
59
+ // (`:705`): os dois **ABRAÇAM o conteúdo**. A primeira versão daqui punha `flex: 1` no campo,
60
+ // que PREENCHE — e o resultado é um campo enorme com o `−` e o `+` jogados nas pontas. Era
61
+ // divergência do nosso próprio CSS, entregue sem declarar, e ela só apareceu quando houve
62
+ // imagem para olhar.
63
+ //
64
+ // ⚠ **A saída é o eixo da HeroUI, e não uma invenção minha:** o `number-field` deles publica
65
+ // `fullWidth: [base, false, group, true]` (`INVENTORY-HEROUI.json`, medido em 22/08/2026).
66
+ // Esticar ou abraçar é DECISÃO DE USO, então vira prop — com o padrão em abraçar, que é o que
67
+ // o nosso CSS já dizia.
68
+ grupo: { flexDirection: "row", alignItems: "center", gap: t.size.space1,
69
+ alignSelf: "flex-start" },
70
+ grupoLargo: { alignSelf: "stretch" },
71
+ // `--space-16` = 64dp, medido. É a largura do `.number-field-input` (`aurea.css:706`), e ela
72
+ // serve ao caso que o componente foi feito para: contador e medida curta. **Moeda formatada
73
+ // não cabe em 64dp — nem aqui nem na web**, e é para isso que existe o `fullWidth`.
61
74
  campo: {
62
- flex: 1, minWidth: t.size.space16 ?? 64, textAlign: "center",
75
+ width: t.size.space16, textAlign: "center",
63
76
  borderWidth: t.size.borderWidth, borderColor: t.color.borderStrong,
64
77
  borderRadius: t.size.radiusControl, backgroundColor: t.color.fieldBg,
65
78
  // A mesma correção de Android do `Input` do Lote 4 — ver `inputs.tsx:280-296`.
66
79
  paddingVertical: 0, textAlignVertical: "center",
67
80
  },
81
+ campoLargo: { width: undefined, flex: 1, minWidth: t.size.space16 },
68
82
  invalido: { borderColor: t.color.danger400 ?? t.color.destructive },
69
83
  desabilitado: { opacity: 0.5 },
70
84
  }));
@@ -218,7 +232,7 @@ const paraEdicao = (n, locale) => {
218
232
  * ⚠ **Formata no blur, e só no blur.** Enquanto o campo tem foco, ele mostra exatamente o que foi
219
233
  * digitado — essa é a decisão inteira da ADR-0024, e o teste que a cobra está no lote.
220
234
  */
221
- export function NumberField({ value, defaultValue, onValueChange, min, max, step = 1, format, locale, disabled, readOnly, size, label, placeholder, keyboardType, icons = { increment: "add", decrement: "subtract" }, style, testID, }) {
235
+ export function NumberField({ value, defaultValue, onValueChange, min, max, step = 1, format, locale, disabled, readOnly, size, fullWidth = false, label, placeholder, keyboardType, icons = { increment: "add", decrement: "subtract" }, style, testID, }) {
222
236
  const t = useAureaTokens();
223
237
  const s = folha(t);
224
238
  const strings = useAureaStrings();
@@ -279,13 +293,35 @@ export function NumberField({ value, defaultValue, onValueChange, min, max, step
279
293
  return direcao === 1 ? (max != null && base >= max) : (min != null && base <= min);
280
294
  };
281
295
  const mostrarBotoes = icons !== false && !readOnly;
282
- return (_jsxs(View, { testID: testID, style: [s.grupo, inativo && s.desabilitado, style], children: [mostrarBotoes && (_jsx(IconButton, { name: icons.decrement, label: strings.decrement, appearance: "ghost", size: tam, disabled: inativo || noLimite(-1), onPress: () => empurrar(-1), testID: testID ? `${testID}-menos` : undefined })), _jsx(TextInput, { testID: testID ? `${testID}-campo` : undefined, value: mostrar, onChangeText: setEmEdicao, onFocus: () => setEmEdicao(paraEdicao(numero, locale)), onBlur: confirmar, editable: !inativo && !readOnly, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, keyboardType: keyboardType ?? tecladoPadrao,
296
+ // 🔴 ACHADO PELA REFERÊNCIA, e não por mim — 12/09/2026. O inventário da HeroUI que já morava
297
+ // no repositório (`audit/activity-2/INVENTORY-HEROUI.json`, medido em 22/08 sobre
298
+ // `@heroui/react@3.2.4`) lista os estados do `number-field` deles:
299
+ //
300
+ // disabled · focus-visible · FOCUS-WITHIN · hovered · invalid · pressed
301
+ //
302
+ // Este componente nasceu **sem estado de foco nenhum** — e era o ÚNICO campo de texto do pacote
303
+ // sem ele: o `Input` do Lote 4 marca (`inputs.tsx:344`), o `Textarea` herda, o `SearchField`
304
+ // marca (`busca.tsx:520`). Inconsistência dentro da própria biblioteca, e invisível em teste
305
+ // até alguém comparar com uma referência.
306
+ //
307
+ // ⚠ **A BORDA vai no CAMPO, e não no grupo — e aqui a Aurea diverge da HeroUI de propósito.**
308
+ // Lá o `Group` carrega a caixa e o `Input` fica nu dentro dela. Aqui não: o `.number-field-group`
309
+ // do nosso CSS (`aurea.css:705`) é só `inline-flex` + `gap`, e quem tem borda é o `.input`
310
+ // (`aurea.css:685`), com os dois botões FORA dela. O estado que faltava é o deles; a geometria
311
+ // continua sendo a nossa.
312
+ const [focado, setFocado] = React.useState(false);
313
+ return (_jsxs(View, { testID: testID, style: [s.grupo, fullWidth && s.grupoLargo, inativo && s.desabilitado, style], children: [mostrarBotoes && (_jsx(IconButton, { name: icons.decrement, label: strings.decrement, appearance: "ghost", size: tam, disabled: inativo || noLimite(-1), onPress: () => empurrar(-1), testID: testID ? `${testID}-menos` : undefined })), _jsx(TextInput, { testID: testID ? `${testID}-campo` : undefined, value: mostrar, onChangeText: setEmEdicao, onFocus: () => { setFocado(true); setEmEdicao(paraEdicao(numero, locale)); }, onBlur: () => { setFocado(false); confirmar(); }, editable: !inativo && !readOnly, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, keyboardType: keyboardType ?? tecladoPadrao,
283
314
  // O nome vem do `Field`, como em todo controle deste pacote — e `label` cobre quem usa o
284
315
  // campo solto. Sem um dos dois, é um nó anônimo para o leitor de tela.
285
316
  accessibilityLabel: label ?? campo?.label, accessibilityHint: campo?.hint, accessibilityState: { disabled: !!inativo }, "aria-invalid": campo?.invalido, style: [
286
317
  s.campo,
318
+ fullWidth && s.campoLargo,
287
319
  { height: alturaDoTamanho(t, tam), fontSize: fonteDoTamanho(t, tam),
288
320
  fontFamily: t.font.ui[400], color: t.color.foreground },
289
321
  campo?.invalido && s.invalido,
322
+ // A ORDEM IMPORTA e é a mesma do `Input` do Lote 4: o inválido vem antes, o foco
323
+ // depois. Um campo inválido que está sendo corrigido tem de mostrar que está ativo —
324
+ // invertendo, a pessoa digita sem pista nenhuma de onde o teclado está batendo.
325
+ focado && { borderColor: t.color.focusStrong },
290
326
  ] }), mostrarBotoes && (_jsx(IconButton, { name: icons.increment, label: strings.increment, appearance: "ghost", size: tam, disabled: inativo || noLimite(1), onPress: () => empurrar(1), testID: testID ? `${testID}-mais` : undefined }))] }));
291
327
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurea-uds/native",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "React Native target for Aurea UDS: the theme x density provider, the first components and the Carbon icons, drawn on react-native-svg.",
5
5
  "keywords": [
6
6
  "aurea",
@@ -47,7 +47,7 @@
47
47
  "NOTICE"
48
48
  ],
49
49
  "dependencies": {
50
- "@aurea-uds/tokens": "^0.8.0"
50
+ "@aurea-uds/tokens": "^0.8.1"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@react-native-community/datetimepicker": ">=8",