@satellatickets/core 0.2.0 → 0.2.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.
@@ -0,0 +1,300 @@
1
+
2
+ import { BrandName, Theme, ThemeName } from "@satellatickets/tokens";
3
+ import { ReactNode } from "react";
4
+ //#region src/form-field/context.d.ts
5
+ /** Lo que `FormField` publica para el control que envuelve (ADR-037). */
6
+ interface FormFieldValue {
7
+ /** `id` del control. `FormField` lo usa en el `htmlFor` de su etiqueta (web). */
8
+ controlId: string;
9
+ /** `id` del texto de ayuda, si lo hay (web). */
10
+ helpId: string | undefined;
11
+ /** `id` del mensaje de error, si lo hay (web). */
12
+ errorId: string | undefined;
13
+ label: string;
14
+ help: string | undefined;
15
+ error: string | undefined;
16
+ disabled: boolean;
17
+ required: boolean;
18
+ }
19
+ interface FormFieldValueOptions {
20
+ label: string;
21
+ help?: string | undefined;
22
+ error?: string | undefined;
23
+ disabled?: boolean | undefined;
24
+ required?: boolean | undefined;
25
+ }
26
+ /** Construye el valor del contexto a partir de un id base (el de `useId()`). */
27
+ declare function createFormFieldValue(baseId: string, { label, help, error, disabled, required }: FormFieldValueOptions): FormFieldValue;
28
+ /** `null` fuera de un `FormField`. */
29
+ declare const FormFieldContext: import("react").Context<FormFieldValue | null>;
30
+ /** Las props propias de un control que se combinan con las de su `FormField`. */
31
+ interface FormFieldControlOptions {
32
+ id?: string | undefined;
33
+ invalid?: boolean | undefined;
34
+ disabled?: boolean | undefined;
35
+ required?: boolean | undefined;
36
+ accessibilityLabel?: string | undefined;
37
+ }
38
+ /** Lo que un control necesita para enlazarse con su `FormField`, en web y en nativo. */
39
+ interface FormFieldControl {
40
+ /** `id` del control (web): el propio o el que espera la etiqueta del campo. */
41
+ id: string | undefined;
42
+ /** Ids de la ayuda y el error para `aria-describedby` (web). */
43
+ describedBy: string | undefined;
44
+ /** Nombre accesible (nativo): el propio o la etiqueta del campo. */
45
+ accessibilityLabel: string | undefined;
46
+ /** El error y la ayuda, uno tras otro, para `accessibilityHint` (nativo). */
47
+ accessibilityHint: string | undefined;
48
+ invalid: boolean;
49
+ disabled: boolean;
50
+ required: boolean;
51
+ }
52
+ /**
53
+ * Combina las props de un control con las de su `FormField`. Las del control
54
+ * mandan en `id` y nombre accesible; `invalid`, `disabled` y `required` se suman:
55
+ * basta con que lo diga uno de los dos.
56
+ */
57
+ declare function resolveFormFieldControl(field: FormFieldValue | null, { id, invalid, disabled, required, accessibilityLabel }?: FormFieldControlOptions): FormFieldControl;
58
+ /** Enlaza un control con el `FormField` que lo contiene, si lo hay. */
59
+ declare function useFormFieldControl(options?: FormFieldControlOptions): FormFieldControl;
60
+ //#endregion
61
+ //#region src/hooks/use-button.d.ts
62
+ interface UseButtonOptions {
63
+ disabled?: boolean | undefined;
64
+ loading?: boolean | undefined;
65
+ onPress?: (() => void) | undefined;
66
+ }
67
+ interface UseButtonResult {
68
+ disabled: boolean;
69
+ loading: boolean;
70
+ /** `false` si está deshabilitado o cargando: el botón no responde a la pulsación. */
71
+ interactive: boolean;
72
+ /** Llama a `onPress` solo si el botón es interactivo. */
73
+ press: () => void;
74
+ }
75
+ /**
76
+ * Comportamiento de `Button` común a web y nativo: un botón deshabilitado o que
77
+ * está cargando no dispara `onPress`. Cada vista añade su accesibilidad
78
+ * (`disabled`/`aria-busy` en web, `accessibilityState` en nativo).
79
+ */
80
+ declare function useButton({ disabled, loading, onPress }: UseButtonOptions): UseButtonResult;
81
+ //#endregion
82
+ //#region src/hooks/use-controllable-state.d.ts
83
+ interface UseControllableStateOptions<T> {
84
+ /** Valor controlado por la app. Si existe, manda sobre el estado interno. */
85
+ value?: T | undefined;
86
+ /** Valor inicial cuando el componente guarda su propio estado. */
87
+ defaultValue: T;
88
+ /** Se llama con el valor nuevo cuando cambia, en los dos modos. */
89
+ onChange?: ((value: T) => void) | undefined;
90
+ }
91
+ /**
92
+ * Estado controlado o no, a elección de la app (ADR-037). Con `value`, el componente
93
+ * solo avisa del cambio y espera a que la app le pase el valor nuevo; sin él, guarda
94
+ * el estado a partir de `defaultValue`. No avisa si el valor no cambia.
95
+ */
96
+ declare function useControllableState<T>({ value, defaultValue, onChange }: UseControllableStateOptions<T>): [T, (next: T) => void];
97
+ //#endregion
98
+ //#region src/types/text.d.ts
99
+ declare const textVariants: readonly ["hero", "display", "title", "heading", "subheading", "body", "bodySmall", "label", "caption", "code"];
100
+ type TextVariant = (typeof textVariants)[number];
101
+ type TextColorToken = keyof Theme['color']['text'];
102
+ declare const textColors: TextColorToken[];
103
+ declare const textAligns: readonly ["left", "center", "right"];
104
+ type TextAlign = (typeof textAligns)[number];
105
+ /** Tokens que componen una variante tipográfica; ambas vistas los leen del tema. */
106
+ interface TextVariantStyle {
107
+ family: keyof Theme['font']['family'];
108
+ /** Tamaño; `font.lineHeight` comparte las mismas claves (lo comprueba el typecheck de las vistas). */
109
+ size: keyof Theme['font']['size'];
110
+ weight: keyof Theme['font']['weight'];
111
+ uppercase?: boolean;
112
+ /** Espaciado entre letras en px. */
113
+ letterSpacing?: number;
114
+ /** Nivel de encabezado semántico (h1–h4) si la variante es un título. */
115
+ headingLevel?: 1 | 2 | 3 | 4;
116
+ }
117
+ /** Escala tipográfica de Satella: Unbounded para titulares, Hanken Grotesk para texto, IBM Plex Mono para etiquetas. */
118
+ declare const textVariantStyles: Readonly<Record<TextVariant, TextVariantStyle>>;
119
+ interface TextProps {
120
+ variant?: TextVariant | undefined;
121
+ color?: TextColorToken | undefined;
122
+ align?: TextAlign | undefined;
123
+ /** Una sola línea con puntos suspensivos. */
124
+ truncate?: boolean | undefined;
125
+ testID?: string | undefined;
126
+ children?: ReactNode;
127
+ }
128
+ //#endregion
129
+ //#region src/types/link.d.ts
130
+ /**
131
+ * Cuándo se subraya un `Link` (ADR-009). `always` es lo correcto dentro de un párrafo:
132
+ * el color solo no basta para distinguir un enlace. `hover` lo subraya al pasar el
133
+ * puntero o enfocarlo en web, y mientras se pulsa en nativo.
134
+ */
135
+ declare const linkUnderlines: readonly ["always", "hover"];
136
+ type LinkUnderline = (typeof linkUnderlines)[number];
137
+ /** Lo que recibe `onPress` de un `Link`: permite cancelar la navegación por defecto. */
138
+ interface LinkPressEvent {
139
+ /** Cancela la navegación por defecto, por ejemplo para hacerla con el router de la app. */
140
+ preventDefault: () => void;
141
+ readonly defaultPrevented: boolean;
142
+ }
143
+ interface LinkProps {
144
+ /** Destino. En web es el `href` del `<a>`; en nativo se abre con `Linking`. */
145
+ href: string;
146
+ /**
147
+ * Tipografía, con las mismas variantes que `Text`. Sin ella hereda la del texto
148
+ * que lo contiene; fuera de un `Text`, usa `body`.
149
+ */
150
+ variant?: TextVariant | undefined;
151
+ /** Color del texto (por defecto `link`). */
152
+ color?: TextColorToken | undefined;
153
+ underline?: LinkUnderline | undefined;
154
+ /**
155
+ * Se llama antes de navegar. Con `event.preventDefault()` la librería no navega y
156
+ * la app decide qué hacer. En web no se llama si el clic lleva un modificador
157
+ * (Ctrl, Cmd, Mayús, Alt) o no es del botón principal: ahí manda el navegador.
158
+ */
159
+ onPress?: ((event: LinkPressEvent) => void) | undefined;
160
+ /** Nombre accesible cuando el texto visible no basta para describir el destino. */
161
+ accessibilityLabel?: string | undefined;
162
+ /** `data-testid` en web, `testID` en nativo. */
163
+ testID?: string | undefined;
164
+ children: ReactNode;
165
+ }
166
+ //#endregion
167
+ //#region src/hooks/use-link.d.ts
168
+ interface UseLinkOptions {
169
+ onPress?: ((event: LinkPressEvent) => void) | undefined;
170
+ }
171
+ interface UseLinkResult {
172
+ /**
173
+ * Llama a `onPress` y devuelve `true` si la navegación por defecto debe seguir,
174
+ * es decir, si nadie llamó a `preventDefault()`.
175
+ */
176
+ press: () => boolean;
177
+ }
178
+ /**
179
+ * Comportamiento de `Link` común a web y nativo: `onPress` se llama antes de navegar
180
+ * y puede cancelar la navegación por defecto. Cada vista aporta esa navegación
181
+ * (el `<a>` en web, `Linking` en nativo).
182
+ */
183
+ declare function useLink({ onPress }: UseLinkOptions): UseLinkResult;
184
+ //#endregion
185
+ //#region src/types/ui-provider.d.ts
186
+ /** Modo de tema que acepta `UIProvider` (ADR-010). */
187
+ declare const themeModes: readonly ["light", "dark", "system"];
188
+ type ThemeMode = (typeof themeModes)[number];
189
+ /** Esquema resuelto: siempre uno de los temas definidos en tokens. */
190
+ type ColorScheme = ThemeName;
191
+ interface UIProviderProps {
192
+ /** Tema claro, oscuro o el del sistema (por defecto `system`). */
193
+ theme?: ThemeMode | undefined;
194
+ /** Marca registrada en tokens. Sin marca se usa la identidad por defecto. */
195
+ brand?: BrandName | undefined;
196
+ children: ReactNode;
197
+ }
198
+ //#endregion
199
+ //#region src/theme/context.d.ts
200
+ interface UIContextValue {
201
+ theme: Theme;
202
+ colorScheme: ColorScheme;
203
+ mode: ThemeMode;
204
+ brand: BrandName | undefined;
205
+ }
206
+ declare function createUIContextValue(mode: ThemeMode, systemScheme: ColorScheme, brand?: BrandName): UIContextValue;
207
+ /** Sin `UIProvider` se usa el tema claro por defecto, para que los componentes funcionen aislados. */
208
+ declare const UIContext: import("react").Context<UIContextValue>;
209
+ declare function useUIContext(): UIContextValue;
210
+ /** Tema resuelto (tokens con la marca aplicada). Imprescindible en las vistas nativas (ADR-008). */
211
+ declare function useTheme(): Theme;
212
+ /** `'light' | 'dark'` ya resuelto, también con `theme="system"`. */
213
+ declare function useColorScheme(): ColorScheme;
214
+ declare function useBrand(): BrandName | undefined;
215
+ //#endregion
216
+ //#region src/types/feedback.d.ts
217
+ /**
218
+ * Los cuatro significados de `color.feedback` en los tokens: éxito, aviso, error e
219
+ * información. Los comparten los componentes que comunican un estado.
220
+ */
221
+ declare const feedbackTones: readonly ["success", "warning", "danger", "info"];
222
+ type FeedbackTone = (typeof feedbackTones)[number];
223
+ declare function isFeedbackTone(value: string): value is FeedbackTone;
224
+ //#endregion
225
+ //#region src/types/toast.d.ts
226
+ /** Acción opcional de un toast, por ejemplo "Deshacer". */
227
+ interface ToastAction {
228
+ label: string;
229
+ onPress: () => void;
230
+ }
231
+ /** Lo que recibe `show()` de `useToast` (ADR-039). */
232
+ interface ToastOptions {
233
+ /** Qué comunica (por defecto `info`). Fija el icono y la urgencia con que se anuncia. */
234
+ tone?: FeedbackTone | undefined;
235
+ /** El mensaje, en una línea. */
236
+ title: string;
237
+ /** Detalle opcional. */
238
+ description?: string | undefined;
239
+ /**
240
+ * Milisegundos hasta que se cierra solo (por defecto 5000). Con `0` no se cierra
241
+ * solo: necesita `closeLabel`, una `action` o una llamada a `dismiss`.
242
+ */
243
+ duration?: number | undefined;
244
+ /** Botón de acción. Al pulsarlo se llama a `onPress` y el toast se cierra. */
245
+ action?: ToastAction | undefined;
246
+ /**
247
+ * Si existe, el toast muestra un botón de cierre con este nombre accesible
248
+ * ("Cerrar aviso"). La librería no trae textos propios.
249
+ */
250
+ closeLabel?: string | undefined;
251
+ }
252
+ /** Un toast ya en la cola: sus opciones resueltas y su identificador. */
253
+ interface ToastItem {
254
+ id: string;
255
+ tone: FeedbackTone;
256
+ title: string;
257
+ description: string | undefined;
258
+ duration: number;
259
+ action: ToastAction | undefined;
260
+ closeLabel: string | undefined;
261
+ }
262
+ /** Lo que devuelve `useToast`. */
263
+ interface ToastApi {
264
+ /** Muestra un toast y devuelve su `id`. */
265
+ show: (options: ToastOptions) => string;
266
+ /** Cierra el toast con ese `id`; sin `id`, los cierra todos. */
267
+ dismiss: (id?: string) => void;
268
+ }
269
+ //#endregion
270
+ //#region src/toast/store.d.ts
271
+ /** Milisegundos que un toast permanece visible si no se indica otra cosa. */
272
+ declare const TOAST_DEFAULT_DURATION = 5000;
273
+ /** Toasts visibles a la vez. Al llegar uno más, se retira el más antiguo. */
274
+ declare const TOAST_MAX_VISIBLE = 3;
275
+ interface ToastStore extends ToastApi {
276
+ /** Para `useSyncExternalStore`: avisa de cada cambio y devuelve cómo dejar de escuchar. */
277
+ subscribe: (listener: () => void) => () => void;
278
+ /** Los toasts visibles, del más antiguo al más reciente. Cambia de identidad con cada cambio. */
279
+ getSnapshot: () => readonly ToastItem[];
280
+ /** Detiene el cierre automático, por ejemplo mientras el puntero está sobre los toasts. */
281
+ pause: () => void;
282
+ /** Reanuda el cierre automático con el tiempo que le quedaba a cada toast. */
283
+ resume: () => void;
284
+ }
285
+ /**
286
+ * La cola de toasts de un `UIProvider` (ADR-039): alta, baja, cierre automático, pausa
287
+ * y límite de visibles. No depende de React ni de la plataforma.
288
+ */
289
+ declare function createToastStore(): ToastStore;
290
+ //#endregion
291
+ //#region src/toast/context.d.ts
292
+ /** La cola de toasts del `UIProvider` más cercano; `null` fuera de uno. */
293
+ declare const ToastContext: import("react").Context<ToastStore | null>;
294
+ /**
295
+ * Muestra y cierra toasts (ADR-039). Necesita un `UIProvider` por encima: es él quien
296
+ * guarda la cola y los pinta.
297
+ */
298
+ declare function useToast(): ToastApi;
299
+ //#endregion
300
+ export { useFormFieldControl as $, LinkUnderline as A, textVariants as B, UIProviderProps as C, useLink as D, UseLinkResult as E, TextVariant as F, useButton as G, useControllableState as H, TextVariantStyle as I, FormFieldControlOptions as J, FormFieldContext as K, textAligns as L, TextAlign as M, TextColorToken as N, LinkPressEvent as O, TextProps as P, resolveFormFieldControl as Q, textColors as R, ThemeMode as S, UseLinkOptions as T, UseButtonOptions as U, UseControllableStateOptions as V, UseButtonResult as W, FormFieldValueOptions as X, FormFieldValue as Y, createFormFieldValue as Z, useBrand as _, ToastStore as a, useUIContext as b, ToastApi as c, FeedbackTone as d, feedbackTones as f, createUIContextValue as g, UIContextValue as h, TOAST_MAX_VISIBLE as i, linkUnderlines as j, LinkProps as k, ToastItem as l, UIContext as m, useToast as n, createToastStore as o, isFeedbackTone as p, FormFieldControl as q, TOAST_DEFAULT_DURATION as r, ToastAction as s, ToastContext as t, ToastOptions as u, useColorScheme as v, themeModes as w, ColorScheme as x, useTheme as y, textVariantStyles as z };
@@ -0,0 +1,3 @@
1
+
2
+ import { $ as useFormFieldControl, D as useLink, E as UseLinkResult, G as useButton, H as useControllableState, J as FormFieldControlOptions, K as FormFieldContext, Q as resolveFormFieldControl, T as UseLinkOptions, U as UseButtonOptions, V as UseControllableStateOptions, W as UseButtonResult, X as FormFieldValueOptions, Y as FormFieldValue, Z as createFormFieldValue, _ as useBrand, b as useUIContext, g as createUIContextValue, h as UIContextValue, m as UIContext, n as useToast, q as FormFieldControl, t as ToastContext, v as useColorScheme, y as useTheme } from "./client-DpHlKasm.js";
3
+ export { FormFieldContext, FormFieldControl, FormFieldControlOptions, FormFieldValue, FormFieldValueOptions, ToastContext, UIContext, type UIContextValue, UseButtonOptions, UseButtonResult, UseControllableStateOptions, UseLinkOptions, UseLinkResult, createFormFieldValue, createUIContextValue, resolveFormFieldControl, useBrand, useButton, useColorScheme, useControllableState, useFormFieldControl, useLink, useTheme, useToast, useUIContext };
package/dist/client.js ADDED
@@ -0,0 +1,158 @@
1
+ 'use client';
2
+ import { n as resolveColorScheme, r as resolveTheme } from "./resolve-theme-DbIlzBBD.js";
3
+ import { createContext, useCallback, useContext, useState } from "react";
4
+ //#region src/form-field/context.ts
5
+ /** Un texto vacío cuenta como ausente: `error={errores.email}` suele llegar así. */
6
+ function present(text) {
7
+ return text === void 0 || text === "" ? void 0 : text;
8
+ }
9
+ /** Construye el valor del contexto a partir de un id base (el de `useId()`). */
10
+ function createFormFieldValue(baseId, { label, help, error, disabled = false, required = false }) {
11
+ const helpText = present(help);
12
+ const errorText = present(error);
13
+ return {
14
+ controlId: `${baseId}-control`,
15
+ helpId: helpText === void 0 ? void 0 : `${baseId}-help`,
16
+ errorId: errorText === void 0 ? void 0 : `${baseId}-error`,
17
+ label,
18
+ help: helpText,
19
+ error: errorText,
20
+ disabled,
21
+ required
22
+ };
23
+ }
24
+ /** `null` fuera de un `FormField`. */
25
+ const FormFieldContext = createContext(null);
26
+ /**
27
+ * Combina las props de un control con las de su `FormField`. Las del control
28
+ * mandan en `id` y nombre accesible; `invalid`, `disabled` y `required` se suman:
29
+ * basta con que lo diga uno de los dos.
30
+ */
31
+ function resolveFormFieldControl(field, { id, invalid = false, disabled = false, required = false, accessibilityLabel } = {}) {
32
+ if (field === null) return {
33
+ id,
34
+ describedBy: void 0,
35
+ accessibilityLabel,
36
+ accessibilityHint: void 0,
37
+ invalid,
38
+ disabled,
39
+ required
40
+ };
41
+ const describedBy = [field.helpId, field.errorId].filter((part) => part !== void 0).join(" ");
42
+ const hint = [field.error, field.help].filter((part) => part !== void 0).join(" ");
43
+ return {
44
+ id: id ?? field.controlId,
45
+ describedBy: describedBy === "" ? void 0 : describedBy,
46
+ accessibilityLabel: accessibilityLabel ?? field.label,
47
+ accessibilityHint: hint === "" ? void 0 : hint,
48
+ invalid: invalid || field.error !== void 0,
49
+ disabled: disabled || field.disabled,
50
+ required: required || field.required
51
+ };
52
+ }
53
+ /** Enlaza un control con el `FormField` que lo contiene, si lo hay. */
54
+ function useFormFieldControl(options) {
55
+ return resolveFormFieldControl(useContext(FormFieldContext), options);
56
+ }
57
+ //#endregion
58
+ //#region src/hooks/use-button.ts
59
+ /**
60
+ * Comportamiento de `Button` común a web y nativo: un botón deshabilitado o que
61
+ * está cargando no dispara `onPress`. Cada vista añade su accesibilidad
62
+ * (`disabled`/`aria-busy` en web, `accessibilityState` en nativo).
63
+ */
64
+ function useButton({ disabled = false, loading = false, onPress }) {
65
+ const interactive = !disabled && !loading;
66
+ return {
67
+ disabled,
68
+ loading,
69
+ interactive,
70
+ press: useCallback(() => {
71
+ if (interactive) onPress?.();
72
+ }, [interactive, onPress])
73
+ };
74
+ }
75
+ //#endregion
76
+ //#region src/hooks/use-controllable-state.ts
77
+ /**
78
+ * Estado controlado o no, a elección de la app (ADR-037). Con `value`, el componente
79
+ * solo avisa del cambio y espera a que la app le pase el valor nuevo; sin él, guarda
80
+ * el estado a partir de `defaultValue`. No avisa si el valor no cambia.
81
+ */
82
+ function useControllableState({ value, defaultValue, onChange }) {
83
+ const [internal, setInternal] = useState(defaultValue);
84
+ const controlled = value !== void 0;
85
+ const current = controlled ? value : internal;
86
+ return [current, useCallback((next) => {
87
+ if (Object.is(next, current)) return;
88
+ if (!controlled) setInternal(next);
89
+ onChange?.(next);
90
+ }, [
91
+ controlled,
92
+ current,
93
+ onChange
94
+ ])];
95
+ }
96
+ //#endregion
97
+ //#region src/hooks/use-link.ts
98
+ /**
99
+ * Comportamiento de `Link` común a web y nativo: `onPress` se llama antes de navegar
100
+ * y puede cancelar la navegación por defecto. Cada vista aporta esa navegación
101
+ * (el `<a>` en web, `Linking` en nativo).
102
+ */
103
+ function useLink({ onPress }) {
104
+ return { press: useCallback(() => {
105
+ let prevented = false;
106
+ onPress?.({
107
+ preventDefault: () => {
108
+ prevented = true;
109
+ },
110
+ get defaultPrevented() {
111
+ return prevented;
112
+ }
113
+ });
114
+ return !prevented;
115
+ }, [onPress]) };
116
+ }
117
+ //#endregion
118
+ //#region src/theme/context.ts
119
+ function createUIContextValue(mode, systemScheme, brand) {
120
+ const colorScheme = resolveColorScheme(mode, systemScheme);
121
+ return {
122
+ theme: resolveTheme(colorScheme, brand),
123
+ colorScheme,
124
+ mode,
125
+ brand
126
+ };
127
+ }
128
+ /** Sin `UIProvider` se usa el tema claro por defecto, para que los componentes funcionen aislados. */
129
+ const UIContext = createContext(createUIContextValue("light", "light"));
130
+ function useUIContext() {
131
+ return useContext(UIContext);
132
+ }
133
+ /** Tema resuelto (tokens con la marca aplicada). Imprescindible en las vistas nativas (ADR-008). */
134
+ function useTheme() {
135
+ return useContext(UIContext).theme;
136
+ }
137
+ /** `'light' | 'dark'` ya resuelto, también con `theme="system"`. */
138
+ function useColorScheme() {
139
+ return useContext(UIContext).colorScheme;
140
+ }
141
+ function useBrand() {
142
+ return useContext(UIContext).brand;
143
+ }
144
+ //#endregion
145
+ //#region src/toast/context.ts
146
+ /** La cola de toasts del `UIProvider` más cercano; `null` fuera de uno. */
147
+ const ToastContext = createContext(null);
148
+ /**
149
+ * Muestra y cierra toasts (ADR-039). Necesita un `UIProvider` por encima: es él quien
150
+ * guarda la cola y los pinta.
151
+ */
152
+ function useToast() {
153
+ const store = useContext(ToastContext);
154
+ if (store === null) throw new Error("useToast necesita un <UIProvider> por encima: es él quien pinta los toasts.");
155
+ return store;
156
+ }
157
+ //#endregion
158
+ export { FormFieldContext, ToastContext, UIContext, createFormFieldValue, createUIContextValue, resolveFormFieldControl, useBrand, useButton, useColorScheme, useControllableState, useFormFieldControl, useLink, useTheme, useToast, useUIContext };
package/dist/index.d.ts CHANGED
@@ -1,186 +1,7 @@
1
- import { ReactNode } from "react";
1
+
2
+ import { $ as useFormFieldControl, A as LinkUnderline, B as textVariants, C as UIProviderProps, D as useLink, E as UseLinkResult, F as TextVariant, G as useButton, H as useControllableState, I as TextVariantStyle, J as FormFieldControlOptions, K as FormFieldContext, L as textAligns, M as TextAlign, N as TextColorToken, O as LinkPressEvent, P as TextProps, Q as resolveFormFieldControl, R as textColors, S as ThemeMode, T as UseLinkOptions, U as UseButtonOptions, V as UseControllableStateOptions, W as UseButtonResult, X as FormFieldValueOptions, Y as FormFieldValue, Z as createFormFieldValue, _ as useBrand, a as ToastStore, b as useUIContext, c as ToastApi, d as FeedbackTone, f as feedbackTones, g as createUIContextValue, h as UIContextValue, i as TOAST_MAX_VISIBLE, j as linkUnderlines, k as LinkProps, l as ToastItem, m as UIContext, n as useToast, o as createToastStore, p as isFeedbackTone, q as FormFieldControl, r as TOAST_DEFAULT_DURATION, s as ToastAction, t as ToastContext, u as ToastOptions, v as useColorScheme, w as themeModes, x as ColorScheme, y as useTheme, z as textVariantStyles } from "./client-DpHlKasm.js";
2
3
  import { BrandName, Theme, ThemeName, ThemeOverrides } from "@satellatickets/tokens";
