@genarou/blazir-icons 1.2.11 → 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 +141 -214
- package/dist/Icon.svelte.d.ts +15 -5
- package/dist/IconBase.svelte +148 -23
- package/dist/types/svelte-events.d.ts +9 -0
- package/package.json +3 -2
package/dist/Icon.svelte
CHANGED
|
@@ -1,251 +1,145 @@
|
|
|
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";
|
|
11
7
|
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
el: HTMLElement;
|
|
18
|
-
onViewportChange?: (v: boolean) => void;
|
|
19
|
-
onSizeChange?: (w: number, h: number) => void;
|
|
20
|
-
};
|
|
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 };
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
private pools = new Map<
|
|
24
|
-
RootKey,
|
|
25
|
-
{ io: IntersectionObserver; entries: Set<Entry> }
|
|
26
|
-
>();
|
|
14
|
+
type ActionEntry<T = HTMLElement> = [ActionFn<T, any>, any?];
|
|
27
15
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!pool) {
|
|
31
|
-
const io = new IntersectionObserver(
|
|
32
|
-
(entries) => {
|
|
33
|
-
const p = this.pools.get(root);
|
|
34
|
-
if (!p) return;
|
|
35
|
-
for (const e of entries) {
|
|
36
|
-
const tgt = e.target as HTMLElement;
|
|
37
|
-
for (const item of p.entries) {
|
|
38
|
-
if (item.el === tgt) {
|
|
39
|
-
item.onViewportChange?.(e.isIntersecting);
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
{ root: root as Element | undefined, rootMargin: "200px" }
|
|
46
|
-
);
|
|
47
|
-
pool = { io, entries: new Set() };
|
|
48
|
-
this.pools.set(root, pool);
|
|
49
|
-
}
|
|
16
|
+
type IconPresetName = keyof typeof iconPresets;
|
|
17
|
+
type IconVariantName = keyof typeof iconVariants;
|
|
50
18
|
|
|
51
|
-
pool.entries.add(entry);
|
|
52
|
-
pool.io.observe(entry.el);
|
|
53
|
-
|
|
54
|
-
return () => {
|
|
55
|
-
const p = this.pools.get(root);
|
|
56
|
-
if (!p) return;
|
|
57
|
-
p.io.unobserve(entry.el);
|
|
58
|
-
p.entries.delete(entry);
|
|
59
|
-
if (p.entries.size === 0) {
|
|
60
|
-
p.io.disconnect();
|
|
61
|
-
this.pools.delete(root);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
class ResizePool {
|
|
68
|
-
private ro: ResizeObserver | null = null;
|
|
69
|
-
private entries = new Set<Entry>();
|
|
70
|
-
|
|
71
|
-
private ensure() {
|
|
72
|
-
if (this.ro || typeof ResizeObserver === "undefined") return;
|
|
73
|
-
this.ro = new ResizeObserver((ros) => {
|
|
74
|
-
for (const r of ros) {
|
|
75
|
-
const tgt = r.target as HTMLElement;
|
|
76
|
-
for (const item of this.entries) {
|
|
77
|
-
if (item.el === tgt) {
|
|
78
|
-
item.onSizeChange?.(tgt.offsetWidth, tgt.offsetHeight);
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
register(entry: Entry) {
|
|
87
|
-
this.ensure();
|
|
88
|
-
this.entries.add(entry);
|
|
89
|
-
this.ro?.observe(entry.el);
|
|
90
|
-
entry.onSizeChange?.(entry.el.offsetWidth, entry.el.offsetHeight); // inicial
|
|
91
|
-
return () => {
|
|
92
|
-
this.ro?.unobserve(entry.el);
|
|
93
|
-
this.entries.delete(entry);
|
|
94
|
-
if (this.entries.size === 0) {
|
|
95
|
-
this.ro?.disconnect();
|
|
96
|
-
this.ro = null;
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const IO_POOL = new IntersectionPool();
|
|
103
|
-
const RO_POOL = new ResizePool();
|
|
104
|
-
|
|
105
|
-
function getScrollRoot(el: HTMLElement | null): Element | undefined {
|
|
106
|
-
let p: HTMLElement | null = el?.parentElement ?? null;
|
|
107
|
-
while (p) {
|
|
108
|
-
const s = getComputedStyle(p);
|
|
109
|
-
if (/(auto|scroll)/.test(`${s.overflow}${s.overflowX}${s.overflowY}`))
|
|
110
|
-
return p;
|
|
111
|
-
p = p.parentElement;
|
|
112
|
-
}
|
|
113
|
-
return undefined;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
117
|
-
// PROPS
|
|
118
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
119
19
|
interface DynamicIconProps extends Omit<IconProps, "children"> {
|
|
120
|
-
name
|
|
121
|
-
preset?:
|
|
122
|
-
variant?:
|
|
123
|
-
|
|
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[];
|
|
124
27
|
}
|
|
125
28
|
|
|
126
|
-
const
|
|
127
|
-
const inputProps = $derived(() => ({ skeleton: false, ...props }));
|
|
29
|
+
const p: DynamicIconProps = $props();
|
|
128
30
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
inputProps().preset ? iconPresets[inputProps().preset!] : {}
|
|
132
|
-
);
|
|
133
|
-
const variantProps = $derived(() =>
|
|
134
|
-
inputProps().variant ? iconVariants[inputProps().variant!] : {}
|
|
135
|
-
);
|
|
136
|
-
const mergedProps = $derived(() => ({
|
|
137
|
-
...presetProps(),
|
|
138
|
-
...variantProps(),
|
|
139
|
-
...inputProps(),
|
|
140
|
-
}));
|
|
31
|
+
let iconName = $state<IconName | null>(null);
|
|
32
|
+
let Comp = $state<any>(null);
|
|
141
33
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const { skeleton, animationDuration, animationDelay, ...rest } =
|
|
145
|
-
mergedProps();
|
|
34
|
+
let presetProps = $state<Partial<IconProps>>({});
|
|
35
|
+
let variantProps = $state<Partial<IconProps>>({});
|
|
146
36
|
|
|
147
|
-
|
|
148
|
-
|
|
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>("");
|
|
149
41
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
42
|
+
// Key para forzar remonte y reiniciar animaciones
|
|
43
|
+
let mountKey = $state(0);
|
|
44
|
+
|
|
45
|
+
// name -> componente
|
|
46
|
+
$effect(() => {
|
|
47
|
+
iconName = p.name ?? null;
|
|
48
|
+
Comp = iconName ? iconRegistry[iconName] : null;
|
|
155
49
|
});
|
|
156
50
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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] : {};
|
|
160
59
|
});
|
|
161
60
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
61
|
+
// Limpia class/style/actions del host antes de pasar al hijo
|
|
62
|
+
$effect(() => {
|
|
63
|
+
const {
|
|
64
|
+
class: _class,
|
|
65
|
+
style: _style,
|
|
66
|
+
actions: _actions,
|
|
67
|
+
...restRaw
|
|
68
|
+
} = { ...presetProps, ...variantProps, ...p } as Record<string, unknown>;
|
|
69
|
+
|
|
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
|
+
});
|
|
171
79
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
}
|
|
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
|
+
});
|
|
181
89
|
|
|
90
|
+
// Host attrs
|
|
182
91
|
$effect(() => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (!hostEl || isVisible) return;
|
|
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 ?? ""}`;
|
|
98
|
+
});
|
|
191
99
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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 = [];
|
|
198
113
|
},
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
if (typeof ResizeObserver !== "undefined") {
|
|
202
|
-
unregRO = RO_POOL.register({
|
|
203
|
-
el: hostEl,
|
|
204
|
-
onSizeChange: (w, h) => {
|
|
205
|
-
hasSize = w > 0 && h > 0;
|
|
206
|
-
tryMount();
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
} else {
|
|
210
|
-
hasSize = hostEl.offsetWidth > 0 && hostEl.offsetHeight > 0;
|
|
211
|
-
tryMount();
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return () => {
|
|
215
|
-
unregIO?.();
|
|
216
|
-
unregIO = null;
|
|
217
|
-
unregRO?.();
|
|
218
|
-
unregRO = null;
|
|
219
114
|
};
|
|
220
|
-
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Reanima al recibir el evento del action (asegúrate de que el action emita "lazyMount")
|
|
118
|
+
function onLazyMount() {
|
|
119
|
+
mountKey++;
|
|
120
|
+
}
|
|
221
121
|
</script>
|
|
222
122
|
|
|
223
123
|
{#if Comp}
|
|
224
124
|
<span
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
125
|
+
class={hostClass}
|
|
126
|
+
style={hostStyle}
|
|
127
|
+
use:applyActions={p.actions ?? []}
|
|
128
|
+
onlazyMount={onLazyMount}
|
|
229
129
|
>
|
|
230
|
-
{#
|
|
231
|
-
<Comp {...
|
|
232
|
-
{
|
|
233
|
-
<span class="bz-icon-skeleton" aria-hidden="true"></span>
|
|
234
|
-
{/if}
|
|
130
|
+
{#key mountKey}
|
|
131
|
+
<Comp {...child} />
|
|
132
|
+
{/key}
|
|
235
133
|
</span>
|
|
236
|
-
{:else}
|
|
237
|
-
<span class="bz-icon-error" data-icon={
|
|
238
|
-
⚠️ Icon not found: {
|
|
134
|
+
{:else if iconName}
|
|
135
|
+
<span class="bz-icon-error" data-icon={iconName}>
|
|
136
|
+
⚠️ Icon not found: {iconName}
|
|
239
137
|
</span>
|
|
138
|
+
{:else}
|
|
139
|
+
<span class="bz-icon-skeleton" aria-hidden="true"></span>
|
|
240
140
|
{/if}
|
|
241
141
|
|
|
242
142
|
<style>
|
|
243
|
-
.bz-icon-wrapper {
|
|
244
|
-
display: inline-flex;
|
|
245
|
-
align-items: center;
|
|
246
|
-
justify-content: center;
|
|
247
|
-
line-height: 1;
|
|
248
|
-
}
|
|
249
143
|
.bz-icon-skeleton {
|
|
250
144
|
width: 100%;
|
|
251
145
|
height: 100%;
|
|
@@ -265,4 +159,37 @@
|
|
|
265
159
|
border-radius: var(--ui-radius, 0.25rem);
|
|
266
160
|
font-weight: 600;
|
|
267
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
|
+
}
|
|
268
195
|
</style>
|
package/dist/Icon.svelte.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { type IconName } from "./icons/registry";
|
|
2
|
-
import {
|
|
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
|
|
6
|
-
preset?:
|
|
7
|
-
variant?:
|
|
8
|
-
|
|
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[];
|
|
9
19
|
}
|
|
10
20
|
declare const Icon: import("svelte").Component<DynamicIconProps, {}, "">;
|
|
11
21
|
type Icon = ReturnType<typeof Icon>;
|
package/dist/IconBase.svelte
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
+
<!-- IconBase.svelte -->
|
|
1
2
|
<script lang="ts">
|
|
2
3
|
import { type IconEffectOptions, iconEffects } from "./effects.js";
|
|
4
|
+
|
|
3
5
|
import type { IconMode, IconProps } from "./types.js";
|
|
4
|
-
import {
|
|
5
|
-
combineTransforms,
|
|
6
|
-
getAnimationClasses,
|
|
7
|
-
getAnimationStyle,
|
|
8
|
-
} from "./utils/animations.js";
|
|
6
|
+
import { combineTransforms, getAnimationStyle } from "./utils/animations.js";
|
|
9
7
|
import {
|
|
10
8
|
commonDefaults,
|
|
11
9
|
modeDefaults,
|
|
@@ -30,26 +28,31 @@
|
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
const props: IconProps & { mode?: IconMode } = $props();
|
|
31
|
+
|
|
32
|
+
// Hover (nativo + heredado)
|
|
33
33
|
let internalHovered = $state(false);
|
|
34
34
|
const isHovered = $derived(
|
|
35
35
|
internalHovered || (props.parentHoverContext?.hovered ?? false)
|
|
36
36
|
);
|
|
37
37
|
|
|
38
|
+
// Color efectivo
|
|
38
39
|
const DEFAULT_ICON_COLOR = "var(--icon-fg, var(--ui-muted-fg, currentColor))";
|
|
39
40
|
const effectiveColor = $derived(
|
|
40
41
|
(isHovered && props.hoverColor ? props.hoverColor : props.color) ??
|
|
41
42
|
DEFAULT_ICON_COLOR
|
|
42
43
|
);
|
|
43
44
|
|
|
45
|
+
// Visuales / defaults
|
|
44
46
|
const mode = $derived(props.mode ?? "solid");
|
|
45
47
|
const klass = $derived(normalizeClass(props));
|
|
46
48
|
const common = $derived(commonDefaults(props));
|
|
47
|
-
|
|
48
49
|
const propsWithEffectiveColor = $derived({ ...props, color: effectiveColor });
|
|
49
50
|
const visual = $derived(modeDefaults(mode, propsWithEffectiveColor));
|
|
50
|
-
|
|
51
|
+
|
|
52
|
+
// Sólo para variables de timing (no clases de animación)
|
|
51
53
|
const timingStyle = $derived(getAnimationStyle(props));
|
|
52
54
|
|
|
55
|
+
// Transforms declarativos (NO incluye rotate/flip; esos van en el <g> geometry)
|
|
53
56
|
const cssTransformOnly = $derived(
|
|
54
57
|
combineTransforms({
|
|
55
58
|
...props,
|
|
@@ -59,39 +62,75 @@
|
|
|
59
62
|
})
|
|
60
63
|
);
|
|
61
64
|
|
|
65
|
+
// Duraciones / easing
|
|
66
|
+
const hoverMs = $derived(
|
|
67
|
+
normalizeMs(props.transitionMs ?? props.animationDuration)
|
|
68
|
+
);
|
|
69
|
+
const hoverEase = $derived(
|
|
70
|
+
props.transitionEasing ?? props.animationEasing ?? DEFAULT_EASING
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Spin continuo → inline animation (sin clases)
|
|
62
74
|
const spinDuration = $derived(
|
|
63
75
|
props.spin
|
|
64
76
|
? props.spin === true
|
|
65
77
|
? "1s"
|
|
66
78
|
: typeof props.spin === "number"
|
|
67
79
|
? `${props.spin}ms`
|
|
68
|
-
: props.spin
|
|
80
|
+
: String(props.spin)
|
|
69
81
|
: null
|
|
70
82
|
);
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
);
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
);
|
|
84
|
+
// Pulse/Bounce/Wiggle/Heartbeat/Elastic continuos (boolean props) → inline animation
|
|
85
|
+
const wantsPulse = $derived(!!props.pulse);
|
|
86
|
+
const wantsBounce = $derived(!!props.bounce);
|
|
87
|
+
const wantsWiggle = $derived(!!props.wiggle);
|
|
88
|
+
const wantsHeartbeat = $derived(!!props.heartbeat);
|
|
89
|
+
const wantsElastic = $derived(!!props.elastic);
|
|
78
90
|
|
|
91
|
+
// Construye style inline (NO escribimos transform cuando haya capa FX)
|
|
79
92
|
const style = $derived(() => {
|
|
80
93
|
const parts: string[] = [];
|
|
94
|
+
|
|
95
|
+
// estilo previo del usuario
|
|
81
96
|
if (props.style)
|
|
82
97
|
parts.push(props.style.endsWith(";") ? props.style : `${props.style};`);
|
|
98
|
+
|
|
99
|
+
// color
|
|
100
|
+
if (effectiveColor) parts.push(`color:${effectiveColor};`);
|
|
101
|
+
|
|
102
|
+
// transform declarativo (translate/scale fuera de rotate/flip)
|
|
83
103
|
if (cssTransformOnly) parts.push(`transform:${cssTransformOnly};`);
|
|
84
104
|
parts.push("transform-origin:center;");
|
|
85
|
-
|
|
105
|
+
|
|
106
|
+
// timing variables
|
|
86
107
|
if (timingStyle) parts.push(timingStyle);
|
|
87
|
-
|
|
108
|
+
|
|
109
|
+
// transición en hover
|
|
88
110
|
parts.push(
|
|
89
111
|
`transition:color ${hoverMs}ms ${hoverEase},fill ${hoverMs}ms ${hoverEase},` +
|
|
90
112
|
`stroke ${hoverMs}ms ${hoverEase},transform ${hoverMs}ms ${hoverEase},opacity ${hoverMs}ms ${hoverEase};`
|
|
91
113
|
);
|
|
114
|
+
|
|
115
|
+
// Animaciones continuas inline (evitamos clases para no chocar)
|
|
116
|
+
const animations: string[] = [];
|
|
117
|
+
if (spinDuration)
|
|
118
|
+
animations.push(`__icon_spin ${spinDuration} linear infinite`);
|
|
119
|
+
if (wantsPulse)
|
|
120
|
+
animations.push(`bz-icon-pulse 1200ms cubic-bezier(.2,.8,.2,1) infinite`);
|
|
121
|
+
if (wantsBounce) animations.push(`bz-icon-bounce 400ms ease infinite`);
|
|
122
|
+
if (wantsWiggle)
|
|
123
|
+
animations.push(`bz-icon-wiggle 200ms ease-in-out infinite`);
|
|
124
|
+
if (wantsElastic)
|
|
125
|
+
animations.push(`bz-icon-elastic 300ms ease-out infinite`);
|
|
126
|
+
if (wantsHeartbeat)
|
|
127
|
+
animations.push(`bz-icon-heartbeat 1000ms ease-in-out infinite`);
|
|
128
|
+
if (animations.length) parts.push(`animation:${animations.join(",")};`);
|
|
129
|
+
|
|
92
130
|
return parts.join(" ");
|
|
93
131
|
});
|
|
94
132
|
|
|
133
|
+
// A11y
|
|
95
134
|
const ariaHidden = $derived(props.decorative ? "true" : undefined);
|
|
96
135
|
const computedTitleId = $derived(
|
|
97
136
|
props.title ? (props.titleId ?? `bz-icon-title-${uid()}`) : undefined
|
|
@@ -103,9 +142,12 @@
|
|
|
103
142
|
!props.decorative && props.title ? computedTitleId : undefined
|
|
104
143
|
);
|
|
105
144
|
|
|
145
|
+
// Tamaño final
|
|
106
146
|
const finalSize = $derived(
|
|
107
147
|
typeof common.size === "number" ? `${common.size}px` : common.size
|
|
108
148
|
);
|
|
149
|
+
|
|
150
|
+
// Atributos seguros
|
|
109
151
|
const safeAttrs = $derived(() => {
|
|
110
152
|
const a = { ...(props.attrs ?? {}) };
|
|
111
153
|
delete (a as any).width;
|
|
@@ -113,10 +155,12 @@
|
|
|
113
155
|
return a;
|
|
114
156
|
});
|
|
115
157
|
|
|
158
|
+
// 👇 Importante: NO agregamos animationClasses aquí para evitar choques
|
|
116
159
|
const finalClass = $derived(
|
|
117
|
-
`bz-icon ${klass} ${
|
|
160
|
+
`bz-icon ${klass} ${props.chevronState ? "bz-icon-chevron" : ""}`.trim()
|
|
118
161
|
);
|
|
119
162
|
|
|
163
|
+
// Rotate/Flip a nivel de GEOMETRÍA (en el <g>)
|
|
120
164
|
function getViewBoxCenter(vb: string) {
|
|
121
165
|
const [minX, minY, w, h] = vb.trim().split(/\s+/).map(Number);
|
|
122
166
|
return { cx: minX + w / 2, cy: minY + h / 2 };
|
|
@@ -141,9 +185,13 @@
|
|
|
141
185
|
return cmds.join(" ");
|
|
142
186
|
});
|
|
143
187
|
|
|
188
|
+
// Capa FX: efectos programáticos (scale/slide/rotateOnHover/pressScale)
|
|
144
189
|
let svgRef: SVGSVGElement | null = $state(null);
|
|
190
|
+
let fxLayer: SVGGElement | null = $state(null);
|
|
191
|
+
|
|
145
192
|
$effect(() => {
|
|
146
|
-
if (!
|
|
193
|
+
if (!fxLayer) return;
|
|
194
|
+
|
|
147
195
|
const effectsOpts: IconEffectOptions = { ...(props.effects ?? {}) };
|
|
148
196
|
const a = props.attrs ?? {};
|
|
149
197
|
if (a.spinOnHover) effectsOpts.spinOnHover = true;
|
|
@@ -155,10 +203,10 @@
|
|
|
155
203
|
if (a.heartbeatOnActive) effectsOpts.heartbeatOnActive = true;
|
|
156
204
|
if (a.hoverScale) effectsOpts.hoverScale = a.hoverScale;
|
|
157
205
|
if (a.pressScale) effectsOpts.pressScale = a.pressScale;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
206
|
+
|
|
207
|
+
// ⛳ Aplica transforms sobre fxLayer (no sobre el <svg>)
|
|
208
|
+
const controller = iconEffects(fxLayer, effectsOpts);
|
|
209
|
+
return () => controller.destroy();
|
|
162
210
|
});
|
|
163
211
|
|
|
164
212
|
function handleMouseEnter() {
|
|
@@ -195,7 +243,9 @@
|
|
|
195
243
|
onmouseleave={handleMouseLeave}
|
|
196
244
|
>
|
|
197
245
|
{#if props.title}<title id={computedTitleId}>{props.title}</title>{/if}
|
|
198
|
-
|
|
246
|
+
|
|
247
|
+
<!-- 🎯 CAPA FX: aquí se aplican transformaciones programáticas (iconEffects) -->
|
|
248
|
+
<g bind:this={fxLayer} transform={svgTransform()}>
|
|
199
249
|
{@render props.children?.()}
|
|
200
250
|
</g>
|
|
201
251
|
</svg>
|
|
@@ -217,4 +267,79 @@
|
|
|
217
267
|
}
|
|
218
268
|
}
|
|
219
269
|
}
|
|
270
|
+
|
|
271
|
+
/* Keyframes usados por animaciones inline */
|
|
272
|
+
@keyframes __icon_spin {
|
|
273
|
+
to {
|
|
274
|
+
transform: rotate(360deg);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
@keyframes bz-icon-bounce {
|
|
278
|
+
0% {
|
|
279
|
+
transform: translateY(0);
|
|
280
|
+
}
|
|
281
|
+
35% {
|
|
282
|
+
transform: translateY(-18%);
|
|
283
|
+
}
|
|
284
|
+
70% {
|
|
285
|
+
transform: translateY(0);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
@keyframes bz-icon-pulse {
|
|
289
|
+
0%,
|
|
290
|
+
100% {
|
|
291
|
+
transform: scale(1);
|
|
292
|
+
opacity: 1;
|
|
293
|
+
}
|
|
294
|
+
50% {
|
|
295
|
+
transform: scale(1.04);
|
|
296
|
+
opacity: 0.92;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
@keyframes bz-icon-wiggle {
|
|
300
|
+
0% {
|
|
301
|
+
transform: rotate(0deg);
|
|
302
|
+
}
|
|
303
|
+
25% {
|
|
304
|
+
transform: rotate(-3deg);
|
|
305
|
+
}
|
|
306
|
+
75% {
|
|
307
|
+
transform: rotate(3deg);
|
|
308
|
+
}
|
|
309
|
+
100% {
|
|
310
|
+
transform: rotate(0deg);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
@keyframes bz-icon-elastic {
|
|
314
|
+
0% {
|
|
315
|
+
transform: scale(1);
|
|
316
|
+
}
|
|
317
|
+
30% {
|
|
318
|
+
transform: scale(0.8);
|
|
319
|
+
}
|
|
320
|
+
60% {
|
|
321
|
+
transform: scale(1.15);
|
|
322
|
+
}
|
|
323
|
+
100% {
|
|
324
|
+
transform: scale(1);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
@keyframes bz-icon-heartbeat {
|
|
328
|
+
0%,
|
|
329
|
+
100% {
|
|
330
|
+
transform: scale(1);
|
|
331
|
+
}
|
|
332
|
+
14% {
|
|
333
|
+
transform: scale(1.1);
|
|
334
|
+
}
|
|
335
|
+
28% {
|
|
336
|
+
transform: scale(1);
|
|
337
|
+
}
|
|
338
|
+
42% {
|
|
339
|
+
transform: scale(1.1);
|
|
340
|
+
}
|
|
341
|
+
70% {
|
|
342
|
+
transform: scale(1);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
220
345
|
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="svelte" />
|
|
2
|
+
|
|
3
|
+
// No exportes nada aquí: debe ser una augmentación global
|
|
4
|
+
declare namespace svelte.JSX {
|
|
5
|
+
interface HTMLAttributes<T> {
|
|
6
|
+
onlazyMount?: (e: CustomEvent<any>) => void;
|
|
7
|
+
onlazyUnmount?: (e: CustomEvent<any>) => void;
|
|
8
|
+
}
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genarou/blazir-icons",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"preview": "vite preview",
|
|
36
36
|
"prepublishOnly": "npm run build",
|
|
37
37
|
"clean": "rm -rf dist",
|
|
38
|
-
"prebuild": "npm run clean"
|
|
38
|
+
"prebuild": "npm run clean",
|
|
39
|
+
"try": "npm run build && vite --config play/vite.config.js"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@sveltejs/package": "^2.5.0",
|