@genarou/blazir-icons 1.1.17 → 1.2.0

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 (162) hide show
  1. package/dist/Icon.svelte +108 -66
  2. package/dist/Icon.svelte.d.ts +6 -3
  3. package/dist/IconBase.svelte +110 -59
  4. package/dist/IconBase.svelte.d.ts +1 -1
  5. package/dist/{actions.d.ts → effects.d.ts} +9 -7
  6. package/dist/effects.js +128 -0
  7. package/dist/{styles/animations.css → icon-animation.css} +19 -52
  8. package/dist/icons/Alternate.svelte +7 -4
  9. package/dist/icons/AnimatedArrowLeft.svelte +7 -7
  10. package/dist/icons/Aws.svelte +7 -6
  11. package/dist/icons/Bag.svelte +8 -4
  12. package/dist/icons/Bank.svelte +13 -0
  13. package/dist/icons/Bank.svelte.d.ts +4 -0
  14. package/dist/icons/Bell.svelte +7 -4
  15. package/dist/icons/Blaze.svelte +3 -9
  16. package/dist/icons/Book.svelte +7 -4
  17. package/dist/icons/Box.svelte +3 -9
  18. package/dist/icons/BoxAdd.svelte +8 -5
  19. package/dist/icons/Building.svelte +3 -5
  20. package/dist/icons/Buy.svelte +8 -5
  21. package/dist/icons/Calendar.svelte +7 -6
  22. package/dist/icons/CalendarEdit.svelte +7 -5
  23. package/dist/icons/CalendarPlus.svelte +7 -5
  24. package/dist/icons/CategoryAdd.svelte +10 -7
  25. package/dist/icons/CategorySearch.svelte +7 -4
  26. package/dist/icons/Chart.svelte +7 -4
  27. package/dist/icons/ChartDoc.svelte +8 -5
  28. package/dist/icons/Check.svelte +8 -5
  29. package/dist/icons/CheckList.svelte +8 -6
  30. package/dist/icons/CheckO.svelte +4 -15
  31. package/dist/icons/ChevronDown.svelte +8 -6
  32. package/dist/icons/ChevronUpDown.svelte +7 -5
  33. package/dist/icons/CircleCheck.svelte +9 -6
  34. package/dist/icons/CircleExclamation.svelte +6 -15
  35. package/dist/icons/CircleInfo.svelte +6 -11
  36. package/dist/icons/CircleQuestion.svelte +8 -15
  37. package/dist/icons/Close.svelte +5 -11
  38. package/dist/icons/Copy.svelte +5 -11
  39. package/dist/icons/CostIcon.svelte +7 -4
  40. package/dist/icons/Csv.svelte +7 -9
  41. package/dist/icons/Dashboard.svelte +12 -4
  42. package/dist/icons/Db.svelte +13 -0
  43. package/dist/icons/Db.svelte.d.ts +4 -0
  44. package/dist/icons/Download.svelte +11 -4
  45. package/dist/icons/DownloadAnimated.svelte +5 -9
  46. package/dist/icons/EditOutline.svelte +17 -12
  47. package/dist/icons/Email.svelte +7 -4
  48. package/dist/icons/EmailAnimated.svelte +25 -35
  49. package/dist/icons/Enterprise.svelte +9 -5
  50. package/dist/icons/Error.svelte +7 -4
  51. package/dist/icons/ErrorO.svelte +7 -5
  52. package/dist/icons/Excel.svelte +23 -25
  53. package/dist/icons/ExcelAnimated.svelte +11 -4
  54. package/dist/icons/Exchange.svelte +8 -5
  55. package/dist/icons/Eye.svelte +10 -9
  56. package/dist/icons/EyeOff.svelte +52 -67
  57. package/dist/icons/Facebook.svelte +3 -10
  58. package/dist/icons/Favorites.svelte +3 -10
  59. package/dist/icons/File.svelte +10 -6
  60. package/dist/icons/FileUploadAnimated.svelte +59 -47
  61. package/dist/icons/FilterOutline.svelte +12 -9
  62. package/dist/icons/Fingerprint.svelte +13 -0
  63. package/dist/icons/Fingerprint.svelte.d.ts +4 -0
  64. package/dist/icons/FormatListGroup.svelte +8 -5
  65. package/dist/icons/Golang.svelte +2 -10
  66. package/dist/icons/Google.svelte +3 -10
  67. package/dist/icons/Group.svelte +7 -4
  68. package/dist/icons/Hamburguer.svelte +2 -21
  69. package/dist/icons/HandShake.svelte +8 -5
  70. package/dist/icons/Height.svelte +7 -5
  71. package/dist/icons/Home.svelte +2 -13
  72. package/dist/icons/Image.svelte +14 -12
  73. package/dist/icons/ImageAnimated.svelte +29 -36
  74. package/dist/icons/Key.svelte +7 -4
  75. package/dist/icons/List.svelte +7 -4
  76. package/dist/icons/ListDots.svelte +7 -4
  77. package/dist/icons/LoadingDots.svelte +18 -13
  78. package/dist/icons/LoadingSquares.svelte +24 -23
  79. package/dist/icons/LoadingSquares.svelte.d.ts +0 -1
  80. package/dist/icons/Location.svelte +6 -4
  81. package/dist/icons/LocationAnimated.svelte +28 -9
  82. package/dist/icons/Lock.svelte +2 -8
  83. package/dist/icons/LockOpen.svelte +3 -9
  84. package/dist/icons/Logout.svelte +8 -17
  85. package/dist/icons/MagnifiyingGlass.svelte +7 -4
  86. package/dist/icons/MainComponent.svelte +7 -5
  87. package/dist/icons/Measure.svelte +7 -4
  88. package/dist/icons/Money.svelte +3 -6
  89. package/dist/icons/Notes.svelte +2 -7
  90. package/dist/icons/ObjectGroup.svelte +1 -26
  91. package/dist/icons/Pdf.svelte +12 -48
  92. package/dist/icons/Phone.svelte +1 -13
  93. package/dist/icons/Plus.svelte +2 -39
  94. package/dist/icons/Png.svelte +2 -13
  95. package/dist/icons/PointSale.svelte +4 -40
  96. package/dist/icons/Powerpoint.svelte +1 -13
  97. package/dist/icons/Product.svelte +2 -38
  98. package/dist/icons/Project.svelte +2 -39
  99. package/dist/icons/RegularSpinner.svelte +13 -49
  100. package/dist/icons/RegularSpinner.svelte.d.ts +2 -0
  101. package/dist/icons/Reset.svelte +3 -6
  102. package/dist/icons/RightArrow.svelte +3 -43
  103. package/dist/icons/SafeSolid.svelte +5 -40
  104. package/dist/icons/Scan.svelte +3 -39
  105. package/dist/icons/Search.svelte +3 -39
  106. package/dist/icons/Security.svelte +4 -41
  107. package/dist/icons/Server.svelte +13 -0
  108. package/dist/icons/Server.svelte.d.ts +4 -0
  109. package/dist/icons/Settings.svelte +3 -38
  110. package/dist/icons/Shield.svelte +1 -13
  111. package/dist/icons/SquareChart.svelte +5 -15
  112. package/dist/icons/Star.svelte +1 -13
  113. package/dist/icons/Supervisor.svelte +3 -38
  114. package/dist/icons/Swap.svelte +3 -43
  115. package/dist/icons/Table.svelte +3 -42
  116. package/dist/icons/Team.svelte +2 -41
  117. package/dist/icons/Trash.svelte +4 -36
  118. package/dist/icons/TrashOutline.svelte +4 -26
  119. package/dist/icons/Truck.svelte +3 -29
  120. package/dist/icons/TruckReturn.svelte +12 -38
  121. package/dist/icons/UpArrow.svelte +6 -5
  122. package/dist/icons/UpDownArrow.svelte +7 -14
  123. package/dist/icons/Upload.svelte +2 -5
  124. package/dist/icons/UploadAnimated.svelte +11 -16
  125. package/dist/icons/UploadLoader.svelte +1 -4
  126. package/dist/icons/User.svelte +3 -5
  127. package/dist/icons/UserTie.svelte +3 -5
  128. package/dist/icons/Wallet.svelte +3 -4
  129. package/dist/icons/Warehouse.svelte +3 -6
  130. package/dist/icons/Warning.svelte +2 -5
  131. package/dist/icons/Word.svelte +1 -13
  132. package/dist/icons/Xml.svelte +1 -13
  133. package/dist/icons/Zip.svelte +4 -9
  134. package/dist/icons/registry.d.ts +4 -1
  135. package/dist/icons/registry.js +8 -0
  136. package/dist/icons-api.d.ts +285 -141
  137. package/dist/icons-api.js +112 -145
  138. package/dist/index.d.ts +9 -9
  139. package/dist/index.js +27 -19
  140. package/dist/presets.d.ts +107 -0
  141. package/dist/presets.js +89 -0
  142. package/dist/styles.d.ts +2 -0
  143. package/dist/types.d.ts +37 -45
  144. package/dist/utils/defaults.d.ts +7 -1
  145. package/dist/utils/defaults.js +77 -17
  146. package/package.json +4 -1
  147. package/dist/DynamicIcon.svelte +0 -45
  148. package/dist/DynamicIcon.svelte.d.ts +0 -7
  149. package/dist/actions.js +0 -150
  150. package/dist/auto-inject.d.ts +0 -1
  151. package/dist/auto-inject.js +0 -8
  152. package/dist/effects/icon-effects.d.ts +0 -5
  153. package/dist/effects/icon-effects.js +0 -222
  154. package/dist/icons/icon-bundle.d.ts +0 -104
  155. package/dist/icons/icon-bundle.js +0 -203
  156. package/dist/icons/icons-effects.css +0 -26
  157. package/dist/icons/injectEffects.d.ts +0 -2
  158. package/dist/icons/injectEffects.js +0 -32
  159. package/dist/styles/css-injection.d.ts +0 -2
  160. package/dist/styles/css-injection.js +0 -5
  161. package/dist/styles/hybrid-inject.d.ts +0 -12
  162. package/dist/styles/hybrid-inject.js +0 -225
