@genarou/blazir-icons 1.2.16 → 1.3.2

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.
Files changed (81) hide show
  1. package/LICENSE +121 -0
  2. package/README.md +1206 -0
  3. package/dist/CustomIcon.svelte +30 -0
  4. package/dist/CustomIcon.svelte.d.ts +14 -0
  5. package/dist/Icon.svelte +282 -102
  6. package/dist/Icon.svelte.d.ts +12 -5
  7. package/dist/IconBadge.svelte +75 -0
  8. package/dist/IconBadge.svelte.d.ts +16 -0
  9. package/dist/IconBase.svelte +89 -57
  10. package/dist/effects.js +1 -3
  11. package/dist/icons/Camera.svelte +19 -0
  12. package/dist/icons/Camera.svelte.d.ts +4 -0
  13. package/dist/icons/Cards.svelte +19 -0
  14. package/dist/icons/Cards.svelte.d.ts +4 -0
  15. package/dist/icons/CloudAlert.svelte +19 -0
  16. package/dist/icons/CloudAlert.svelte.d.ts +4 -0
  17. package/dist/icons/CloudCheck.svelte +19 -0
  18. package/dist/icons/CloudCheck.svelte.d.ts +4 -0
  19. package/dist/icons/CloudDownload.svelte +19 -0
  20. package/dist/icons/CloudDownload.svelte.d.ts +4 -0
  21. package/dist/icons/Colors.svelte +13 -0
  22. package/dist/icons/Colors.svelte.d.ts +4 -0
  23. package/dist/icons/CreditCard.svelte +19 -0
  24. package/dist/icons/CreditCard.svelte.d.ts +4 -0
  25. package/dist/icons/Desktop.svelte +19 -0
  26. package/dist/icons/Desktop.svelte.d.ts +4 -0
  27. package/dist/icons/DoughnutChart.svelte +19 -0
  28. package/dist/icons/DoughnutChart.svelte.d.ts +4 -0
  29. package/dist/icons/Earth.svelte +19 -0
  30. package/dist/icons/Earth.svelte.d.ts +4 -0
  31. package/dist/icons/Globe.svelte +19 -0
  32. package/dist/icons/Globe.svelte.d.ts +4 -0
  33. package/dist/icons/LightHub.svelte +19 -0
  34. package/dist/icons/LightHub.svelte.d.ts +4 -0
  35. package/dist/icons/Link.svelte +19 -0
  36. package/dist/icons/Link.svelte.d.ts +4 -0
  37. package/dist/icons/More.svelte +13 -0
  38. package/dist/icons/More.svelte.d.ts +4 -0
  39. package/dist/icons/Power.svelte +19 -0
  40. package/dist/icons/Power.svelte.d.ts +4 -0
  41. package/dist/icons/Receipt.svelte +19 -0
  42. package/dist/icons/Receipt.svelte.d.ts +4 -0
  43. package/dist/icons/SharedFolder.svelte +13 -0
  44. package/dist/icons/SharedFolder.svelte.d.ts +4 -0
  45. package/dist/icons/Sidebar.svelte +13 -0
  46. package/dist/icons/Sidebar.svelte.d.ts +4 -0
  47. package/dist/icons/Sync.svelte +19 -0
  48. package/dist/icons/Sync.svelte.d.ts +4 -0
  49. package/dist/icons/Tags.svelte +13 -0
  50. package/dist/icons/Tags.svelte.d.ts +4 -0
  51. package/dist/icons/Tools.svelte +20 -0
  52. package/dist/icons/Tools.svelte.d.ts +4 -0
  53. package/dist/icons/Upload.svelte +12 -57
  54. package/dist/icons/Wifi.svelte +19 -0
  55. package/dist/icons/Wifi.svelte.d.ts +4 -0
  56. package/dist/icons/lazy-registry.d.ts +21 -0
  57. package/dist/icons/lazy-registry.js +251 -0
  58. package/dist/icons/registry.d.ts +146 -129
  59. package/dist/icons/registry.js +184 -132
  60. package/dist/icons-api.d.ts +67 -257
  61. package/dist/icons-api.js +84 -453
  62. package/dist/index.d.ts +5 -5
  63. package/dist/index.js +14 -11
  64. package/dist/plugin/index.d.ts +46 -0
  65. package/dist/plugin/index.js +327 -0
  66. package/dist/smart-cache.d.ts +35 -0
  67. package/dist/smart-cache.js +192 -0
  68. package/dist/types.d.ts +19 -2
  69. package/dist/utils/sanitize.d.ts +25 -0
  70. package/dist/utils/sanitize.js +109 -0
  71. package/package.json +23 -13
  72. package/dist/icons/Aws.svelte +0 -19
  73. package/dist/icons/Aws.svelte.d.ts +0 -4
  74. package/dist/icons/Facebook.svelte +0 -18
  75. package/dist/icons/Facebook.svelte.d.ts +0 -4
  76. package/dist/icons/Golang.svelte +0 -17
  77. package/dist/icons/Golang.svelte.d.ts +0 -4
  78. package/dist/icons/Google.svelte +0 -18
  79. package/dist/icons/Google.svelte.d.ts +0 -4
  80. package/dist/icons/Paypal.svelte +0 -21
  81. package/dist/icons/Paypal.svelte.d.ts +0 -4
