@genarou/blazir-icons 1.2.8 → 1.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Icon.svelte CHANGED
@@ -1,122 +1,195 @@
1
+ <!-- DynamicIcon.svelte -->
1
2
  <script lang="ts">
3
+ import { coerceSize } from ".";
2
4
  import { iconRegistry, type IconName } from "./icons/registry";
3
- import {
4
- iconPresets,
5
- iconVariants,
6
- type IconPreset,
7
- type IconVariant,
8
- } from "./presets";
5
+ import { iconPresets, iconVariants } from "./presets";
9
6
  import type { IconProps } from "./types";
10
- import { coerceSize } from "./utils/defaults";
7
+
8
+ // Bridge de actions externas (no importamos lazy aquí)
9
+ type ActionFn<T = HTMLElement, P = any> = (
10
+ node: T,
11
+ params?: P
12
+ ) => void | { update?: (p?: P) => void; destroy?: () => void };
13
+
14
+ type ActionEntry<T = HTMLElement> = [ActionFn<T, any>, any?];
15
+
16
+ type IconPresetName = keyof typeof iconPresets;
17
+ type IconVariantName = keyof typeof iconVariants;
11
18
 
12
19
  interface DynamicIconProps extends Omit<IconProps, "children"> {
13
- name: IconName;
14
- preset?: IconPreset;
15
- variant?: IconVariant;
20
+ name?: IconName;
21
+ preset?: IconPresetName;
22
+ variant?: IconVariantName;
23
+ class?: string;
24
+ style?: string;
25
+ /** Pasas aquí tus actions: p.ej. actions={[[lazyClass, opts]]} */
26
+ actions?: ActionEntry[];
16
27
  }
17
28
 
18
- const props: DynamicIconProps = $props();
29
+ const p: DynamicIconProps = $props();
19
30
 
20
- let internalHovered = $state(false);
21
- const isHovered = $derived(
22
- internalHovered || (props.parentHoverContext?.hovered ?? false)
23
- );
31
+ let iconName = $state<IconName | null>(null);
32
+ let Comp = $state<any>(null);
24
33
 
25
- const Comp = $derived.by(() => iconRegistry[props.name]);
34
+ let presetProps = $state<Partial<IconProps>>({});
35
+ let variantProps = $state<Partial<IconProps>>({});
26
36
 
27
- // Preset y variant (opcionales)
28
- const presetProps = $derived(() =>
29
- props.preset ? iconPresets[props.preset] : {}
30
- );
31
- const variantProps = $derived(() =>
32
- props.variant ? iconVariants[props.variant] : {}
33
- );
37
+ let child = $state<IconProps>({} as IconProps);
38
+ let boxSize = $state<string>("1em");
39
+ let hostClass = $state<string>("bz-icon-wrapper");
40
+ let hostStyle = $state<string>("");
34
41
 
35
- // Merge: preset < variant < props explícitas
36
- const mergedProps = $derived({ ...presetProps, ...variantProps, ...props });
42
+ // Key para forzar remonte y reiniciar animaciones
43
+ let mountKey = $state(0);
37
44
 