package/dist/Icon.svelte CHANGED
@@ -1,80 +1,122 @@
1
1
  <script lang="ts">
2
2
  import { iconRegistry, type IconName } from "./icons/registry";
3
- import type { IconComponent, IconProps } from "./types";
3
+ import {
4
+ iconPresets,
5
+ iconVariants,
6
+ type IconPreset,
7
+ type IconVariant,
8
+ } from "./presets";
9
+ import type { IconProps } from "./types";
4
10
  import { coerceSize } from "./utils/defaults";
5
11
 
6
- let {
7
- name,
8
- size = 24,
9
- color,
10
- stroke,
11
- strokeWidth,
12
- fill,
13
- class: classAttr = "",
14
- className = "",
15
- ariaLabel,
16
- title = "",
17
- rotate = 0,
18
- flipH = false,
19
- flipV = false,
20
- spin = false,
21
- nonScalingStroke = false,
22
- preserveAspectRatio = "xMidYMid meet",
23
- decorative = false,
24
- titleId = "",
25
- testId,
26
- attrs = {},
27
- animationDuration,
28
- animationDelay,
29
- animationEasing,
30
- ...rest
31
- }: { name: IconName } & Partial<IconProps> = $props();
12
+ interface DynamicIconProps extends Omit<IconProps, "children"> {
13
+ name: IconName;
14
+ preset?: IconPreset;
15
+ variant?: IconVariant;
16
+ }
32
17
 