3
- //#region src/form-field/context.d.ts
4
- /** Lo que `FormField` publica para el control que envuelve (ADR-037). */
5
- export interface FormFieldValue {
6
- /** `id` del control. `FormField` lo usa en el `htmlFor` de su etiqueta (web). */
7
- controlId: string;
8
- /** `id` del texto de ayuda, si lo hay (web). */
9
- helpId: string | undefined;
10
- /** `id` del mensaje de error, si lo hay (web). */
11
- errorId: string | undefined;
12
- label: string;
13
- help: string | undefined;
14
- error: string | undefined;
15
- disabled: boolean;
16
- required: boolean;
17
- }
18
- export interface FormFieldValueOptions {
19
- label: string;
20
- help?: string | undefined;
21
- error?: string | undefined;
22
- disabled?: boolean | undefined;
23
- required?: boolean | undefined;
24
- }
25
- /** Construye el valor del contexto a partir de un id base (el de `useId()`). */
26
- export declare function createFormFieldValue(baseId: string, { label, help, error, disabled, required }: FormFieldValueOptions): FormFieldValue;
27
- /** `null` fuera de un `FormField`. */
28
- export declare const FormFieldContext: import("react").Context<FormFieldValue | null>;
29
- /** Las props propias de un control que se combinan con las de su `FormField`. */
30
- export interface FormFieldControlOptions {
31
- id?: string | undefined;
32
- invalid?: boolean | undefined;
33
- disabled?: boolean | undefined;
34
- required?: boolean | undefined;
35
- accessibilityLabel?: string | undefined;
36
- }
37
- /** Lo que un control necesita para enlazarse con su `FormField`, en web y en nativo. */
38
- export interface FormFieldControl {
39
- /** `id` del control (web): el propio o el que espera la etiqueta del campo. */
40
- id: string | undefined;
41
- /** Ids de la ayuda y el error para `aria-describedby` (web). */
42
- describedBy: string | undefined;
43
- /** Nombre accesible (nativo): el propio o la etiqueta del campo. */
44
- accessibilityLabel: string | undefined;
45
- /** El error y la ayuda, uno tras otro, para `accessibilityHint` (nativo). */
46
- accessibilityHint: string | undefined;
47
- invalid: boolean;
48
- disabled: boolean;
49
- required: boolean;
50
- }
51
- /**
52
- * Combina las props de un control con las de su `FormField`. Las del control
53
- * mandan en `id` y nombre accesible; `invalid`, `disabled` y `required` se suman:
54
- * basta con que lo diga uno de los dos.
55
- */
56
- export declare function resolveFormFieldControl(field: FormFieldValue | null, { id, invalid, disabled, required, accessibilityLabel }?: FormFieldControlOptions): FormFieldControl;
57
- /** Enlaza un control con el `FormField` que lo contiene, si lo hay. */
58
- export declare function useFormFieldControl(options?: FormFieldControlOptions): FormFieldControl;
59
- //#endregion
60
- //#region src/hooks/use-button.d.ts
61
- export interface UseButtonOptions {
62
- disabled?: boolean | undefined;
63
- loading?: boolean | undefined;
64
- onPress?: (() => void) | undefined;
65
- }
66
- export interface UseButtonResult {
67
- disabled: boolean;
68
- loading: boolean;
69
- /** `false` si está deshabilitado o cargando: el botón no responde a la pulsación. */
70
- interactive: boolean;
71
- /** Llama a `onPress` solo si el botón es interactivo. */
72
- press: () => void;
73
- }
74
- /**
75
- * Comportamiento de `Button` común a web y nativo: un botón deshabilitado o que
76
- * está cargando no dispara `onPress`. Cada vista añade su accesibilidad
77
- * (`disabled`/`aria-busy` en web, `accessibilityState` en nativo).
78
- */
79
- export declare function useButton({ disabled, loading, onPress }: UseButtonOptions): UseButtonResult;
80
- //#endregion
81
- //#region src/hooks/use-controllable-state.d.ts
82
- export interface UseControllableStateOptions<T> {
83
- /** Valor controlado por la app. Si existe, manda sobre el estado interno. */
84
- value?: T | undefined;
85
- /** Valor inicial cuando el componente guarda su propio estado. */
86
- defaultValue: T;
87
- /** Se llama con el valor nuevo cuando cambia, en los dos modos. */
88
- onChange?: ((value: T) => void) | undefined;
89
- }
90
- /**
91
- * Estado controlado o no, a elección de la app (ADR-037). Con `value`, el componente
92
- * solo avisa del cambio y espera a que la app le pase el valor nuevo; sin él, guarda
93
- * el estado a partir de `defaultValue`. No avisa si el valor no cambia.
94
- */
95
- export declare function useControllableState<T>({ value, defaultValue, onChange }: UseControllableStateOptions<T>): [T, (next: T) => void];
96
- //#endregion
97
- //#region src/types/text.d.ts
98
- export declare const textVariants: readonly ["hero", "display", "title", "heading", "subheading", "body", "bodySmall", "label", "caption", "code"];
99
- export type TextVariant = (typeof textVariants)[number];
100
- export type TextColorToken = keyof Theme['color']['text'];
101
- export declare const textColors: TextColorToken[];
102
- export declare const textAligns: readonly ["left", "center", "right"];
103
- export type TextAlign = (typeof textAligns)[number];
104
- /** Tokens que componen una variante tipográfica; ambas vistas los leen del tema. */
105
- export interface TextVariantStyle {
106
- family: keyof Theme['font']['family'];
107
- /** Tamaño; `font.lineHeight` comparte las mismas claves (lo comprueba el typecheck de las vistas). */
108
- size: keyof Theme['font']['size'];
109
- weight: keyof Theme['font']['weight'];
110
- uppercase?: boolean;
111
- /** Espaciado entre letras en px. */
112
- letterSpacing?: number;
113
- /** Nivel de encabezado semántico (h1–h4) si la variante es un título. */
114
- headingLevel?: 1 | 2 | 3 | 4;
115
- }
116
- /** Escala tipográfica de Satella: Unbounded para titulares, Hanken Grotesk para texto, IBM Plex Mono para etiquetas. */
117
- export declare const textVariantStyles: Readonly<Record<TextVariant, TextVariantStyle>>;
118
- export interface TextProps {
119
- variant?: TextVariant | undefined;
120
- color?: TextColorToken | undefined;
121
- align?: TextAlign | undefined;
122
- /** Una sola línea con puntos suspensivos. */
123
- truncate?: boolean | undefined;
124
- testID?: string | undefined;
125
- children?: ReactNode;
126
- }
127
- //#endregion
128
- //#region src/types/link.d.ts
129
- /**
130
- * Cuándo se subraya un `Link` (ADR-009). `always` es lo correcto dentro de un párrafo:
131
- * el color solo no basta para distinguir un enlace. `hover` lo subraya al pasar el
132
- * puntero o enfocarlo en web, y mientras se pulsa en nativo.
133
- */
134
- export declare const linkUnderlines: readonly ["always", "hover"];
135
- export type LinkUnderline = (typeof linkUnderlines)[number];
136
- /** Lo que recibe `onPress` de un `Link`: permite cancelar la navegación por defecto. */
137
- export interface LinkPressEvent {
138
- /** Cancela la navegación por defecto, por ejemplo para hacerla con el router de la app. */
139
- preventDefault: () => void;
140
- readonly defaultPrevented: boolean;
141
- }
142
- export interface LinkProps {
143
- /** Destino. En web es el `href` del `<a>`; en nativo se abre con `Linking`. */
144
- href: string;
145
- /**
146
- * Tipografía, con las mismas variantes que `Text`. Sin ella hereda la del texto
147
- * que lo contiene; fuera de un `Text`, usa `body`.
148
- */
149
- variant?: TextVariant | undefined;
150
- /** Color del texto (por defecto `link`). */
151
- color?: TextColorToken | undefined;
152
- underline?: LinkUnderline | undefined;
153
- /**
154
- * Se llama antes de navegar. Con `event.preventDefault()` la librería no navega y
155
- * la app decide qué hacer. En web no se llama si el clic lleva un modificador
156
- * (Ctrl, Cmd, Mayús, Alt) o no es del botón principal: ahí manda el navegador.
157
- */
158
- onPress?: ((event: LinkPressEvent) => void) | undefined;
159
- /** Nombre accesible cuando el texto visible no basta para describir el destino. */
160
- accessibilityLabel?: string | undefined;
161
- /** `data-testid` en web, `testID` en nativo. */
162
- testID?: string | undefined;
163
- children: ReactNode;
164
- }
165
- //#endregion
166
- //#region src/hooks/use-link.d.ts
167
- export interface UseLinkOptions {
168
- onPress?: ((event: LinkPressEvent) => void) | undefined;
169
- }
170
- export interface UseLinkResult {
171
- /**
172
- * Llama a `onPress` y devuelve `true` si la navegación por defecto debe seguir,
173
- * es decir, si nadie llamó a `preventDefault()`.
174
- */
175
- press: () => boolean;
176
- }
177
- /**
178
- * Comportamiento de `Link` común a web y nativo: `onPress` se llama antes de navegar
179
- * y puede cancelar la navegación por defecto. Cada vista aporta esa navegación
180
- * (el `<a>` en web, `Linking` en nativo).
181
- */
182
- export declare function useLink({ onPress }: UseLinkOptions): UseLinkResult;
183
- //#endregion
4
+ import { ReactNode } from "react";
184
5
  //#region src/tabs/navigation.d.ts
