@shival99/z-ui 2.2.5 → 2.3.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 (66) hide show
  1. package/assets/css/base.css +4 -0
  2. package/assets/css/overlay.css +82 -0
  3. package/assets/css/tailwind.css +1 -0
  4. package/assets/css/themes/hospital.css +28 -2
  5. package/fesm2022/shival99-z-ui-components-z-button-group.mjs +15 -13
  6. package/fesm2022/shival99-z-ui-components-z-button-group.mjs.map +1 -1
  7. package/fesm2022/shival99-z-ui-components-z-button.mjs +2 -2
  8. package/fesm2022/shival99-z-ui-components-z-button.mjs.map +1 -1
  9. package/fesm2022/shival99-z-ui-components-z-calendar.mjs +23 -17
  10. package/fesm2022/shival99-z-ui-components-z-calendar.mjs.map +1 -1
  11. package/fesm2022/shival99-z-ui-components-z-command.mjs +10 -24
  12. package/fesm2022/shival99-z-ui-components-z-command.mjs.map +1 -1
  13. package/fesm2022/shival99-z-ui-components-z-drawer.mjs +61 -70
  14. package/fesm2022/shival99-z-ui-components-z-drawer.mjs.map +1 -1
  15. package/fesm2022/shival99-z-ui-components-z-gallery.mjs +6 -8
  16. package/fesm2022/shival99-z-ui-components-z-gallery.mjs.map +1 -1
  17. package/fesm2022/shival99-z-ui-components-z-icon.mjs +227 -11
  18. package/fesm2022/shival99-z-ui-components-z-icon.mjs.map +1 -1
  19. package/fesm2022/shival99-z-ui-components-z-marquee.mjs +19 -4
  20. package/fesm2022/shival99-z-ui-components-z-marquee.mjs.map +1 -1
  21. package/fesm2022/shival99-z-ui-components-z-menu.mjs +7 -7
  22. package/fesm2022/shival99-z-ui-components-z-menu.mjs.map +1 -1
  23. package/fesm2022/shival99-z-ui-components-z-modal.mjs +45 -62
  24. package/fesm2022/shival99-z-ui-components-z-modal.mjs.map +1 -1
  25. package/fesm2022/shival99-z-ui-components-z-overlay.mjs +363 -0
  26. package/fesm2022/shival99-z-ui-components-z-overlay.mjs.map +1 -0
  27. package/fesm2022/shival99-z-ui-components-z-popover.mjs +94 -378
  28. package/fesm2022/shival99-z-ui-components-z-popover.mjs.map +1 -1
  29. package/fesm2022/shival99-z-ui-components-z-skeleton.mjs +14 -3
  30. package/fesm2022/shival99-z-ui-components-z-skeleton.mjs.map +1 -1
  31. package/fesm2022/shival99-z-ui-components-z-table.mjs +42 -16
  32. package/fesm2022/shival99-z-ui-components-z-table.mjs.map +1 -1
  33. package/fesm2022/shival99-z-ui-components-z-tabs.mjs +85 -7
  34. package/fesm2022/shival99-z-ui-components-z-tabs.mjs.map +1 -1
  35. package/fesm2022/shival99-z-ui-components-z-toast.mjs +32 -25
  36. package/fesm2022/shival99-z-ui-components-z-toast.mjs.map +1 -1
  37. package/fesm2022/shival99-z-ui-components-z-tooltip.mjs +52 -222
  38. package/fesm2022/shival99-z-ui-components-z-tooltip.mjs.map +1 -1
  39. package/fesm2022/shival99-z-ui-providers.mjs +9 -2
  40. package/fesm2022/shival99-z-ui-providers.mjs.map +1 -1
  41. package/fesm2022/shival99-z-ui-services.mjs +203 -98
  42. package/fesm2022/shival99-z-ui-services.mjs.map +1 -1
  43. package/package.json +9 -1
  44. package/types/shival99-z-ui-components-z-autocomplete.d.ts +1 -1
  45. package/types/shival99-z-ui-components-z-button-group.d.ts +12 -11
  46. package/types/shival99-z-ui-components-z-button.d.ts +2 -1
  47. package/types/shival99-z-ui-components-z-calendar.d.ts +4 -4
  48. package/types/shival99-z-ui-components-z-command.d.ts +2 -4
  49. package/types/shival99-z-ui-components-z-drawer.d.ts +22 -16
  50. package/types/shival99-z-ui-components-z-editor.d.ts +1 -1
  51. package/types/shival99-z-ui-components-z-gallery.d.ts +3 -3
  52. package/types/shival99-z-ui-components-z-icon.d.ts +38 -6
  53. package/types/shival99-z-ui-components-z-marquee.d.ts +7 -1
  54. package/types/shival99-z-ui-components-z-modal.d.ts +53 -53
  55. package/types/shival99-z-ui-components-z-overlay.d.ts +145 -0
  56. package/types/shival99-z-ui-components-z-popover.d.ts +7 -15
  57. package/types/shival99-z-ui-components-z-qrcode.d.ts +1 -1
  58. package/types/shival99-z-ui-components-z-select.d.ts +1 -1
  59. package/types/shival99-z-ui-components-z-skeleton.d.ts +2 -0
  60. package/types/shival99-z-ui-components-z-table.d.ts +7 -3
  61. package/types/shival99-z-ui-components-z-tabs.d.ts +13 -3
  62. package/types/shival99-z-ui-components-z-toast.d.ts +7 -1
  63. package/types/shival99-z-ui-components-z-tooltip.d.ts +9 -7
  64. package/types/shival99-z-ui-components-z-upload.d.ts +3 -3
  65. package/types/shival99-z-ui-providers.d.ts +3 -1
  66. package/types/shival99-z-ui-services.d.ts +73 -37
