@studiolxd/brand 49.19.0 → 49.20.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/CHANGELOG.md +77 -0
- package/dist/_shared/tokens.js +52 -0
- package/dist/_types/atoms/Autocomplete/Autocomplete.d.ts +83 -0
- package/dist/_types/atoms/NumberInput/NumberInput.d.ts +19 -0
- package/dist/_types/atoms/Text/Text.d.ts +12 -2
- package/dist/_types/molecules/AutocompleteField/AutocompleteField.d.ts +57 -0
- package/dist/_types/molecules/NumberInputField/NumberInputField.d.ts +9 -0
- package/dist/autocomplete-field.css +1 -0
- package/dist/autocomplete-field.js +58 -0
- package/dist/autocomplete.css +1 -0
- package/dist/autocomplete.js +145 -0
- package/dist/brand.css +108 -1
- package/dist/number-input-field.js +22 -20
- package/dist/number-input.css +1 -1
- package/dist/number-input.js +54 -44
- package/dist/text.css +1 -1
- package/dist/text.js +6 -5
- package/dist/tokens.css +112 -1
- package/package.json +11 -1
- package/src/tokens/components/autocomplete.css +40 -0
- package/src/tokens/components/autocomplete.ts +7 -0
- package/src/tokens/components/number-input.css +5 -0
- package/src/tokens/components/number-input.ts +2 -2
- package/src/tokens/components/text-inline.css +3 -0
- package/src/tokens/index.css +2 -0
- package/src/tokens/molecules/autocomplete-field.css +17 -0
- package/src/tokens/molecules/autocomplete-field.ts +8 -0
- package/src/tokens/scss/_index.legacy.scss +2 -0
- package/src/tokens/scss/_index.scss +2 -0
- package/src/tokens/scss/components/_autocomplete.scss +37 -0
- package/src/tokens/scss/components/_number-input.scss +5 -0
- package/src/tokens/scss/components/_text-inline.scss +2 -0
- package/src/tokens/scss/molecules/_autocomplete-field.scss +14 -0
- package/src/tokens/surface-dark-derived.css +12 -0
- package/src/tokens/surface-invert.css +13 -0
- package/src/tokens/surface-light.css +13 -0
- package/src/tokens/surface-public.css +6 -0
- package/src/tokens/tokens.json +52 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,83 @@ El paquete sigue [semver](https://semver.org/lang/es/): **patch** para bug fixes
|
|
|
7
7
|
regeneración de `dist`, **minor** para componentes/props/variantes/tokens nuevos, **major**
|
|
8
8
|
para breaking changes.
|
|
9
9
|
|
|
10
|
+
## [49.20.0] — 2026-10-04
|
|
11
|
+
|
|
12
|
+
> **Minor.** `Autocomplete` y `AutocompleteField` (sugerencias con texto libre), tachado en `Text`, y `NumberInput`
|
|
13
|
+
> con `commitMode` (avisar al terminar) y `compact` (para filas de lista), en React, SwiftUI y Compose donde hay
|
|
14
|
+
> versión nativa. Arreglos: color del texto de las filas en Compose y pruebas estables. Todo opcional: sin las props
|
|
15
|
+
> nuevas el HTML no cambia.
|
|
16
|
+
|
|
17
|
+
### Arreglos previos a 49.20.0 (patch)
|
|
18
|
+
|
|
19
|
+
- **Compose · `BrandListItem(text, subtitle)`** pintaba su texto con `BasicText(style = LocalBrandTextStyle.current)`, cuyo
|
|
20
|
+
color es `Unspecified` (negro de Compose): fuera de un `BrandList` salía oscuro sobre fondo oscuro. Ahora la fila fija
|
|
21
|
+
su propia tinta y tipografía (`text.list.*`, `ProvideBrandContent`), como `BrandList`, SwiftUI y React. `BrandSheet`,
|
|
22
|
+
`BrandEmptyState` y `BrandConfirmDialog` no tenían el fallo: pintan sus textos con tokens explícitos y `BrandText`
|
|
23
|
+
cae a la tinta del esquema. Captura nueva `listItemOutsideList` (claro y oscuro).
|
|
24
|
+
- **Storybook · pruebas del Toast** («Test — tintas del aviso en claro/oscuro»): la prueba, no el componente, estaba mal.
|
|
25
|
+
Tras `toast.dismiss()` buscaba `.toast` y recogía el aviso anterior, aún montado mientras sale; al desmontarse
|
|
26
|
+
`getComputedStyle` devolvía `''` en fondo y tinta. Ahora localiza el aviso por su título y mide las tintas de una vez.
|
|
27
|
+
- **Storybook · `test:stories`**: el proyecto `storybook` declara `testTimeout`/`hookTimeout` de 60 s. «Trigger de
|
|
28
|
+
icono» del `Menu` (una story solo de render) agotó los 20 s por carga de la máquina, sin fallo real.
|
|
29
|
+
- **SwiftUI · captura `phrase-mismatch` en macOS** inestable: el `onAppear` daba el foco al campo de la frase y, según
|
|
30
|
+
llegara antes o después de la fotografía, salían el anillo de foco y el cursor. Las capturas ya no piden foco
|
|
31
|
+
(`autofocus: false` en el `init` interno con estado); el componente público lo sigue haciendo.
|
|
32
|
+
Las referencias de iOS de esa captura se regrabaron sin foco (antes llevaban el anillo).
|
|
33
|
+
- **Compose · captura `NumberInputField` `compact`** desfasada (1 px de desplazamiento vertical respecto al render actual;
|
|
34
|
+
regrabada dos veces con bytes idénticos, así que es determinista): referencia actualizada.
|
|
35
|
+
- **Compose · título de `BrandSheet`**: comparado con React y SwiftUI, los tres leen el mismo token
|
|
36
|
+
(`sheet.title-font-size` → `modal.title-font-size`, 32 px, peso 500): no hay diferencia y no se cambia nada.
|
|
37
|
+
|
|
38
|
+
### NumberInput — confirmar al terminar, compacto y etiquetas tipadas (minor)
|
|
39
|
+
|
|
40
|
+
Todo opcional y en las tres plataformas a la vez (React, SwiftUI y Compose): sin las props nuevas, el comportamiento y
|
|
41
|
+
el HTML son los de 49.19.0 (pruebas en `NumberInput.test.tsx` y `NumberInputField.test.tsx`).
|
|
42
|
+
|
|
43
|
+
- **`commitMode: 'change' | 'blur'`** (por defecto `'change'`). Con `'blur'`, lo escrito a mano se avisa **una sola
|
|
44
|
+
vez**, al salir del campo o con Enter (y solo si cambia el valor); Escape descarta lo escrito y vuelve al último
|
|
45
|
+
valor. Los botones − y + avisan al momento en los dos modos. Con `onEmpty`, vaciar y confirmar es «sin valor». Motivo:
|
|
46
|
+
una fila de lista donde cada aviso es una escritura en un servidor. Nativo: `commitMode: .blur` (SwiftUI, al perder el
|
|
47
|
+
foco o con «intro»; Escape con teclado físico) y `NumberInputCommitMode.Blur` (Compose, al perder el foco o «Hecho»).
|
|
48
|
+
- **`compact`** (booleano). Variante para filas de lista, `trailing` de `ListItem`: 32 de alto, botones de 24 y cifra
|
|
49
|
+
de 40 de ancho, sin estirarse; manda sobre `size`. En nativo la zona táctil sigue llegando a 44 pt / 48 dp. Tokens
|
|
50
|
+
nuevos `number-input.compact-*` (`height`, `font-size`, `btn-width`, `field-width`, `padding-inline`).
|
|
51
|
+
- **`decrementLabel` / `incrementLabel`** declaradas en `NumberInputFieldProps` (ya llegaban al control en ejecución
|
|
52
|
+
pero no estaban en el tipo). En nativo ya existían.
|
|
53
|
+
- Fichas de paridad, capturas y pareja con Storybook (`native/apple/Comparisons/NumberInputField/compacto.*`) al día.
|
|
54
|
+
|
|
55
|
+
> **Minor.** Dos piezas de la primera app web (Homenize): un autocompletado que admite texto libre y el tachado en
|
|
56
|
+
> `Text` (React, SwiftUI y Compose). Todo opcional: sin las props nuevas, el HTML de `Text` es idéntico al anterior.
|
|
57
|
+
|
|
58
|
+
### React — `Autocomplete` y `AutocompleteField` (minor)
|
|
59
|
+
|
|
60
|
+
Campo de texto con sugerencias donde **el valor es el texto escrito**: se elige una sugerencia o se queda lo que no
|
|
61
|
+
coincide con ninguna. Componente propio y no una prop de `AsyncSelect` porque el modelo de valor es otro
|
|
62
|
+
(`AsyncSelect` guarda un identificador y pide `selectedOption`; aquí `value` es un `string`).
|
|
63
|
+
|
|
64
|
+
- **API**: `value`/`defaultValue`/`onValueChange` (texto), `onSelect(option)` (solo al elegir: para saber cuál fue),
|
|
65
|
+
sugerencias síncronas con `options` (filtradas por el control, sin mayúsculas ni tildes) o síncronas/asíncronas con
|
|
66
|
+
`onSearch` (con `debounceMs`, descarte de respuestas fuera de orden y `minChars`). `name` y `required` nativos en el
|
|
67
|
+
propio `<input>`.
|
|
68
|
+
- **Combobox ARIA con lista**: `aria-activedescendant`, `aria-expanded`, `aria-controls` solo con la lista abierta;
|
|
69
|
+
flechas, Enter (sin sugerencia marcada se queda con lo escrito y deja enviar el formulario), Escape (no sube al
|
|
70
|
+
`Modal`) y Tab.
|
|
71
|
+
- **Sin texto propio**: sin sugerencias no hay lista ni «sin resultados». No toca el catálogo de mensajes.
|
|
72
|
+
- Misma cara que `AsyncSelect`/`SelectField` por tokens nuevos `autocomplete.*` y `autocomplete-field.*` que apuntan a
|
|
73
|
+
los de `async-select*` (el par oscuro llega por `surface-dark-derived.css`). Sin `surface-dark-*` propios.
|
|
74
|
+
- Registrados: `entry-points.mjs` (+ `clientComponents`), `package.json › exports` (`./autocomplete`,
|
|
75
|
+
`./autocomplete-field`) y `src/index.ts`. Historias, docs `.mdx` y pruebas con teclado.
|
|
76
|
+
- Anotado en `native/README.md` como candidato a versión nativa si una app lo pide.
|
|
77
|
+
|
|
78
|
+
### `Text` — tachado (minor)
|
|
79
|
+
|
|
80
|
+
- **React**: prop `strikethrough` y `as="del"`/`as="s"` (que tachan por sí solos). Clase nueva `text--strikethrough`,
|
|
81
|
+
tinta atenuada (`text-inline.strikethrough-color`, la del texto secundario) y grosor del sistema
|
|
82
|
+
(`text-inline.strikethrough-thickness`); con un `tone` manda el del tono. Sin la prop, el HTML es idéntico
|
|
83
|
+
(prueba en `Text.test.tsx`).
|
|
84
|
+
- **SwiftUI y Compose**: `strikethrough:` / `TextElement.del` y `.s` en `BrandText` / `Text.brand(…)` /
|
|
85
|
+
`brandSpanStyle`, con la ficha de paridad al día.
|
|
86
|
+
|
|
10
87
|
## [49.19.0] — 2026-10-04
|
|
11
88
|
|
|
12
89
|
> **Minor.** Primera versión con **Brand nativo**: tokens, fuentes y componentes para SwiftUI (iOS 17 y macOS 14) y
|
package/dist/_shared/tokens.js
CHANGED
|
@@ -316,6 +316,40 @@ var e = {
|
|
|
316
316
|
"--async-select-empty-font-family": "\"Google Sans Flex\", system-ui, sans-serif",
|
|
317
317
|
"--async-select-empty-font-size": "1rem",
|
|
318
318
|
"--async-select-empty-color": "#4a4a4a",
|
|
319
|
+
"--autocomplete-bg": "#ffffff",
|
|
320
|
+
"--autocomplete-border-color": "#111e30",
|
|
321
|
+
"--autocomplete-border-radius": "0px",
|
|
322
|
+
"--autocomplete-border-style": "solid",
|
|
323
|
+
"--autocomplete-border-width": "1px",
|
|
324
|
+
"--autocomplete-disabled-cursor": "not-allowed",
|
|
325
|
+
"--autocomplete-color": "#111e30",
|
|
326
|
+
"--autocomplete-content-bg": "#ffffff",
|
|
327
|
+
"--autocomplete-content-border-color": "#111e30",
|
|
328
|
+
"--autocomplete-content-gap": "0.5rem",
|
|
329
|
+
"--autocomplete-content-z-index": "400",
|
|
330
|
+
"--autocomplete-content-max-height": "var(--available-height)",
|
|
331
|
+
"--autocomplete-error-border-color": "#b30000",
|
|
332
|
+
"--autocomplete-focus-ring-color": "#111e30",
|
|
333
|
+
"--autocomplete-focus-ring-width": "2px",
|
|
334
|
+
"--autocomplete-font-family": "\"Google Sans Flex\", system-ui, sans-serif",
|
|
335
|
+
"--autocomplete-font-size": "1rem",
|
|
336
|
+
"--autocomplete-font-weight": "300",
|
|
337
|
+
"--autocomplete-height": "2.5rem",
|
|
338
|
+
"--autocomplete-item-highlighted-bg": "#111e30",
|
|
339
|
+
"--autocomplete-item-highlighted-color": "#ffffff",
|
|
340
|
+
"--autocomplete-item-padding-block": "0.5rem",
|
|
341
|
+
"--autocomplete-lg-font-size": "1.25rem",
|
|
342
|
+
"--autocomplete-lg-height": "3rem",
|
|
343
|
+
"--autocomplete-lg-item-padding-block": "0.75rem",
|
|
344
|
+
"--autocomplete-lg-padding-inline": "1.5rem",
|
|
345
|
+
"--autocomplete-line-height": "1",
|
|
346
|
+
"--autocomplete-padding-inline": "1rem",
|
|
347
|
+
"--autocomplete-placeholder-color": "#4a4a4a",
|
|
348
|
+
"--autocomplete-sm-font-size": "0.875rem",
|
|
349
|
+
"--autocomplete-sm-height": "2rem",
|
|
350
|
+
"--autocomplete-sm-item-padding-block": "0.25rem",
|
|
351
|
+
"--autocomplete-sm-padding-inline": "0.5rem",
|
|
352
|
+
"--autocomplete-item-selected-font-weight": "500",
|
|
319
353
|
"--avatar-size-sm": "2rem",
|
|
320
354
|
"--avatar-size-md": "2.5rem",
|
|
321
355
|
"--avatar-size-lg": "3rem",
|
|
@@ -1284,6 +1318,11 @@ var e = {
|
|
|
1284
1318
|
"--number-input-lg-font-size": "1.25rem",
|
|
1285
1319
|
"--number-input-lg-height": "3rem",
|
|
1286
1320
|
"--number-input-lg-padding-inline": "1.5rem",
|
|
1321
|
+
"--number-input-compact-height": "2rem",
|
|
1322
|
+
"--number-input-compact-font-size": "0.875rem",
|
|
1323
|
+
"--number-input-compact-btn-width": "1.5rem",
|
|
1324
|
+
"--number-input-compact-field-width": "2.5rem",
|
|
1325
|
+
"--number-input-compact-padding-inline": "0.25rem",
|
|
1287
1326
|
"--org-switcher-trigger-padding-block": "0.5rem",
|
|
1288
1327
|
"--org-switcher-trigger-padding-inline": "0.75rem",
|
|
1289
1328
|
"--org-switcher-trigger-gap": "0.75rem",
|
|
@@ -1835,6 +1874,8 @@ var e = {
|
|
|
1835
1874
|
"--text-inline-muted-color": "#4a4a4a",
|
|
1836
1875
|
"--text-inline-destructive-color": "#b30000",
|
|
1837
1876
|
"--text-inline-success-color": "#006616",
|
|
1877
|
+
"--text-inline-strikethrough-color": "#4a4a4a",
|
|
1878
|
+
"--text-inline-strikethrough-thickness": "1px",
|
|
1838
1879
|
"--text-font-family": "\"Google Sans Flex\", system-ui, sans-serif",
|
|
1839
1880
|
"--text-font-size": "1rem",
|
|
1840
1881
|
"--text-font-weight": "300",
|
|
@@ -2125,6 +2166,17 @@ var e = {
|
|
|
2125
2166
|
"--async-select-field-helper-font-weight": "300",
|
|
2126
2167
|
"--async-select-field-helper-line-height": "1.5",
|
|
2127
2168
|
"--async-select-field-helper-color": "#111e30",
|
|
2169
|
+
"--autocomplete-field-gap": "0.5rem",
|
|
2170
|
+
"--autocomplete-field-error-font-family": "\"Google Sans Flex\", system-ui, sans-serif",
|
|
2171
|
+
"--autocomplete-field-error-font-size": "0.875rem",
|
|
2172
|
+
"--autocomplete-field-error-font-weight": "300",
|
|
2173
|
+
"--autocomplete-field-error-line-height": "1.5",
|
|
2174
|
+
"--autocomplete-field-error-color": "#b30000",
|
|
2175
|
+
"--autocomplete-field-helper-font-family": "\"Google Sans Flex\", system-ui, sans-serif",
|
|
2176
|
+
"--autocomplete-field-helper-font-size": "0.875rem",
|
|
2177
|
+
"--autocomplete-field-helper-font-weight": "300",
|
|
2178
|
+
"--autocomplete-field-helper-line-height": "1.5",
|
|
2179
|
+
"--autocomplete-field-helper-color": "#111e30",
|
|
2128
2180
|
"--avatar-upload-gap": "1rem",
|
|
2129
2181
|
"--avatar-upload-body-gap": "0.5rem",
|
|
2130
2182
|
"--avatar-upload-ring-width": "2px",
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Popover as BasePopover } from '@base-ui/react/popover';
|
|
2
|
+
import './Autocomplete.css';
|
|
3
|
+
export interface AutocompleteOption {
|
|
4
|
+
/** Identifica la sugerencia: es lo que recibe `onSelect` para saber cuál se eligió. */
|
|
5
|
+
value: string;
|
|
6
|
+
/** El texto de la sugerencia: es lo que se escribe en el campo al elegirla. */
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AutocompleteProps {
|
|
10
|
+
/**
|
|
11
|
+
* El texto del campo (controlado). **El valor es el texto escrito**, haya o
|
|
12
|
+
* no una sugerencia que coincida: elegir una sugerencia solo lo rellena.
|
|
13
|
+
*/
|
|
14
|
+
value?: string;
|
|
15
|
+
/** Texto inicial cuando el campo no está controlado. */
|
|
16
|
+
defaultValue?: string;
|
|
17
|
+
/** Se llama con el texto nuevo, tanto al escribir como al elegir una sugerencia. */
|
|
18
|
+
onValueChange?: (value: string) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Se llama, además de `onValueChange`, cuando el usuario **elige** una
|
|
21
|
+
* sugerencia: sirve para saber cuál fue (su `value`) frente a un texto libre.
|
|
22
|
+
*/
|
|
23
|
+
onSelect?: (option: AutocompleteOption) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Sugerencias **síncronas**: una lista fija que el propio control filtra por
|
|
26
|
+
* lo escrito (sin distinguir mayúsculas ni tildes). Es excluyente con
|
|
27
|
+
* `onSearch`.
|
|
28
|
+
*/
|
|
29
|
+
options?: AutocompleteOption[];
|
|
30
|
+
/**
|
|
31
|
+
* Sugerencias **asíncronas** (o síncronas, calculadas por el consumidor): se
|
|
32
|
+
* llama con lo escrito, tras `debounceMs`, y devuelve la lista. Si lanza,
|
|
33
|
+
* simplemente no hay sugerencias: el texto escrito sigue valiendo.
|
|
34
|
+
*/
|
|
35
|
+
onSearch?: (query: string) => AutocompleteOption[] | Promise<AutocompleteOption[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Milisegundos de rebote entre la última tecla y `onSearch`. Default: 200.
|
|
38
|
+
* A 0 se busca en cada tecla. No afecta a `options`.
|
|
39
|
+
*/
|
|
40
|
+
debounceMs?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Caracteres mínimos para sugerir al escribir. Default: 1. La flecha abajo
|
|
43
|
+
* abre la lista aunque no se llegue al mínimo.
|
|
44
|
+
*/
|
|
45
|
+
minChars?: number;
|
|
46
|
+
/** Pista dentro del campo. El control no emite más texto por su cuenta. */
|
|
47
|
+
placeholder?: string;
|
|
48
|
+
disabled?: boolean;
|
|
49
|
+
readOnly?: boolean;
|
|
50
|
+
size?: 'sm' | 'md' | 'lg';
|
|
51
|
+
id?: string;
|
|
52
|
+
/** Nombre del campo en el formulario: el propio `<input>` lleva el texto. */
|
|
53
|
+
name?: string;
|
|
54
|
+
/** Marca el estado de error: aplica la clase `autocomplete--error` y `aria-invalid`. */
|
|
55
|
+
error?: boolean;
|
|
56
|
+
/** Campo obligatorio: `required` nativo, porque lo que se envía es el propio texto. */
|
|
57
|
+
required?: boolean;
|
|
58
|
+
maxLength?: number;
|
|
59
|
+
/** Se llama al salir del control (react-hook-form lo usa para validar). */
|
|
60
|
+
onBlur?: React.FocusEventHandler<HTMLInputElement>;
|
|
61
|
+
/** Se añade DESPUÉS de las clases propias del componente. */
|
|
62
|
+
className?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Nombre accesible cuando el control va suelto, y de la lista de
|
|
65
|
+
* sugerencias. En un campo lo nombra la etiqueta (`htmlFor`), que este
|
|
66
|
+
* atributo pisaría: no lo pongas ahí.
|
|
67
|
+
*/
|
|
68
|
+
'aria-label'?: string;
|
|
69
|
+
'aria-describedby'?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Nodo DOM donde montar el portal de la lista (reenviado a Base UI
|
|
72
|
+
* `Portal.container`). Mismo contrato que `AsyncSelect`: por defecto, el nodo
|
|
73
|
+
* de la superficie que llegue por contexto, o `document.body`.
|
|
74
|
+
*/
|
|
75
|
+
container?: React.ComponentPropsWithoutRef<typeof BasePopover.Portal>['container'];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Campo de texto con sugerencias. A diferencia de `AsyncSelect`, **no obliga a
|
|
79
|
+
* elegir**: el valor es el texto escrito y una sugerencia es solo una forma de
|
|
80
|
+
* escribirlo más deprisa. Patrón ARIA de combobox con lista (`aria-activedescendant`):
|
|
81
|
+
* el foco no sale nunca del `<input>`. El `ref` va a ese `<input>`.
|
|
82
|
+
*/
|
|
83
|
+
export declare const Autocomplete: import("react").ForwardRefExoticComponent<AutocompleteProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -10,6 +10,12 @@ export interface NumberInputMessages {
|
|
|
10
10
|
/** Nombre accesible del botón que suma un paso. */
|
|
11
11
|
increment: string;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Cuándo avisa `onChange` de lo escrito a mano: con cada tecla (`'change'`, lo de
|
|
15
|
+
* siempre) o una sola vez al confirmar (`'blur'`). Los botones − y + avisan al
|
|
16
|
+
* momento en los dos modos.
|
|
17
|
+
*/
|
|
18
|
+
export type NumberInputCommitMode = 'change' | 'blur';
|
|
13
19
|
export interface NumberInputProps extends Omit<ComponentPropsWithoutRef<'input'>, 'size' | 'type' | 'value' | 'defaultValue' | 'onChange'> {
|
|
14
20
|
/**
|
|
15
21
|
* Valor controlado. `null` es «sin valor»: el campo se muestra vacío (y el
|
|
@@ -25,6 +31,19 @@ export interface NumberInputProps extends Omit<ComponentPropsWithoutRef<'input'>
|
|
|
25
31
|
disabled?: boolean;
|
|
26
32
|
readOnly?: boolean;
|
|
27
33
|
size?: 'sm' | 'md' | 'lg';
|
|
34
|
+
/**
|
|
35
|
+
* Variante para filas de lista (`trailing` de `ListItem`): botones y cifra
|
|
36
|
+
* justos, del ancho de 2–3 dígitos, sin estirarse. Es una variante de la
|
|
37
|
+
* talla `sm`, no una talla más: manda sobre `size`.
|
|
38
|
+
*/
|
|
39
|
+
compact?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Cuándo se avisa de lo escrito a mano. `'change'` (por defecto): con cada
|
|
42
|
+
* tecla. `'blur'`: una sola vez al salir del campo o al pulsar Enter; Escape
|
|
43
|
+
* descarta lo escrito y vuelve al último valor. Con `onEmpty`, dejar el campo
|
|
44
|
+
* vacío se avisa igual, al confirmar. − y + avisan al momento en los dos modos.
|
|
45
|
+
*/
|
|
46
|
+
commitMode?: NumberInputCommitMode;
|
|
28
47
|
error?: boolean;
|
|
29
48
|
id?: string;
|
|
30
49
|
name?: string;
|
|
@@ -5,13 +5,21 @@ export interface TextProps extends React.ComponentPropsWithoutRef<'span'> {
|
|
|
5
5
|
* `span` no añade significado, `em` marca énfasis de lectura (cambia cómo se
|
|
6
6
|
* dice la frase) y `strong` marca importancia. No es una prop de estilo.
|
|
7
7
|
*/
|
|
8
|
-
as?: 'span' | 'em' | 'strong';
|
|
8
|
+
as?: 'span' | 'em' | 'strong' | 'del' | 's';
|
|
9
9
|
/**
|
|
10
10
|
* Intención del fragmento. `destructive` es la palabra que dice que algo se
|
|
11
11
|
* pierde («esta acción **borra** el curso»); `success`, la que dice que salió
|
|
12
12
|
* bien; `muted`, una aclaración secundaria.
|
|
13
13
|
*/
|
|
14
14
|
tone?: 'default' | 'muted' | 'destructive' | 'success';
|
|
15
|
+
/**
|
|
16
|
+
* Tacha el fragmento con una línea y lo atenúa (tinta secundaria): lo que ya
|
|
17
|
+
* está hecho, como un producto que ya está en el carrito. Es **solo
|
|
18
|
+
* aspecto**: no dice nada al lector de pantalla. Si el tachado significa algo
|
|
19
|
+
* («este precio ya no vale»), usa `as="del"` o `as="s"`, que además lo tachan.
|
|
20
|
+
* Con un `tone` manda el color del tono.
|
|
21
|
+
*/
|
|
22
|
+
strikethrough?: boolean;
|
|
15
23
|
/**
|
|
16
24
|
* Idioma de **este fragmento**, cuando no es el de la página: una cita, un
|
|
17
25
|
* término sin traducir, un segmento de traducción. Marca el idioma para el
|
|
@@ -25,11 +33,13 @@ export interface TextProps extends React.ComponentPropsWithoutRef<'span'> {
|
|
|
25
33
|
}
|
|
26
34
|
/**
|
|
27
35
|
* Texto **en línea**: un trozo de una frase que hay que marcar sin salirse de
|
|
28
|
-
* ella.
|
|
36
|
+
* ella. Tres usos, y ningún otro:
|
|
29
37
|
*
|
|
30
38
|
* - **Otro idioma** (`lang`): el fragmento se anuncia con la voz correcta y se
|
|
31
39
|
* corta según sus reglas. Antes esto era un `<span lang>` a mano en cada
|
|
32
40
|
* producto.
|
|
41
|
+
* - **Tachado** (`strikethrough`, o `as="del"`/`as="s"`): lo hecho o lo que ya
|
|
42
|
+
* no vale, en tinta atenuada.
|
|
33
43
|
* - **Intención** (`tone`): énfasis con carga —destructiva o de logro— en tinta
|
|
34
44
|
* de feedback. Es color de texto sobre la superficie, **nunca un relleno**:
|
|
35
45
|
* una palabra resaltada dentro de un párrafo no lleva fondo.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import './AutocompleteField.css';
|
|
2
|
+
import { Autocomplete } from '../../atoms/Autocomplete/Autocomplete';
|
|
3
|
+
import type { AutocompleteOption } from '../../atoms/Autocomplete/Autocomplete';
|
|
4
|
+
export type { AutocompleteOption };
|
|
5
|
+
export interface AutocompleteFieldProps {
|
|
6
|
+
/** `id` del control. Si no se pasa, se genera con `useId`. */
|
|
7
|
+
id?: string;
|
|
8
|
+
label: string;
|
|
9
|
+
/**
|
|
10
|
+
* Oculta la etiqueta a la vista (sigue leyéndola el lector de pantalla).
|
|
11
|
+
* Por defecto `false`: la etiqueta se ve.
|
|
12
|
+
* Sin valor, lo decide quien lo envuelva: dentro de un `FieldRow` que no
|
|
13
|
+
* es la primera de la lista, la etiqueta se oculta sola.
|
|
14
|
+
*/
|
|
15
|
+
labelHidden?: boolean;
|
|
16
|
+
/** El texto del campo (controlado). El valor es lo escrito, coincida o no con una sugerencia. */
|
|
17
|
+
value?: string;
|
|
18
|
+
/** Texto inicial cuando el campo no está controlado. */
|
|
19
|
+
defaultValue?: string;
|
|
20
|
+
/** Se llama con el texto nuevo, al escribir y al elegir una sugerencia. */
|
|
21
|
+
onValueChange?: (value: string) => void;
|
|
22
|
+
/** Se llama, además, cuando se elige una sugerencia: para saber cuál fue. */
|
|
23
|
+
onSelect?: (option: AutocompleteOption) => void;
|
|
24
|
+
/** Sugerencias síncronas: lista fija que el control filtra por lo escrito. Excluyente con `onSearch`. */
|
|
25
|
+
options?: AutocompleteOption[];
|
|
26
|
+
/** Sugerencias asíncronas (o calculadas por el consumidor), con rebote. */
|
|
27
|
+
onSearch?: (query: string) => AutocompleteOption[] | Promise<AutocompleteOption[]>;
|
|
28
|
+
/** Milisegundos de rebote antes de llamar a `onSearch`. Default: 200. */
|
|
29
|
+
debounceMs?: number;
|
|
30
|
+
/** Caracteres mínimos para sugerir al escribir. Default: 1. */
|
|
31
|
+
minChars?: number;
|
|
32
|
+
placeholder?: string;
|
|
33
|
+
/** Nombre del campo en el formulario: el propio `<input>` lleva el texto. */
|
|
34
|
+
name?: string;
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
readOnly?: boolean;
|
|
37
|
+
/** Campo obligatorio (`required` nativo). */
|
|
38
|
+
required?: boolean;
|
|
39
|
+
maxLength?: number;
|
|
40
|
+
/** Marca el control en error sin mensaje. Un `errorMessage` ya lo implica. */
|
|
41
|
+
error?: boolean;
|
|
42
|
+
/** Mensaje de error: se anuncia (`role="alert"`) y pone el control en error. */
|
|
43
|
+
errorMessage?: string;
|
|
44
|
+
/** Texto de ayuda, enlazado por `aria-describedby`. */
|
|
45
|
+
helperText?: string;
|
|
46
|
+
size?: 'sm' | 'md' | 'lg';
|
|
47
|
+
/** Se añade DESPUÉS de las clases propias (el consumidor añade, no sustituye). */
|
|
48
|
+
className?: string;
|
|
49
|
+
/** Nodo DOM donde montar el portal de la lista (ver `Autocomplete`). */
|
|
50
|
+
container?: React.ComponentProps<typeof Autocomplete>['container'];
|
|
51
|
+
onBlur?: React.FocusEventHandler<HTMLInputElement>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* El `Autocomplete` como campo de formulario: etiqueta, ayuda y error. El
|
|
55
|
+
* `ref` va al `<input>`; el `className`, al contenedor.
|
|
56
|
+
*/
|
|
57
|
+
export declare const AutocompleteField: import("react").ForwardRefExoticComponent<AutocompleteFieldProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ComponentPropsWithoutRef } from 'react';
|
|
2
2
|
import './NumberInputField.css';
|
|
3
|
+
import { type NumberInputCommitMode } from '../../atoms/NumberInput/NumberInput';
|
|
3
4
|
export interface NumberInputFieldProps extends Omit<ComponentPropsWithoutRef<'input'>, 'size' | 'type' | 'value' | 'defaultValue' | 'onChange'> {
|
|
4
5
|
/** `id` del control. Si no se pasa, se genera con `useId`. */
|
|
5
6
|
id?: string;
|
|
@@ -26,6 +27,14 @@ export interface NumberInputFieldProps extends Omit<ComponentPropsWithoutRef<'in
|
|
|
26
27
|
/** Texto de ayuda, enlazado por `aria-describedby`. */
|
|
27
28
|
helperText?: string;
|
|
28
29
|
size?: 'sm' | 'md' | 'lg';
|
|
30
|
+
/** Variante para filas de lista: botones y cifra justos. Ver `NumberInput`. */
|
|
31
|
+
compact?: boolean;
|
|
32
|
+
/** Cuándo se avisa de lo escrito a mano: con cada tecla (`'change'`) o al salir/Enter (`'blur'`). Ver `NumberInput`. */
|
|
33
|
+
commitMode?: NumberInputCommitMode;
|
|
34
|
+
/** aria-label del botón que resta. Sin default: sale de `numberInput.decrement` del `BrandMessagesProvider`. */
|
|
35
|
+
decrementLabel?: string;
|
|
36
|
+
/** aria-label del botón que suma. Sin default: sale de `numberInput.increment` del `BrandMessagesProvider`. */
|
|
37
|
+
incrementLabel?: string;
|
|
29
38
|
/** Recibe el valor ya normalizado, no el evento. */
|
|
30
39
|
onChange?: (value: number) => void;
|
|
31
40
|
/** Quien teclea dejó el campo vacío; con ella, vaciar es un valor (`null`). Ver `NumberInput`. */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.autocomplete-field{gap:var(--autocomplete-field-gap);flex-direction:column;display:flex}.autocomplete-field__helper{font-family:var(--autocomplete-field-helper-font-family);font-size:var(--autocomplete-field-helper-font-size);font-weight:var(--autocomplete-field-helper-font-weight);line-height:var(--autocomplete-field-helper-line-height);color:var(--autocomplete-field-helper-color)}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import './autocomplete-field.css';
|
|
3
|
+
import { Autocomplete as e } from "./autocomplete.js";
|
|
4
|
+
import { n as t } from "./_shared/form-size.js";
|
|
5
|
+
import { ErrorText as n } from "./error-text.js";
|
|
6
|
+
import { Label as r } from "./label.js";
|
|
7
|
+
import { n as i } from "./_shared/field-labels.js";
|
|
8
|
+
import { forwardRef as a, useId as o } from "react";
|
|
9
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
10
|
+
//#region src/stories/molecules/AutocompleteField/AutocompleteField.tsx
|
|
11
|
+
var l = a(function({ id: a, label: l, labelHidden: u, value: d, defaultValue: f, onValueChange: p, onSelect: m, options: h, onSearch: g, debounceMs: _, minChars: v, placeholder: y, name: b, disabled: x, readOnly: S, size: C, required: w, maxLength: T, error: E = !1, errorMessage: D, helperText: O, className: k, container: A, onBlur: j }, M) {
|
|
12
|
+
let N = i(u), P = t(C), F = o(), I = a ?? F, L = D ? `${I}-error` : void 0, R = O ? `${I}-helper` : void 0, z = [L, R].filter(Boolean).join(" ") || void 0, B = E || !!D;
|
|
13
|
+
return /* @__PURE__ */ c("div", {
|
|
14
|
+
className: ["autocomplete-field", k].filter(Boolean).join(" "),
|
|
15
|
+
children: [
|
|
16
|
+
/* @__PURE__ */ s(r, {
|
|
17
|
+
htmlFor: I,
|
|
18
|
+
hidden: N,
|
|
19
|
+
size: P,
|
|
20
|
+
children: l
|
|
21
|
+
}),
|
|
22
|
+
/* @__PURE__ */ s(e, {
|
|
23
|
+
ref: M,
|
|
24
|
+
id: I,
|
|
25
|
+
name: b,
|
|
26
|
+
value: d,
|
|
27
|
+
defaultValue: f,
|
|
28
|
+
onValueChange: p,
|
|
29
|
+
onSelect: m,
|
|
30
|
+
options: h,
|
|
31
|
+
onSearch: g,
|
|
32
|
+
debounceMs: _,
|
|
33
|
+
minChars: v,
|
|
34
|
+
placeholder: y,
|
|
35
|
+
disabled: x,
|
|
36
|
+
readOnly: S,
|
|
37
|
+
size: P,
|
|
38
|
+
required: w,
|
|
39
|
+
maxLength: T,
|
|
40
|
+
error: B,
|
|
41
|
+
container: A,
|
|
42
|
+
"aria-describedby": z,
|
|
43
|
+
onBlur: j
|
|
44
|
+
}),
|
|
45
|
+
D && /* @__PURE__ */ s(n, {
|
|
46
|
+
id: L,
|
|
47
|
+
children: D
|
|
48
|
+
}),
|
|
49
|
+
O && /* @__PURE__ */ s("span", {
|
|
50
|
+
id: R,
|
|
51
|
+
className: "autocomplete-field__helper",
|
|
52
|
+
children: O
|
|
53
|
+
})
|
|
54
|
+
]
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
//#endregion
|
|
58
|
+
export { l as AutocompleteField };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.autocomplete{align-items:center;gap:var(--autocomplete-content-gap);block-size:var(--autocomplete-height);padding-block:0;padding-inline:var(--autocomplete-padding-inline);font-family:var(--autocomplete-font-family);font-size:var(--autocomplete-font-size);line-height:var(--autocomplete-line-height);font-weight:var(--autocomplete-font-weight);color:var(--autocomplete-color);background-color:var(--autocomplete-bg);border-width:var(--autocomplete-border-width);border-style:var(--autocomplete-border-style);border-color:var(--autocomplete-border-color);border-radius:var(--autocomplete-border-radius);box-sizing:border-box;width:100%;display:inline-flex}.autocomplete:focus-within{outline:var(--autocomplete-focus-ring-width) solid var(--autocomplete-focus-ring-color);outline-offset:calc(-1 * (var(--autocomplete-focus-ring-width) + var(--autocomplete-border-width)))}.autocomplete.autocomplete--disabled{opacity:.5;cursor:var(--autocomplete-disabled-cursor);pointer-events:none}.autocomplete__input{all:unset;cursor:text;min-width:0;font-family:inherit;font-size:inherit;line-height:inherit;font-weight:inherit;color:inherit;flex:1}.autocomplete__input::placeholder{color:var(--autocomplete-placeholder-color)}.autocomplete__input:disabled{cursor:var(--autocomplete-disabled-cursor)}.autocomplete__content{background-color:var(--autocomplete-content-bg);border-width:var(--autocomplete-border-width);border-style:var(--autocomplete-border-style);border-color:var(--autocomplete-content-border-color);min-width:var(--anchor-width);max-height:var(--autocomplete-content-max-height,var(--available-height));overscroll-behavior:contain;overflow-y:auto}.autocomplete__content[data-side=bottom]{border-top:none}.autocomplete__content[data-side=top]{border-bottom:none}.autocomplete__item{all:unset;box-sizing:border-box;width:100%;padding-block:var(--autocomplete-item-padding-block);padding-inline:var(--autocomplete-padding-inline);font-size:var(--autocomplete-font-size);font-family:var(--autocomplete-font-family);color:var(--autocomplete-color);cursor:pointer;align-items:center;display:flex}.autocomplete__item:focus-visible,.autocomplete__item:hover{background-color:var(--autocomplete-item-highlighted-bg);color:var(--autocomplete-item-highlighted-color)}.autocomplete__item.autocomplete__item--selected{font-weight:var(--autocomplete-item-selected-font-weight)}.autocomplete__item.autocomplete__item--active{background-color:var(--autocomplete-item-highlighted-bg);color:var(--autocomplete-item-highlighted-color)}.autocomplete__content .autocomplete__item+.autocomplete__item,.autocomplete__content[data-side=bottom] .autocomplete__item:first-child{border-top:var(--autocomplete-border-width) var(--autocomplete-border-style) var(--autocomplete-content-border-color)}.autocomplete__content[data-side=top] .autocomplete__item:last-child{border-bottom:var(--autocomplete-border-width) var(--autocomplete-border-style) var(--autocomplete-content-border-color)}.autocomplete.autocomplete--sm{font-size:var(--autocomplete-sm-font-size);--autocomplete-height:var(--autocomplete-sm-height);padding-inline:var(--autocomplete-sm-padding-inline)}.autocomplete__content--sm .autocomplete__item{font-size:var(--autocomplete-sm-font-size);padding-block:var(--autocomplete-sm-item-padding-block)}.autocomplete.autocomplete--lg{font-size:var(--autocomplete-lg-font-size);--autocomplete-height:var(--autocomplete-lg-height);padding-inline:var(--autocomplete-lg-padding-inline)}.autocomplete__content--lg .autocomplete__item{font-size:var(--autocomplete-lg-font-size);padding-block:var(--autocomplete-lg-item-padding-block)}.autocomplete__positioner{z-index:var(--autocomplete-content-z-index)}.autocomplete.autocomplete--error{border-color:var(--autocomplete-error-border-color)}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import './autocomplete.css';
|
|
3
|
+
import { Spinner as e } from "./spinner.js";
|
|
4
|
+
import { n as t } from "./_shared/portal-container.js";
|
|
5
|
+
import { forwardRef as n, useCallback as r, useEffect as i, useId as a, useRef as o, useState as s } from "react";
|
|
6
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
7
|
+
import { Popover as u } from "@base-ui/react/popover";
|
|
8
|
+
//#region src/stories/atoms/Autocomplete/Autocomplete.tsx
|
|
9
|
+
function ee(e, t) {
|
|
10
|
+
typeof e == "function" ? e(t) : e && (e.current = t);
|
|
11
|
+
}
|
|
12
|
+
function d(e) {
|
|
13
|
+
return e.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
var f = n(function({ value: n, defaultValue: f = "", onValueChange: p, onSelect: m, options: h, onSearch: g, debounceMs: _ = 200, minChars: v = 1, placeholder: y, disabled: b, readOnly: x, size: S = "md", id: C, name: w, error: T = !1, required: E, maxLength: D, onBlur: O, className: k, "aria-label": A, "aria-describedby": j, container: M }, N) {
|
|
16
|
+
let te = t(M), [ne, re] = s(f), [ie, P] = s(!1), [ae, F] = s(!1), [I, L] = s([]), [R, z] = s(-1), B = o(null), V = o(0), H = o(null), U = o(null), W = a(), G = a(), K = n === void 0 ? ne : n, q = ie && I.length > 0, J = (e) => `${G}-opt-${e}`, Y = r((e) => {
|
|
17
|
+
let t = ++V.current;
|
|
18
|
+
if (g) {
|
|
19
|
+
F(!0), Promise.resolve().then(() => g(e)).then((e) => e, () => []).then((e) => {
|
|
20
|
+
t === V.current && (L(e), z(-1), F(!1));
|
|
21
|
+
});
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
let n = d(e);
|
|
25
|
+
L((h ?? []).filter((e) => d(e.label).includes(n))), z(-1);
|
|
26
|
+
}, [g, h]);
|
|
27
|
+
i(() => () => {
|
|
28
|
+
V.current += 1, B.current && clearTimeout(B.current);
|
|
29
|
+
}, []);
|
|
30
|
+
function X(e) {
|
|
31
|
+
B.current && clearTimeout(B.current), g && _ > 0 ? B.current = setTimeout(() => Y(e), _) : Y(e);
|
|
32
|
+
}
|
|
33
|
+
function Z() {
|
|
34
|
+
B.current && clearTimeout(B.current), V.current += 1, F(!1), P(!1), z(-1);
|
|
35
|
+
}
|
|
36
|
+
function Q(e) {
|
|
37
|
+
n === void 0 && re(e), p?.(e);
|
|
38
|
+
}
|
|
39
|
+
function oe(e) {
|
|
40
|
+
let t = e.target.value;
|
|
41
|
+
if (Q(t), t.length < v) {
|
|
42
|
+
Z();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
P(!0), X(t);
|
|
46
|
+
}
|
|
47
|
+
function $(e) {
|
|
48
|
+
Q(e.label), m?.(e), Z(), L([]);
|
|
49
|
+
}
|
|
50
|
+
function se(e) {
|
|
51
|
+
b || x || (e.key === "ArrowDown" ? (e.preventDefault(), q ? z((e) => Math.min(e + 1, I.length - 1)) : (P(!0), X(K))) : e.key === "ArrowUp" ? (e.preventDefault(), q && z((e) => Math.max(e - 1, -1))) : e.key === "Enter" ? q && R >= 0 && I[R] ? (e.preventDefault(), $(I[R])) : q && Z() : e.key === "Escape" ? q && (e.preventDefault(), e.stopPropagation(), Z()) : e.key === "Tab" && Z());
|
|
52
|
+
}
|
|
53
|
+
function ce(e, t) {
|
|
54
|
+
if (!e) {
|
|
55
|
+
if (t.reason === "outside-press") {
|
|
56
|
+
let e = t.event?.target;
|
|
57
|
+
if (e instanceof Node && U.current?.contains(e)) return;
|
|
58
|
+
}
|
|
59
|
+
Z();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let le = [
|
|
63
|
+
"autocomplete",
|
|
64
|
+
S === "md" ? "" : `autocomplete--${S}`,
|
|
65
|
+
b ? "autocomplete--disabled" : "",
|
|
66
|
+
T ? "autocomplete--error" : "",
|
|
67
|
+
k ?? ""
|
|
68
|
+
].filter(Boolean).join(" "), ue = ["autocomplete__content", S === "md" ? "" : `autocomplete__content--${S}`].filter(Boolean).join(" ");
|
|
69
|
+
return /* @__PURE__ */ l(u.Root, {
|
|
70
|
+
open: q,
|
|
71
|
+
onOpenChange: ce,
|
|
72
|
+
children: [/* @__PURE__ */ l("div", {
|
|
73
|
+
ref: U,
|
|
74
|
+
className: le,
|
|
75
|
+
"data-popup-open": q || void 0,
|
|
76
|
+
children: [/* @__PURE__ */ c("input", {
|
|
77
|
+
ref: (e) => {
|
|
78
|
+
H.current = e, ee(N, e);
|
|
79
|
+
},
|
|
80
|
+
id: C,
|
|
81
|
+
name: w,
|
|
82
|
+
type: "text",
|
|
83
|
+
className: "autocomplete__input",
|
|
84
|
+
value: K,
|
|
85
|
+
onChange: oe,
|
|
86
|
+
onKeyDown: se,
|
|
87
|
+
placeholder: y,
|
|
88
|
+
disabled: b,
|
|
89
|
+
readOnly: x,
|
|
90
|
+
required: E,
|
|
91
|
+
maxLength: D,
|
|
92
|
+
"aria-label": A,
|
|
93
|
+
"aria-describedby": j,
|
|
94
|
+
"aria-invalid": T || void 0,
|
|
95
|
+
"aria-expanded": q,
|
|
96
|
+
"aria-haspopup": "listbox",
|
|
97
|
+
"aria-controls": q ? W : void 0,
|
|
98
|
+
"aria-activedescendant": q && R >= 0 ? J(R) : void 0,
|
|
99
|
+
autoComplete: "off",
|
|
100
|
+
role: "combobox",
|
|
101
|
+
"aria-autocomplete": "list",
|
|
102
|
+
onBlur: O
|
|
103
|
+
}), ae && /* @__PURE__ */ c(e, {
|
|
104
|
+
size: "sm",
|
|
105
|
+
"aria-hidden": !0
|
|
106
|
+
})]
|
|
107
|
+
}), /* @__PURE__ */ c(u.Portal, {
|
|
108
|
+
container: te,
|
|
109
|
+
children: /* @__PURE__ */ c(u.Positioner, {
|
|
110
|
+
className: "autocomplete__positioner",
|
|
111
|
+
anchor: U,
|
|
112
|
+
align: "start",
|
|
113
|
+
sideOffset: -1,
|
|
114
|
+
children: /* @__PURE__ */ c(u.Popup, {
|
|
115
|
+
className: ue,
|
|
116
|
+
initialFocus: !1,
|
|
117
|
+
finalFocus: !1,
|
|
118
|
+
children: /* @__PURE__ */ c("div", {
|
|
119
|
+
role: "listbox",
|
|
120
|
+
"aria-label": A ?? y,
|
|
121
|
+
id: W,
|
|
122
|
+
children: I.map((e, t) => {
|
|
123
|
+
let n = e.label === K, r = R === t;
|
|
124
|
+
return /* @__PURE__ */ c("div", {
|
|
125
|
+
id: J(t),
|
|
126
|
+
role: "option",
|
|
127
|
+
"aria-selected": r,
|
|
128
|
+
className: [
|
|
129
|
+
"autocomplete__item",
|
|
130
|
+
n ? "autocomplete__item--selected" : "",
|
|
131
|
+
r ? "autocomplete__item--active" : ""
|
|
132
|
+
].filter(Boolean).join(" "),
|
|
133
|
+
onPointerDown: (e) => e.preventDefault(),
|
|
134
|
+
onClick: () => $(e),
|
|
135
|
+
children: e.label
|
|
136
|
+
}, e.value);
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
})]
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
//#endregion
|
|
145
|
+
export { f as Autocomplete };
|