@genarou/blazir-icons 1.2.13 → 1.2.15
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 +122 -61
- package/dist/IconBase.svelte +158 -111
- package/dist/effects.d.ts +2 -0
- package/dist/effects.js +187 -95
- package/dist/global.d.ts +8 -0
- package/dist/icons/EmailAnimated.svelte +7 -32
- package/dist/icons/FileUploadAnimated.svelte +53 -68
- package/dist/icons/Heart.svelte +22 -0
- package/dist/icons/Heart.svelte.d.ts +4 -0
- package/dist/icons/ImageAnimated.svelte +38 -41
- package/dist/icons/Moon.svelte +19 -0
- package/dist/icons/Moon.svelte.d.ts +4 -0
- package/dist/icons/Png.svelte +17 -37
- package/dist/icons/Rocket.svelte +19 -0
- package/dist/icons/Rocket.svelte.d.ts +4 -0
- package/dist/icons/Share.svelte +19 -0
- package/dist/icons/Share.svelte.d.ts +4 -0
- package/dist/icons/Sun.svelte +19 -0
- package/dist/icons/Sun.svelte.d.ts +4 -0
- package/dist/icons/Timer.svelte +19 -0
- package/dist/icons/Timer.svelte.d.ts +4 -0
- package/dist/icons/World.svelte +19 -0
- package/dist/icons/World.svelte.d.ts +4 -0
- package/dist/icons/Xml.svelte +16 -18
- package/dist/icons/registry.d.ts +7 -0
- package/dist/icons/registry.js +14 -0
- package/dist/icons-api.d.ts +8 -1
- package/dist/icons-api.js +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/utils/debounce.d.ts +20 -0
- package/dist/utils/debounce.js +88 -0
- package/package.json +6 -2
package/dist/Icon.svelte
CHANGED
|
@@ -28,84 +28,126 @@
|
|
|
28
28
|
|
|
29
29
|
const p: DynamicIconProps = $props();
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// 🔥 OPTIMIZACIÓN: Cache para props combinadas
|
|
32
|
+
const propsCache = new Map<string, any>();
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// 🔥 OPTIMIZACIÓN: Derivados en lugar de effects + states
|
|
35
|
+
const iconName = $derived(p.name ?? null);
|
|
36
|
+
const Comp = $derived(iconName ? iconRegistry[iconName] : null);
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
let hostClass = $state<string>("bz-icon-wrapper");
|
|
40
|
-
let hostStyle = $state<string>("");
|
|
41
|
-
|
|
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;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// preset/variant -> props parciales
|
|
52
|
-
$effect(() => {
|
|
38
|
+
// 🔥 OPTIMIZACIÓN: Preset y variant como derivados
|
|
39
|
+
const presetProps = $derived(() => {
|
|
53
40
|
const key = p.preset as IconPresetName | undefined;
|
|
54
|
-
|
|
41
|
+
return key ? iconPresets[key] : {};
|
|
55
42
|
});
|
|
56
|
-
|
|
43
|
+
|
|
44
|
+
const variantProps = $derived(() => {
|
|
57
45
|
const key = p.variant as IconVariantName | undefined;
|
|
58
|
-
|
|
46
|
+
return key ? iconVariants[key] : {};
|
|
59
47
|
});
|
|
60
48
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
49
|
+
// 🔥 OPTIMIZACIÓN: Helper para normalizar ms
|
|
50
|
+
function toMs(v: unknown): string | undefined {
|
|
51
|
+
if (v == null) return undefined;
|
|
52
|
+
return typeof v === "number" ? `${v}ms` : String(v);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 🔥 OPTIMIZACIÓN: Child props con memoización
|
|
56
|
+
const child = $derived(() => {
|
|
57
|
+
const cacheKey = `${p.preset || ""}-${p.variant || ""}-${p.name || ""}`;
|
|
58
|
+
|
|
59
|
+
// Check cache primero
|
|
60
|
+
const cached = propsCache.get(cacheKey);
|
|
61
|
+
if (cached && cached.timestamp > Date.now() - 1000) {
|
|
62
|
+
// Cache válido por 1s
|
|
63
|
+
return { ...cached.props, ...p };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const merged = {
|
|
67
|
+
...presetProps(),
|
|
68
|
+
...variantProps(),
|
|
69
|
+
...p,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Extraer y eliminar propiedades que no van al hijo
|
|
73
|
+
const { class: _class, style: _style, actions: _actions, ...rest } = merged;
|
|
74
|
+
|
|
75
|
+
// Normalizar animationDuration y animationDelay
|
|
76
|
+
const normalized: any = { ...rest };
|
|
77
|
+
|
|
78
|
+
if (normalized.animationDuration !== undefined) {
|
|
79
|
+
normalized.animationDuration = toMs(normalized.animationDuration);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (normalized.animationDelay !== undefined) {
|
|
83
|
+
normalized.animationDelay = toMs(normalized.animationDelay);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const result = normalized as IconProps;
|
|
87
|
+
|
|
88
|
+
// Guardar en cache
|
|
89
|
+
propsCache.set(cacheKey, {
|
|
90
|
+
props: result,
|
|
91
|
+
timestamp: Date.now(),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Limpiar cache viejo (mantener solo últimos 50)
|
|
95
|
+
if (propsCache.size > 50) {
|
|
96
|
+
const firstKey = propsCache.keys().next().value;
|
|
97
|
+
if (firstKey !== undefined) {
|
|
98
|
+
propsCache.delete(firstKey);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return result;
|
|
78
103
|
});
|
|
79
104
|
|
|
80
|
-
//
|
|
81
|
-
$
|
|
105
|
+
// 🔥 OPTIMIZACIÓN: Box size como derivado
|
|
106
|
+
const boxSize = $derived(() => {
|
|
82
107
|
const rawSize =
|
|
83
108
|
(p as any).size ??
|
|
84
|
-
(presetProps as any).size ??
|
|
85
|
-
(variantProps as any).size;
|
|
109
|
+
(presetProps() as any).size ??
|
|
110
|
+
(variantProps() as any).size;
|
|
86
111
|
const s = coerceSize(rawSize, 24);
|
|
87
|
-
|
|
112
|
+
return typeof s === "number" ? `${s}px` : s;
|
|
88
113
|
});
|
|
89
114
|
|
|
90
|
-
// Host
|
|
91
|
-
$
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
// 🔥 OPTIMIZACIÓN: Host class como derivado
|
|
116
|
+
const hostClass = $derived(`bz-icon-wrapper ${p.class ?? ""}`.trim());
|
|
117
|
+
|
|
118
|
+
// 🔥 OPTIMIZACIÓN: Host style como derivado
|
|
119
|
+
const hostStyle = $derived(() => {
|
|
120
|
+
const size = boxSize();
|
|
121
|
+
return (
|
|
122
|
+
`--bz-icon-size:${size};` +
|
|
123
|
+
`width:${size};height:${size};` +
|
|
124
|
+
`inline-size:${size};block-size:${size};` +
|
|
125
|
+
`${p.style ?? ""}`
|
|
126
|
+
);
|
|
98
127
|
});
|
|
99
128
|
|
|
100
|
-
//
|
|
129
|
+
// Key para forzar remonte y reiniciar animaciones
|
|
130
|
+
let mountKey = $state(0);
|
|
131
|
+
|
|
132
|
+
// 🔥 OPTIMIZACIÓN: Bridge de actions más eficiente
|
|
101
133
|
function applyActions(node: HTMLElement, entries: ActionEntry[] = []) {
|
|
102
|
-
|
|
103
|
-
|
|
134
|
+
if (!entries || entries.length === 0) {
|
|
135
|
+
return { update() {}, destroy() {} };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let handles = entries
|
|
139
|
+
.map(([fn, params]) => fn?.(node, params))
|
|
140
|
+
.filter(Boolean);
|
|
141
|
+
|
|
104
142
|
return {
|
|
105
143
|
update(next: ActionEntry[] = []) {
|
|
144
|
+
// Cleanup anterior
|
|
106
145
|
handles.forEach((h) => h?.destroy?.());
|
|
107
|
-
|
|
108
|
-
|
|
146
|
+
|
|
147
|
+
// Aplicar nuevos
|
|
148
|
+
handles = next
|
|
149
|
+
.map(([fn, params]) => fn?.(node, params))
|
|
150
|
+
.filter(Boolean);
|
|
109
151
|
},
|
|
110
152
|
destroy() {
|
|
111
153
|
handles.forEach((h) => h?.destroy?.());
|
|
@@ -114,7 +156,7 @@
|
|
|
114
156
|
};
|
|
115
157
|
}
|
|
116
158
|
|
|
117
|
-
// Reanima al recibir el evento del action
|
|
159
|
+
// Reanima al recibir el evento del action
|
|
118
160
|
function onLazyMount() {
|
|
119
161
|
mountKey++;
|
|
120
162
|
}
|
|
@@ -123,12 +165,12 @@
|
|
|
123
165
|
{#if Comp}
|
|
124
166
|
<span
|
|
125
167
|
class={hostClass}
|
|
126
|
-
style={hostStyle}
|
|
168
|
+
style={hostStyle()}
|
|
127
169
|
use:applyActions={p.actions ?? []}
|
|
128
170
|
onlazyMount={onLazyMount}
|
|
129
171
|
>
|
|
130
172
|
{#key mountKey}
|
|
131
|
-
<Comp {...child} />
|
|
173
|
+
<Comp {...child()} />
|
|
132
174
|
{/key}
|
|
133
175
|
</span>
|
|
134
176
|
{:else if iconName}
|
|
@@ -140,6 +182,25 @@
|
|
|
140
182
|
{/if}
|
|
141
183
|
|
|
142
184
|
<style>
|
|
185
|
+
.bz-icon-wrapper {
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
display: inline-flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
justify-content: center;
|
|
190
|
+
position: relative;
|
|
191
|
+
pointer-events: auto;
|
|
192
|
+
contain: layout style paint;
|
|
193
|
+
content-visibility: auto;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.bz-icon-wrapper :global(svg) {
|
|
197
|
+
pointer-events: bounding-box !important;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.bz-icon-wrapper :global(svg *) {
|
|
201
|
+
pointer-events: none !important;
|
|
202
|
+
}
|
|
203
|
+
|
|
143
204
|
.bz-icon-skeleton {
|
|
144
205
|
width: 100%;
|
|
145
206
|
height: 100%;
|
package/dist/IconBase.svelte
CHANGED
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
<!-- IconBase.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { type IconEffectOptions, iconEffects } from "./effects.js";
|
|
4
|
-
|
|
5
4
|
import type { IconMode, IconProps } from "./types.js";
|
|
6
|
-
import {
|
|
5
|
+
import { getAnimationStyle } from "./utils/animations.js";
|
|
7
6
|
import {
|
|
8
7
|
commonDefaults,
|
|
9
8
|
modeDefaults,
|
|
10
9
|
normalizeClass,
|
|
11
10
|
} from "./utils/defaults.js";
|
|
12
11
|
|
|
12
|
+
// Constantes
|
|
13
13
|
const DEFAULT_MS = 180;
|
|
14
14
|
const DEFAULT_EASING = "cubic-bezier(.2,.8,.2,1)";
|
|
15
|
+
const DEFAULT_ICON_COLOR = "var(--icon-fg, var(--ui-muted-fg, currentColor))";
|
|
16
|
+
|
|
17
|
+
const MS_REGEX = /ms$/;
|
|
18
|
+
const S_REGEX = /s$/;
|
|
15
19
|
|
|
16
20
|
function normalizeMs(v?: number | string): number {
|
|
17
21
|
if (v == null) return DEFAULT_MS;
|
|
18
22
|
if (typeof v === "number") return v;
|
|
19
23
|
const s = String(v).trim();
|
|
20
|
-
if (
|
|
21
|
-
if (
|
|
24
|
+
if (MS_REGEX.test(s)) return parseFloat(s);
|
|
25
|
+
if (S_REGEX.test(s)) return parseFloat(s) * 1000;
|
|
22
26
|
const n = parseFloat(s);
|
|
23
27
|
return Number.isFinite(n) ? n : DEFAULT_MS;
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
let uidCounter = 0;
|
|
26
31
|
function uid(): string {
|
|
27
|
-
return
|
|
32
|
+
return `bz-${Date.now().toString(36)}-${(uidCounter++).toString(36)}`;
|
|
28
33
|
}
|
|
29
34
|
|
|
35
|
+
// Props
|
|
30
36
|
const props: IconProps & { mode?: IconMode } = $props();
|
|
31
37
|
|
|
32
38
|
// Hover (nativo + heredado)
|
|
@@ -35,34 +41,23 @@
|
|
|
35
41
|
internalHovered || (props.parentHoverContext?.hovered ?? false)
|
|
36
42
|
);
|
|
37
43
|
|
|
38
|
-
// Color
|
|
39
|
-
const DEFAULT_ICON_COLOR = "var(--icon-fg, var(--ui-muted-fg, currentColor))";
|
|
44
|
+
// Color
|
|
40
45
|
const effectiveColor = $derived(
|
|
41
46
|
(isHovered && props.hoverColor ? props.hoverColor : props.color) ??
|
|
42
47
|
DEFAULT_ICON_COLOR
|
|
43
48
|
);
|
|
44
49
|
|
|
45
|
-
//
|
|
50
|
+
// Mode
|
|
46
51
|
const mode = $derived(props.mode ?? "solid");
|
|
52
|
+
|
|
53
|
+
// Derivados base
|
|
47
54
|
const klass = $derived(normalizeClass(props));
|
|
48
55
|
const common = $derived(commonDefaults(props));
|
|
49
56
|
const propsWithEffectiveColor = $derived({ ...props, color: effectiveColor });
|
|
50
57
|
const visual = $derived(modeDefaults(mode, propsWithEffectiveColor));
|
|
51
|
-
|
|
52
|
-
// Sólo para variables de timing (no clases de animación)
|
|
53
58
|
const timingStyle = $derived(getAnimationStyle(props));
|
|
54
59
|
|
|
55
|
-
//
|
|
56
|
-
const cssTransformOnly = $derived(
|
|
57
|
-
combineTransforms({
|
|
58
|
-
...props,
|
|
59
|
-
rotate: undefined as any,
|
|
60
|
-
flipH: false,
|
|
61
|
-
flipV: false,
|
|
62
|
-
})
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// Duraciones / easing
|
|
60
|
+
// Transiciones
|
|
66
61
|
const hoverMs = $derived(
|
|
67
62
|
normalizeMs(props.transitionMs ?? props.animationDuration)
|
|
68
63
|
);
|
|
@@ -70,62 +65,149 @@
|
|
|
70
65
|
props.transitionEasing ?? props.animationEasing ?? DEFAULT_EASING
|
|
71
66
|
);
|
|
72
67
|
|
|
73
|
-
//
|
|
74
|
-
const spinDuration = $derived(
|
|
75
|
-
props.spin
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
: null
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
// Pulse/Bounce/Wiggle/Heartbeat/Elastic continuos (boolean props) → inline animation
|
|
68
|
+
// Animaciones continuas
|
|
69
|
+
const spinDuration = $derived(() => {
|
|
70
|
+
if (!props.spin) return null;
|
|
71
|
+
if (props.spin === true) return "1s";
|
|
72
|
+
return typeof props.spin === "number"
|
|
73
|
+
? `${props.spin}ms`
|
|
74
|
+
: String(props.spin);
|
|
75
|
+
});
|
|
85
76
|
const wantsPulse = $derived(!!props.pulse);
|
|
86
77
|
const wantsBounce = $derived(!!props.bounce);
|
|
87
78
|
const wantsWiggle = $derived(!!props.wiggle);
|
|
88
79
|
const wantsHeartbeat = $derived(!!props.heartbeat);
|
|
89
80
|
const wantsElastic = $derived(!!props.elastic);
|
|
90
81
|
|
|
91
|
-
//
|
|
82
|
+
// ===== Chevron state ⇒ grados (se animará por CSS) =====
|
|
83
|
+
const effectiveChevronState = $derived(() => {
|
|
84
|
+
if (props.chevronState) return props.chevronState;
|
|
85
|
+
if (props.chevronOpen !== undefined) {
|
|
86
|
+
return props.chevronOpen ? "open" : "closed";
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const chevronDeg = $derived((): number | null => {
|
|
92
|
+
switch (effectiveChevronState()) {
|
|
93
|
+
case "open":
|
|
94
|
+
case "down":
|
|
95
|
+
return 90;
|
|
96
|
+
case "up":
|
|
97
|
+
return -90;
|
|
98
|
+
case "left":
|
|
99
|
+
return 180;
|
|
100
|
+
case "right":
|
|
101
|
+
case "closed":
|
|
102
|
+
return 0;
|
|
103
|
+
default:
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Clase final
|
|
109
|
+
const finalClass = $derived(
|
|
110
|
+
`bz-icon ${klass}${effectiveChevronState() ? " bz-icon-chevron" : ""}`.trim()
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// ===== SVG transform (solo rotate/flip declarativos) =====
|
|
114
|
+
// Rot/flip en <g> para no interferir con la transición del <svg>
|
|
115
|
+
function getViewBoxCenter(vb: string) {
|
|
116
|
+
const parts = vb.trim().split(/\s+/);
|
|
117
|
+
const minX = Number(parts[0]);
|
|
118
|
+
const minY = Number(parts[1]);
|
|
119
|
+
const w = Number(parts[2]);
|
|
120
|
+
const h = Number(parts[3]);
|
|
121
|
+
return { cx: minX + w / 2, cy: minY + h / 2 };
|
|
122
|
+
}
|
|
123
|
+
const center = $derived(getViewBoxCenter(common.viewBox));
|
|
124
|
+
|
|
125
|
+
const svgTransform = $derived(() => {
|
|
126
|
+
const cmds: string[] = [];
|
|
127
|
+
|
|
128
|
+
// Rotate (prop)
|
|
129
|
+
if (props.rotate != null) {
|
|
130
|
+
const r =
|
|
131
|
+
typeof props.rotate === "number"
|
|
132
|
+
? props.rotate
|
|
133
|
+
: parseFloat(String(props.rotate));
|
|
134
|
+
if (!isNaN(r)) cmds.push(`rotate(${r} ${center.cx} ${center.cy})`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Flip (prop)
|
|
138
|
+
if (props.flipH || props.flipV) {
|
|
139
|
+
const sx = props.flipH ? -1 : 1;
|
|
140
|
+
const sy = props.flipV ? -1 : 1;
|
|
141
|
+
cmds.push(
|
|
142
|
+
`translate(${center.cx} ${center.cy}) scale(${sx} ${sy}) translate(${-center.cx} ${-center.cy})`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ⛔️ OJO: chevron NO va aquí (aquí no hay transición CSS)
|
|
147
|
+
return cmds.length ? cmds.join(" ") : "";
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// ===== Style inline del <svg> (incluye transición y chevron por CSS) =====
|
|
92
151
|
const style = $derived(() => {
|
|
93
152
|
const parts: string[] = [];
|
|
94
153
|
|
|
95
154
|
// estilo previo del usuario
|
|
96
|
-
if (props.style)
|
|
155
|
+
if (props.style) {
|
|
97
156
|
parts.push(props.style.endsWith(";") ? props.style : `${props.style};`);
|
|
157
|
+
}
|
|
98
158
|
|
|
99
159
|
// color
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// transform declarativo (translate/scale fuera de rotate/flip)
|
|
103
|
-
if (cssTransformOnly) parts.push(`transform:${cssTransformOnly};`);
|
|
104
|
-
parts.push("transform-origin:center;");
|
|
160
|
+
parts.push(`color:${effectiveColor};`);
|
|
105
161
|
|
|
106
|
-
// timing
|
|
107
|
-
|
|
162
|
+
// Vars de timing (si las hay)
|
|
163
|
+
const tstyle = timingStyle;
|
|
164
|
+
if (tstyle) parts.push(tstyle);
|
|
108
165
|
|
|
109
|
-
// transición
|
|
166
|
+
// transición para transform/opacidad/stroke/fill
|
|
110
167
|
parts.push(
|
|
111
168
|
`transition:color ${hoverMs}ms ${hoverEase},fill ${hoverMs}ms ${hoverEase},` +
|
|
112
169
|
`stroke ${hoverMs}ms ${hoverEase},transform ${hoverMs}ms ${hoverEase},opacity ${hoverMs}ms ${hoverEase};`
|
|
113
170
|
);
|
|
114
171
|
|
|
115
|
-
//
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
172
|
+
// ✅ Var CSS para chevron (si hay estado). Usamos rotate(Xdeg)
|
|
173
|
+
const deg = chevronDeg();
|
|
174
|
+
if (deg != null) {
|
|
175
|
+
parts.push(`--bz-chevron-rot: rotate(${deg}deg);`);
|
|
176
|
+
} else {
|
|
177
|
+
// mantener consistente
|
|
178
|
+
parts.push(`--bz-chevron-rot: rotate(0deg);`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Transform del SVG: incluir SIEMPRE la var del chevron al final
|
|
182
|
+
// (puedes añadir más términos aquí si usas CSS-only transforms)
|
|
183
|
+
parts.push(`transform-origin:center;`);
|
|
184
|
+
parts.push(`transform: var(--bz-chevron-rot);`);
|
|
185
|
+
|
|
186
|
+
// Animaciones declarativas (spin/pulse/etc) — se apilan con transform
|
|
187
|
+
const spin = spinDuration();
|
|
188
|
+
if (
|
|
189
|
+
spin ||
|
|
190
|
+
wantsPulse ||
|
|
191
|
+
wantsBounce ||
|
|
192
|
+
wantsWiggle ||
|
|
193
|
+
wantsElastic ||
|
|
194
|
+
wantsHeartbeat
|
|
195
|
+
) {
|
|
196
|
+
const animations: string[] = [];
|
|
197
|
+
if (spin) animations.push(`__icon_spin ${spin} linear infinite`);
|
|
198
|
+
if (wantsPulse)
|
|
199
|
+
animations.push(
|
|
200
|
+
`bz-icon-pulse 1200ms cubic-bezier(.2,.8,.2,1) infinite`
|
|
201
|
+
);
|
|
202
|
+
if (wantsBounce) animations.push(`bz-icon-bounce 400ms ease infinite`);
|
|
203
|
+
if (wantsWiggle)
|
|
204
|
+
animations.push(`bz-icon-wiggle 200ms ease-in-out infinite`);
|
|
205
|
+
if (wantsElastic)
|
|
206
|
+
animations.push(`bz-icon-elastic 300ms ease-out infinite`);
|
|
207
|
+
if (wantsHeartbeat)
|
|
208
|
+
animations.push(`bz-icon-heartbeat 1000ms ease-in-out infinite`);
|
|
209
|
+
parts.push(`animation:${animations.join(",")};`);
|
|
210
|
+
}
|
|
129
211
|
|
|
130
212
|
return parts.join(" ");
|
|
131
213
|
});
|
|
@@ -142,69 +224,37 @@
|
|
|
142
224
|
!props.decorative && props.title ? computedTitleId : undefined
|
|
143
225
|
);
|
|
144
226
|
|
|
145
|
-
// Tamaño
|
|
227
|
+
// Tamaño y attrs
|
|
146
228
|
const finalSize = $derived(
|
|
147
229
|
typeof common.size === "number" ? `${common.size}px` : common.size
|
|
148
230
|
);
|
|
149
|
-
|
|
150
|
-
// Atributos seguros
|
|
151
231
|
const safeAttrs = $derived(() => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return a;
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// 👇 Importante: NO agregamos animationClasses aquí para evitar choques
|
|
159
|
-
const finalClass = $derived(
|
|
160
|
-
`bz-icon ${klass} ${props.chevronState ? "bz-icon-chevron" : ""}`.trim()
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
// Rotate/Flip a nivel de GEOMETRÍA (en el <g>)
|
|
164
|
-
function getViewBoxCenter(vb: string) {
|
|
165
|
-
const [minX, minY, w, h] = vb.trim().split(/\s+/).map(Number);
|
|
166
|
-
return { cx: minX + w / 2, cy: minY + h / 2 };
|
|
167
|
-
}
|
|
168
|
-
const center = $derived(getViewBoxCenter(common.viewBox));
|
|
169
|
-
const svgTransform = $derived(() => {
|
|
170
|
-
const cmds: string[] = [];
|
|
171
|
-
if (props.rotate != null) {
|
|
172
|
-
const r =
|
|
173
|
-
typeof props.rotate === "number"
|
|
174
|
-
? props.rotate
|
|
175
|
-
: parseFloat(String(props.rotate));
|
|
176
|
-
if (!isNaN(r)) cmds.push(`rotate(${r} ${center.cx} ${center.cy})`);
|
|
177
|
-
}
|
|
178
|
-
if (props.flipH || props.flipV) {
|
|
179
|
-
const sx = props.flipH ? -1 : 1,
|
|
180
|
-
sy = props.flipV ? -1 : 1;
|
|
181
|
-
cmds.push(
|
|
182
|
-
`translate(${center.cx} ${center.cy}) scale(${sx} ${sy}) translate(${-center.cx} ${-center.cy})`
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
return cmds.join(" ");
|
|
232
|
+
if (!props.attrs) return {};
|
|
233
|
+
const { width, height, ...rest } = props.attrs as any;
|
|
234
|
+
return rest;
|
|
186
235
|
});
|
|
187
236
|
|
|
188
|
-
//
|
|
237
|
+
// FX layer
|
|
189
238
|
let svgRef: SVGSVGElement | null = $state(null);
|
|
190
239
|
let fxLayer: SVGGElement | null = $state(null);
|
|
191
240
|
|
|
241
|
+
// Efectos (incluye heartbeatOnHover si viene en attrs/effects)
|
|
192
242
|
$effect(() => {
|
|
193
243
|
if (!fxLayer) return;
|
|
194
|
-
|
|
195
244
|
const effectsOpts: IconEffectOptions = { ...(props.effects ?? {}) };
|
|
196
|
-
const a = props.attrs
|
|
197
|
-
if (a
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
245
|
+
const a = props.attrs;
|
|
246
|
+
if (a) {
|
|
247
|
+
if (a.spinOnHover) effectsOpts.spinOnHover = true;
|
|
248
|
+
if (a.bounceOnHover) effectsOpts.bounceOnHover = true;
|
|
249
|
+
if (a.wiggleOnHover) effectsOpts.wiggleOnHover = true;
|
|
250
|
+
if (a.slideOnHover) effectsOpts.slideOnHover = a.slideOnHover;
|
|
251
|
+
if (a.morphOnHover) effectsOpts.morphOnHover = a.morphOnHover;
|
|
252
|
+
if (a.elasticOnClick) effectsOpts.elasticOnClick = true;
|
|
253
|
+
if (a.heartbeatOnActive) effectsOpts.heartbeatOnActive = true;
|
|
254
|
+
if ((a as any).heartbeatOnHover) effectsOpts.heartbeatOnHover = true;
|
|
255
|
+
if (a.hoverScale) effectsOpts.hoverScale = a.hoverScale;
|
|
256
|
+
if (a.pressScale) effectsOpts.pressScale = a.pressScale;
|
|
257
|
+
}
|
|
208
258
|
const controller = iconEffects(fxLayer, effectsOpts);
|
|
209
259
|
return () => controller.destroy();
|
|
210
260
|
});
|
|
@@ -243,8 +293,6 @@
|
|
|
243
293
|
onmouseleave={handleMouseLeave}
|
|
244
294
|
>
|
|
245
295
|
{#if props.title}<title id={computedTitleId}>{props.title}</title>{/if}
|
|
246
|
-
|
|
247
|
-
<!-- 🎯 CAPA FX: aquí se aplican transformaciones programáticas (iconEffects) -->
|
|
248
296
|
<g bind:this={fxLayer} transform={svgTransform()}>
|
|
249
297
|
{@render props.children?.()}
|
|
250
298
|
</g>
|
|
@@ -268,7 +316,6 @@
|
|
|
268
316
|
}
|
|
269
317
|
}
|
|
270
318
|
|
|
271
|
-
/* Keyframes usados por animaciones inline */
|
|
272
319
|
@keyframes __icon_spin {
|
|
273
320
|
to {
|
|
274
321
|
transform: rotate(360deg);
|
package/dist/effects.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export type IconEffectOptions = {
|
|
|
6
6
|
morphOnHover?: "play" | "pause" | "menu" | "close" | "arrow" | "check";
|
|
7
7
|
elasticOnClick?: boolean;
|
|
8
8
|
heartbeatOnActive?: boolean;
|
|
9
|
+
/** ✅ nuevo: heartbeat al hacer hover */
|
|
10
|
+
heartbeatOnHover?: boolean;
|
|
9
11
|
pulse?: boolean;
|
|
10
12
|
hoverScale?: number;
|
|
11
13
|
pressScale?: number;
|