@aurea-uds/native 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/inputs.d.ts CHANGED
@@ -61,10 +61,34 @@ export interface InputProps {
61
61
  /**
62
62
  * O teclado que sobe. **O plano do consumidor pede `numeric` nos campos de medida e moeda**, e
63
63
  * é a prop que faz a diferença entre digitar 12,4 em dois toques ou em oito.
64
+ *
65
+ * ⚠ **Teclado não é formato, e esta prop resolve só a metade fácil** — foi a segunda lacuna
66
+ * que o consumidor mediu. Moeda, medida decimal e contador são o `NumberField` (Lote 7,
67
+ * `numero.tsx`), que formata no blur e devolve `number`. Este campo continua devolvendo texto.
64
68
  */
65
69
  keyboardType?: KeyboardTypeOptions;
66
70
  secureTextEntry?: boolean;
67
71
  autoCapitalize?: "none" | "sentences" | "words" | "characters";
72
+ /**
73
+ * Normaliza o texto **quando o foco sai** — placa, documento, telefone.
74
+ *
75
+ * ⚠ **A Aurea entrega o MOMENTO, não o formato**, e isso é a [ADR-0024] inteira: formatar
76
+ * enquanto se digita é o defeito que o USWDS publicou com reprovação WCAG registrada e que o
77
+ * MUI abandonou na v6. O formato é conhecimento de DOMÍNIO — a Aurea não sabe o que é um
78
+ * documento válido em lugar nenhum, e uma tabela de formatos por país dentro de uma biblioteca
79
+ * de interface envelhece sem ninguém perceber.
80
+ *
81
+ * <Input value={placa} onChangeText={setPlaca} formatOnBlur={(v) => v.toUpperCase()} />
82
+ *
83
+ * ⚠ **Exige o campo CONTROLADO** (`value` + `onChangeText`), e a diferença com a web é honesta:
84
+ * lá o componente escreve em `e.currentTarget.value` e o `<input>` não controlado obedece. Aqui
85
+ * o texto do `TextInput` não controlado vive dentro do nó nativo, e escrevê-lo de fora exigiria
86
+ * `setNativeProps` — API que a Nova Arquitetura desencoraja. Então o que sai daqui é o valor
87
+ * formatado por `onChangeText`; quem guarda o estado desenha.
88
+ *
89
+ * Para MOEDA e MEDIDA não use isto: use o `NumberField`, que já faz a conta e devolve `number`.
90
+ */
91
+ formatOnBlur?: (value: string) => string;
68
92
  onBlur?: () => void;
69
93
  onFocus?: () => void;
70
94
  /** Várias linhas. É o que o `Textarea` liga — raramente se põe à mão. */
@@ -79,7 +103,7 @@ export interface InputProps {
79
103
  * (`aurea.css:686`); **não há ponteiro no telefone**. O foco continua existindo e é o que marca o
80
104
  * campo ativo.
81
105
  */
82
- export declare function Input({ value, defaultValue, onChangeText, placeholder, disabled, size, keyboardType, secureTextEntry, autoCapitalize, onBlur, onFocus, multiline, style, testID, }: InputProps): React.JSX.Element;
106
+ export declare function Input({ value, defaultValue, onChangeText, placeholder, disabled, size, keyboardType, secureTextEntry, autoCapitalize, formatOnBlur, onBlur, onFocus, multiline, style, testID, }: InputProps): React.JSX.Element;
83
107
  export interface TextareaProps extends InputProps {
84
108
  /** Linhas visíveis. Vira altura mínima, porque no RN não há `rows`. */
85
109
  rows?: number;
@@ -169,6 +193,12 @@ export interface SelectProps {
169
193
  * ⚠ **O papel é `button` e não `combobox`.** `combobox` promete um campo em que se DIGITA para
170
194
  * filtrar; isto só abre uma lista. Prometer o que não se faz é o mesmo defeito do `tablist` no
171
195
  * Lote 3.
196
+ *
197
+ * ⚠ **Quem DIGITA para filtrar é o `Combobox`** (Lote 7, `busca.tsx`), e ele nasceu desta
198
+ * lacuna — o consumidor bateu nela no cadastro. **A escolha entre os dois é de tamanho de
199
+ * lista, e a linha é dura:** este monta todos os itens num `ScrollView` (abaixo), o que serve
200
+ * para unidades, estados e tipos; um catálogo de milhares de linhas aqui não fica lento, trava.
201
+ * O `Combobox` usa `FlatList` e aceita busca remota.
172
202
  */
173
203
  export declare function Select({ items, value, onChange, placeholder, disabled, size, chevron, style, testID, }: SelectProps): React.JSX.Element;
174
204
  export interface FormProps extends ViewProps {
package/dist/inputs.js CHANGED
@@ -147,14 +147,25 @@ export function Label({ children, trailing, style, ...rest }) {
147
147
  * (`aurea.css:686`); **não há ponteiro no telefone**. O foco continua existindo e é o que marca o
148
148
  * campo ativo.
149
149
  */
150
- export function Input({ value, defaultValue, onChangeText, placeholder, disabled, size, keyboardType, secureTextEntry, autoCapitalize, onBlur, onFocus, multiline, style, testID, }) {
150
+ export function Input({ value, defaultValue, onChangeText, placeholder, disabled, size, keyboardType, secureTextEntry, autoCapitalize, formatOnBlur, onBlur, onFocus, multiline, style, testID, }) {
151
151
  const t = useAureaTokens();
152
152
  const s = folha(t);
153
153
  const campo = useCampo();
154
154
  const tam = size ?? campo?.size ?? "md";
155
155
  const inativo = disabled ?? campo?.disabled;
156
156
  const [focado, setFocado] = React.useState(false);
157
- return (_jsx(TextInput, { testID: testID, value: value, defaultValue: defaultValue, onChangeText: onChangeText, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, editable: !inativo, keyboardType: keyboardType, secureTextEntry: secureTextEntry, autoCapitalize: autoCapitalize, multiline: multiline, onFocus: () => { setFocado(true); onFocus?.(); }, onBlur: () => { setFocado(false); onBlur?.(); },
157
+ return (_jsx(TextInput, { testID: testID, value: value, defaultValue: defaultValue, onChangeText: onChangeText, placeholder: placeholder, placeholderTextColor: t.color.subtleForeground, editable: !inativo, keyboardType: keyboardType, secureTextEntry: secureTextEntry, autoCapitalize: autoCapitalize, multiline: multiline, onFocus: () => { setFocado(true); onFocus?.(); }, onBlur: () => {
158
+ setFocado(false);
159
+ // Só avisa quando o texto MUDOU: emitir o mesmo valor a cada saída de foco faria o app
160
+ // re-renderizar sem motivo, e num formulário de dez campos isso é dez renders por
161
+ // preenchimento. Mesma guarda da web (`inputs-client.tsx:194`).
162
+ if (formatOnBlur && value != null) {
163
+ const formatado = formatOnBlur(value);
164
+ if (formatado !== value)
165
+ onChangeText?.(formatado);
166
+ }
167
+ onBlur?.();
168
+ },
158
169
  // O nome e a dica vêm do `Field` — ver o bloco acima sobre por que a ligação da web não
159
170
  // atravessa. Sem isto, o campo é um nó anônimo para o leitor de tela.
160
171
  accessibilityLabel: campo?.label, accessibilityHint: campo?.hint, accessibilityState: { disabled: !!inativo }, "aria-invalid": campo?.invalido, style: [
@@ -319,6 +330,12 @@ export function SegmentedControl({ items, value, onChange, label, disabled, styl
319
330
  * ⚠ **O papel é `button` e não `combobox`.** `combobox` promete um campo em que se DIGITA para
320
331
  * filtrar; isto só abre uma lista. Prometer o que não se faz é o mesmo defeito do `tablist` no
321
332
  * Lote 3.
333
+ *
334
+ * ⚠ **Quem DIGITA para filtrar é o `Combobox`** (Lote 7, `busca.tsx`), e ele nasceu desta
335
+ * lacuna — o consumidor bateu nela no cadastro. **A escolha entre os dois é de tamanho de
336
+ * lista, e a linha é dura:** este monta todos os itens num `ScrollView` (abaixo), o que serve
337
+ * para unidades, estados e tipos; um catálogo de milhares de linhas aqui não fica lento, trava.
338
+ * O `Combobox` usa `FlatList` e aceita busca remota.
322
339
  */
323
340
  export function Select({ items, value, onChange, placeholder, disabled, size, chevron = "chevron--down", style, testID, }) {
324
341
  const t = useAureaTokens();
@@ -0,0 +1,98 @@
1
+ import * as React from "react";
2
+ import { type ImageSourcePropType, type StyleProp, type ViewStyle } from "react-native";
3
+ import { type IconName } from "./icon.js";
4
+ /** URL ou `require()` de um asset local — as duas formas do `Image` do RN, como no `Avatar`. */
5
+ export type AureaImageSource = ImageSourcePropType | string;
6
+ export interface ImageProps {
7
+ source: AureaImageSource;
8
+ /**
9
+ * O nome acessível. **É o `alt` da web, e é obrigatório pelo mesmo motivo que o `label` do
10
+ * `IconButton`:** uma foto sem texto alternativo não diz nada a quem não a vê. Decorativa de
11
+ * verdade se escreve `alt=""` — explícito, como manda a WAI.
12
+ */
13
+ alt: string;
14
+ /** `16/9`, `"16/9"`, `"16:9"` ou `1.777…`. Sem ela, a imagem ocupa a altura que o `style` der. */
15
+ ratio?: number | string;
16
+ fit?: "cover" | "contain";
17
+ /** O que aparece no lugar quando o bitmap não vem. Sem ele, o glifo `image` sobre a caixa reservada. */
18
+ fallback?: React.ReactNode;
19
+ /** O glifo do substituto. Registre-o, ou passe `false`. */
20
+ fallbackIcon?: IconName | false;
21
+ /**
22
+ * Desenha OUTRO componente de imagem com a nossa pele — é o caso do consumidor que já usa
23
+ * `expo-image` por cache de disco.
24
+ *
25
+ * <Image render={<ExpoImage contentFit="cover" transition={150} />} source={u} alt="…" />
26
+ *
27
+ * Recebe `source`, `style`, `onError` e o nome acessível. **`fit` NÃO é traduzido para ele** —
28
+ * `resizeMode` é do `Image` do RN, e o `expo-image` chama a mesma coisa de `contentFit`; quem
29
+ * passa o elemento escreve a prop dele.
30
+ */
31
+ render?: React.ReactElement;
32
+ onError?: () => void;
33
+ style?: StyleProp<ViewStyle>;
34
+ testID?: string;
35
+ }
36
+ /**
37
+ * A foto, com a caixa reservada e um substituto quando ela não vem.
38
+ *
39
+ * ```tsx
40
+ * <Image source={foto.uri} alt="Frente do item" ratio="4/3" />
41
+ * ```
42
+ *
43
+ * ⚠ **A caixa nasce reservada e pintada** com a mesma superfície do `Skeleton` — sem `ratio`, o
44
+ * layout salta quando o bitmap chega, e num telefone esse salto acontece com o dedo já a caminho
45
+ * do botão.
46
+ *
47
+ * ⚠ **O substituto continua sendo a imagem para quem usa leitor de tela**, com o mesmo `alt`.
48
+ * Trocar o bitmap por uma caixa muda o que se vê, não o que a foto É — e o `role="image"` mapeia
49
+ * nos dois sistemas (`ReactAccessibilityDelegate.kt:461`, `RCTConversions.h:96`).
50
+ */
51
+ export declare function Image({ source, alt, ratio, fit, fallback, fallbackIcon, render, onError, style, testID, }: ImageProps): React.JSX.Element;
52
+ export interface AureaGalleryItem {
53
+ id: string;
54
+ source: AureaImageSource;
55
+ alt: string;
56
+ caption?: React.ReactNode;
57
+ }
58
+ export interface GalleryProps {
59
+ items: AureaGalleryItem[];
60
+ /** O nome da grade para o leitor de tela. Sem ele, a frase `galleryLabel` do provider. */
61
+ label?: string;
62
+ /** O `id` escolhido. **A galeria não guarda escolha** — é constante do app, como na web. */
63
+ selected?: string;
64
+ onSelect?: (id: string) => void;
65
+ /** Tocar abre a foto grande no `Dialog` que já existe. */
66
+ zoom?: boolean;
67
+ /** Proporção dos ladrilhos. Padrão **1** (quadrado), como o `ratio="1/1"` da web. */
68
+ ratio?: number | string;
69
+ /**
70
+ * Largura mínima de cada ladrilho, em dp. O `--gallery-min` da web é `8rem` — e `1rem = 16dp`,
71
+ * medido, então **128**.
72
+ */
73
+ minTileWidth?: number;
74
+ style?: StyleProp<ViewStyle>;
75
+ testID?: string;
76
+ }
77
+ /**
78
+ * A grade de fotos, e a foto grande quando se toca.
79
+ *
80
+ * ```tsx
81
+ * <Gallery items={fotos} zoom selected={atual} onSelect={setAtual} />
82
+ * ```
83
+ *
84
+ * ⚠ **Ampliar é DIÁLOGO, e diálogo já existe.** É a trava que a web escreveu no item L2 e ela
85
+ * atravessa inteira: a foto grande abre no `Dialog` do Lote 5, com o confinamento de foco que o
86
+ * `Modal` do RN dá pelo sistema e a saída pelo botão VOLTAR do Android. Uma segunda superfície
87
+ * flutuante aqui seria uma segunda linguagem.
88
+ *
89
+ * ⚠ **Legenda visível torna a miniatura decorativa**, e quem exigiu isso na web foi o axe, não a
90
+ * teoria: com `alt` e legenda dizendo a mesma coisa, o leitor de tela anuncia duas vezes seguidas.
91
+ * Aqui a tradução é outra (não há `alt=""` no RN) mas a regra é a mesma — **com legenda, quem
92
+ * carrega o nome é o LADRILHO, e a imagem sai da árvore**. O `alt` não se perde: ele continua
93
+ * nomeando a foto ampliada, que é onde não há legenda ao lado.
94
+ *
95
+ * ⚠ **Sem `onSelect` e sem `zoom` os ladrilhos não são alvos.** Um `Pressable` que não faz nada é
96
+ * um alvo que engana quem navega por leitor de tela — mesma decisão da web, mesma razão.
97
+ */
98
+ export declare function Gallery({ items, label, selected, onSelect, zoom, ratio, minTileWidth, style, testID, }: GalleryProps): React.JSX.Element;
package/dist/midia.js ADDED
@@ -0,0 +1,181 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Aurea nativo — a MÍDIA: `Image` e `Gallery`.
3
+ //
4
+ // Lote 7 do `NATIVE.md` §8, terceira das três lacunas medidas pelo consumidor — e a que ele
5
+ // classificou como acabamento. A frase dele é exata: *"O `PhotoInput` entra foto. Nada mostra
6
+ // foto."* Conferido: o `sistema.tsx:184-225` devolve `AureaPhoto {uri, width, height}` e o único
7
+ // componente do pacote que desenha um bitmap é o `Avatar` (`display.tsx:279-297`), que é outro
8
+ // papel — um retrato redondo do tamanho de um controle, com iniciais por trás.
9
+ //
10
+ // ── A RESPOSTA "USA O `Image` DO RN E NÃO É LACUNA" FOI CONSIDERADA, E RECUSADA ──────────────
11
+ // Era uma das três saídas possíveis, e é tentadora porque o RN já tem um `Image`. Ela cai por
12
+ // medição, não por gosto: a ficha da web (`packages/contracts/registry/Image.json`) diz que o
13
+ // componente é *"uma imagem que RESERVA A CAIXA antes dos bytes chegarem, e CAI PARA UM
14
+ // SUBSTITUTO quando eles nunca chegam"*. O `Image` cru do RN não faz nenhuma das duas.
15
+ //
16
+ // E o custo de não fazê-las já está pago e registrado nesta casa: o `Avatar` foi medido em
17
+ // 31/07/2026 com `src` quebrado e **não caía no substituto**. O conserto virou o `useEffect` por
18
+ // `source` do `display.tsx:283-284`. Mandar cada tela do app reescrever isso é devolver ao
19
+ // consumidor o problema que o design system existe para resolver — e é a terceira vez que esta
20
+ // casa corrigiria a mesma coisa em três lugares (a regra do `CLAUDE.md` sobre correção local).
21
+ //
22
+ // ── O QUE FOI MEDIDO NO CSS, COM A LINHA ─────────────────────────────────────────────────────
23
+ // .image aurea.css:1434 bloco, 100% de largura, `object-fit:cover`, raio LG, fundo surface-3
24
+ // .image-contain :1435 `object-fit:contain`, fundo transparente
25
+ // .image-broken :1436 grade centrada, cor mutedForeground
26
+ // .gallery :1448 grade auto-fill de `--gallery-min` (8rem), gap space-3
27
+ // .gallery-tile :1450 grade com gap space-1, padding space-1, raio LG
28
+ // .gallery-tile:not(.is-selected) :1458 fundo transparente, cor mutedForeground
29
+ // .gallery-caption :1459 textSm
30
+ //
31
+ // ── TRÊS COISAS DA WEB NÃO ATRAVESSAM, E CADA UMA POR UM MOTIVO DIFERENTE ────────────────────
32
+ // `loading="lazy"` não existe no RN — a decodificação preguiçosa é do motor de lista, não da
33
+ // imagem. Quem quer isso usa `FlatList`, que é o que a `Gallery` faz;
34
+ // `ratio="16/9"` a web aceita a STRING do CSS; o `aspectRatio` do RN é **número**. Aqui as
35
+ // duas formas entram, e a string é convertida — quem porta uma tela da web
36
+ // não deveria descobrir isso por um layout de altura zero;
37
+ // `render` a web troca o elemento pelo `useRender` do Base UI. Aqui é `cloneElement`,
38
+ // e serve ao mesmo caso real: o consumidor que já usa `expo-image`.
39
+ import * as React from "react";
40
+ import { Image as ImageRN, Pressable, View, } from "react-native";
41
+ import { criarFolha } from "./estilos.js";
42
+ import { Icon } from "./icon.js";
43
+ import { Grid } from "./layout.js";
44
+ import { Dialog } from "./overlays.js";
45
+ import { Text } from "./text.js";
46
+ import { useAureaStrings, useAureaTokens } from "./theme.js";
47
+ const folha = criarFolha((t) => ({
48
+ // O fundo NÃO é enfeite: ele é o marcador de carregamento inteiro. A web decidiu isso em
49
+ // prosa (`media-client.tsx`, "O MARCADOR DE CARREGAMENTO NÃO TEM ESTADO") e a razão vale aqui
50
+ // com mais força — um `useState` por imagem numa galeria de trinta fotos é trinta renders a
51
+ // mais numa lista que já rola.
52
+ imagem: {
53
+ width: "100%",
54
+ borderRadius: t.size.radiusLg,
55
+ backgroundColor: t.color.surface3 ?? t.color.muted,
56
+ },
57
+ contain: { backgroundColor: "transparent" },
58
+ quebrada: { alignItems: "center", justifyContent: "center" },
59
+ ladrilho: { gap: t.size.space1, padding: t.size.space1, borderRadius: t.size.radiusLg },
60
+ ladrilhoEscolhido: { backgroundColor: t.color.secondary },
61
+ }));
62
+ /** `"16/9"`, `"4:3"` ou o número que o RN quer. Devolve `undefined` quando não dá para ler. */
63
+ function razao(v) {
64
+ if (v == null)
65
+ return undefined;
66
+ if (typeof v === "number")
67
+ return Number.isFinite(v) && v > 0 ? v : undefined;
68
+ // A web escreve `16/9`; alguém vai escrever `16:9`. As duas entram.
69
+ const partes = v.split(/[/:]/);
70
+ if (partes.length === 2) {
71
+ const a = Number(partes[0]), b = Number(partes[1]);
72
+ if (Number.isFinite(a) && Number.isFinite(b) && b !== 0)
73
+ return a / b;
74
+ }
75
+ const n = Number(v);
76
+ return Number.isFinite(n) && n > 0 ? n : undefined;
77
+ }
78
+ // Interno de propósito: não sai pelo barril. O consumidor escreve `string` ou `require()` e o
79
+ // componente converte — expor o conversor seria superfície pública sem caso de uso.
80
+ const fonteDaImagem = (s) => typeof s === "string" ? { uri: s } : s;
81
+ /**
82
+ * A foto, com a caixa reservada e um substituto quando ela não vem.
83
+ *
84
+ * ```tsx
85
+ * <Image source={foto.uri} alt="Frente do item" ratio="4/3" />
86
+ * ```
87
+ *
88
+ * ⚠ **A caixa nasce reservada e pintada** com a mesma superfície do `Skeleton` — sem `ratio`, o
89
+ * layout salta quando o bitmap chega, e num telefone esse salto acontece com o dedo já a caminho
90
+ * do botão.
91
+ *
92
+ * ⚠ **O substituto continua sendo a imagem para quem usa leitor de tela**, com o mesmo `alt`.
93
+ * Trocar o bitmap por uma caixa muda o que se vê, não o que a foto É — e o `role="image"` mapeia
94
+ * nos dois sistemas (`ReactAccessibilityDelegate.kt:461`, `RCTConversions.h:96`).
95
+ */
96
+ export function Image({ source, alt, ratio, fit = "cover", fallback, fallbackIcon = "image", render, onError, style, testID, }) {
97
+ const t = useAureaTokens();
98
+ const s = folha(t);
99
+ const [quebrou, setQuebrou] = React.useState(false);
100
+ // A `source` nova merece uma tentativa nova — é a mesma linha do `Avatar` (`display.tsx:283`),
101
+ // e sem ela uma URL quebrada deixa um buraco permanente mesmo depois de o app trocar a foto.
102
+ React.useEffect(() => { setQuebrou(false); }, [source]);
103
+ const proporcao = razao(ratio);
104
+ const caixa = [
105
+ s.imagem,
106
+ fit === "contain" && s.contain,
107
+ proporcao != null && { aspectRatio: proporcao },
108
+ style,
109
+ ];
110
+ if (quebrou) {
111
+ return (_jsx(View, { testID: testID, style: [caixa, s.quebrada], accessible: true, accessibilityRole: "image", accessibilityLabel: alt || undefined, children: fallback ?? (fallbackIcon
112
+ ? _jsx(Icon, { name: fallbackIcon, size: "lg", color: t.color.mutedForeground })
113
+ : null) }));
114
+ }
115
+ const comuns = {
116
+ source: fonteDaImagem(source),
117
+ style: caixa,
118
+ onError: () => { setQuebrou(true); onError?.(); },
119
+ // `alt=""` é decorativo explícito: some da árvore em vez de entrar com nome vazio.
120
+ accessible: alt !== "",
121
+ accessibilityRole: alt !== "" ? "image" : undefined,
122
+ accessibilityLabel: alt !== "" ? alt : undefined,
123
+ testID,
124
+ };
125
+ // `cloneElement` e não `useRender`: o idioma do Base UI não existe aqui, e o que o caso real
126
+ // precisa é de um elemento pronto recebendo as nossas props. As props do consumidor vêm
127
+ // primeiro no objeto do elemento e as nossas depois — `source` e `onError` são o contrato
128
+ // deste componente, e deixá-las sobrescrevíveis seria prometer o substituto e não entregá-lo.
129
+ if (render)
130
+ return React.cloneElement(render, comuns);
131
+ // ⚠ O molde é `ImageStyle` e não `ViewStyle`, e a conversão é CONSCIENTE: os dois tipos só
132
+ // divergem em duas coisas — o `overflow` do `ViewStyle` aceita `"scroll"`, que o `ImageStyle`
133
+ // não tem, e o `ImageStyle` soma `resizeMode`/`tintColor`/`overlayColor`. **Nada aqui escreve
134
+ // nenhuma das duas**: a folha põe largura, raio, fundo e proporção, e o `resizeMode` vai como
135
+ // prop, ao lado. A API pública fica em `ViewStyle` de propósito — quem chama pensa em caixa, e
136
+ // fazer o consumidor importar `ImageStyle` para passar um raio seria vazar o primitivo.
137
+ return _jsx(ImageRN, { ...comuns, style: caixa, resizeMode: fit });
138
+ }
139
+ /**
140
+ * A grade de fotos, e a foto grande quando se toca.
141
+ *
142
+ * ```tsx
143
+ * <Gallery items={fotos} zoom selected={atual} onSelect={setAtual} />
144
+ * ```
145
+ *
146
+ * ⚠ **Ampliar é DIÁLOGO, e diálogo já existe.** É a trava que a web escreveu no item L2 e ela
147
+ * atravessa inteira: a foto grande abre no `Dialog` do Lote 5, com o confinamento de foco que o
148
+ * `Modal` do RN dá pelo sistema e a saída pelo botão VOLTAR do Android. Uma segunda superfície
149
+ * flutuante aqui seria uma segunda linguagem.
150
+ *
151
+ * ⚠ **Legenda visível torna a miniatura decorativa**, e quem exigiu isso na web foi o axe, não a
152
+ * teoria: com `alt` e legenda dizendo a mesma coisa, o leitor de tela anuncia duas vezes seguidas.
153
+ * Aqui a tradução é outra (não há `alt=""` no RN) mas a regra é a mesma — **com legenda, quem
154
+ * carrega o nome é o LADRILHO, e a imagem sai da árvore**. O `alt` não se perde: ele continua
155
+ * nomeando a foto ampliada, que é onde não há legenda ao lado.
156
+ *
157
+ * ⚠ **Sem `onSelect` e sem `zoom` os ladrilhos não são alvos.** Um `Pressable` que não faz nada é
158
+ * um alvo que engana quem navega por leitor de tela — mesma decisão da web, mesma razão.
159
+ */
160
+ export function Gallery({ items, label, selected, onSelect, zoom, ratio = 1, minTileWidth = 128, style, testID, }) {
161
+ const s = folha(useAureaTokens());
162
+ const strings = useAureaStrings();
163
+ const [ampliado, setAmpliado] = React.useState(null);
164
+ const interativo = !!onSelect || !!zoom;
165
+ const aberto = items.find((i) => i.id === ampliado) ?? null;
166
+ return (_jsxs(_Fragment, { children: [_jsx(Grid, { testID: testID, minColumnWidth: minTileWidth, accessibilityRole: "list", accessibilityLabel: label ?? strings.galleryLabel, style: style, children: items.map((item) => {
167
+ const temLegenda = item.caption != null;
168
+ const miolo = (_jsxs(_Fragment, { children: [_jsx(Image, { source: item.source, alt: temLegenda ? "" : item.alt, ratio: ratio }), temLegenda && (typeof item.caption === "string"
169
+ ? _jsx(Text, { size: "sm", numberOfLines: 2, children: item.caption })
170
+ : item.caption)] }));
171
+ if (!interativo) {
172
+ return _jsx(View, { style: s.ladrilho, children: miolo }, item.id);
173
+ }
174
+ return (_jsx(Pressable, { testID: testID ? `${testID}-${item.id}` : undefined, onPress: () => { onSelect?.(item.id); if (zoom)
175
+ setAmpliado(item.id); }, accessibilityRole: "imagebutton",
176
+ // Com legenda, o nome do ladrilho é a legenda e a imagem já saiu da árvore (o
177
+ // `alt=""` acima). Sem legenda, o nome é o `alt`.
178
+ accessibilityLabel: temLegenda && typeof item.caption === "string"
179
+ ? item.caption : item.alt, accessibilityState: { selected: item.id === selected }, style: [s.ladrilho, item.id === selected && s.ladrilhoEscolhido], children: miolo }, item.id));
180
+ }) }), zoom && (_jsx(Dialog, { open: aberto != null, title: aberto ? (typeof aberto.caption === "string" ? aberto.caption : aberto.alt) : "", onClose: () => setAmpliado(null), testID: testID ? `${testID}-ampliada` : undefined, children: aberto && _jsx(Image, { source: aberto.source, alt: aberto.alt, fit: "contain", ratio: ratio }) }))] }));
181
+ }
@@ -0,0 +1,100 @@
1
+ import * as React from "react";
2
+ import { type StyleProp, type ViewStyle } from "react-native";
3
+ import { type AureaFieldSize } from "./inputs.js";
4
+ /** Os separadores de um locale, derivados de um número-sonda. */
5
+ export type AureaSeparadores = {
6
+ decimal: string;
7
+ grupo: string;
8
+ };
9
+ /** Descobre o separador decimal e o de milhar do locale. Memoizado por locale. */
10
+ export declare function separadoresDoLocale(locale?: string): AureaSeparadores;
11
+ /**
12
+ * Formata um número para EXIBIÇÃO. Nunca para o valor que o app guarda.
13
+ *
14
+ * ⚠ Sem `Intl`, devolve o número cru com o separador decimal do locale — **não** uma moeda
15
+ * montada à mão. Ver a nota no topo do módulo.
16
+ */
17
+ export declare function formatarNumero(n: number, locale?: string, format?: Intl.NumberFormatOptions): string;
18
+ /**
19
+ * Lê de volta o que a pessoa digitou. **Devolve `null` quando não há número** — e `null` não é
20
+ * zero: um campo vazio e um campo com `0` são coisas diferentes num lançamento.
21
+ *
22
+ * Aceita o que a pessoa realmente digita ou cola: `R$ 1.234,50`, `1 234,50`, `-12,4`, `12.4`.
23
+ * A regra é simples e por isso previsível — **tudo que não é dígito, sinal ou o separador
24
+ * decimal DO LOCALE é lixo** e sai fora, inclusive o separador de milhar.
25
+ */
26
+ export declare function lerNumero(texto: string, locale?: string): number | null;
27
+ export interface NumberFieldProps {
28
+ /** Controlado. `null` é **vazio**, e é diferente de `0`. */
29
+ value?: number | null;
30
+ defaultValue?: number;
31
+ onValueChange?: (v: number | null) => void;
32
+ min?: number;
33
+ max?: number;
34
+ /** Quanto os botões somam e tiram. Padrão **1**. */
35
+ step?: number;
36
+ /**
37
+ * As opções do `Intl.NumberFormat`. **Mesma prop, mesmo tipo e mesmo significado da web** —
38
+ * lá elas iam para o Base UI, aqui vão para o `Intl` direto.
39
+ *
40
+ * moeda {style: "currency", currency: "BRL"} com locale "pt-BR"
41
+ * medida {maximumFractionDigits: 1} 12,4
42
+ * contador nenhuma — o padrão já é o inteiro agrupado
43
+ *
44
+ * ⚠ **`notation: "compact"` não é aceito**, e a recusa é medida: está quebrado no motor nos
45
+ * dois sistemas (motor#768, motor#1035). Em `__DEV__` sai aviso; em produção a opção é
46
+ * ignorada, porque um "1,2 mi" errado numa tela de lançamento é pior que "1.234.567".
47
+ */
48
+ format?: Intl.NumberFormatOptions;
49
+ /** O locale do `Intl`. Sem ele, o do aparelho. */
50
+ locale?: string;
51
+ disabled?: boolean;
52
+ /** Mostra o valor e não deixa editar — os botões também somem. */
53
+ readOnly?: boolean;
54
+ size?: AureaFieldSize;
55
+ /** O nome para o leitor de tela quando não há `Field` em volta. */
56
+ label?: string;
57
+ placeholder?: string;
58
+ /**
59
+ * O teclado. Sem ele a escolha é derivada: **`decimal-pad`**, ou o teclado com sinal quando
60
+ * `min` é negativo — porque o `decimal-pad` do iOS **não tem tecla de menos**, e um campo que
61
+ * aceita −5 e não deixa digitá-lo é um campo quebrado.
62
+ */
63
+ keyboardType?: "numeric" | "decimal-pad" | "number-pad" | "numbers-and-punctuation";
64
+ /** Os glifos dos botões. Registre-os, ou passe `false` para tirar os dois. */
65
+ icons?: {
66
+ increment: IconNameLocal;
67
+ decrement: IconNameLocal;
68
+ } | false;
69
+ style?: StyleProp<ViewStyle>;
70
+ testID?: string;
71
+ }
72
+ type IconNameLocal = string;
73
+ /**
74
+ * O número que se digita OU se empurra de um em um.
75
+ *
76
+ * ```tsx
77
+ * <Field label="Valor">
78
+ * <NumberField value={valor} onValueChange={setValor}
79
+ * format={{style: "currency", currency: "BRL"}} locale="pt-BR" />
80
+ * </Field>
81
+ *
82
+ * <Field label="Litros">
83
+ * <NumberField value={litros} onValueChange={setLitros}
84
+ * format={{maximumFractionDigits: 1}} locale="pt-BR" min={0} />
85
+ * </Field>
86
+ *
87
+ * <Field label="Quantidade">
88
+ * <NumberField value={qtd} onValueChange={setQtd} min={0} step={1} />
89
+ * </Field>
90
+ * ```
91
+ *
92
+ * ⚠ **O que sai por `onValueChange` é o número CRU, sempre.** `1234.5`, nunca `"R$ 1.234,50"`.
93
+ * É a mesma trava da ADR-0024 — lá o motor renderiza um input escondido com o valor cru; aqui não
94
+ * há formulário nativo para esconder nada, então o contrato É a assinatura da função.
95
+ *
96
+ * ⚠ **Formata no blur, e só no blur.** Enquanto o campo tem foco, ele mostra exatamente o que foi
97
+ * digitado — essa é a decisão inteira da ADR-0024, e o teste que a cobra está no lote.
98
+ */
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;
100
+ export {};