@@ -1,6 +1,5 @@
1
- import { NgComponentOutlet } from '@angular/common';
2
1
  import * as i0 from '@angular/core';
3
- import { isDevMode, input, signal, computed, effect, ViewEncapsulation, ChangeDetectionStrategy, Component, makeEnvironmentProviders, inject } from '@angular/core';
2
+ import { isDevMode, input, signal, computed, viewChild, ViewContainerRef, effect, ViewEncapsulation, ChangeDetectionStrategy, Component, makeEnvironmentProviders, inject } from '@angular/core';
4
3
  import { NgIconComponent, provideNgIconLoader, withCaching } from '@ng-icons/core';
5
4
  import { zMergeClasses } from '@shival99/z-ui/utils';
6
5
  import * as animatedIcons from 'ng-animated-icons';
@@ -109,6 +108,7 @@ class ZIconComponent {
109
108
  ...(ngDevMode ? [{ debugName: "_svgFetchError" }] : /* istanbul ignore next */ []));
110
109
  zClasses = computed(() => zMergeClasses(zIconVariants({ zSize: this.zSize() }), this.class(), this.zStrokeWidth() === 0 ? 'stroke-none' : '', 'inline-flex shrink-0'), /* @ts-ignore */
111
110
  ...(ngDevMode ? [{ debugName: "zClasses" }] : /* istanbul ignore next */ []));