185
6
  /** Lo mínimo que hace falta saber de una pestaña para moverse entre ellas. */
186
7
  export interface NavigableTab {
@@ -199,37 +20,6 @@ export declare function firstEnabledTab(items: readonly NavigableTab[]): string
199
20
  */
200
21
  export declare function getTabInDirection(items: readonly NavigableTab[], current: string, direction: TabDirection): string;
201
22
  //#endregion
202
- //#region src/types/ui-provider.d.ts
203
- /** Modo de tema que acepta `UIProvider` (ADR-010). */
204
- export declare const themeModes: readonly ["light", "dark", "system"];
205
- export type ThemeMode = (typeof themeModes)[number];
206
- /** Esquema resuelto: siempre uno de los temas definidos en tokens. */
207
- export type ColorScheme = ThemeName;
208
- export interface UIProviderProps {
209
- /** Tema claro, oscuro o el del sistema (por defecto `system`). */
210
- theme?: ThemeMode | undefined;
211
- /** Marca registrada en tokens. Sin marca se usa la identidad por defecto. */
212
- brand?: BrandName | undefined;
213
- children: ReactNode;
214
- }
215
- //#endregion
216
- //#region src/theme/context.d.ts
217
- interface UIContextValue {
218
- theme: Theme;
219
- colorScheme: ColorScheme;
220
- mode: ThemeMode;
221
- brand: BrandName | undefined;
222
- }
223
- export declare function createUIContextValue(mode: ThemeMode, systemScheme: ColorScheme, brand?: BrandName): UIContextValue;
224
- /** Sin `UIProvider` se usa el tema claro por defecto, para que los componentes funcionen aislados. */
225
- export declare const UIContext: import("react").Context<UIContextValue>;
226
- export declare function useUIContext(): UIContextValue;
227
- /** Tema resuelto (tokens con la marca aplicada). Imprescindible en las vistas nativas (ADR-008). */
228
- export declare function useTheme(): Theme;
229
- /** `'light' | 'dark'` ya resuelto, también con `theme="system"`. */
230
- export declare function useColorScheme(): ColorScheme;
231
- export declare function useBrand(): BrandName | undefined;
232
- //#endregion
233
23
  //#region src/theme/resolve-theme.d.ts
234
24
  /** Combina los overrides de una marca sobre un tema (ADR-010). Los arrays (sombras) se sustituyen enteros. */
235
25
  export declare function mergeTheme(base: Theme, overrides: ThemeOverrides): Theme;
@@ -237,90 +27,6 @@ export declare function mergeTheme(base: Theme, overrides: ThemeOverrides): Them
237
27
  export declare function resolveTheme(scheme: ThemeName, brand?: BrandName): Theme;
238
28
  export declare function resolveColorScheme(mode: ThemeMode, systemScheme: ColorScheme): ColorScheme;
239
29
  //#endregion
240
- //#region src/types/feedback.d.ts
241
- /**
242
- * Los cuatro significados de `color.feedback` en los tokens: éxito, aviso, error e
243
- * información. Los comparten los componentes que comunican un estado.
244
- */
245
- export declare const feedbackTones: readonly ["success", "warning", "danger", "info"];
246
- export type FeedbackTone = (typeof feedbackTones)[number];
247
- export declare function isFeedbackTone(value: string): value is FeedbackTone;
248
- //#endregion
249
- //#region src/types/toast.d.ts
250
- /** Acción opcional de un toast, por ejemplo "Deshacer". */
251
- export interface ToastAction {
252
- label: string;
253
- onPress: () => void;
254
- }
255
- /** Lo que recibe `show()` de `useToast` (ADR-039). */
256
- export interface ToastOptions {
257
- /** Qué comunica (por defecto `info`). Fija el icono y la urgencia con que se anuncia. */
258
- tone?: FeedbackTone | undefined;
259
- /** El mensaje, en una línea. */
260
- title: string;
261
- /** Detalle opcional. */
262
- description?: string | undefined;
263
- /**
264
- * Milisegundos hasta que se cierra solo (por defecto 5000). Con `0` no se cierra
265
- * solo: necesita `closeLabel`, una `action` o una llamada a `dismiss`.
266
- */
267
- duration?: number | undefined;
268
- /** Botón de acción. Al pulsarlo se llama a `onPress` y el toast se cierra. */
269
- action?: ToastAction | undefined;
270
- /**
271
- * Si existe, el toast muestra un botón de cierre con este nombre accesible
272
- * ("Cerrar aviso"). La librería no trae textos propios.
273
- */
274
- closeLabel?: string | undefined;
275
- }
276
- /** Un toast ya en la cola: sus opciones resueltas y su identificador. */
277
- export interface ToastItem {
278
- id: string;
279
- tone: FeedbackTone;
280
- title: string;
281
- description: string | undefined;
282
- duration: number;
283
- action: ToastAction | undefined;
284
- closeLabel: string | undefined;
285
- }
286
- /** Lo que devuelve `useToast`. */
287
- export interface ToastApi {
288
- /** Muestra un toast y devuelve su `id`. */
289
- show: (options: ToastOptions) => string;
290
- /** Cierra el toast con ese `id`; sin `id`, los cierra todos. */
291
- dismiss: (id?: string) => void;
292
- }
293
- //#endregion
294
- //#region src/toast/store.d.ts
295
- /** Milisegundos que un toast permanece visible si no se indica otra cosa. */
296
- export declare const TOAST_DEFAULT_DURATION = 5000;
297
- /** Toasts visibles a la vez. Al llegar uno más, se retira el más antiguo. */
298
- export declare const TOAST_MAX_VISIBLE = 3;
299
- interface ToastStore extends ToastApi {
300
- /** Para `useSyncExternalStore`: avisa de cada cambio y devuelve cómo dejar de escuchar. */
301
- subscribe: (listener: () => void) => () => void;
302
- /** Los toasts visibles, del más antiguo al más reciente. Cambia de identidad con cada cambio. */
303
- getSnapshot: () => readonly ToastItem[];
304
- /** Detiene el cierre automático, por ejemplo mientras el puntero está sobre los toasts. */
305
- pause: () => void;
306
- /** Reanuda el cierre automático con el tiempo que le quedaba a cada toast. */
307
- resume: () => void;
308
- }
309
- /**
310
- * La cola de toasts de un `UIProvider` (ADR-039): alta, baja, cierre automático, pausa
311
- * y límite de visibles. No depende de React ni de la plataforma.
312
- */
313
- export declare function createToastStore(): ToastStore;
314
- //#endregion
315
- //#region src/toast/context.d.ts
316
- /** La cola de toasts del `UIProvider` más cercano; `null` fuera de uno. */
317
- export declare const ToastContext: import("react").Context<ToastStore | null>;
318
- /**
319
- * Muestra y cierra toasts (ADR-039). Necesita un `UIProvider` por encima: es él quien
320
- * guarda la cola y los pinta.
321
- */
322
- export declare function useToast(): ToastApi;
323
- //#endregion
324
30
  //#region src/types/alert.d.ts
325
31
  interface AlertBaseProps {
326
32
  /** Qué comunica (por defecto `info`). Fija el color y el icono. */
@@ -884,4 +590,4 @@ export interface TextAreaProps {
884
590
  testID?: string | undefined;
885
591
  }
886
592
  //#endregion
887
- export type { ToastStore, UIContextValue };
593
+ export { ColorScheme, FeedbackTone, FormFieldContext, FormFieldControl, FormFieldControlOptions, FormFieldValue, FormFieldValueOptions, LinkPressEvent, LinkProps, LinkUnderline, TOAST_DEFAULT_DURATION, TOAST_MAX_VISIBLE, TextAlign, TextColorToken, TextProps, TextVariant, TextVariantStyle, ThemeMode, ToastAction, ToastApi, ToastContext, ToastItem, ToastOptions, type ToastStore, UIContext, type UIContextValue, UIProviderProps, UseButtonOptions, UseButtonResult, UseControllableStateOptions, UseLinkOptions, UseLinkResult, createFormFieldValue, createToastStore, createUIContextValue, feedbackTones, isFeedbackTone, linkUnderlines, resolveFormFieldControl, textAligns, textColors, textVariantStyles, textVariants, themeModes, useBrand, useButton, useColorScheme, useControllableState, useFormFieldControl, useLink, useTheme, useToast, useUIContext };
package/dist/index.js CHANGED
@@ -1,119 +1,7 @@
1
- import { createContext, useCallback, useContext, useState } from "react";
2
- import { brands, themes } from "@satellatickets/tokens";
3
- //#region src/form-field/context.ts
4
- /** Un texto vacío cuenta como ausente: `error={errores.email}` suele llegar así. */
5
- function present(text) {
6
- return text === void 0 || text === "" ? void 0 : text;
7
- }
8
- /** Construye el valor del contexto a partir de un id base (el de `useId()`). */
9
- function createFormFieldValue(baseId, { label, help, error, disabled = false, required = false }) {
10
- const helpText = present(help);
11
- const errorText = present(error);
12
- return {
13
- controlId: `${baseId}-control`,
14
- helpId: helpText === void 0 ? void 0 : `${baseId}-help`,
15
- errorId: errorText === void 0 ? void 0 : `${baseId}-error`,
16
- label,
17
- help: helpText,
18
- error: errorText,
19
- disabled,
20
- required
21
- };
22
- }
23
- /** `null` fuera de un `FormField`. */
24
- const FormFieldContext = createContext(null);
25
- /**
26
- * Combina las props de un control con las de su `FormField`. Las del control
27
- * mandan en `id` y nombre accesible; `invalid`, `disabled` y `required` se suman:
28
- * basta con que lo diga uno de los dos.
29
- */
30
- function resolveFormFieldControl(field, { id, invalid = false, disabled = false, required = false, accessibilityLabel } = {}) {
31
- if (field === null) return {
32
- id,
33
- describedBy: void 0,
34
- accessibilityLabel,
35
- accessibilityHint: void 0,
36
- invalid,
37
- disabled,
38
- required
39
- };
40
- const describedBy = [field.helpId, field.errorId].filter((part) => part !== void 0).join(" ");
41
- const hint = [field.error, field.help].filter((part) => part !== void 0).join(" ");
42
- return {
43
- id: id ?? field.controlId,
44
- describedBy: describedBy === "" ? void 0 : describedBy,
45
- accessibilityLabel: accessibilityLabel ?? field.label,
46
- accessibilityHint: hint === "" ? void 0 : hint,
47
- invalid: invalid || field.error !== void 0,
48
- disabled: disabled || field.disabled,
49
- required: required || field.required
50
- };
51
- }
52
- /** Enlaza un control con el `FormField` que lo contiene, si lo hay. */
53
- function useFormFieldControl(options) {
54
- return resolveFormFieldControl(useContext(FormFieldContext), options);
55
- }
56
- //#endregion
57
- //#region src/hooks/use-button.ts
58
- /**
59
- * Comportamiento de `Button` común a web y nativo: un botón deshabilitado o que
60
- * está cargando no dispara `onPress`. Cada vista añade su accesibilidad
61
- * (`disabled`/`aria-busy` en web, `accessibilityState` en nativo).
62
- */
63
- function useButton({ disabled = false, loading = false, onPress }) {
64
- const interactive = !disabled && !loading;
65
- return {
66
- disabled,
67
- loading,
68
- interactive,
69
- press: useCallback(() => {
70
- if (interactive) onPress?.();
71
- }, [interactive, onPress])
72
- };
73
- }
74
- //#endregion
75
- //#region src/hooks/use-controllable-state.ts
76
- /**
77
- * Estado controlado o no, a elección de la app (ADR-037). Con `value`, el componente
78
- * solo avisa del cambio y espera a que la app le pase el valor nuevo; sin él, guarda
79
- * el estado a partir de `defaultValue`. No avisa si el valor no cambia.
80
- */
81
- function useControllableState({ value, defaultValue, onChange }) {
82
- const [internal, setInternal] = useState(defaultValue);
83
- const controlled = value !== void 0;
84
- const current = controlled ? value : internal;
85
- return [current, useCallback((next) => {
86
- if (Object.is(next, current)) return;
87
- if (!controlled) setInternal(next);
88
- onChange?.(next);
89
- }, [
90
- controlled,
91
- current,
92
- onChange
93
- ])];
94
- }
95
- //#endregion
96
- //#region src/hooks/use-link.ts
97
- /**
98
- * Comportamiento de `Link` común a web y nativo: `onPress` se llama antes de navegar
99
- * y puede cancelar la navegación por defecto. Cada vista aporta esa navegación
100
- * (el `<a>` en web, `Linking` en nativo).
101
- */
102
- function useLink({ onPress }) {
103
- return { press: useCallback(() => {
104
- let prevented = false;
105
- onPress?.({
106
- preventDefault: () => {
107
- prevented = true;
108
- },
109
- get defaultPrevented() {
110
- return prevented;
111
- }
112
- });
113
- return !prevented;
114
- }, [onPress]) };
115
- }
116
- //#endregion
1
+
2
+ import { n as resolveColorScheme, r as resolveTheme, t as mergeTheme } from "./resolve-theme-DbIlzBBD.js";
3
+ import { themes } from "@satellatickets/tokens";
4
+ export * from "./client.js";
117
5
  //#region src/tabs/navigation.ts
118
6
  /** Hacia dónde mueve el foco una tecla: flechas, Inicio y Fin. */
119
7
  const tabDirections = [
@@ -144,79 +32,6 @@ function getTabInDirection(items, current, direction) {
144
32
  return enabled[(index + (direction === "next" ? 1 : -1) + enabled.length) % enabled.length] ?? current;
145
33
  }
146
34
  //#endregion
147
- //#region src/theme/resolve-theme.ts
148
- function isPlainObject(value) {
149
- return typeof value === "object" && value !== null && !Array.isArray(value);
150
- }
151
- function merge(base, overrides) {
152
- const result = { ...base };
153
- for (const [key, value] of Object.entries(overrides)) {
154
- if (value === void 0) continue;
155
- const current = base[key];
156
- result[key] = isPlainObject(current) && isPlainObject(value) ? merge(current, value) : value;
157
- }
158
- return result;
159
- }
160
- /** Combina los overrides de una marca sobre un tema (ADR-010). Los arrays (sombras) se sustituyen enteros. */
161
- function mergeTheme(base, overrides) {
162
- return merge(base, overrides);
163
- }
164
- const cache = /* @__PURE__ */ new Map();
165
- /** Tema resuelto para un esquema y una marca; memoizado para que la identidad sea estable. */
166
- function resolveTheme(scheme, brand) {
167
- const key = `${scheme}/${brand ?? ""}`;
168
- const cached = cache.get(key);
169
- if (cached) return cached;
170
- const base = themes[scheme];
171
- const overrides = brand === void 0 ? void 0 : brands[brand]?.[scheme];
172
- const resolved = overrides === void 0 ? base : mergeTheme(base, overrides);
173
- cache.set(key, resolved);
174
- return resolved;
175
- }
176
- function resolveColorScheme(mode, systemScheme) {
177
- return mode === "system" ? systemScheme : mode;
178
- }
179
- //#endregion
180
- //#region src/theme/context.ts
181
- function createUIContextValue(mode, systemScheme, brand) {
182
- const colorScheme = resolveColorScheme(mode, systemScheme);
183
- return {
184
- theme: resolveTheme(colorScheme, brand),
185
- colorScheme,
186
- mode,
187
- brand
188
- };
189
- }
190
- /** Sin `UIProvider` se usa el tema claro por defecto, para que los componentes funcionen aislados. */
191
- const UIContext = createContext(createUIContextValue("light", "light"));
192
- function useUIContext() {
193
- return useContext(UIContext);
194
- }
195
- /** Tema resuelto (tokens con la marca aplicada). Imprescindible en las vistas nativas (ADR-008). */
196
- function useTheme() {
197
- return useContext(UIContext).theme;
198
- }
199
- /** `'light' | 'dark'` ya resuelto, también con `theme="system"`. */
200
- function useColorScheme() {
201
- return useContext(UIContext).colorScheme;
202
- }
203
- function useBrand() {
204
- return useContext(UIContext).brand;
205
- }
206
- //#endregion
207
- //#region src/toast/context.ts
208
- /** La cola de toasts del `UIProvider` más cercano; `null` fuera de uno. */
209
- const ToastContext = createContext(null);
210
- /**
211
- * Muestra y cierra toasts (ADR-039). Necesita un `UIProvider` por encima: es él quien
212
- * guarda la cola y los pinta.
213
- */
214
- function useToast() {
215
- const store = useContext(ToastContext);
216
- if (store === null) throw new Error("useToast necesita un <UIProvider> por encima: es él quien pinta los toasts.");
217
- return store;
218
- }
219
- //#endregion
220
35
  //#region src/toast/store.ts
221
36
  /** Milisegundos que un toast permanece visible si no se indica otra cosa. */
222
37
  const TOAST_DEFAULT_DURATION = 5e3;
@@ -573,4 +388,4 @@ const themeModes = [
573
388
  "system"
574
389
  ];
575
390
  //#endregion
576
- export { FormFieldContext, TOAST_DEFAULT_DURATION, TOAST_MAX_VISIBLE, ToastContext, UIContext, backgroundTokens, badgeVariants, borderColorTokens, buttonIconSize, buttonVariants, cardVariants, controlSizes, createFormFieldValue, createToastStore, createUIContextValue, dividerOrientations, feedbackTones, firstEnabledTab, getTabInDirection, iconButtonIconSize, iconSizePx, iconSizes, inputIconSize, inputTypes, isFeedbackTone, linkUnderlines, mergeTheme, modalPresentations, radiusTokens, resolveColorScheme, resolveFormFieldControl, resolveTheme, shadowTokens, skeletonShapes, spaceTokens, spinnerSizePx, spinnerSizes, stackAligns, stackDirections, stackJustifies, tabDirections, textAligns, textColors, textVariantStyles, textVariants, themeModes, useBrand, useButton, useColorScheme, useControllableState, useFormFieldControl, useLink, useTheme, useToast, useUIContext };
391
+ export { TOAST_DEFAULT_DURATION, TOAST_MAX_VISIBLE, backgroundTokens, badgeVariants, borderColorTokens, buttonIconSize, buttonVariants, cardVariants, controlSizes, createToastStore, dividerOrientations, feedbackTones, firstEnabledTab, getTabInDirection, iconButtonIconSize, iconSizePx, iconSizes, inputIconSize, inputTypes, isFeedbackTone, linkUnderlines, mergeTheme, modalPresentations, radiusTokens, resolveColorScheme, resolveTheme, shadowTokens, skeletonShapes, spaceTokens, spinnerSizePx, spinnerSizes, stackAligns, stackDirections, stackJustifies, tabDirections, textAligns, textColors, textVariantStyles, textVariants, themeModes };
@@ -0,0 +1,36 @@
1
+
2
+ import { brands, themes } from "@satellatickets/tokens";
3
+ //#region src/theme/resolve-theme.ts
4
+ function isPlainObject(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ function merge(base, overrides) {
8
+ const result = { ...base };
9
+ for (const [key, value] of Object.entries(overrides)) {
10
+ if (value === void 0) continue;
11
+ const current = base[key];
12
+ result[key] = isPlainObject(current) && isPlainObject(value) ? merge(current, value) : value;
13
+ }
14
+ return result;
15
+ }
16
+ /** Combina los overrides de una marca sobre un tema (ADR-010). Los arrays (sombras) se sustituyen enteros. */
17
+ function mergeTheme(base, overrides) {
18
+ return merge(base, overrides);
19
+ }
20
+ const cache = /* @__PURE__ */ new Map();
21
+ /** Tema resuelto para un esquema y una marca; memoizado para que la identidad sea estable. */
22
+ function resolveTheme(scheme, brand) {
23
+ const key = `${scheme}/${brand ?? ""}`;
24
+ const cached = cache.get(key);
25
+ if (cached) return cached;
26
+ const base = themes[scheme];
27
+ const overrides = brand === void 0 ? void 0 : brands[brand]?.[scheme];
28
+ const resolved = overrides === void 0 ? base : mergeTheme(base, overrides);
29
+ cache.set(key, resolved);
30
+ return resolved;
31
+ }
32
+ function resolveColorScheme(mode, systemScheme) {
33
+ return mode === "system" ? systemScheme : mode;
34
+ }
35
+ //#endregion
36
+ export { resolveColorScheme as n, resolveTheme as r, mergeTheme as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@satellatickets/core",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Lógica headless de satella-ui: contratos de props, constantes de variantes, contexto de tema y hooks compartidos por React y React Native",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,8 +40,8 @@
40
40
  "tsdown": "^0.23.0",
41
41
  "typescript": "^5.9.3",
42
42
  "vitest": "^5.0.3",
43
- "@satellatickets/tsconfig": "0.0.0",
44
- "@satellatickets/eslint-config": "0.0.0"
43
+ "@satellatickets/eslint-config": "0.0.0",
44
+ "@satellatickets/tsconfig": "0.0.0"
45
45
  },
46
46
  "scripts": {
47
47
  "build": "tsdown",