33
- // Comp tipado como el mismo tipo que usa tu registry
34
- const Comp: IconComponent | undefined = $derived(iconRegistry?.[name]);
18
+ const props: DynamicIconProps = $props();
35
19
 
36
- // ANTES: const colorOverrides = $derived(() => color !== undefined ? {...} : {...})
37
- // AHORA:
38
- const colorOverrides = $derived(
39
- color !== undefined
40
- ? { color, stroke: stroke ?? "none", fill: fill ?? color }
41
- : { stroke, fill }
20
+ let internalHovered = $state(false);
21
+ const isHovered = $derived(
22
+ internalHovered || (props.parentHoverContext?.hovered ?? false)
42
23
  );
43
24
 
44
- // ❌ ANTES: const passProps = $derived(() => ({ ... }))
45
- // ✅ AHORA:
46
- const passProps = $derived({
47
- size: coerceSize(size, 24),
48
- ...colorOverrides,
49
- strokeWidth,
50
- className: classAttr || className,
51
- ariaLabel,
52
- title,
53
- rotate,
54
- flipH,
55
- flipV,
56
- spin,
57
- nonScalingStroke,
58
- preserveAspectRatio,
59
- decorative,
60
- titleId,
61
- testId,
62
- attrs,
63
- animationDuration:
64
- typeof animationDuration === "number"
65
- ? `${animationDuration}ms`
66
- : animationDuration,
67
- animationDelay:
68
- typeof animationDelay === "number"
69
- ? `${animationDelay}ms`
70
- : animationDelay,
71
- animationEasing,
72
- ...rest,
25
+ const Comp = $derived(iconRegistry[props.name]);
26
+
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
+ );
34
+
35
+ // Merge: preset < variant < props explícitas
36
+ const mergedProps = $derived({ ...presetProps, ...variantProps, ...props });
37
+
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;
73
43
  });