111
+ animatedIconHost = viewChild('animatedIconHost', { ...(ngDevMode ? { debugName: "animatedIconHost" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
112
112
  animatedIcon = computed(() => {
113
113
  const animatedType = this.zAnimatedType();
114
114
  if (!animatedType) {
@@ -117,13 +117,19 @@ class ZIconComponent {
117
117
  return getZAnimatedIconComponent(animatedType);
118
118
  }, /* @ts-ignore */
119
119
  ...(ngDevMode ? [{ debugName: "animatedIcon" }] : /* istanbul ignore next */ []));
120
- animatedIconInputs = computed(() => ({
120
+ _animatedIconInputs = computed(() => ({
121
121
  animate: this.shouldAnimate(),
122
122
  color: 'currentColor',
123
123
  size: Number(this.normalizedSize()),
124
124
  strokeWidth: this.zStrokeWidth(),
125
125
  }), /* @ts-ignore */
126
- ...(ngDevMode ? [{ debugName: "animatedIconInputs" }] : /* istanbul ignore next */ []));
126
+ ...(ngDevMode ? [{ debugName: "_animatedIconInputs" }] : /* istanbul ignore next */ []));
127
+ _animatedIconRef = null;
128
+ _animatedIconType = null;
129
+ _animatedIconListenerCleanup = null;
130
+ _animatedIconRestartTimeoutId = null;
131
+ _isPersistentAnimate = computed(() => this.zAnimationTrigger() === 'always' || this.zAnimate(), /* @ts-ignore */
132
+ ...(ngDevMode ? [{ debugName: "_isPersistentAnimate" }] : /* istanbul ignore next */ []));
127
133
  shouldAnimate = computed(() => {
128
134
  const trigger = this.zAnimationTrigger();
129
135
  if (trigger === 'always' || this.zAnimate()) {
@@ -142,6 +148,33 @@ class ZIconComponent {
142
148
  }, /* @ts-ignore */
143
149
  ...(ngDevMode ? [{ debugName: "shouldAnimate" }] : /* istanbul ignore next */ []));
144
150
  constructor() {
151
+ effect(() => {
152
+ const host = this.animatedIconHost();
153
+ const type = this.animatedIcon();
154
+ if (!host) {
155
+ this._destroyAnimatedIconRef();
156
+ return;
157
+ }
158
+ if (type !== this._animatedIconType) {
159
+ this._destroyAnimatedIconRef();
160
+ this._animatedIconType = type;
161
+ host.clear();
162
+ if (type) {
163
+ this._animatedIconRef = host.createComponent(type);
164
+ this._attachAnimatedIconLoopListener(this._animatedIconRef);
165
+ }
166
+ }
167
+ const ref = this._animatedIconRef;
168
+ if (ref) {
169
+ const inputs = this._animatedIconInputs();
170
+ for (const [key, value] of Object.entries(inputs)) {
171
+ ref.setInput(key, value);
172
+ }
173
+ // Signal inputs set imperatively via setInput() don't reliably schedule
174
+ // change detection for a dynamically created OnPush child, so force it.
175
+ ref.changeDetectorRef.detectChanges();
176
+ }
177
+ });
145
178
  effect(onCleanup => {
146
179
  const svgInput = this.zSvg().trim();
147
180
  if (!svgInput) {
@@ -202,6 +235,45 @@ class ZIconComponent {
202
235
  });
203
236
  });
204
237
  }
238
+ _destroyAnimatedIconRef() {
239
+ this._cancelAnimatedIconRestart();
240
+ this._animatedIconListenerCleanup?.();
241
+ this._animatedIconListenerCleanup = null;
242
+ this._animatedIconRef?.destroy();
243
+ this._animatedIconRef = null;
244
+ this._animatedIconType = null;
245
+ }
246
+ _attachAnimatedIconLoopListener(ref) {
247
+ const nativeElement = ref.location.nativeElement;
248
+ const handler = () => this._restartAnimatedIconLoop(ref);
249
+ nativeElement.addEventListener('animationend', handler);
250
+ this._animatedIconListenerCleanup = () => nativeElement.removeEventListener('animationend', handler);
251
+ }
252
+ _restartAnimatedIconLoop(ref) {
253
+ if (this._animatedIconRef !== ref || !this._isPersistentAnimate()) {
254
+ return;
255
+ }
256
+ this._cancelAnimatedIconRestart();
257
+ ref.setInput('animate', false);
258
+ ref.changeDetectorRef.detectChanges();
259
+ this._animatedIconRestartTimeoutId = setTimeout(() => {
260
+ this._animatedIconRestartTimeoutId = null;
261
+ if (this._animatedIconRef !== ref || !this._isPersistentAnimate()) {
262
+ return;
263
+ }
264
+ ref.setInput('animate', true);
265
+ ref.changeDetectorRef.detectChanges();
266
+ }, 50);
267
+ }
268
+ _cancelAnimatedIconRestart() {
269
+ if (this._animatedIconRestartTimeoutId !== null) {
270
+ clearTimeout(this._animatedIconRestartTimeoutId);
271
+ this._animatedIconRestartTimeoutId = null;
272
+ }
273
+ }
274
+ ngOnDestroy() {
275
+ this._destroyAnimatedIconRef();
276
+ }
205
277
  _isRawSvg(value) {
206
278
  return value.startsWith('<svg');
207
279
  }
@@ -261,11 +333,11 @@ class ZIconComponent {
261
333
  }
262
334
  }
263
335
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ZIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
264
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: ZIconComponent, isStandalone: true, selector: "z-icon, [z-icon]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, zType: { classPropertyName: "zType", publicName: "zType", isSignal: true, isRequired: false, transformFunction: null }, zAnimatedType: { classPropertyName: "zAnimatedType", publicName: "zAnimatedType", isSignal: true, isRequired: false, transformFunction: null }, zAnimate: { classPropertyName: "zAnimate", publicName: "zAnimate", isSignal: true, isRequired: false, transformFunction: null }, zAnimationTrigger: { classPropertyName: "zAnimationTrigger", publicName: "zAnimationTrigger", isSignal: true, isRequired: false, transformFunction: null }, zSize: { classPropertyName: "zSize", publicName: "zSize", isSignal: true, isRequired: false, transformFunction: null }, zStrokeWidth: { classPropertyName: "zStrokeWidth", publicName: "zStrokeWidth", isSignal: true, isRequired: false, transformFunction: null }, zSvg: { classPropertyName: "zSvg", publicName: "zSvg", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "setHoverActive(true)", "mouseleave": "setHoverActive(false)", "focusin": "setFocusActive(true)", "focusout": "setFocusActive(false)", "touchstart": "setTouchActive(true)", "touchend": "setTouchActive(false)" }, properties: { "class": "zClasses()", "style.width": "normalizedSize() + \"px\"", "style.height": "normalizedSize() + \"px\"" } }, ngImport: i0, template: "@if (animatedIcon()) {\n <ng-container [ngComponentOutlet]=\"animatedIcon()\" [ngComponentOutletInputs]=\"animatedIconInputs()\" />\n} @else if (resolvedSvg()) {\n <ng-icon [svg]=\"resolvedSvg()\" [size]=\"normalizedSize()\" [strokeWidth]=\"zStrokeWidth()\" />\n} @else if (!zSvg()) {\n <ng-icon [name]=\"zType()\" [size]=\"normalizedSize()\" [strokeWidth]=\"zStrokeWidth()\" />\n}\n", dependencies: [{ kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
336
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: ZIconComponent, isStandalone: true, selector: "z-icon, [z-icon]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, zType: { classPropertyName: "zType", publicName: "zType", isSignal: true, isRequired: false, transformFunction: null }, zAnimatedType: { classPropertyName: "zAnimatedType", publicName: "zAnimatedType", isSignal: true, isRequired: false, transformFunction: null }, zAnimate: { classPropertyName: "zAnimate", publicName: "zAnimate", isSignal: true, isRequired: false, transformFunction: null }, zAnimationTrigger: { classPropertyName: "zAnimationTrigger", publicName: "zAnimationTrigger", isSignal: true, isRequired: false, transformFunction: null }, zSize: { classPropertyName: "zSize", publicName: "zSize", isSignal: true, isRequired: false, transformFunction: null }, zStrokeWidth: { classPropertyName: "zStrokeWidth", publicName: "zStrokeWidth", isSignal: true, isRequired: false, transformFunction: null }, zSvg: { classPropertyName: "zSvg", publicName: "zSvg", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "setHoverActive(true)", "mouseleave": "setHoverActive(false)", "focusin": "setFocusActive(true)", "focusout": "setFocusActive(false)", "touchstart": "setTouchActive(true)", "touchend": "setTouchActive(false)" }, properties: { "class": "zClasses()", "style.width": "normalizedSize() + \"px\"", "style.height": "normalizedSize() + \"px\"" } }, viewQueries: [{ propertyName: "animatedIconHost", first: true, predicate: ["animatedIconHost"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "@if (animatedIcon()) {\n <ng-container #animatedIconHost />\n} @else if (resolvedSvg()) {\n <ng-icon [svg]=\"resolvedSvg()\" [size]=\"normalizedSize()\" [strokeWidth]=\"zStrokeWidth()\" />\n} @else if (!zSvg()) {\n <ng-icon [name]=\"zType()\" [size]=\"normalizedSize()\" [strokeWidth]=\"zStrokeWidth()\" />\n}\n", dependencies: [{ kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
265
337
  }
266
338
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ZIconComponent, decorators: [{
267
339
  type: Component,
268
- args: [{ selector: 'z-icon, [z-icon]', imports: [NgIconComponent, NgComponentOutlet], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
340
+ args: [{ selector: 'z-icon, [z-icon]', imports: [NgIconComponent], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
269
341
  '[class]': 'zClasses()',
270
342
  '[style.width]': 'normalizedSize() + "px"',
271
343
  '[style.height]': 'normalizedSize() + "px"',
@@ -275,9 +347,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
275
347
  '(focusout)': 'setFocusActive(false)',
276
348
  '(touchstart)': 'setTouchActive(true)',
277
349
  '(touchend)': 'setTouchActive(false)',
278
- }, template: "@if (animatedIcon()) {\n <ng-container [ngComponentOutlet]=\"animatedIcon()\" [ngComponentOutletInputs]=\"animatedIconInputs()\" />\n} @else if (resolvedSvg()) {\n <ng-icon [svg]=\"resolvedSvg()\" [size]=\"normalizedSize()\" [strokeWidth]=\"zStrokeWidth()\" />\n} @else if (!zSvg()) {\n <ng-icon [name]=\"zType()\" [size]=\"normalizedSize()\" [strokeWidth]=\"zStrokeWidth()\" />\n}\n" }]
279
- }], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], zType: [{ type: i0.Input, args: [{ isSignal: true, alias: "zType", required: false }] }], zAnimatedType: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAnimatedType", required: false }] }], zAnimate: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAnimate", required: false }] }], zAnimationTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAnimationTrigger", required: false }] }], zSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSize", required: false }] }], zStrokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "zStrokeWidth", required: false }] }], zSvg: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSvg", required: false }] }] } });
350
+ }, template: "@if (animatedIcon()) {\n <ng-container #animatedIconHost />\n} @else if (resolvedSvg()) {\n <ng-icon [svg]=\"resolvedSvg()\" [size]=\"normalizedSize()\" [strokeWidth]=\"zStrokeWidth()\" />\n} @else if (!zSvg()) {\n <ng-icon [name]=\"zType()\" [size]=\"normalizedSize()\" [strokeWidth]=\"zStrokeWidth()\" />\n}\n" }]
351
+ }], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], zType: [{ type: i0.Input, args: [{ isSignal: true, alias: "zType", required: false }] }], zAnimatedType: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAnimatedType", required: false }] }], zAnimate: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAnimate", required: false }] }], zAnimationTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAnimationTrigger", required: false }] }], zSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSize", required: false }] }], zStrokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "zStrokeWidth", required: false }] }], zSvg: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSvg", required: false }] }], animatedIconHost: [{ type: i0.ViewChild, args: ['animatedIconHost', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
280
352
 