38
- // Color efectivo con fallback
39
- const effectiveColor = $derived(() => {
40
- const baseColor = mergedProps.color || "currentColor";
41
- if (isHovered && mergedProps.hoverColor) return mergedProps.hoverColor;
42
- return baseColor;
45
+ // name -> componente
46
+ $effect(() => {
47
+ iconName = p.name ?? null;
48
+ Comp = iconName ? iconRegistry[iconName] : null;
43
49
  });
44
50
 
45
- // Normalizar durations (string ms)
46
- const normalizeAnimationValue = (
47
- value?: number | string
48
- ): string | undefined =>
49
- value === undefined
50
- ? undefined
51
- : typeof value === "number"
52
- ? `${value}ms`
53
- : value;
54
-
55
- // Props finales para el Icono concreto
56
- const iconProps = $derived(() => {
51
+ // preset/variant -> props parciales
52
+ $effect(() => {
53
+ const key = p.preset as IconPresetName | undefined;
54
+ presetProps = key ? iconPresets[key] : {};
55
+ });
56
+ $effect(() => {
57
+ const key = p.variant as IconVariantName | undefined;
58
+ variantProps = key ? iconVariants[key] : {};
59
+ });
60
+
61
+ // Limpia class/style/actions del host antes de pasar al hijo
62
+ $effect(() => {
57
63
  const {
58
- name,
59
- preset,
60
- variant,
61
- size,
62
- color,
63
- hoverColor,
64
- activeColor,
65
- parentHoverContext,
66
- animationDuration,
67
- animationDelay,
68
- ...rest
69
- } = mergedProps;
64
+ class: _class,
65
+ style: _style,
66
+ actions: _actions,
67
+ ...restRaw
68
+ } = { ...presetProps, ...variantProps, ...p } as Record<string, unknown>;
70
69
 
71
- return {
72
- ...rest,
73
- size: coerceSize(size, 24),
74
- color: effectiveColor(),
75
- animationDuration: normalizeAnimationValue(animationDuration),
76
- animationDelay: normalizeAnimationValue(animationDelay),
77
- };
70
+ const toMs = (v: unknown) =>
71
+ v == null ? undefined : typeof v === "number" ? `${v}ms` : (v as string);
72
+
73
+ child = {
74
+ ...(restRaw as IconProps),
75
+ animationDuration: toMs((restRaw as any).animationDuration),
76
+ animationDelay: toMs((restRaw as any).animationDelay),
77
+ } as IconProps;
78
+ });
79
+
80
+ // Tamaño del wrapper
81
+ $effect(() => {
82
+ const rawSize =
83
+ (p as any).size ??
84
+ (presetProps as any).size ??
85
+ (variantProps as any).size;
86
+ const s = coerceSize(rawSize, 24);
87
+ boxSize = typeof s === "number" ? `${s}px` : s;
88
+ });
89
+
90
+ // Host attrs
91
+ $effect(() => {
92
+ hostClass = `bz-icon-wrapper ${p.class ?? ""}`.trim();
93
+ hostStyle =
94
+ `--bz-icon-size:${boxSize};` +
95
+ `width:${boxSize};height:${boxSize};` +
96
+ `inline-size:${boxSize};block-size:${boxSize};` +
97
+ `${p.style ?? ""}`;
78
98
  });
79
99
 
80
- function handleMouseEnter() {
81
- internalHovered = true;
100
+ // Bridge para aplicar actions externas
101
+ function applyActions(node: HTMLElement, entries: ActionEntry[] = []) {
102
+ let handles =
103
+ entries?.map(([fn, params]) => fn?.(node, params)).filter(Boolean) ?? [];
104
+ return {
105
+ update(next: ActionEntry[] = []) {
106
+ handles.forEach((h) => h?.destroy?.());
107
+ handles =
108
+ next?.map(([fn, params]) => fn?.(node, params)).filter(Boolean) ?? [];
109
+ },
110
+ destroy() {
111
+ handles.forEach((h) => h?.destroy?.());
112
+ handles = [];
113
+ },
114
+ };
82
115
  }
83
- function handleMouseLeave() {
84
- internalHovered = false;
116
+
117
+ // Reanima al recibir el evento del action (asegúrate de que el action emita "lazyMount")
118
+ function onLazyMount() {
119
+ mountKey++;
85
120
  }
86
121
  </script>
87
122
 
88
123
  {#if Comp}
89
124
  <span
90
- class="bz-icon-wrapper"
91
- role="presentation"
92
- onmouseenter={handleMouseEnter}
93
- onmouseleave={handleMouseLeave}
125
+ class={hostClass}
126
+ style={hostStyle}
127
+ use:applyActions={p.actions ?? []}
128
+ onlazyMount={onLazyMount}
94
129
  >
95
- <Comp {...iconProps()} />
130
+ {#key mountKey}
131
+ <Comp {...child} />
132
+ {/key}
96
133
  </span>
97
- {:else}
98
- <span class="bz-icon-error" data-icon={props.name}>
99
- ⚠️ Icon not found: {props.name}
134
+ {:else if iconName}
135
+ <span class="bz-icon-error" data-icon={iconName}>
136
+ ⚠️ Icon not found: {iconName}
100
137
  </span>
138
+ {:else}
139
+ <span class="bz-icon-skeleton" aria-hidden="true"></span>
101
140
  {/if}
102
141
 
103
142
  <style>
104
- .bz-icon-wrapper {
105
- display: inline-flex;
106
- align-items: center;
107
- justify-content: center;
108
- line-height: 1;
143
+ .bz-icon-skeleton {
144
+ width: 100%;
145
+ height: 100%;
146
+ border-radius: var(--ui-radius, 0.25rem);
147
+ background: color-mix(in oklab, currentColor 12%, transparent);
148
+ opacity: 0.5;
109
149
  }
110
150
  .bz-icon-error {
111
151
  display: inline-block;
112
152
  width: 1em;
113
153
  height: 1em;
114
154
  background: var(--danger-color, #ef4444);
115
- color: var(--danger-foreground, white);
155
+ color: var(--danger-foreground, #fff);
116
156
  text-align: center;
117
157
  line-height: 1;
118
158
  font-size: 0.75rem;
119
159
  border-radius: var(--ui-radius, 0.25rem);
120
160
  font-weight: 600;
121
161
  }
162
+ .is-entering {
163
+ opacity: 0.001;
164
+ transform: translateY(4px);
165
+ animation: ic-enter 160ms ease-out forwards;
166
+ }
167
+ .is-leaving {
168
+ opacity: 1;
169
+ transform: none;
170
+ animation: ic-leave 160ms ease-in forwards;
171
+ }
172
+ .is-mounted {
173
+ opacity: 1;
174
+ transform: none;
175
+ }
176
+ @keyframes ic-enter {
177
+ to {
178
+ opacity: 1;
179
+ transform: none;
180
+ }
181
+ }
182
+ @keyframes ic-leave {
183
+ to {
184
+ opacity: 0.001;
185
+ transform: translateY(4px);
186
+ }
187
+ }
188
+ @media (prefers-reduced-motion: reduce) {
189
+ .is-entering,
190
+ .is-leaving {
191
+ animation: none;
192
+ transform: none;
193
+ }
194
+ }
122
195
  </style>
@@ -1,10 +1,21 @@
1
1
  import { type IconName } from "./icons/registry";
2
- import { type IconPreset, type IconVariant } from "./presets";
2
+ import { iconPresets, iconVariants } from "./presets";
3
3
  import type { IconProps } from "./types";
4
+ type ActionFn<T = HTMLElement, P = any> = (node: T, params?: P) => void | {
5
+ update?: (p?: P) => void;
6
+ destroy?: () => void;
7
+ };
8
+ type ActionEntry<T = HTMLElement> = [ActionFn<T, any>, any?];
9
+ type IconPresetName = keyof typeof iconPresets;
10
+ type IconVariantName = keyof typeof iconVariants;
4
11
  interface DynamicIconProps extends Omit<IconProps, "children"> {
5
- name: IconName;
6
- preset?: IconPreset;
7
- variant?: IconVariant;
12
+ name?: IconName;
13
+ preset?: IconPresetName;
14
+ variant?: IconVariantName;
15
+ class?: string;
16
+ style?: string;
17
+ /** Pasas aquí tus actions: p.ej. actions={[[lazyClass, opts]]} */
18
+ actions?: ActionEntry[];
8
19
  }
9
20
  declare const Icon: import("svelte").Component<DynamicIconProps, {}, "">;
10
21
  type Icon = ReturnType<typeof Icon>;