44
+
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(() => {
57
+ 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;
70
+
71
+ return {
72
+ ...rest,
73
+ size: coerceSize(size, 24),
74
+ color: effectiveColor,
75
+ animationDuration: normalizeAnimationValue(animationDuration),
76
+ animationDelay: normalizeAnimationValue(animationDelay),
77
+ };
78
+ });
79
+
80
+ function handleMouseEnter() {
81
+ internalHovered = true;
82
+ }
83
+ function handleMouseLeave() {
84
+ internalHovered = false;
85
+ }
74
86
  </script>
75
87
 
76
88
  {#if Comp}
77
- <Comp {...passProps} />
89
+ <span
90
+ class="bz-icon-wrapper"
91
+ role="presentation"
92
+ onmouseenter={handleMouseEnter}
93
+ onmouseleave={handleMouseLeave}
94
+ >
95
+ <Comp {...iconProps} />
96
+ </span>
78
97
  {:else}
79
- <span class="inline-block text-xs text-red-500">Unknown icon: {name}</span>
98
+ <span class="bz-icon-error" data-icon={props.name}>
99
+ ⚠️ Icon not found: {props.name}
100
+ </span>
80
101
  {/if}
102
+
103
+ <style>
104
+ .bz-icon-wrapper {
105
+ display: inline-flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ line-height: 1;
109
+ }
110
+ .bz-icon-error {
111
+ display: inline-block;
112
+ width: 1em;
113
+ height: 1em;
114
+ background: var(--danger-color, #ef4444);
115
+ color: var(--danger-foreground, white);
116
+ text-align: center;
117
+ line-height: 1;
118
+ font-size: 0.75rem;
119
+ border-radius: var(--ui-radius, 0.25rem);
120
+ font-weight: 600;
121
+ }
122
+ </style>
@@ -1,8 +1,11 @@
1
1
  import { type IconName } from "./icons/registry";
2
+ import { type IconPreset, type IconVariant } from "./presets";
2
3
  import type { IconProps } from "./types";
3
- type $$ComponentProps = {
4
+ interface DynamicIconProps extends Omit<IconProps, "children"> {
4
5
  name: IconName;
5
- } & Partial<IconProps>;
6
- declare const Icon: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ preset?: IconPreset;
7
+ variant?: IconVariant;
8
+ }
9
+ declare const Icon: import("svelte").Component<DynamicIconProps, {}, "">;
7
10
  type Icon = ReturnType<typeof Icon>;
8
11
  export default Icon;
@@ -1,31 +1,55 @@
1
- <!-- 📁 src/lib/IconBase.svelte -->
2
1
  <script lang="ts">
3
- import type { IconMode, IconProps } from "./types.js";
2
+ import { IconEffectOptions, iconEffects } from "./effects.js";
3
+ import type { IconMode, IconProps } from "./types";
4
4
  import {
5
5
  combineTransforms,
6
6
  getAnimationClasses,
7
7
  getAnimationStyle,
8
- } from "./utils/animations.js";
8
+ } from "./utils/animations";
9
9
  import {
10
10
  commonDefaults,
11
11
  modeDefaults,
12
12
  normalizeClass,
13
- } from "./utils/defaults.js";
13
+ } from "./utils/defaults";
14
+
15
+ // id simple para <title>
16
+ function uid(): string {
17
+ return Math.random().toString(36).slice(2);
18
+ }
14
19
 
15
- // Props del icono + modo visual
16
20
  const props: IconProps & { mode?: IconMode } = $props();
17
21
 
18
- // ====== Derivados visuales / clases / defaults ======
22
+ // ── Interacción ────────────────────────────────────────────────────────────
23
+ let internalHovered = $state(false);
24
+ const isHovered = $derived(
25
+ internalHovered || (props.parentHoverContext?.hovered ?? false)
26
+ );
27
+
28
+ // ── Color por defecto dark-first con fallbacks ─────────────────────────────
29
+ // 1) --icon-fg (si existe)
30
+ // 2) --ui-muted-fg (ya lo defines distinto en claro/oscuro)
31
+ // 3) currentColor (último fallback)
32
+ const DEFAULT_ICON_COLOR = "var(--icon-fg, var(--ui-muted-fg, currentColor))";
33
+
34
+ const effectiveColor = $derived(
35
+ (isHovered && props.hoverColor ? props.hoverColor : props.color) ??
36
+ DEFAULT_ICON_COLOR
37
+ );
38
+
39
+ // ── Modo / clases / defaults compartidos ───────────────────────────────────
19
40
  const mode = $derived(props.mode ?? "solid");