353
+ /**
354
+ * Optional bundled icon overrides used before the CDN loader fallback.
355
+ *
356
+ * The default registry is intentionally empty so icon families remain lazy.
357
+ */
281
358
  const Z_ICONS = {};
282
359
 
283
360
  /**
@@ -298,6 +375,14 @@ function normalizeLucideSvg(svg) {
298
375
  const cssVar = 'style="stroke-width: var(--ng-icon__stroke-width, 2)"';
299
376
  return svg.replace(/stroke-width="[^"]*"/gi, cssVar);
300
377
  }
378
+ /**
379
+ * Normalizes line-based SVG families to the shared ng-icon stroke variable.
380
+ * This keeps `zStrokeWidth` consistent across the different icon sources.
381
+ */
382
+ function normalizeStrokeSvg(svg) {
383
+ const cssVar = 'style="stroke-width: var(--ng-icon__stroke-width, 2)"';
384
+ return svg.replace(/stroke-width="[^"]*"/gi, cssVar);
385
+ }
301
386
  /**
302
387
  * Phosphor SVGs already ship with fill="currentColor" and no stroke-width,
303
388
  * so no normalization is required. Kept as an explicit passthrough for parity
@@ -382,6 +467,10 @@ async function getFallbackIcon(name) {
382
467
  // console.warn(`[ZIcon] Using bundled fallback for "${name}"`);
383
468
  return icon;
384
469
  }
470
+ const familyIcon = await getFallbackIconFromFamily(name);
471
+ if (familyIcon) {
472
+ return familyIcon;
473
+ }
385
474
  const lucideIcons = await import('@ng-icons/lucide');
386
475
  const matchedIcon = Reflect.get(lucideIcons, name);
387
476
  if (typeof matchedIcon === 'string') {
@@ -389,6 +478,54 @@ async function getFallbackIcon(name) {
389
478
  }
390
479
  return lucideIcons.lucideCircleHelp;
391
480
  }
481
+ /**
482
+ * Resolves an icon from the local ng-icons package for CDN failure fallback.
483
+ * Dynamic imports keep the complete family out of the normal application path.
484
+ */
485
+ async function getFallbackIconFromFamily(name) {
486
+ if (name.startsWith('tabler')) {
487
+ const icons = name.endsWith('Fill')
488
+ ? await import('@ng-icons/tabler-icons/fill')
489
+ : await import('@ng-icons/tabler-icons');
490
+ const icon = Reflect.get(icons, name);
491
+ return typeof icon === 'string' ? icon : null;
492
+ }
493
+ if (name.startsWith('hero')) {
494
+ const icons = name.endsWith('Micro')
495
+ ? await import('@ng-icons/heroicons/micro')
496
+ : name.endsWith('Mini')
497
+ ? await import('@ng-icons/heroicons/mini')
498
+ : name.endsWith('Solid')
499
+ ? await import('@ng-icons/heroicons/solid')
500
+ : await import('@ng-icons/heroicons/outline');
501
+ const icon = Reflect.get(icons, name);
502
+ return typeof icon === 'string' ? icon : null;
503
+ }
504
+ if (name.startsWith('iconoir')) {
505
+ const icons = name.endsWith('Solid')
506
+ ? await import('@ng-icons/iconoir/solid')
507
+ : await import('@ng-icons/iconoir/regular');
508
+ const icon = Reflect.get(icons, name);
509
+ return typeof icon === 'string' ? icon : null;
510
+ }
511
+ if (name.startsWith('phosphor')) {
512
+ const { weight } = convertPhosphorIconName(name);
513
+ const icons = weight === 'bold'
514
+ ? await import('@ng-icons/phosphor-icons/bold')
515
+ : weight === 'fill'
516
+ ? await import('@ng-icons/phosphor-icons/fill')
517
+ : weight === 'light'
518
+ ? await import('@ng-icons/phosphor-icons/light')
519
+ : weight === 'thin'
520
+ ? await import('@ng-icons/phosphor-icons/thin')
521
+ : weight === 'duotone'
522
+ ? await import('@ng-icons/phosphor-icons/duotone')
523
+ : await import('@ng-icons/phosphor-icons/regular');
524
+ const icon = Reflect.get(icons, name);
525
+ return typeof icon === 'string' ? icon : null;
526
+ }
527
+ return null;
528
+ }
392
529
  /**
393
530
  * Converts ng-icons icon name to Lucide CDN format.
394
531
  * Examples:
@@ -461,6 +598,67 @@ function convertPhosphorIconName(name) {
461
598
  const iconName = weight === 'regular' ? kebab : `${kebab}-${weight}`;
462
599
  return { weight, iconName };
463
600
  }
601
+ /**
602
+ * Converts a Tabler icon name to its CDN folder and kebab-case asset name.
603
+ * Tabler's filled package uses a `Fill` suffix in its ng-icons exports.
604
+ */
605
+ function convertTablerIconName(name) {
606
+ const isFilled = name.endsWith('Fill');
607
+ const iconPart = (isFilled ? name.slice(0, -'Fill'.length) : name).replace(/^tabler/, '');
608
+ const iconName = iconPart
609
+ .replace(/([A-Z])/g, '-$1')
610
+ .replace(/(\d+)/g, '-$1')
611
+ .replace(/(\d)([a-z])/g, '$1-$2')
612
+ .toLowerCase()
613
+ .replace(/^-/, '')
614
+ .replace(/--+/g, '-');
615
+ return { style: isFilled ? 'filled' : 'outline', iconName };
616
+ }
617
+ /**
618
+ * Converts a Heroicons export name to its official size/style asset path.
619
+ * Mini and micro exports are solid-only 20px and 16px variants respectively.
620
+ */
621
+ function convertHeroIconName(name) {
622
+ let iconPart = name.replace(/^hero/, '');
623
+ let size = '24';
624
+ let style = 'outline';
625
+ if (iconPart.endsWith('Micro')) {
626
+ size = '16';
627
+ style = 'solid';
628
+ iconPart = iconPart.slice(0, -'Micro'.length);
629
+ }
630
+ else if (iconPart.endsWith('Mini')) {
631
+ size = '20';
632
+ style = 'solid';
633
+ iconPart = iconPart.slice(0, -'Mini'.length);
634
+ }
635
+ else if (iconPart.endsWith('Solid')) {
636
+ style = 'solid';
637
+ iconPart = iconPart.slice(0, -'Solid'.length);
638
+ }
639
+ const iconName = iconPart
640
+ .replace(/([A-Z])/g, '-$1')
641
+ .replace(/(\d+)/g, '-$1')
642
+ .replace(/(\d)([a-z])/g, '$1-$2')
643
+ .toLowerCase()
644
+ .replace(/^-/, '')
645
+ .replace(/--+/g, '-');
646
+ return { size, style, iconName };
647
+ }
648
+ /**
649
+ * Converts an Iconoir export name to its style folder and kebab-case asset.
650
+ */
651
+ function convertIconoirIconName(name) {
652
+ const isSolid = name.endsWith('Solid');
653
+ const iconPart = (isSolid ? name.slice(0, -'Solid'.length) : name).replace(/^iconoir/, '');
654
+ const iconName = iconPart
655
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
656
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
657
+ .toLowerCase()
658
+ .replace(/^-/, '')
659
+ .replace(/--+/g, '-');
660
+ return { style: isSolid ? 'solid' : 'regular', iconName };
661
+ }
464
662
  /**
465
663
  * Converts ng-icons-style Huge icon name to the @hugeicons/core-free-icons
466
664
  * CDN module name. We expose icons with a `huge` prefix over the original
@@ -508,6 +706,9 @@ function convertBrandIconName(name) {
508
706
  const LUCIDE_CDN_URL = 'https://cdn.jsdelivr.net/npm/lucide-static@latest/icons';
509
707
  const SAX_CDN_URL = 'https://cdn.jsdelivr.net/gh/placetopay-org/iconsax-vue@main/src/Base';
510
708
  const PHOSPHOR_CDN_URL = 'https://cdn.jsdelivr.net/npm/@phosphor-icons/core@latest/assets';
709
+ const TABLER_CDN_URL = 'https://cdn.jsdelivr.net/npm/@tabler/icons@latest/icons';
710
+ const HEROICONS_CDN_URL = 'https://cdn.jsdelivr.net/npm/heroicons@latest';
711
+ const ICONOIR_CDN_URL = 'https://cdn.jsdelivr.net/npm/iconoir@latest/icons';
511
712
  const HUGE_CDN_URL = 'https://cdn.jsdelivr.net/npm/@hugeicons/core-free-icons@4.2.1/dist/esm';
512
713
  const BRAND_CDN_URL = 'https://thesvg.org/icons';
513
714
  const ICON_CDN_TIMEOUT_MS = 4000;
@@ -520,9 +721,9 @@ function retryIconRequest(error, retryCount) {
520
721
  return timer(ICON_CDN_RETRY_DELAY_MS * retryCount);
521
722
  }
522
723
  /**
523
- * Provides hybrid icon loading from CDN with fallback to bundled icons:
524
- * - Primary: Load icons from CDN (Lucide & Iconsax)
525
- * - Fallback: Use bundled icons from Z_ICONS if CDN fails
724
+ * Provides hybrid icon loading from CDN with fallback to local icon packages.
725
+ * The default path keeps full icon families lazy, while a CDN failure loads
726
+ * only the requested family's package as a fallback.
526
727
  *
527
728
  * @example
528
729
  * ```typescript
@@ -593,6 +794,21 @@ function provideZIconLoader() {
593
794
  const url = `${PHOSPHOR_CDN_URL}/${weight}/${iconName}.svg`;
594
795
  return loadWithCache(name, url, http, normalizePhosphorSvg);
595
796
  }
797
+ if (name.startsWith('tabler')) {
798
+ const { style, iconName } = convertTablerIconName(name);
799
+ const url = `${TABLER_CDN_URL}/${style}/${iconName}.svg`;
800
+ return loadWithCache(name, url, http, normalizeStrokeSvg);
801
+ }
802
+ if (name.startsWith('hero')) {
803
+ const { size, style, iconName } = convertHeroIconName(name);
804
+ const url = `${HEROICONS_CDN_URL}/${size}/${style}/${iconName}.svg`;
805
+ return loadWithCache(name, url, http, normalizeStrokeSvg);
806
+ }
807
+ if (name.startsWith('iconoir')) {
808
+ const { style, iconName } = convertIconoirIconName(name);
809
+ const url = `${ICONOIR_CDN_URL}/${style}/${iconName}.svg`;
810
+ return loadWithCache(name, url, http, normalizeStrokeSvg);
811
+ }
596
812
  if (name.startsWith('huge')) {
597
813
  const moduleName = convertHugeIconName(name);
598
814
  const url = `${HUGE_CDN_URL}/${moduleName}.js`;