@kaufmann-lab/ui 0.1.16 → 0.1.18
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/README.md +1 -1
- package/fesm2022/kaufmann-lab-ui-auth.mjs +322 -0
- package/fesm2022/kaufmann-lab-ui-auth.mjs.map +1 -0
- package/fesm2022/kaufmann-lab-ui-business.mjs +453 -0
- package/fesm2022/kaufmann-lab-ui-business.mjs.map +1 -0
- package/fesm2022/kaufmann-lab-ui-core.mjs +5906 -0
- package/fesm2022/kaufmann-lab-ui-core.mjs.map +1 -0
- package/fesm2022/kaufmann-lab-ui-map.mjs +1637 -0
- package/fesm2022/kaufmann-lab-ui-map.mjs.map +1 -0
- package/fesm2022/kaufmann-lab-ui-mobile.mjs +92 -0
- package/fesm2022/kaufmann-lab-ui-mobile.mjs.map +1 -0
- package/fesm2022/kaufmann-lab-ui-shared.mjs +269 -0
- package/fesm2022/kaufmann-lab-ui-shared.mjs.map +1 -0
- package/fesm2022/kaufmann-lab-ui.mjs +23 -8448
- package/fesm2022/kaufmann-lab-ui.mjs.map +1 -1
- package/kaufmann-lab-ui-0.1.18.tgz +0 -0
- package/package.json +25 -1
- package/types/kaufmann-lab-ui-auth.d.ts +240 -0
- package/types/kaufmann-lab-ui-business.d.ts +298 -0
- package/types/kaufmann-lab-ui-core.d.ts +3288 -0
- package/types/kaufmann-lab-ui-map.d.ts +631 -0
- package/types/kaufmann-lab-ui-mobile.d.ts +53 -0
- package/types/kaufmann-lab-ui-shared.d.ts +154 -0
- package/types/kaufmann-lab-ui.d.ts +5 -4620
- package/fesm2022/mapbox-gl-csp.worker.js +0 -6
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { signal, inject, DestroyRef } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Separador de miles genérico — sin signo, sin asumir que el valor es
|
|
5
|
+
* dinero. Cubre casos como kilometraje/horómetro (`formatNumber(45231,
|
|
6
|
+
* { locale: 'en-US' })` → `'45,231'`), no solo moneda.
|
|
7
|
+
*
|
|
8
|
+
* formatNumber(16800); // '16.800'
|
|
9
|
+
* formatNumber(16800.5, { decimals: 2 }); // '16.800,50'
|
|
10
|
+
* formatNumber(45231, { locale: 'en-US' }); // '45,231'
|
|
11
|
+
*/
|
|
12
|
+
function formatNumber(value, options = {}) {
|
|
13
|
+
const { decimals = 0, locale = 'es' } = options;
|
|
14
|
+
return value.toLocaleString(locale, {
|
|
15
|
+
minimumFractionDigits: decimals,
|
|
16
|
+
maximumFractionDigits: decimals,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Formatea un monto de dinero — compone sobre `formatNumber` agregando el
|
|
21
|
+
* signo. Mismos defaults que la convención de OTD (punto de miles, sin
|
|
22
|
+
* decimales, prefijo `$`), overrideables para otra moneda/país/formato:
|
|
23
|
+
*
|
|
24
|
+
* formatCurrency(16800); // '$16.800'
|
|
25
|
+
* formatCurrency(16800.5, { decimals: 2 }); // '$16.800,50'
|
|
26
|
+
* formatCurrency(16800, { sign: '' }); // '16.800'
|
|
27
|
+
* formatCurrency(16800, { sign: 'US$', locale: 'en-US' }); // 'US$16,800'
|
|
28
|
+
*/
|
|
29
|
+
function formatCurrency(amount, options = {}) {
|
|
30
|
+
const { sign = '$', ...numberOptions } = options;
|
|
31
|
+
return `${sign}${formatNumber(amount, numberOptions)}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Inverso de `formatNumber` — recupera el número crudo de un valor ya
|
|
35
|
+
* formateado con el mismo `locale`. Solo soporta `locale` cuyo separador de
|
|
36
|
+
* miles y decimal sean `'.'`/`','` (`es`) o `','`/`'.'` (`en-US`) — son los
|
|
37
|
+
* 2 casos reales del ecosistema hoy; si aparece un tercer locale con otros
|
|
38
|
+
* separadores, extender acá antes de usarlo.
|
|
39
|
+
*/
|
|
40
|
+
function parseNumber(formatted, locale = 'es') {
|
|
41
|
+
const [thousands, decimal] = locale === 'en-US' ? [',', '.'] : ['.', ','];
|
|
42
|
+
const withoutThousands = formatted.split(thousands).join('');
|
|
43
|
+
const withDecimalPoint = withoutThousands.replace(decimal, '.');
|
|
44
|
+
return Number(withDecimalPoint.replace(/[^0-9.-]/g, ''));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Inverso de `formatCurrency` — recupera el número crudo de un monto ya
|
|
48
|
+
* formateado, descartando cualquier signo/prefijo de moneda.
|
|
49
|
+
*
|
|
50
|
+
* parseCurrency('$16.800'); // 16800
|
|
51
|
+
*/
|
|
52
|
+
function parseCurrency(formatted, locale = 'es') {
|
|
53
|
+
return parseNumber(formatted, locale);
|
|
54
|
+
}
|
|
55
|
+
const CLOCK_FORMAT = new Intl.DateTimeFormat('es-CL', {
|
|
56
|
+
hour: '2-digit',
|
|
57
|
+
minute: '2-digit',
|
|
58
|
+
hourCycle: 'h23',
|
|
59
|
+
});
|
|
60
|
+
/** "12:40" — hora en formato de 24 horas. */
|
|
61
|
+
function formatClockTime(date) {
|
|
62
|
+
return CLOCK_FORMAT.format(date);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Quita espacios (incluido cualquier espacio en blanco intermedio, no solo
|
|
66
|
+
* al principio/final) y puntos de un identificador de documento — un
|
|
67
|
+
* espacio invisible (ej. pegado por accidente desde otra fuente) es un
|
|
68
|
+
* motivo de rechazo silencioso real que un usuario no sabe explicarse.
|
|
69
|
+
* Genérico: sirve tanto para RUT (`formatChileanRut` ya lo usa por
|
|
70
|
+
* dentro) como para cualquier otro identificador con el mismo problema.
|
|
71
|
+
*
|
|
72
|
+
* sanitizeDocumentId(' 51 00004-8 '); // '5100004-8'
|
|
73
|
+
* sanitizeDocumentId('12.345.678-9'); // '12345678-9'
|
|
74
|
+
*/
|
|
75
|
+
function sanitizeDocumentId(raw) {
|
|
76
|
+
return raw.replace(/[\s.]/g, '');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Formatea un RUT chileno insertando el guion automáticamente a medida
|
|
80
|
+
* que se escribe — igual que un input de tarjeta de crédito, el usuario
|
|
81
|
+
* solo tipea dígitos (y opcionalmente `k`/`K` como dígito verificador) y
|
|
82
|
+
* el guion aparece solo antes del último carácter. Solo da forma al
|
|
83
|
+
* string — no valida si el dígito verificador es matemáticamente
|
|
84
|
+
* correcto (ver `isValidChileanRut` para eso) ni si la cuenta existe
|
|
85
|
+
* (eso lo confirma GAM del lado servidor al autenticar).
|
|
86
|
+
*
|
|
87
|
+
* formatChileanRut('123456789'); // '12345678-9'
|
|
88
|
+
* formatChileanRut('5100004-8'); // '5100004-8' (ya formateado, idempotente)
|
|
89
|
+
* formatChileanRut('5100004k'); // '5100004-k'
|
|
90
|
+
* formatChileanRut('510000 4-8 '); // '5100004-8' (espacios ignorados)
|
|
91
|
+
*/
|
|
92
|
+
function formatChileanRut(raw) {
|
|
93
|
+
const clean = sanitizeDocumentId(raw).replace(/-/g, '').toUpperCase();
|
|
94
|
+
if (clean.length <= 1) {
|
|
95
|
+
return clean;
|
|
96
|
+
}
|
|
97
|
+
const body = clean.slice(0, -1).replace(/[^0-9]/g, '');
|
|
98
|
+
const checkDigit = clean.slice(-1);
|
|
99
|
+
return `${body}-${checkDigit}`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Calcula el dígito verificador real de un RUT chileno con el
|
|
103
|
+
* algoritmo estándar módulo 11 — devuelve `'0'`-`'9'` o `'K'`.
|
|
104
|
+
* `body` debe ser solo dígitos, sin guion ni dígito verificador (ej.
|
|
105
|
+
* `'20236276'`, no `'20236276-1'`).
|
|
106
|
+
*/
|
|
107
|
+
function computeChileanRutCheckDigit(body) {
|
|
108
|
+
let sum = 0;
|
|
109
|
+
let multiplier = 2;
|
|
110
|
+
for (let i = body.length - 1; i >= 0; i--) {
|
|
111
|
+
sum += Number(body[i]) * multiplier;
|
|
112
|
+
multiplier = multiplier < 7 ? multiplier + 1 : 2;
|
|
113
|
+
}
|
|
114
|
+
const remainder = 11 - (sum % 11);
|
|
115
|
+
if (remainder === 11)
|
|
116
|
+
return '0';
|
|
117
|
+
if (remainder === 10)
|
|
118
|
+
return 'K';
|
|
119
|
+
return String(remainder);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Valida un RUT chileno YA formateado (`cuerpo-DV`, ej. `'12345678-9'`)
|
|
123
|
+
* contra el algoritmo módulo 11 real — a diferencia del `pattern` de
|
|
124
|
+
* `document-id.config.ts` en cada portal consumidor (que solo exige la
|
|
125
|
+
* FORMA: dígitos + guion + dígito verificador), esta función confirma
|
|
126
|
+
* que el dígito verificador sea matemáticamente correcto para ese
|
|
127
|
+
* cuerpo. Sigue sin confirmar que la cuenta exista — eso solo lo sabe
|
|
128
|
+
* GAM al autenticar — pero atrapa el error tipográfico más común
|
|
129
|
+
* (invertir/cambiar un dígito) antes de intentar el login.
|
|
130
|
+
*
|
|
131
|
+
* isValidChileanRut('20236276-1'); // true (DV real de ese cuerpo)
|
|
132
|
+
* isValidChileanRut('20236276-8'); // false (DV real es '1', no '8')
|
|
133
|
+
* isValidChileanRut('no-es-rut'); // false (ni siquiera tiene la forma)
|
|
134
|
+
*/
|
|
135
|
+
function isValidChileanRut(rut) {
|
|
136
|
+
const match = /^(\d{7,8})-([\dkK])$/.exec(sanitizeDocumentId(rut));
|
|
137
|
+
if (!match) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
const [, body, checkDigit] = match;
|
|
141
|
+
return computeChileanRutCheckDigit(body).toUpperCase() === checkDigit.toUpperCase();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Signal reactivo con el estado actual de un media query — para cuando un
|
|
146
|
+
* componente necesita SABER en qué lado de un breakpoint está (no solo
|
|
147
|
+
* mostrar/ocultar con CSS). Un `@media` en `styles` no alcanza cuando la
|
|
148
|
+
* decisión no es puramente visual: por ejemplo, un componente hijo que
|
|
149
|
+
* recibe un input booleano (`[collapsed]`) no puede "leer" un breakpoint
|
|
150
|
+
* CSS del padre — necesita que alguien en TypeScript se lo diga.
|
|
151
|
+
*
|
|
152
|
+
* Encontrado al construir `SidebarShell`: el estado `collapsed` (colapso
|
|
153
|
+
* del rail en desktop) se pasaba tal cual a cada `kf-sidebar-nav-item`, sin
|
|
154
|
+
* importar el viewport. En mobile, donde el rail deslizado siempre muestra
|
|
155
|
+
* etiquetas completas (ver estilos de `SidebarShell`), los ítems seguían
|
|
156
|
+
* ocultando su label si el usuario había dejado el rail colapsado en
|
|
157
|
+
* desktop antes de achicar la ventana — un CSS que dice "en mobile se ve
|
|
158
|
+
* ancho" no cambia el valor del input que ya llegó al hijo.
|
|
159
|
+
*
|
|
160
|
+
* Debe llamarse dentro de un contexto de inyección (el `constructor` de un
|
|
161
|
+
* componente, o un campo de clase inicializado ahí — mismo requisito que
|
|
162
|
+
* cualquier función que use `inject()`).
|
|
163
|
+
*
|
|
164
|
+
* protected readonly isMobile = matchMediaSignal('(max-width: 900px)');
|
|
165
|
+
* // ...
|
|
166
|
+
* [collapsed]="collapsed() && !isMobile()"
|
|
167
|
+
*/
|
|
168
|
+
function matchMediaSignal(query) {
|
|
169
|
+
const mediaQueryList = matchMedia(query);
|
|
170
|
+
const state = signal(mediaQueryList.matches, /* @ts-ignore */
|
|
171
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
172
|
+
const onChange = (event) => state.set(event.matches);
|
|
173
|
+
mediaQueryList.addEventListener('change', onChange);
|
|
174
|
+
inject(DestroyRef).onDestroy(() => mediaQueryList.removeEventListener('change', onChange));
|
|
175
|
+
return state.asReadonly();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Navegación estándar de ARIA tabs/listbox/radiogroup: flechas mueven el
|
|
180
|
+
* foco entre opciones y activan la seleccionada. Usado por `kf-tabs`,
|
|
181
|
+
* `kf-folder-tabs` (orientación horizontal) y `kf-selectable-card-group`
|
|
182
|
+
* (vertical) — todos comparten la misma forma (lista de opciones con
|
|
183
|
+
* `value`, botones identificados por `idPrefix + value`).
|
|
184
|
+
*
|
|
185
|
+
* `orientation` decide qué par de flechas avanza/retrocede — mismo patrón
|
|
186
|
+
* que exige el WAI-ARIA Authoring Practices para radiogroup vertical
|
|
187
|
+
* (ArrowDown/ArrowUp) vs. tablist horizontal (ArrowRight/ArrowLeft). Home/End
|
|
188
|
+
* funcionan igual en ambas orientaciones.
|
|
189
|
+
*
|
|
190
|
+
* `select` es responsabilidad del consumidor: esta función solo calcula
|
|
191
|
+
* cuál es la siguiente opción y mueve el foco al botón correspondiente.
|
|
192
|
+
*/
|
|
193
|
+
function handleArrowKeyNavigation(event, options, currentValue, idPrefix, select, orientation = 'horizontal') {
|
|
194
|
+
const currentIndex = options.findIndex((option) => option.value === currentValue);
|
|
195
|
+
const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown';
|
|
196
|
+
const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp';
|
|
197
|
+
let nextIndex;
|
|
198
|
+
switch (event.key) {
|
|
199
|
+
case nextKey:
|
|
200
|
+
nextIndex = (currentIndex + 1) % options.length;
|
|
201
|
+
break;
|
|
202
|
+
case prevKey:
|
|
203
|
+
nextIndex = (currentIndex - 1 + options.length) % options.length;
|
|
204
|
+
break;
|
|
205
|
+
case 'Home':
|
|
206
|
+
nextIndex = 0;
|
|
207
|
+
break;
|
|
208
|
+
case 'End':
|
|
209
|
+
nextIndex = options.length - 1;
|
|
210
|
+
break;
|
|
211
|
+
default:
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
event.preventDefault();
|
|
215
|
+
const nextOption = options[nextIndex];
|
|
216
|
+
select(nextOption.value);
|
|
217
|
+
const host = event.currentTarget.parentElement;
|
|
218
|
+
const nextId = `${idPrefix}${nextOption.value}`;
|
|
219
|
+
const nextButton = Array.from(host?.querySelectorAll('button') ?? []).find((button) => button.id === nextId);
|
|
220
|
+
nextButton?.focus();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Normaliza texto para comparación de búsqueda insensible a mayúsculas y
|
|
225
|
+
* diacríticos — así "cor" encuentre "Córdoba" sin necesidad de tildes.
|
|
226
|
+
*
|
|
227
|
+
* Compartida entre `Select` y `MultiSelect` (`/core`, los dos componentes con
|
|
228
|
+
* buscador interno auto-activado por `searchThreshold`) y `QuotePicker`
|
|
229
|
+
* (`/business`).
|
|
230
|
+
*/
|
|
231
|
+
function normalizeForSearch(text) {
|
|
232
|
+
return text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/*
|
|
236
|
+
* Public API Surface of ui/shared — entry point secundario, `@kaufmann-lab/ui/shared`.
|
|
237
|
+
*
|
|
238
|
+
* `format`/`media-query`/`arrow-key-navigation`/`search-text`: helpers sin UI
|
|
239
|
+
* propia (ningún `Component`/`Directive` con template — puro TS
|
|
240
|
+
* framework-agnostic salvo `media-query`, que solo usa
|
|
241
|
+
* `DestroyRef`/`Signal`/`inject`/`signal` de `@angular/core`, sin depender de
|
|
242
|
+
* ningún componente del catálogo).
|
|
243
|
+
*
|
|
244
|
+
* Este entry point no importa nada de `@kaufmann-lab/ui/core` ni de ningún
|
|
245
|
+
* otro entry point de la librería: es la hoja del grafo de dependencias. Eso
|
|
246
|
+
* permite que cualquier otro (`/core`, `/business`, `/mobile`, `/map`) lo
|
|
247
|
+
* consuma vía `'@kaufmann-lab/ui/shared'`, y que la raíz `'@kaufmann-lab/ui'`
|
|
248
|
+
* lo reexporte, sin crear una dependencia circular. ng-packagr rechaza en
|
|
249
|
+
* build ("Entry point X has a circular dependency on Y") cualquier par de
|
|
250
|
+
* entry points que se necesiten mutuamente — un helper nuevo que dependa de
|
|
251
|
+
* un componente de `/core` no puede vivir acá.
|
|
252
|
+
*
|
|
253
|
+
* Los archivos fuente viven acá (`./lib/shared/`), no reexportados desde otro
|
|
254
|
+
* directorio: ng-packagr calcula un `rootDir` propio por entry point (el
|
|
255
|
+
* directorio del `entryFile`) y rechaza cualquier archivo fuente importado
|
|
256
|
+
* desde afuera de ese `rootDir` (`TS6059`).
|
|
257
|
+
*
|
|
258
|
+
* Los archivos de `lib/shared/` que viven en `@kaufmann-lab/ui/core`
|
|
259
|
+
* (`select-overlay.ts`, `format-input.directive.ts`, y los `.css` sueltos de
|
|
260
|
+
* esa carpeta) no están en este entry point: son UI/overlay y una directiva,
|
|
261
|
+
* no helpers puros.
|
|
262
|
+
*/
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Generated bundle index. Do not edit.
|
|
266
|
+
*/
|
|
267
|
+
|
|
268
|
+
export { formatChileanRut, formatClockTime, formatCurrency, formatNumber, handleArrowKeyNavigation, isValidChileanRut, matchMediaSignal, normalizeForSearch, parseCurrency, parseNumber, sanitizeDocumentId };
|
|
269
|
+
//# sourceMappingURL=kaufmann-lab-ui-shared.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kaufmann-lab-ui-shared.mjs","sources":["../../../projects/ui/shared/src/lib/shared/format.ts","../../../projects/ui/shared/src/lib/shared/media-query.ts","../../../projects/ui/shared/src/lib/shared/arrow-key-navigation.ts","../../../projects/ui/shared/src/lib/shared/search-text.ts","../../../projects/ui/shared/src/public-api.ts","../../../projects/ui/shared/src/kaufmann-lab-ui-shared.ts"],"sourcesContent":["/** Opciones de `formatNumber` — todas opcionales. */\nexport interface FormatNumberOptions {\n /** Cantidad de decimales a mostrar. Default `0`. */\n decimals?: number;\n /** Locale de `Intl`/`toLocaleString` — decide separador de miles y de\n * decimales. Default `'es'` (punto de miles, coma decimal). */\n locale?: string;\n}\n\n/**\n * Separador de miles genérico — sin signo, sin asumir que el valor es\n * dinero. Cubre casos como kilometraje/horómetro (`formatNumber(45231,\n * { locale: 'en-US' })` → `'45,231'`), no solo moneda.\n *\n * formatNumber(16800); // '16.800'\n * formatNumber(16800.5, { decimals: 2 }); // '16.800,50'\n * formatNumber(45231, { locale: 'en-US' }); // '45,231'\n */\nexport function formatNumber(value: number, options: FormatNumberOptions = {}): string {\n const { decimals = 0, locale = 'es' } = options;\n return value.toLocaleString(locale, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n });\n}\n\n/** Opciones de `formatCurrency` — extiende `FormatNumberOptions` con el\n * signo/prefijo de moneda. */\nexport interface FormatCurrencyOptions extends FormatNumberOptions {\n /** Símbolo o prefijo antepuesto al monto. Default `'$'`. */\n sign?: string;\n}\n\n/**\n * Formatea un monto de dinero — compone sobre `formatNumber` agregando el\n * signo. Mismos defaults que la convención de OTD (punto de miles, sin\n * decimales, prefijo `$`), overrideables para otra moneda/país/formato:\n *\n * formatCurrency(16800); // '$16.800'\n * formatCurrency(16800.5, { decimals: 2 }); // '$16.800,50'\n * formatCurrency(16800, { sign: '' }); // '16.800'\n * formatCurrency(16800, { sign: 'US$', locale: 'en-US' }); // 'US$16,800'\n */\nexport function formatCurrency(amount: number, options: FormatCurrencyOptions = {}): string {\n const { sign = '$', ...numberOptions } = options;\n return `${sign}${formatNumber(amount, numberOptions)}`;\n}\n\n/**\n * Inverso de `formatNumber` — recupera el número crudo de un valor ya\n * formateado con el mismo `locale`. Solo soporta `locale` cuyo separador de\n * miles y decimal sean `'.'`/`','` (`es`) o `','`/`'.'` (`en-US`) — son los\n * 2 casos reales del ecosistema hoy; si aparece un tercer locale con otros\n * separadores, extender acá antes de usarlo.\n */\nexport function parseNumber(formatted: string, locale = 'es'): number {\n const [thousands, decimal] = locale === 'en-US' ? [',', '.'] : ['.', ','];\n const withoutThousands = formatted.split(thousands).join('');\n const withDecimalPoint = withoutThousands.replace(decimal, '.');\n return Number(withDecimalPoint.replace(/[^0-9.-]/g, ''));\n}\n\n/**\n * Inverso de `formatCurrency` — recupera el número crudo de un monto ya\n * formateado, descartando cualquier signo/prefijo de moneda.\n *\n * parseCurrency('$16.800'); // 16800\n */\nexport function parseCurrency(formatted: string, locale = 'es'): number {\n return parseNumber(formatted, locale);\n}\n\nconst CLOCK_FORMAT = new Intl.DateTimeFormat('es-CL', {\n hour: '2-digit',\n minute: '2-digit',\n hourCycle: 'h23',\n});\n\n/** \"12:40\" — hora en formato de 24 horas. */\nexport function formatClockTime(date: Date): string {\n return CLOCK_FORMAT.format(date);\n}\n\n/**\n * Quita espacios (incluido cualquier espacio en blanco intermedio, no solo\n * al principio/final) y puntos de un identificador de documento — un\n * espacio invisible (ej. pegado por accidente desde otra fuente) es un\n * motivo de rechazo silencioso real que un usuario no sabe explicarse.\n * Genérico: sirve tanto para RUT (`formatChileanRut` ya lo usa por\n * dentro) como para cualquier otro identificador con el mismo problema.\n *\n * sanitizeDocumentId(' 51 00004-8 '); // '5100004-8'\n * sanitizeDocumentId('12.345.678-9'); // '12345678-9'\n */\nexport function sanitizeDocumentId(raw: string): string {\n return raw.replace(/[\\s.]/g, '');\n}\n\n/**\n * Formatea un RUT chileno insertando el guion automáticamente a medida\n * que se escribe — igual que un input de tarjeta de crédito, el usuario\n * solo tipea dígitos (y opcionalmente `k`/`K` como dígito verificador) y\n * el guion aparece solo antes del último carácter. Solo da forma al\n * string — no valida si el dígito verificador es matemáticamente\n * correcto (ver `isValidChileanRut` para eso) ni si la cuenta existe\n * (eso lo confirma GAM del lado servidor al autenticar).\n *\n * formatChileanRut('123456789'); // '12345678-9'\n * formatChileanRut('5100004-8'); // '5100004-8' (ya formateado, idempotente)\n * formatChileanRut('5100004k'); // '5100004-k'\n * formatChileanRut('510000 4-8 '); // '5100004-8' (espacios ignorados)\n */\nexport function formatChileanRut(raw: string): string {\n const clean = sanitizeDocumentId(raw).replace(/-/g, '').toUpperCase();\n if (clean.length <= 1) {\n return clean;\n }\n const body = clean.slice(0, -1).replace(/[^0-9]/g, '');\n const checkDigit = clean.slice(-1);\n return `${body}-${checkDigit}`;\n}\n\n/**\n * Calcula el dígito verificador real de un RUT chileno con el\n * algoritmo estándar módulo 11 — devuelve `'0'`-`'9'` o `'K'`.\n * `body` debe ser solo dígitos, sin guion ni dígito verificador (ej.\n * `'20236276'`, no `'20236276-1'`).\n */\nfunction computeChileanRutCheckDigit(body: string): string {\n let sum = 0;\n let multiplier = 2;\n for (let i = body.length - 1; i >= 0; i--) {\n sum += Number(body[i]) * multiplier;\n multiplier = multiplier < 7 ? multiplier + 1 : 2;\n }\n const remainder = 11 - (sum % 11);\n if (remainder === 11) return '0';\n if (remainder === 10) return 'K';\n return String(remainder);\n}\n\n/**\n * Valida un RUT chileno YA formateado (`cuerpo-DV`, ej. `'12345678-9'`)\n * contra el algoritmo módulo 11 real — a diferencia del `pattern` de\n * `document-id.config.ts` en cada portal consumidor (que solo exige la\n * FORMA: dígitos + guion + dígito verificador), esta función confirma\n * que el dígito verificador sea matemáticamente correcto para ese\n * cuerpo. Sigue sin confirmar que la cuenta exista — eso solo lo sabe\n * GAM al autenticar — pero atrapa el error tipográfico más común\n * (invertir/cambiar un dígito) antes de intentar el login.\n *\n * isValidChileanRut('20236276-1'); // true (DV real de ese cuerpo)\n * isValidChileanRut('20236276-8'); // false (DV real es '1', no '8')\n * isValidChileanRut('no-es-rut'); // false (ni siquiera tiene la forma)\n */\nexport function isValidChileanRut(rut: string): boolean {\n const match = /^(\\d{7,8})-([\\dkK])$/.exec(sanitizeDocumentId(rut));\n if (!match) {\n return false;\n }\n const [, body, checkDigit] = match;\n return computeChileanRutCheckDigit(body).toUpperCase() === checkDigit.toUpperCase();\n}\n","import { DestroyRef, Signal, inject, signal } from '@angular/core';\n\n/**\n * Signal reactivo con el estado actual de un media query — para cuando un\n * componente necesita SABER en qué lado de un breakpoint está (no solo\n * mostrar/ocultar con CSS). Un `@media` en `styles` no alcanza cuando la\n * decisión no es puramente visual: por ejemplo, un componente hijo que\n * recibe un input booleano (`[collapsed]`) no puede \"leer\" un breakpoint\n * CSS del padre — necesita que alguien en TypeScript se lo diga.\n *\n * Encontrado al construir `SidebarShell`: el estado `collapsed` (colapso\n * del rail en desktop) se pasaba tal cual a cada `kf-sidebar-nav-item`, sin\n * importar el viewport. En mobile, donde el rail deslizado siempre muestra\n * etiquetas completas (ver estilos de `SidebarShell`), los ítems seguían\n * ocultando su label si el usuario había dejado el rail colapsado en\n * desktop antes de achicar la ventana — un CSS que dice \"en mobile se ve\n * ancho\" no cambia el valor del input que ya llegó al hijo.\n *\n * Debe llamarse dentro de un contexto de inyección (el `constructor` de un\n * componente, o un campo de clase inicializado ahí — mismo requisito que\n * cualquier función que use `inject()`).\n *\n * protected readonly isMobile = matchMediaSignal('(max-width: 900px)');\n * // ...\n * [collapsed]=\"collapsed() && !isMobile()\"\n */\nexport function matchMediaSignal(query: string): Signal<boolean> {\n const mediaQueryList = matchMedia(query);\n const state = signal(mediaQueryList.matches);\n\n const onChange = (event: MediaQueryListEvent) => state.set(event.matches);\n mediaQueryList.addEventListener('change', onChange);\n\n inject(DestroyRef).onDestroy(() => mediaQueryList.removeEventListener('change', onChange));\n\n return state.asReadonly();\n}\n","/**\n * Navegación estándar de ARIA tabs/listbox/radiogroup: flechas mueven el\n * foco entre opciones y activan la seleccionada. Usado por `kf-tabs`,\n * `kf-folder-tabs` (orientación horizontal) y `kf-selectable-card-group`\n * (vertical) — todos comparten la misma forma (lista de opciones con\n * `value`, botones identificados por `idPrefix + value`).\n *\n * `orientation` decide qué par de flechas avanza/retrocede — mismo patrón\n * que exige el WAI-ARIA Authoring Practices para radiogroup vertical\n * (ArrowDown/ArrowUp) vs. tablist horizontal (ArrowRight/ArrowLeft). Home/End\n * funcionan igual en ambas orientaciones.\n *\n * `select` es responsabilidad del consumidor: esta función solo calcula\n * cuál es la siguiente opción y mueve el foco al botón correspondiente.\n */\nexport function handleArrowKeyNavigation<T extends { value: string }>(\n event: KeyboardEvent,\n options: T[],\n currentValue: string,\n idPrefix: string,\n select: (value: string) => void,\n orientation: 'horizontal' | 'vertical' = 'horizontal',\n): void {\n const currentIndex = options.findIndex((option) => option.value === currentValue);\n const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown';\n const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp';\n\n let nextIndex: number;\n switch (event.key) {\n case nextKey:\n nextIndex = (currentIndex + 1) % options.length;\n break;\n case prevKey:\n nextIndex = (currentIndex - 1 + options.length) % options.length;\n break;\n case 'Home':\n nextIndex = 0;\n break;\n case 'End':\n nextIndex = options.length - 1;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n const nextOption = options[nextIndex];\n select(nextOption.value);\n\n const host = (event.currentTarget as HTMLElement).parentElement;\n const nextId = `${idPrefix}${nextOption.value}`;\n const nextButton = Array.from(host?.querySelectorAll<HTMLButtonElement>('button') ?? []).find(\n (button) => button.id === nextId,\n );\n nextButton?.focus();\n}\n","/**\n * Normaliza texto para comparación de búsqueda insensible a mayúsculas y\n * diacríticos — así \"cor\" encuentre \"Córdoba\" sin necesidad de tildes.\n *\n * Compartida entre `Select` y `MultiSelect` (`/core`, los dos componentes con\n * buscador interno auto-activado por `searchThreshold`) y `QuotePicker`\n * (`/business`).\n */\nexport function normalizeForSearch(text: string): string {\n return text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();\n}\n","/*\n * Public API Surface of ui/shared — entry point secundario, `@kaufmann-lab/ui/shared`.\n *\n * `format`/`media-query`/`arrow-key-navigation`/`search-text`: helpers sin UI\n * propia (ningún `Component`/`Directive` con template — puro TS\n * framework-agnostic salvo `media-query`, que solo usa\n * `DestroyRef`/`Signal`/`inject`/`signal` de `@angular/core`, sin depender de\n * ningún componente del catálogo).\n *\n * Este entry point no importa nada de `@kaufmann-lab/ui/core` ni de ningún\n * otro entry point de la librería: es la hoja del grafo de dependencias. Eso\n * permite que cualquier otro (`/core`, `/business`, `/mobile`, `/map`) lo\n * consuma vía `'@kaufmann-lab/ui/shared'`, y que la raíz `'@kaufmann-lab/ui'`\n * lo reexporte, sin crear una dependencia circular. ng-packagr rechaza en\n * build (\"Entry point X has a circular dependency on Y\") cualquier par de\n * entry points que se necesiten mutuamente — un helper nuevo que dependa de\n * un componente de `/core` no puede vivir acá.\n *\n * Los archivos fuente viven acá (`./lib/shared/`), no reexportados desde otro\n * directorio: ng-packagr calcula un `rootDir` propio por entry point (el\n * directorio del `entryFile`) y rechaza cualquier archivo fuente importado\n * desde afuera de ese `rootDir` (`TS6059`).\n *\n * Los archivos de `lib/shared/` que viven en `@kaufmann-lab/ui/core`\n * (`select-overlay.ts`, `format-input.directive.ts`, y los `.css` sueltos de\n * esa carpeta) no están en este entry point: son UI/overlay y una directiva,\n * no helpers puros.\n */\n\nexport * from './lib/shared/format';\nexport * from './lib/shared/media-query';\nexport * from './lib/shared/arrow-key-navigation';\nexport * from './lib/shared/search-text';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|