20
41
  const klass = $derived(normalizeClass(props));
21
- const common = $derived(commonDefaults(props)); // size, viewBox, preserveAspectRatio
22
- const visual = $derived(modeDefaults(mode, props)); // lo que ya tienes
42
+ const common = $derived(commonDefaults(props));
23
43
 
44
+ // Pasar color efectivo al pipeline visual
45
+ const propsWithEffectiveColor = $derived({ ...props, color: effectiveColor });
46
+ const visual = $derived(modeDefaults(mode, propsWithEffectiveColor));
47
+
48
+ // ── Animaciones (clases + timing inline) ───────────────────────────────────
24
49
  const animationClasses = $derived(getAnimationClasses(props).join(" "));
25
- const timingStyle = $derived(getAnimationStyle(props)); // duration/delay/easing
50
+ const timingStyle = $derived(getAnimationStyle(props));
26
51
 
27
- // ⚠️ Solo CSS transforms semánticos (chevron/morph/slide).
28
- // Quitamos rotate/flip del combine para no duplicar (rotate/flip van en <g transform>).
52
+ // ── Transform sólo CSS (no colisionar con transform del <g>) ──────────────
29
53
  const cssTransformOnly = $derived(
30
54
  combineTransforms({
31
55
  ...props,
@@ -35,74 +59,80 @@
35
59
  })
36
60
  );
37
61
 
38
- // Sólo variable para spin (sin animation: inline)
39
- const spinStyle = $derived(
40
- (() => {
41
- const s = props.spin;
42
- if (!s) return "";
43
- const dur = s === true ? "1s" : typeof s === "number" ? `${s}ms` : s;
44
- return `--bz-spin: ${dur};`;
45
- })()
62
+ // ── Duración de spin como custom prop ──────────────────────────────────────
63
+ const spinDuration = $derived(
64
+ props.spin
65
+ ? props.spin === true
66
+ ? "1s"
67
+ : typeof props.spin === "number"
68
+ ? `${props.spin}ms`
69
+ : props.spin
70
+ : null
46
71
  );
47
72
 
48
- // Estilo final inline (añadimos transform-origin/box, timing y variable de spin)
49
- const style = $derived(
50
- `${props.style || ""}${props.style && !props.style.endsWith(";") ? ";" : ""}` +
51
- (cssTransformOnly ? `transform: ${cssTransformOnly};` : "") +
52
- `transform-origin: center;` + // deja 'transform-box' fuera si TS te marca error
53
- (spinStyle ? spinStyle : "") +
54
- (timingStyle ? timingStyle : "") +
55
- (props.color ? `color: ${props.color};` : "") // ← ★ clave para currentColor
56
- );
73
+ // ── style final (string) ───────────────────────────────────────────────────
74
+ const style = $derived(() => {
75
+ const parts: string[] = [];
76
+ if (props.style)
77
+ parts.push(props.style.endsWith(";") ? props.style : `${props.style};`);
78
+ if (cssTransformOnly) parts.push(`transform: ${cssTransformOnly};`);
79
+ parts.push("transform-origin: center;");
80
+ if (spinDuration) parts.push(`--bz-spin: ${spinDuration};`);
81
+ if (timingStyle) parts.push(timingStyle);
82
+ // 💡 clave: color por defecto dark-first
83
+ if (effectiveColor) parts.push(`color: ${effectiveColor};`);
84
+ return parts.join(" ");
85
+ });
57
86
 
58
- // A11y derivados
87
+ // ── A11y ───────────────────────────────────────────────────────────────────
59
88
  const ariaHidden = $derived(props.decorative ? "true" : undefined);
89
+ const computedTitleId = $derived(
90
+ props.title ? (props.titleId ?? `bz-icon-title-${uid()}`) : undefined
91
+ );
60
92
  const ariaLabel = $derived(
61
93
  props.decorative ? undefined : props.ariaLabel || undefined
62
94
  );
63
95
  const ariaLabelledby = $derived(
64
- !props.decorative && props.title ? props.titleId : undefined
96
+ !props.decorative && props.title ? computedTitleId : undefined
65
97
  );
66
98
 
67
- // Size final como string para width/height
99
+ // ── Tamaño final ───────────────────────────────────────────────────────────
68
100
  const finalSize = $derived(
69
101
  typeof common.size === "number" ? `${common.size}px` : common.size
70
102
  );
71
103
 
72
- // Atributos seguros (evitar que pisen width/height del svg)
104
+ // ── Attrs seguros (no permitir width/height externos) ──────────────────────
73
105
  const safeAttrs = $derived(() => {
74
- const a = { ...(props.attrs ?? {}) } as Record<string, any>;
75
- delete a.width;
76
- delete a.height;
106
+ const a = { ...(props.attrs ?? {}) };
107
+ delete (a as any).width;
108
+ delete (a as any).height;
77
109
  return a;
78
110
  });
79
111
 
112
+ // ── Clase final ────────────────────────────────────────────────────────────
80
113
  const finalClass = $derived(
81
114
  `${klass} ${animationClasses} ${props.chevronState ? "bz-icon-chevron" : ""}`.trim()
82
115
  );
83
116
 
84
- // ====== ROTATE / FLIPS en <g transform> (SVG nativo, centrado) ======
117
+ // ── Centro del viewBox para rot/flip exactos en SVG ────────────────────────
85
118
  function getViewBoxCenter(vb: string) {
86
119
  const parts = vb.trim().split(/\s+/).map(Number);
87
120
  const [minX, minY, w, h] = parts.length === 4 ? parts : [0, 0, 24, 24];
88
121
  return { cx: minX + w / 2, cy: minY + h / 2 };
89
122
  }
90
-
91
- const { cx, cy } = $derived(getViewBoxCenter(common.viewBox));
123
+ const center = $derived(getViewBoxCenter(common.viewBox));
124
+ const cx = $derived(center.cx);
125
+ const cy = $derived(center.cy);
92
126
 
93
127
  const svgTransform = $derived(() => {
94
128
  const cmds: string[] = [];
95
-
96
- // rotate
97
129
  if (props.rotate != null) {
98
130
  const r =
99
131
  typeof props.rotate === "number"
100
132
  ? props.rotate
101
133
  : parseFloat(String(props.rotate));
102
- if (!Number.isNaN(r)) cmds.push(`rotate(${r} ${cx} ${cy})`);
134
+ if (!isNaN(r)) cmds.push(`rotate(${r} ${cx} ${cy})`);
103
135
  }
104
-
105
- // flips
106
136
  if (props.flipH || props.flipV) {
107
137
  const sx = props.flipH ? -1 : 1;
108
138
  const sy = props.flipV ? -1 : 1;
@@ -110,27 +140,46 @@
110
140
  `translate(${cx} ${cy}) scale(${sx} ${sy}) translate(${-cx} ${-cy})`
111
141
  );
112
142
  }
113
-
114
143
  return cmds.join(" ");
115
144
  });