@@ -0,0 +1,46 @@
1
+ /**
2
+ * blazir-icons — Vite / Rollup plugin
3
+ *
4
+ * Escanea tu código fuente en build-time y genera un registry mínimo
5
+ * que solo incluye los íconos que realmente usas.
6
+ *
7
+ * Compatible con: Vite, Rollup.
8
+ * Sin plugin: funciona exactamente igual que antes (registry completo).
9
+ *
10
+ * Uso en vite.config.ts:
11
+ * import { blazirIconsPlugin } from '@genarou/blazir-icons/plugin'
12
+ * plugins: [blazirIconsPlugin()]
13
+ */
14
+ export interface BlazerIconsPluginOptions {
15
+ /**
16
+ * Directorio raíz del proyecto a escanear.
17
+ * Por defecto: process.cwd()
18
+ */
19
+ root?: string;
20
+ /**
21
+ * Extensiones a escanear. Por defecto: ['.svelte', '.ts', '.tsx', '.js', '.jsx']
22
+ */
23
+ extensions?: string[];
24
+ }
25
+ export declare function blazirIconsPlugin(options?: BlazerIconsPluginOptions): {
26
+ name: string;
27
+ enforce: "pre";
28
+ configResolved(config: {
29
+ root?: string;
30
+ }): void;
31
+ buildStart(): void;
32
+ resolveId(id: string, importer?: string): "\0virtual:blazir-icons/registry" | null | undefined;
33
+ load(id: string): string | undefined;
34
+ handleHotUpdate({ file, read, server }: {
35
+ file: string;
36
+ read: () => Promise<string>;
37
+ server: {
38
+ moduleGraph: {
39
+ getModuleById: (id: string) => unknown;
40
+ invalidateModule: (mod: unknown) => void;
41
+ };
42
+ };
43
+ }): void;
44
+ watchChange(id: string): void;
45
+ };
46
+ export default blazirIconsPlugin;
@@ -0,0 +1,327 @@
1
+ /**
2
+ * blazir-icons — Vite / Rollup plugin
3
+ *
4
+ * Escanea tu código fuente en build-time y genera un registry mínimo
5
+ * que solo incluye los íconos que realmente usas.
6
+ *
7
+ * Compatible con: Vite, Rollup.
8
+ * Sin plugin: funciona exactamente igual que antes (registry completo).
9
+ *
10
+ * Uso en vite.config.ts:
11
+ * import { blazirIconsPlugin } from '@genarou/blazir-icons/plugin'
12
+ * plugins: [blazirIconsPlugin()]
13
+ */
14
+ import { readFileSync, readdirSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+ // ============================================================================
17
+ // MAPA COMPLETO: clave del registry → nombre del archivo .svelte
18
+ // (necesario porque no siempre coinciden, e.g. search → MagnifiyingGlass)
19
+ // ============================================================================
20
+ const ICON_FILE_MAP = {
21
+ ai: 'Ai',
22
+ alternate: 'Alternate',
23
+ animatedArrowLeft: 'AnimatedArrowLeft',
24
+ attachment: 'Attachment',
25
+ bag: 'Bag',
26
+ bank: 'Bank',
27
+ bell: 'Bell',
28
+ blaze: 'Blaze',
29
+ book: 'Book',
30
+ box: 'Box',
31
+ boxAdd: 'BoxAdd',
32
+ building: 'Building',
33
+ calendar: 'Calendar',
34
+ calendarEdit: 'CalendarEdit',
35
+ calendarPlus: 'CalendarPlus',
36
+ cart: 'Cart',
37
+ category: 'Category',
38
+ categoryAdd: 'CategoryAdd',
39
+ categorySearch: 'CategorySearch',
40
+ chart: 'Chart',
41
+ chartDoc: 'ChartDoc',
42
+ chat: 'Chat',
43
+ check: 'Check',
44
+ checkList: 'CheckList',
45
+ checkO: 'CheckO',
46
+ chevronDown: 'ChevronDown',
47
+ chevronUpDown: 'ChevronUpDown',
48
+ circleCheck: 'CircleCheck',
49
+ circleExclamation: 'CircleExclamation',
50
+ circleExclamationO: 'CircleExclamationOutlined',
51
+ circleInfo: 'CircleInfo',
52
+ circleInfoO: 'CircleInfoOutlined',
53
+ circleQuestion: 'CircleQuestion',
54
+ circleQuestionO: 'CircleQuestionOutlined',
55
+ close: 'Close',
56
+ colors: 'Colors',
57
+ contact: 'Contact',
58
+ copy: 'Copy',
59
+ costIcon: 'CostIcon',
60
+ csv: 'Csv',
61
+ dashboard: 'Dashboard',
62
+ dashboardO: 'DashboardOutlined',
63
+ db: 'Db',
64
+ download: 'Download',
65
+ downloadAnimated: 'DownloadAnimated',
66
+ edit: 'Edit',
67
+ editOutline: 'EditOutline',
68
+ email: 'Email',
69
+ emailAnimated: 'EmailAnimated',
70
+ enterprise: 'Enterprise',
71
+ error: 'Error',
72
+ errorO: 'ErrorO',
73
+ excel: 'Excel',
74
+ excelAnimated: 'ExcelAnimated',
75
+ exchange: 'Exchange',
76
+ eye: 'Eye',
77
+ eyeOff: 'EyeOff',
78
+ favorites: 'Favorites',
79
+ file: 'File',
80
+ fileUpdateAnimated: 'FileUploadAnimated',
81
+ filter: 'FilterOutline',
82
+ filterOutline: 'FilterOutline',
83
+ fingerprint: 'Fingerprint',
84
+ folder: 'Folder',
85
+ form: 'Form',
86
+ formatListGroup: 'FormatListGroup',
87
+ group: 'Group',
88
+ hamburguer: 'Hamburguer',
89
+ handShake: 'HandShake',
90
+ heart: 'Heart',
91
+ height: 'Height',
92
+ home: 'Home',
93
+ image: 'Image',
94
+ imageAnimated: 'ImageAnimated',
95
+ key: 'Key',
96
+ list: 'List',
97
+ listDots: 'ListDots',
98
+ loadingDots: 'LoadingDots',
99
+ loadingRegular: 'RegularSpinner',
100
+ location: 'Location',
101
+ locationAnimated: 'LocationAnimated',
102
+ lock: 'Lock',
103
+ lockOpen: 'LockOpen',
104
+ logout: 'Logout',
105
+ lose: 'Lose',
106
+ magnifyingGlass: 'MagnifiyingGlass',
107
+ measure: 'Measure',
108
+ money: 'Money',
109
+ moon: 'Moon',
110
+ more: 'More',
111
+ notes: 'Notes',
112
+ objectGroup: 'ObjectGroup',
113
+ pay: 'Pay',
114
+ pdf: 'Pdf',
115
+ phone: 'Phone',
116
+ plus: 'Plus',
117
+ png: 'Png',
118
+ pointSale: 'PointSale',
119
+ powerPoint: 'PowerPoint',
120
+ print: 'Print',
121
+ product: 'Product',
122
+ profit: 'Profit',
123
+ project: 'Project',
124
+ refresh: 'Update',
125
+ reset: 'Reset',
126
+ rightArrow: 'RightArrow',
127
+ rocket: 'Rocket',
128
+ safeSolid: 'SafeSolid',
129
+ save: 'Save',
130
+ scan: 'Scan',
131
+ search: 'MagnifiyingGlass',
132
+ security: 'Security',
133
+ send: 'Send',
134
+ server: 'Server',
135
+ settings: 'Settings',
136
+ share: 'Share',
137
+ sharedFolder: 'SharedFolder',
138
+ shield: 'Shield',
139
+ sidebar: 'Sidebar',
140
+ squareChart: 'SquareChart',
141
+ star: 'Star',
142
+ sun: 'Sun',
143
+ supervisor: 'Supervisor',
144
+ swap: 'Swap',
145
+ table: 'Table',
146
+ tags: 'Tags',
147
+ team: 'Team',
148
+ timer: 'Timer',
149
+ tools: 'Tools',
150
+ trash: 'Trash',
151
+ trashOutline: 'TrashOutline',
152
+ truck: 'Truck',
153
+ truckReturn: 'TruckReturn',
154
+ upArrow: 'UpArrow',
155
+ upDownArrow: 'UpDownArrow',
156
+ upload: 'Upload',
157
+ uploadAnimated: 'UploadAnimated',
158
+ uploadLoader: 'UploadLoader',
159
+ user: 'User',
160
+ userTie: 'UserTie',
161
+ wallet: 'Wallet',
162
+ warehouse: 'Warehouse',
163
+ warning: 'Warning',
164
+ word: 'Word',
165
+ world: 'World',
166
+ xml: 'Xml',
167
+ zip: 'Zip',
168
+ };
169
+ const ALL_KEYS = new Set(Object.keys(ICON_FILE_MAP));
170
+ // ============================================================================
171
+ // PATRONES DE DETECCIÓN
172
+ // ============================================================================
173
+ // Detecta: name="check" | name='check' | name={"check"} | name={'check'}
174
+ const STATIC_RE = /\bname=(?:"([a-zA-Z][a-zA-Z0-9]*)"|'([a-zA-Z][a-zA-Z0-9]*)'|\{["']([a-zA-Z][a-zA-Z0-9]*)["']\})/g;
175
+ // Detecta uso dinámico: name={expr} (no string literal)
176
+ const DYNAMIC_RE = /\bname=\{(?!["'])/;
177
+ // ID del módulo virtual que reemplaza al registry completo
178
+ const VIRTUAL_ID = 'virtual:blazir-icons/registry';
179
+ const RESOLVED_ID = '\0virtual:blazir-icons/registry';
180
+ // ============================================================================
181
+ // PLUGIN
182
+ // ============================================================================
183
+ export function blazirIconsPlugin(options = {}) {
184
+ const detected = new Set();
185
+ let hasDynamic = false;
186
+ let scanned = false;
187
+ let root = options.root ?? process.cwd();
188
+ const extensions = options.extensions ?? ['.svelte', '.ts', '.tsx', '.js', '.jsx'];
189
+ // --------------------------------------------------------------------------
190
+ // Extrae nombres de íconos de un bloque de código fuente
191
+ // --------------------------------------------------------------------------
192
+ function scanContent(content) {
193
+ STATIC_RE.lastIndex = 0;
194
+ for (const m of content.matchAll(STATIC_RE)) {
195
+ const name = m[1] ?? m[2] ?? m[3];
196
+ if (name && ALL_KEYS.has(name))
197
+ detected.add(name);
198
+ }
199
+ if (!hasDynamic && DYNAMIC_RE.test(content)) {
200
+ hasDynamic = true;
201
+ }
202
+ }
203
+ // --------------------------------------------------------------------------
204
+ // Escanea recursivamente un directorio (ignora node_modules, .git, dist)
205
+ // --------------------------------------------------------------------------
206
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.svelte-kit', '.vite', 'build']);
207
+ function scanDir(dir) {
208
+ let entries;
209
+ try {
210
+ entries = readdirSync(dir, { withFileTypes: true });
211
+ }
212
+ catch {
213
+ return;
214
+ }
215
+ for (const entry of entries) {
216
+ const full = join(dir, entry.name);
217
+ if (entry.isDirectory()) {
218
+ if (!SKIP_DIRS.has(entry.name))
219
+ scanDir(full);
220
+ }
221
+ else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
222
+ try {
223
+ scanContent(readFileSync(full, 'utf-8'));
224
+ }
225
+ catch {
226
+ // archivo ilegible — ignorar
227
+ }
228
+ }
229
+ }
230
+ }
231
+ // --------------------------------------------------------------------------
232
+ // Asegura que el scan se haya hecho al menos una vez
233
+ // --------------------------------------------------------------------------
234
+ function ensureScanned() {
235
+ if (scanned)
236
+ return;
237
+ scanned = true;
238
+ scanDir(root);
239
+ }
240
+ // --------------------------------------------------------------------------
241
+ // Genera el código del registry mínimo
242
+ // --------------------------------------------------------------------------
243
+ function generateRegistry() {
244
+ const lines = [];
245
+ const entries = [];
246
+ for (const key of detected) {
247
+ const file = ICON_FILE_MAP[key];
248
+ if (!file)
249
+ continue;
250
+ lines.push(`import _${key} from '@genarou/blazir-icons/icons/${file}';`);
251
+ entries.push(` ${key}: _${key}`);
252
+ }
253
+ lines.push(`\nexport const iconRegistry = {\n${entries.join(',\n')}\n};`);
254
+ lines.push(`export type IconName = keyof typeof iconRegistry;`);
255
+ lines.push(`export type IconComponent = any;`);
256
+ return lines.join('\n');
257
+ }
258
+ // --------------------------------------------------------------------------
259
+ // Plugin object (compatible con Vite y Rollup)
260
+ // --------------------------------------------------------------------------
261
+ return {
262
+ name: 'blazir-icons',
263
+ enforce: 'pre',
264
+ // Vite: captura el root configurado
265
+ configResolved(config) {
266
+ if (config.root)
267
+ root = config.root;
268
+ },
269
+ // Prod build: escanear todos los archivos antes de comenzar
270
+ buildStart() {
271
+ ensureScanned();
272
+ },
273
+ // Intercepta la importación del registry desde Icon.svelte
274
+ resolveId(id, importer) {
275
+ if (id === VIRTUAL_ID)
276
+ return RESOLVED_ID;
277
+ // Solo interceptar cuando podemos optimizar:
278
+ // - el importer es Icon.svelte de blazir-icons
279
+ // - el id es el registry
280
+ const isRegistryImport = (id.includes('registry') || id === './icons/registry.js') &&
281
+ importer?.includes('blazir-icons') &&
282
+ importer?.includes('Icon.svelte');
283
+ if (isRegistryImport) {
284
+ ensureScanned();
285
+ // Si hay uso dinámico o no detectamos nada → dejar pasar (registry completo)
286
+ if (hasDynamic || detected.size === 0)
287
+ return null;
288
+ return RESOLVED_ID;
289
+ }
290
+ },
291
+ // Genera el módulo virtual con solo los íconos detectados
292
+ load(id) {
293
+ if (id !== RESOLVED_ID)
294
+ return;
295
+ ensureScanned();
296
+ return generateRegistry();
297
+ },
298
+ // Vite dev server: re-escanear archivos modificados
299
+ handleHotUpdate({ file, read, server }) {
300
+ if (!file.includes('node_modules') && extensions.some((ext) => file.endsWith(ext))) {
301
+ read().then((content) => {
302
+ const prevSize = detected.size;
303
+ const prevDynamic = hasDynamic;
304
+ scanContent(content);
305
+ // Si se detectaron nuevos íconos o cambió el estado dinámico → invalidar el módulo virtual
306
+ if (detected.size !== prevSize || hasDynamic !== prevDynamic) {
307
+ const mod = server.moduleGraph.getModuleById(RESOLVED_ID);
308
+ if (mod)
309
+ server.moduleGraph.invalidateModule(mod);
310
+ }
311
+ });
312
+ }
313
+ },
314
+ // Rollup watch mode: re-escanear archivos modificados
315
+ watchChange(id) {
316
+ if (!id.includes('node_modules') && extensions.some((ext) => id.endsWith(ext))) {
317
+ try {
318
+ scanContent(readFileSync(id, 'utf-8'));
319
+ }
320
+ catch {
321
+ // ignorar archivos ilegibles
322
+ }
323
+ }
324
+ },
325
+ };
326
+ }
327
+ export default blazirIconsPlugin;
@@ -0,0 +1,35 @@
1
+ import type { IconProps } from './types.js';
2
+ export declare class LRU<K, V> {
3
+ private readonly max;
4
+ private readonly map;
5
+ constructor(max: number);
6
+ get(key: K): V | undefined;
7
+ set(key: K, val: V): void;
8
+ has(key: K): boolean;
9
+ get size(): number;
10
+ clear(): void;
11
+ }
12
+ /**
13
+ * Devuelve un objeto FROZEN y ESTABLE para la combinación (preset, variant).
14
+ *
15
+ * Garantías:
16
+ * - Mismos argumentos → EXACTAMENTE la misma referencia (Object.is === true)
17
+ * - El objeto es inmutable (Object.freeze)
18
+ * - Sin TTL: la validez es estructural (los presets son constantes de módulo)
19
+ */
20
+ export declare function getPresetVariantMerge(preset: string | undefined, variant: string | undefined): Readonly<Partial<IconProps>>;
21
+ /**
22
+ * Retorna true si todos los valores de props son primitivos seguros para cache.
23
+ * Complejidad: O(n) donde n = número de props.
24
+ */
25
+ export declare function arePropsStable(props: Record<string, unknown>): boolean;
26
+ /**
27
+ * Construye una clave estructural para el cache completo de props.
28
+ * Solo incluye props primitivas y estables.
29
+ *
30
+ * ⚠️ Solo llamar cuando arePropsStable() === true.
31
+ */
32
+ export declare function buildStructuralKey(props: Record<string, unknown>): string;
33
+ export declare function getCachedMergedProps(key: string): Readonly<IconProps> | undefined;
34
+ export declare function setCachedMergedProps(key: string, props: Readonly<IconProps>): void;
35
+ export declare function toSizePx(s: number | string): string;
@@ -0,0 +1,192 @@
1
+ // src/lib/smart-cache.ts
2
+ // Cache adaptativo inteligente para blazir-icons
3
+ // Diseñado para máxima estabilidad referencial y rendimiento en listas grandes
4
+ import { iconPresets, iconVariants } from './presets.js';
5
+ // ============================================================================
6
+ // MICRO-LRU — O(1) get/set con Map de inserción-orden como cola
7
+ // Map en V8 mantiene orden de inserción; delete + re-insert = mover al final
8
+ // ============================================================================
9
+ export class LRU {
10
+ constructor(max) {
11
+ this.max = max;
12
+ this.map = new Map();
13
+ }
14
+ get(key) {
15
+ if (!this.map.has(key))
16
+ return undefined;
17
+ // Promover a "más reciente": delete + re-insert
18
+ const val = this.map.get(key);
19
+ this.map.delete(key);
20
+ this.map.set(key, val);
21
+ return val;
22
+ }
23
+ set(key, val) {
24
+ if (this.map.has(key)) {
25
+ this.map.delete(key);
26
+ }
27
+ else if (this.map.size >= this.max) {
28
+ // Evictar el LRU (primer elemento = menos reciente)
29
+ this.map.delete(this.map.keys().next().value);
30
+ }
31
+ this.map.set(key, val);
32
+ }
33
+ has(key) {
34
+ return this.map.has(key);
35
+ }
36
+ get size() {
37
+ return this.map.size;
38
+ }
39
+ clear() {
40
+ this.map.clear();
41
+ }
42
+ }
43
+ // ============================================================================
44
+ // CACHE PRESET × VARIANT
45
+ //
46
+ // Propósito: devolver EXACTAMENTE la misma referencia de objeto para la misma
47
+ // combinación (preset, variant). Esto permite que $derived en Icon.svelte NO
48
+ // cree nuevos objetos si preset/variant no cambiaron.
49
+ //
50
+ // Bounds: len(presets) × len(variants) ≈ 12 × 8 = 96 entradas máximo.
51
+ // → No necesita evicción. Plain Map, determinista.
52
+ // ============================================================================
53
+ const _pvCache = new Map();
54
+ /**
55
+ * Devuelve un objeto FROZEN y ESTABLE para la combinación (preset, variant).
56
+ *
57
+ * Garantías:
58
+ * - Mismos argumentos → EXACTAMENTE la misma referencia (Object.is === true)
59
+ * - El objeto es inmutable (Object.freeze)
60
+ * - Sin TTL: la validez es estructural (los presets son constantes de módulo)
61
+ */
62
+ export function getPresetVariantMerge(preset, variant) {
63
+ // Usar \x01 como separador para evitar colisiones "ab|c" vs "a|bc"
64
+ const key = `${preset ?? '\x00'}\x01${variant ?? '\x00'}`;
65
+ const hit = _pvCache.get(key);
66
+ if (hit)
67
+ return hit;
68
+ const pp = preset
69
+ ? (iconPresets[preset] ?? {})
70
+ : {};
71
+ const vp = variant
72
+ ? (iconVariants[variant] ?? {})
73
+ : {};
74
+ // Orden: variant gana sobre preset (igual que el merge original)
75
+ const merged = Object.freeze({ ...pp, ...vp });
76
+ _pvCache.set(key, merged);
77
+ return merged;
78
+ }
79
+ // ============================================================================
80
+ // DETECCIÓN DE ESTABILIDAD DE PROPS
81
+ //
82
+ // Props "estables": todos sus valores son primitivos (string, number, boolean)
83
+ // Props "inestables": contienen objetos, funciones o arrays
84
+ //
85
+ // Solo las props estables son candidatas al cache estructural completo.
86
+ // ============================================================================
87
+ /**
88
+ * Props que SIEMPRE son inestables (objetos, funciones, arrays).
89
+ * Si aparecen en los props del usuario → no cachear el resultado completo.
90
+ */
91
+ const UNSTABLE_KEYS = new Set([
92
+ 'actions',
93
+ 'effects',
94
+ 'attrs',
95
+ 'parentHoverContext',
96
+ 'children',
97
+ ]);
98
+ /**
99
+ * Props excluidas del cache key (se pasan al hijo pero no afectan
100
+ * a la merge de base, o son strings que varían libremente).
101
+ */
102
+ const SKIP_IN_KEY = new Set([
103
+ 'style',
104
+ 'class',
105
+ 'className',
106
+ 'actions',
107
+ 'effects',
108
+ 'attrs',
109
+ 'parentHoverContext',
110
+ 'children',
111
+ // preset/variant se resuelven por separado (ya están en baseProps)
112
+ 'preset',
113
+ 'variant',
114
+ // lazy es meta-prop de Icon.svelte, no llega al hijo
115
+ 'lazy',
116
+ ]);
117
+ /**
118
+ * Retorna true si todos los valores de props son primitivos seguros para cache.
119
+ * Complejidad: O(n) donde n = número de props.
120
+ */
121
+ export function arePropsStable(props) {
122
+ for (const k in props) {
123
+ if (UNSTABLE_KEYS.has(k))
124
+ return false;
125
+ const v = props[k];
126
+ if (v !== null && v !== undefined && typeof v === 'object')
127
+ return false;
128
+ if (typeof v === 'function')
129
+ return false;
130
+ }
131
+ return true;
132
+ }
133
+ // ============================================================================
134
+ // CLAVE ESTRUCTURAL
135
+ //
136
+ // Clave determinista derivada de las props primitivas (excluye las inestables
137
+ // y las meta-props). Las claves se ordenan alfabéticamente para asegurar
138
+ // que dos objetos con las mismas props en distinto orden generen la misma clave.
139
+ // ============================================================================
140
+ /**
141
+ * Construye una clave estructural para el cache completo de props.
142
+ * Solo incluye props primitivas y estables.
143
+ *
144
+ * ⚠️ Solo llamar cuando arePropsStable() === true.
145
+ */
146
+ export function buildStructuralKey(props) {
147
+ const parts = [];
148
+ // Ordenar para determinismo (orden de props puede variar)
149
+ const keys = Object.keys(props).sort();
150
+ for (const k of keys) {
151
+ if (SKIP_IN_KEY.has(k))
152
+ continue;
153
+ const v = props[k];
154
+ if (v === undefined)
155
+ continue;
156
+ // Separador: \x02 es poco probable en valores reales
157
+ parts.push(`${k}\x02${v}`);
158
+ }
159
+ return parts.join('\x03');
160
+ }
161
+ // ============================================================================
162
+ // CACHE COMPLETO DE MERGED PROPS (LRU, solo props estables)
163
+ //
164
+ // Scope: global (compartido entre instancias del mismo componente).
165
+ // Esto es correcto porque la función de merge es pura cuando los inputs
166
+ // son solo primitivos.
167
+ //
168
+ // Tamaño: 128 entradas. Para listas con M iconos distintos: hasta 128 configs
169
+ // únicas se cachean. Más allá, el LRU evicta las menos usadas.
170
+ // ============================================================================
171
+ const _mergedPropsCache = new LRU(128);
172
+ export function getCachedMergedProps(key) {
173
+ return _mergedPropsCache.get(key);
174
+ }
175
+ export function setCachedMergedProps(key, props) {
176
+ _mergedPropsCache.set(key, props);
177
+ }
178
+ // ============================================================================
179
+ // ESTABILIDAD REFERENCIAL PARA SIZE
180
+ //
181
+ // El tamaño de la caja se deriva de (p.size ?? preset.size ?? variant.size).
182
+ // Cacheamos el string resultado para evitar crear nuevos strings en cada render.
183
+ // ============================================================================
184
+ const _sizeStringCache = new Map();
185
+ export function toSizePx(s) {
186
+ const hit = _sizeStringCache.get(s);
187
+ if (hit)
188
+ return hit;
189
+ const result = typeof s === 'number' ? `${s}px` : s;
190
+ _sizeStringCache.set(s, result);
191
+ return result;
192
+ }
package/dist/types.d.ts CHANGED
@@ -34,6 +34,14 @@ export interface IconProps {
34
34
  elastic?: boolean;
35
35
  hoverColor?: string;
36
36
  activeColor?: string;
37
+ /** Apaga el icono visualmente (opacity 0.4) y bloquea hover/pointer-events. */
38
+ disabled?: boolean;
39
+ /** Reemplaza el icono con un spinner hasta que sea false. Reutiliza la animación spin interna. */
40
+ loading?: boolean;
41
+ /** Texto del tooltip. Se muestra al hacer hover sobre el icono. */
42
+ tooltip?: string;
43
+ /** Delay antes de mostrar el tooltip en ms. Default: 0 (instantáneo). */
44
+ tooltipDelay?: number;
37
45
  parentHoverContext?: {
38
46
  hovered: boolean;
39
47
  } | null;
@@ -53,9 +61,18 @@ export interface IconProps {
53
61
  effects?: IconEffectOptions;
54
62
  children?: any;
55
63
  mode?: IconMode;
56
- /** Duración genérica de transiciones/hover (ms o '200ms'/'0.2s'). */
64
+ /**
65
+ * Velocidad de transición hover — preset semántico.
66
+ * - `"instant"` → 0ms
67
+ * - `"fast"` → 120ms
68
+ * - `"normal"` → 250ms ← default
69
+ * - `"slow"` → 400ms
70
+ * - `"sluggish"` → 600ms
71
+ */
72
+ hoverSpeed?: "instant" | "fast" | "normal" | "slow" | "sluggish";
73
+ /** Duración exacta de la transición hover (ms o '300ms'/'0.3s'). Prevalece sobre hoverSpeed. */
57
74
  transitionMs?: number | string;
58
- /** Easing para transiciones/hover. */
75
+ /** Easing CSS para la transición hover. Ej: 'ease', 'ease-in-out', 'cubic-bezier(...)'. */
59
76
  transitionEasing?: string;
60
77
  }
61
78
  export type { IconName } from "./icons/registry";
@@ -0,0 +1,25 @@
1
+ /**
2
+ * sanitize.ts — Lightweight SVG sanitizer. Zero dependencies.
3
+ *
4
+ * Browser: uses the native DOMParser with image/svg+xml for robust, spec-compliant parsing.
5
+ * SSR: regex-based fallback that strips the most dangerous vectors.
6
+ *
7
+ * Threat model (SVG inner content — not the <svg> tag itself):
8
+ * ✗ <script> blocks
9
+ * ✗ <foreignObject> — embeds arbitrary HTML
10
+ * ✗ <iframe>, <object>, <embed>, <link>, <base>
11
+ * ✗ Event handlers — on*, xlink:actuate, etc.
12
+ * ✗ javascript: / vbscript: / data: URIs in href / src / action
13
+ * ✗ CSS expression() / javascript: in style attributes
14
+ */
15
+ /**
16
+ * Sanitizes raw SVG inner markup against XSS vectors.
17
+ *
18
+ * Safe to call with content from external APIs or user input.
19
+ * For static, trusted SVG strings defined in your own code, pass
20
+ * `sanitize={false}` to `CustomIcon` to skip the overhead entirely.
21
+ *
22
+ * @param raw - SVG inner markup (paths, circles, etc. — not the <svg> tag)
23
+ * @returns Sanitized SVG markup string, empty string on parse failure
24
+ */
25
+ export declare function sanitizeSvg(raw: string): string;