116
145
 
117
- const visualColor = $derived(() => {
118
- if (!props.color) return visual;
119
-
120
- const v = { ...visual };
146
+ // ── Hover handlers ─────────────────────────────────────────────────────────
147
+ function handleMouseEnter() {
148
+ internalHovered = true;
149
+ }
150
+ function handleMouseLeave() {
151
+ internalHovered = false;
152
+ }
121
153
 
122
- if (mode === "solid") {
123
- v.fill = props.color; // Fuerza el color aquí
124
- } else {
125
- v.stroke = props.color;
154
+ // ── Efectos declarativos (props.effects > attrs.* retrocompat) ─────────────
155
+ let svgRef: SVGSVGElement | null = $state(null);
156
+
157
+ $effect(() => {
158
+ if (!svgRef) return;
159
+
160
+ const effectsOpts: IconEffectOptions = { ...(props.effects ?? {}) };
161
+ const a = props.attrs ?? {};
162
+ if (a.spinOnHover) effectsOpts.spinOnHover = true;
163
+ if (a.bounceOnHover) effectsOpts.bounceOnHover = true;
164
+ if (a.wiggleOnHover) effectsOpts.wiggleOnHover = true;
165
+ if (a.slideOnHover) effectsOpts.slideOnHover = a.slideOnHover;
166
+ if (a.morphOnHover) effectsOpts.morphOnHover = a.morphOnHover;
167
+ if (a.elasticOnClick) effectsOpts.elasticOnClick = true;
168
+ if (a.heartbeatOnActive) effectsOpts.heartbeatOnActive = true;
169
+ if (a.hoverScale) effectsOpts.hoverScale = a.hoverScale;
170
+ if (a.pressScale) effectsOpts.pressScale = a.pressScale;
171
+
172
+ if (Object.keys(effectsOpts).length > 0) {
173
+ const controller = iconEffects(svgRef, effectsOpts);
174
+ return () => controller.destroy();
126
175
  }
127
- return v;
128
176
  });
129
177
  </script>
130
178
 
131
179
  <svg
180
+ bind:this={svgRef}
132
181
  xmlns="http://www.w3.org/2000/svg"
133
- {...safeAttrs}
182
+ {...safeAttrs()}
134
183
  width={finalSize}
135
184
  height={finalSize}
136
185
  viewBox={common.viewBox}
@@ -141,16 +190,18 @@
141
190
  aria-label={ariaLabel}
142
191
  aria-labelledby={ariaLabelledby}
143
192
  focusable="false"
144
- {style}
145
- fill={visualColor().fill}
146
- stroke={visualColor().stroke}
147
- stroke-width={visualColor().strokeWidth}
193
+ style={style()}
194
+ fill={visual.fill}
195
+ stroke={visual.stroke}
196
+ stroke-width={visual.strokeWidth}
148
197
  stroke-linecap={props.strokeLinecap}
149
198
  stroke-linejoin={props.strokeLinejoin}
150
199
  vector-effect={props.nonScalingStroke ? "non-scaling-stroke" : undefined}
151
200
  data-testid={props.testId}
201
+ onmouseenter={handleMouseEnter}
202
+ onmouseleave={handleMouseLeave}
152
203
  >
153
- {#if props.title}<title id={props.titleId}>{props.title}</title>{/if}
204
+ {#if props.title}<title id={computedTitleId}>{props.title}</title>{/if}
154
205
  <g transform={svgTransform()}>
155
206
  {@render props.children?.()}
156
207
  </g>
@@ -1,4 +1,4 @@
1
- import type { IconMode, IconProps } from "./types.js";
1
+ import type { IconMode, IconProps } from "./types";
2
2
  type $$ComponentProps = IconProps & {
3
3
  mode?: IconMode;
4
4
  };
@@ -1,13 +1,15 @@
1
1
  export type IconEffectOptions = {
2
- hoverColor?: string;
3
- activeColor?: string;
4
- hoverScale?: number;
5
- pressScale?: number;
6
- rotateOnHover?: number;
7
2
  spinOnHover?: boolean;
8
3
  bounceOnHover?: boolean;
4
+ wiggleOnHover?: boolean;
5
+ slideOnHover?: "up" | "down" | "left" | "right";
6
+ morphOnHover?: "play" | "pause" | "menu" | "close" | "arrow" | "check";
7
+ elasticOnClick?: boolean;
8
+ heartbeatOnActive?: boolean;
9
9
  pulse?: boolean;
10
- opacityHover?: number;
10
+ hoverScale?: number;
11
+ pressScale?: number;
12
+ rotateOnHover?: number;
11
13
  transitionMs?: number;
12
14
  easing?: string;
13
15
  cursor?: string;
@@ -15,7 +17,7 @@ export type IconEffectOptions = {
15
17
  focusRing?: boolean;
16
18
  kbFocusAttr?: string;
17
19
  };
18
- export declare function iconEffects(node: HTMLElement, opts?: IconEffectOptions): {
20
+ export declare function iconEffects(node: Element, opts?: IconEffectOptions): {
19
21
  update(next?: IconEffectOptions): void;
20
22
  destroy(): void;
21
23
  };
@@ -0,0 +1,128 @@
1
+ function getPrefersReducedMotion() {
2
+ if (typeof window === "undefined")
3
+ return false;
4
+ return (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false);
5
+ }
6
+ export function iconEffects(node, opts = {}) {
7
+ const prefersReducedMotion = getPrefersReducedMotion();
8
+ let hovering = false;
9
+ let pressing = false;
10
+ let rafId = 0;
11
+ // Setup inicial
12
+ if (opts.tooltip)
13
+ node.setAttribute("title", opts.tooltip);
14
+ if (opts.cursor && node instanceof HTMLElement)
15
+ node.style.cursor = opts.cursor;
16
+ const transitionMs = opts.transitionMs ?? 160;
17
+ const easing = opts.easing ?? "cubic-bezier(.2,.8,.2,1)";
18
+ if (!prefersReducedMotion &&
19
+ (node instanceof HTMLElement || node instanceof SVGElement)) {
20
+ node.style.transition = `transform ${transitionMs}ms ${easing}`;
21
+ }
22
+ if (opts.pulse && !prefersReducedMotion) {
23
+ node.classList.add("bz-icon-pulse");
24
+ }
25
+ function applyAnimations() {
26
+ cancelAnimationFrame(rafId);
27
+ rafId = requestAnimationFrame(() => {
28
+ if (prefersReducedMotion)
29
+ return;
30
+ const parts = [];
31
+ // Escala
32
+ const scale = hovering
33
+ ? pressing && opts.pressScale
34
+ ? opts.pressScale
35
+ : opts.hoverScale ?? 1
36
+ : 1;
37
+ if (scale !== 1)
38
+ parts.push(`scale(${scale})`);
39
+ // Rotación
40
+ if (hovering && opts.rotateOnHover)
41
+ parts.push(`rotate(${opts.rotateOnHover}deg)`);
42
+ // Slide
43
+ if (hovering && opts.slideOnHover) {
44
+ const slideMap = {
45
+ up: "translateY(-4px)",
46
+ down: "translateY(4px)",
47
+ left: "translateX(-4px)",
48
+ right: "translateX(4px)",
49
+ };
50
+ parts.push(slideMap[opts.slideOnHover]);
51
+ }
52
+ if (node instanceof HTMLElement || node instanceof SVGElement) {
53
+ node.style.transform = parts.join(" ");
54
+ }
55
+ // Clases de animación CSS
56
+ node.classList.toggle("bz-icon-spin", !!(hovering && opts.spinOnHover));
57
+ node.classList.toggle("bz-icon-bounce", !!(hovering && opts.bounceOnHover));
58
+ node.classList.toggle("bz-icon-wiggle", !!(hovering && opts.wiggleOnHover));
59
+ node.classList.toggle("bz-icon-heartbeat", !!(pressing && opts.heartbeatOnActive));
60
+ node.classList.toggle("bz-icon-elastic", !!(pressing && opts.elasticOnClick));
61
+ if (opts.morphOnHover) {
62
+ node.classList.toggle(`bz-icon-morph-${opts.morphOnHover}`, hovering);
63
+ }
64
+ });
65
+ }
66
+ // Handlers
67
+ function onEnter() {
68
+ hovering = true;
69
+ applyAnimations();
70
+ }
71
+ function onLeave() {
72
+ hovering = false;
73
+ pressing = false;
74
+ applyAnimations();
75
+ }
76
+ function onDown() {
77
+ pressing = true;
78
+ applyAnimations();
79
+ }
80
+ function onUp() {
81
+ pressing = false;
82
+ applyAnimations();
83
+ }
84
+ // Nota: eventos de teclado SIN { passive: true }
85
+ function onKeydown(e) {
86
+ if (opts.focusRing !== false && e.key === "Tab") {
87
+ node.setAttribute(opts.kbFocusAttr ?? "data-bz-kb-focus", "true");
88
+ }
89
+ }
90
+ function onMousedown() {
91
+ if (opts.focusRing !== false) {
92
+ node.removeAttribute(opts.kbFocusAttr ?? "data-bz-kb-focus");
93
+ }
94
+ }
95
+ // Listeners
96
+ node.addEventListener("pointerenter", onEnter, { passive: true });
97
+ node.addEventListener("pointerleave", onLeave, { passive: true });
98
+ node.addEventListener("pointerdown", onDown, { passive: true });
99
+ node.addEventListener("pointerup", onUp, { passive: true });
100
+ node.addEventListener("keydown", onKeydown); // ← sin passive
101
+ node.addEventListener("mousedown", onMousedown, { passive: true });
102
+ return {
103
+ update(next) {
104
+ if (!next)
105
+ return;
106
+ if (next.tooltip !== undefined) {
107
+ if (next.tooltip)
108
+ node.setAttribute("title", next.tooltip);
109
+ else
110
+ node.removeAttribute("title");
111
+ }
112
+ if (next.cursor !== undefined && node instanceof HTMLElement) {
113
+ node.style.cursor = next.cursor || "";
114
+ }
115
+ Object.assign(opts, next);
116
+ applyAnimations();
117
+ },
118
+ destroy() {
119
+ cancelAnimationFrame(rafId);
120
+ node.removeEventListener("pointerenter", onEnter);
121
+ node.removeEventListener("pointerleave", onLeave);
122
+ node.removeEventListener("pointerdown", onDown);
123
+ node.removeEventListener("pointerup", onUp);
124
+ node.removeEventListener("keydown", onKeydown);
125
+ node.removeEventListener("mousedown", onMousedown);
126
+ },
127
+ };
128
+ }