@ngxsmk/theme 1.3.2 → 2.0.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.
@@ -1,6 +1,6 @@
1
1
  import { DOCUMENT } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, signal, computed, effect, Injectable } from '@angular/core';
3
+ import { inject, Injectable, signal, computed, effect } from '@angular/core';
4
4
 
5
5
  /**
6
6
  * Design token model for the NGXSMK universal theme engine.
@@ -148,7 +148,7 @@ const DEFAULT_SEMANTIC = {
148
148
  };
149
149
  const DEFAULT_FONT_FAMILY = {
150
150
  sans: "'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif",
151
- mono: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace",
151
+ mono: "'Geist Mono', 'Geist Mono Fallback', monospace",
152
152
  };
153
153
  const DEFAULT_TYPOGRAPHY = {
154
154
  display: {
@@ -213,6 +213,20 @@ const TRACKING = {
213
213
  normal: '0',
214
214
  wide: '0.08em',
215
215
  };
216
+ /** Font-weight tokens — standalone scale for direct use outside typography presets. */
217
+ const FONT_WEIGHT = {
218
+ regular: '400',
219
+ medium: '500',
220
+ semibold: '600',
221
+ bold: '700',
222
+ };
223
+ /** Line-height tokens — unitless multipliers for flexible leading control. */
224
+ const LEADING = {
225
+ none: '1',
226
+ tight: '1.25',
227
+ normal: '1.5',
228
+ relaxed: '1.625',
229
+ };
216
230
  const SPACING = {
217
231
  '0': '0px',
218
232
  '0-5': '0.125rem',
@@ -339,51 +353,34 @@ function scaleVars(prefix, scale) {
339
353
  }
340
354
  return vars;
341
355
  }
342
- /** Tokens that do not change between light and dark mode. */
343
- function staticVars(theme) {
344
- const vars = {
345
- ...scaleVars('brand', theme.brand),
346
- ...scaleVars('secondary', theme.secondary),
347
- ...scaleVars('neutral', theme.neutral),
348
- '--ngxsmk-font-sans': theme.fontFamily.sans,
349
- '--ngxsmk-font-mono': theme.fontFamily.mono,
350
- '--ngxsmk-radius-base': theme.radius[theme.radiusBase],
351
- '--ngxsmk-control-height': CONTROL_HEIGHT,
352
- // Motion Tokens
353
- '--ngxsmk-motion-duration': 'var(--ngxsmk-duration-normal)',
354
- '--ngxsmk-motion-ease': 'var(--ngxsmk-ease-in-out)',
355
- // Interactive-state motion: shared lift/press transforms. Neutralized to
356
- // `none` under prefers-reduced-motion (see buildThemeCss).
357
- '--ngxsmk-hover-lift': 'translateY(-1px)',
358
- '--ngxsmk-press-scale': 'scale(0.98)',
359
- // Focus ring: THE focus indicator for every component. Aliases the
360
- // mode-aware --ngxsmk-shadow-focus; the error variant derives from the
361
- // semantic error role so invalid controls ring red in both modes.
362
- '--ngxsmk-focus-ring': 'var(--ngxsmk-shadow-focus)',
363
- '--ngxsmk-focus-ring-error': '0 0 0 2px color-mix(in srgb, var(--ngxsmk-color-error) 15%, transparent), ' +
364
- '0 0 0 4px color-mix(in srgb, var(--ngxsmk-color-error) 45%, transparent)',
365
- // Component-specific Tokens (Buttons)
356
+ /** Default CSS custom properties for each component, emitted once in :root. */
357
+ const COMPONENT_TOKEN_DEFAULTS = {
358
+ button: {
366
359
  '--ngxsmk-button-bg': 'var(--ngxsmk-color-primary)',
367
360
  '--ngxsmk-button-color': 'var(--ngxsmk-color-on-primary)',
368
361
  '--ngxsmk-button-font-family': 'var(--ngxsmk-font-sans)',
369
362
  '--ngxsmk-button-font-weight': '500',
370
363
  '--ngxsmk-button-radius': 'var(--ngxsmk-radius-base)',
371
364
  '--ngxsmk-button-disabled-opacity': '0.5',
372
- // Component-specific Tokens (Cards)
365
+ },
366
+ card: {
373
367
  '--ngxsmk-card-bg': 'var(--ngxsmk-color-surface)',
374
368
  '--ngxsmk-card-radius': 'var(--ngxsmk-radius-xl)',
375
369
  '--ngxsmk-card-shadow': 'var(--ngxsmk-shadow-sm)',
376
370
  '--ngxsmk-card-border-color': 'var(--ngxsmk-color-outline)',
377
- // Component-specific Tokens (Accordion)
371
+ },
372
+ accordion: {
378
373
  '--ngxsmk-accordion-bg': 'var(--ngxsmk-color-surface)',
379
374
  '--ngxsmk-accordion-radius': 'var(--ngxsmk-radius-lg)',
380
375
  '--ngxsmk-accordion-border-color': 'var(--ngxsmk-color-outline)',
381
- // Component-specific Tokens (Progress)
376
+ },
377
+ progress: {
382
378
  '--ngxsmk-progress-height': '0.5rem',
383
379
  '--ngxsmk-progress-bg': 'var(--ngxsmk-color-surface-variant)',
384
380
  '--ngxsmk-progress-color': 'var(--ngxsmk-color-primary)',
385
381
  '--ngxsmk-progress-radius': 'var(--ngxsmk-radius-full)',
386
- // Component-specific Tokens (Switch)
382
+ },
383
+ switch: {
387
384
  '--ngxsmk-switch-width': '2.25rem',
388
385
  '--ngxsmk-switch-height': '1.25rem',
389
386
  '--ngxsmk-switch-radius': 'var(--ngxsmk-radius-full)',
@@ -391,6 +388,74 @@ function staticVars(theme) {
391
388
  '--ngxsmk-switch-thumb-size': '0.875rem',
392
389
  '--ngxsmk-switch-thumb-bg': 'var(--ngxsmk-color-surface)',
393
390
  '--ngxsmk-switch-checked-bg': 'var(--ngxsmk-color-primary)',
391
+ },
392
+ spreadsheet: {
393
+ '--ngxsmk-spreadsheet-bg': 'var(--ngxsmk-color-surface)',
394
+ '--ngxsmk-spreadsheet-header-bg': 'var(--ngxsmk-color-surface-variant)',
395
+ '--ngxsmk-spreadsheet-header-color': 'var(--ngxsmk-color-on-surface-variant)',
396
+ '--ngxsmk-spreadsheet-border': 'var(--ngxsmk-color-outline)',
397
+ '--ngxsmk-spreadsheet-grid-color': 'var(--ngxsmk-color-outline-variant)',
398
+ '--ngxsmk-spreadsheet-hover-bg': 'var(--ngxsmk-color-surface-hover)',
399
+ '--ngxsmk-spreadsheet-selected-bg': 'color-mix(in srgb, var(--ngxsmk-color-primary) 12%, transparent)',
400
+ '--ngxsmk-spreadsheet-active-border': 'var(--ngxsmk-color-primary)',
401
+ '--ngxsmk-spreadsheet-font': 'var(--ngxsmk-font-sans)',
402
+ '--ngxsmk-spreadsheet-font-mono': 'var(--ngxsmk-font-mono)',
403
+ '--ngxsmk-spreadsheet-row-height': '2.25rem',
404
+ '--ngxsmk-spreadsheet-header-height': '2.5rem',
405
+ '--ngxsmk-spreadsheet-cell-padding': '6px 10px',
406
+ '--ngxsmk-spreadsheet-cell-font-size': 'var(--ngxsmk-text-body-sm-size)',
407
+ '--ngxsmk-spreadsheet-scrollbar-size': '8px',
408
+ '--ngxsmk-spreadsheet-shadow': 'var(--ngxsmk-shadow-sm)',
409
+ '--ngxsmk-spreadsheet-radius': 'var(--ngxsmk-radius-lg)',
410
+ '--ngxsmk-spreadsheet-edit-bg': 'var(--ngxsmk-color-surface)',
411
+ '--ngxsmk-spreadsheet-edit-border': 'var(--ngxsmk-color-primary)',
412
+ '--ngxsmk-spreadsheet-row-hover-bg': 'var(--ngxsmk-color-surface-hover)',
413
+ '--ngxsmk-spreadsheet-col-hover-bg': 'color-mix(in srgb, var(--ngxsmk-color-primary) 6%, transparent)',
414
+ '--ngxsmk-spreadsheet-frozen-shadow': '2px 0 4px rgba(0,0,0,0.08)',
415
+ '--ngxsmk-spreadsheet-selection-range': 'color-mix(in srgb, var(--ngxsmk-color-primary) 8%, transparent)',
416
+ },
417
+ inputGroup: {
418
+ '--ngxsmk-input-group-bg': 'var(--ngxsmk-color-surface)',
419
+ '--ngxsmk-input-group-border': 'var(--ngxsmk-color-outline-strong)',
420
+ '--ngxsmk-input-group-radius': 'var(--ngxsmk-radius-md)',
421
+ '--ngxsmk-input-group-shadow': 'none',
422
+ '--ngxsmk-input-group-focus-border': 'var(--ngxsmk-color-primary)',
423
+ '--ngxsmk-input-group-focus-shadow': '0 0 0 3px color-mix(in srgb, var(--ngxsmk-color-primary) 15%, transparent)',
424
+ '--ngxsmk-input-group-error-border': 'var(--ngxsmk-color-error)',
425
+ '--ngxsmk-input-group-error-focus-shadow': '0 0 0 3px color-mix(in srgb, var(--ngxsmk-color-error) 15%, transparent)',
426
+ '--ngxsmk-input-group-success-border': 'var(--ngxsmk-color-success)',
427
+ '--ngxsmk-input-group-warning-border': 'var(--ngxsmk-color-warning)',
428
+ '--ngxsmk-input-group-height': '2.5rem',
429
+ '--ngxsmk-input-group-padding': '0.75rem',
430
+ '--ngxsmk-input-group-gap': 'var(--ngxsmk-space-2)',
431
+ '--ngxsmk-input-group-font': 'var(--ngxsmk-font-sans)',
432
+ },
433
+ };
434
+ /** Tokens that do not change between light and dark mode. */
435
+ function staticVars(theme) {
436
+ const vars = {
437
+ ...scaleVars('brand', theme.brand),
438
+ ...scaleVars('secondary', theme.secondary),
439
+ ...scaleVars('neutral', theme.neutral),
440
+ '--ngxsmk-font-sans': theme.fontFamily.sans,
441
+ '--ngxsmk-font-mono': theme.fontFamily.mono,
442
+ '--ngxsmk-radius-base': theme.radius[theme.radiusBase],
443
+ '--ngxsmk-control-height': CONTROL_HEIGHT,
444
+ // Motion Tokens
445
+ '--ngxsmk-motion-duration': 'var(--ngxsmk-duration-normal)',
446
+ '--ngxsmk-motion-ease': 'var(--ngxsmk-ease-in-out)',
447
+ // Interactive-state motion: shared lift/press transforms. Neutralized to
448
+ // `none` under prefers-reduced-motion (see buildThemeCss).
449
+ '--ngxsmk-hover-lift': 'translateY(-1px)',
450
+ '--ngxsmk-press-scale': 'scale(0.98)',
451
+ // Focus ring: THE focus indicator for every component. Aliases the
452
+ // mode-aware --ngxsmk-shadow-focus; the error variant derives from the
453
+ // semantic error role so invalid controls ring red in both modes.
454
+ '--ngxsmk-focus-ring': 'var(--ngxsmk-shadow-focus)',
455
+ '--ngxsmk-focus-ring-error': '0 0 0 2px color-mix(in srgb, var(--ngxsmk-color-error) 15%, transparent), ' +
456
+ '0 0 0 4px color-mix(in srgb, var(--ngxsmk-color-error) 45%, transparent)',
457
+ // Component-specific token defaults
458
+ ...Object.values(COMPONENT_TOKEN_DEFAULTS).reduce((merged, tokens) => ({ ...merged, ...tokens }), {}),
394
459
  };
395
460
  for (const [key, value] of Object.entries(CONTROL_HEIGHTS)) {
396
461
  vars[`--ngxsmk-control-height-${key}`] = value;
@@ -404,6 +469,12 @@ function staticVars(theme) {
404
469
  for (const [key, value] of Object.entries(TRACKING)) {
405
470
  vars[`--ngxsmk-tracking-${key}`] = value;
406
471
  }
472
+ for (const [key, value] of Object.entries(FONT_WEIGHT)) {
473
+ vars[`--ngxsmk-font-weight-${key}`] = value;
474
+ }
475
+ for (const [key, value] of Object.entries(LEADING)) {
476
+ vars[`--ngxsmk-leading-${key}`] = value;
477
+ }
407
478
  for (const [key, value] of Object.entries(theme.spacing)) {
408
479
  vars[`--ngxsmk-space-${key}`] = value;
409
480
  }
@@ -476,6 +547,19 @@ function lightVars(theme) {
476
547
  '--ngxsmk-color-ring': theme.brandBase,
477
548
  '--ngxsmk-color-backdrop': 'rgb(1 18 40 / 0.4)',
478
549
  '--ngxsmk-shadow-focus': `0 0 0 2px ${theme.brand[100]}, 0 0 0 4px color-mix(in srgb, ${theme.brandBase} 45%, transparent)`,
550
+ // Categorical chart palette — a fixed 8-hue qualitative set (NOT derived
551
+ // from the brand, so adjacent series stay distinguishable across themes),
552
+ // stepped for the light surface. Validated for the light chart surface:
553
+ // CVD worst-adjacent ΔE 24.2, chroma/lightness in-band. Charts read these
554
+ // via getComputedStyle; consumers can override any slot to rebrand series.
555
+ '--ngxsmk-chart-1': '#2A78D6',
556
+ '--ngxsmk-chart-2': '#1BAF7A',
557
+ '--ngxsmk-chart-3': '#EDA100',
558
+ '--ngxsmk-chart-4': '#008300',
559
+ '--ngxsmk-chart-5': '#4A3AA7',
560
+ '--ngxsmk-chart-6': '#E34948',
561
+ '--ngxsmk-chart-7': '#E87BA4',
562
+ '--ngxsmk-chart-8': '#EB6834',
479
563
  };
480
564
  const tertiary = deriveScale(rotateHue(theme.brandBase, 64));
481
565
  vars['--ngxsmk-color-tertiary'] = tertiary[500];
@@ -530,6 +614,18 @@ function darkVars(theme) {
530
614
  '--ngxsmk-color-ring': theme.brand[400],
531
615
  '--ngxsmk-color-backdrop': 'rgb(17 17 18 / 0.6)',
532
616
  '--ngxsmk-shadow-focus': `0 0 0 2px color-mix(in srgb, ${theme.brand[400]} 25%, transparent), 0 0 0 4px color-mix(in srgb, ${theme.brand[400]} 45%, transparent)`,
617
+ // Categorical chart palette — the same eight hues as light, re-stepped for
618
+ // the dark surface (not a different palette). Validated for the dark chart
619
+ // surface: all slots ≥ 3:1 contrast, CVD worst-adjacent ΔE 10.3 (floor band,
620
+ // relies on the legend/labels charts already render for secondary encoding).
621
+ '--ngxsmk-chart-1': '#3987E5',
622
+ '--ngxsmk-chart-2': '#199E70',
623
+ '--ngxsmk-chart-3': '#C98500',
624
+ '--ngxsmk-chart-4': '#008300',
625
+ '--ngxsmk-chart-5': '#9085E9',
626
+ '--ngxsmk-chart-6': '#E66767',
627
+ '--ngxsmk-chart-7': '#D55181',
628
+ '--ngxsmk-chart-8': '#D95926',
533
629
  };
534
630
  const tertiary = deriveScale(rotateHue(theme.brandBase, 64));
535
631
  vars['--ngxsmk-color-tertiary'] = tertiary[400];
@@ -658,19 +754,174 @@ const presets = {
658
754
  rose: rosePreset,
659
755
  };
660
756
 
661
- const STORAGE_KEY = 'ngxsmk-theme-mode';
757
+ /**
758
+ * Maps NGXSMK semantic color roles to Ionic's color variable namespace.
759
+ *
760
+ * Ionic expects:
761
+ * --ion-color-primary, --ion-color-primary-rgb, --ion-color-primary-shade,
762
+ * --ion-color-primary-tint, --ion-color-secondary, etc.
763
+ *
764
+ * This adapter produces the subset Ionic needs for theming.
765
+ */
766
+ const ionicVarsAdapter = {
767
+ name: 'ionic',
768
+ vars(theme) {
769
+ const vars = {};
770
+ // Primary
771
+ vars['--ion-color-primary'] = theme.brandBase;
772
+ vars['--ion-color-primary-rgb'] = hexToRgbStr(theme.brandBase);
773
+ vars['--ion-color-primary-shade'] = shiftLightnessHex(theme.brandBase, -14);
774
+ vars['--ion-color-primary-tint'] = shiftLightnessHex(theme.brandBase, 8);
775
+ // Secondary
776
+ vars['--ion-color-secondary'] = theme.secondaryBase;
777
+ vars['--ion-color-secondary-rgb'] = hexToRgbStr(theme.secondaryBase);
778
+ vars['--ion-color-secondary-shade'] = shiftLightnessHex(theme.secondaryBase, -14);
779
+ vars['--ion-color-secondary-tint'] = shiftLightnessHex(theme.secondaryBase, 8);
780
+ // Tertiary (derived from brand hue + 64)
781
+ const tertiary = deriveScale(rotateHue(theme.brandBase, 64));
782
+ vars['--ion-color-tertiary'] = tertiary[500];
783
+ vars['--ion-color-tertiary-rgb'] = hexToRgbStr(tertiary[500]);
784
+ vars['--ion-color-tertiary-shade'] = tertiary[600];
785
+ vars['--ion-color-tertiary-tint'] = tertiary[400];
786
+ // Semantic status colors
787
+ for (const [name, hex] of Object.entries(theme.semantic)) {
788
+ vars[`--ion-color-${name}`] = hex;
789
+ vars[`--ion-color-${name}-rgb`] = hexToRgbStr(hex);
790
+ }
791
+ // Background / surface
792
+ vars['--ion-background-color'] = theme.neutral[50];
793
+ vars['--ion-background-color-rgb'] = hexToRgbStr(theme.neutral[50]);
794
+ vars['--ion-text-color'] = '#0A1317';
795
+ vars['--ion-text-color-rgb'] = '10, 19, 23';
796
+ // Typography
797
+ vars['--ion-font-family'] = theme.fontFamily.sans;
798
+ // Border radius (Ionic uses --ion-border-radius)
799
+ vars['--ion-border-radius'] = theme.radius[theme.radiusBase];
800
+ return vars;
801
+ },
802
+ };
803
+ /** Default NGXSMK adapter — emits `--ngxsmk-*` variables. */
804
+ const cssVarsAdapter = {
805
+ name: 'ngxsmk',
806
+ vars: () => ({}), // Not used directly — buildThemeCss handles this
807
+ };
808
+ // ─── Helpers ──────────────────────────────────────────────
809
+ function hexToRgbStr(hex) {
810
+ const h = hex.replace('#', '');
811
+ const r = parseInt(h.substring(0, 2), 16);
812
+ const g = parseInt(h.substring(2, 4), 16);
813
+ const b = parseInt(h.substring(4, 6), 16);
814
+ return `${r}, ${g}, ${b}`;
815
+ }
816
+ function shiftLightnessHex(hex, amount) {
817
+ // Simple hex lightness shift — import from color.ts would be circular
818
+ const h = hex.replace('#', '');
819
+ let r = parseInt(h.substring(0, 2), 16);
820
+ let g = parseInt(h.substring(2, 4), 16);
821
+ let b = parseInt(h.substring(4, 6), 16);
822
+ if (amount > 0) {
823
+ r = Math.min(255, r + Math.round((255 - r) * (amount / 100)));
824
+ g = Math.min(255, g + Math.round((255 - g) * (amount / 100)));
825
+ b = Math.min(255, b + Math.round((255 - b) * (amount / 100)));
826
+ }
827
+ else {
828
+ const factor = 1 + amount / 100;
829
+ r = Math.max(0, Math.round(r * factor));
830
+ g = Math.max(0, Math.round(g * factor));
831
+ b = Math.max(0, Math.round(b * factor));
832
+ }
833
+ return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
834
+ }
835
+
662
836
  const STYLE_ID = 'ngxsmk-theme';
837
+ const IONIC_STYLE_ID = 'ngxsmk-ionic-theme';
838
+ /**
839
+ * Injects generated theme CSS into the document at runtime.
840
+ *
841
+ * Decoupled from dark-mode switching so consumers who only need
842
+ * token injection (e.g. the CLI theme-preview) don't pull in
843
+ * class-toggle / localStorage / matchMedia logic.
844
+ *
845
+ * SSR-safe: all DOM access goes through the injected document.
846
+ */
847
+ class NgxThemeInjectorService {
848
+ constructor() {
849
+ this.document = inject(DOCUMENT);
850
+ }
851
+ /**
852
+ * Apply a theme at runtime. Generates token CSS from the config and swaps
853
+ * it into a dedicated `<style>` element, overriding the build-time theme.
854
+ *
855
+ * @param adapter Optional output adapter. Defaults to the standard NGXSMK
856
+ * `--ngxsmk-*` CSS variables. Pass `ionicVarsAdapter` to also generate
857
+ * `--ion-*` variables for Ionic components.
858
+ */
859
+ applyTheme(config, adapter) {
860
+ const head = this.document.head;
861
+ let style = this.document.getElementById(STYLE_ID);
862
+ if (!style) {
863
+ style = this.document.createElement('style');
864
+ style.id = STYLE_ID;
865
+ head.appendChild(style);
866
+ }
867
+ let css = buildThemeCss(config);
868
+ if (adapter) {
869
+ const resolved = resolveTheme(config);
870
+ const platformVars = adapter.vars(resolved);
871
+ css += '\n' + this.buildAdapterCss(adapter.name, platformVars);
872
+ }
873
+ style.textContent = css;
874
+ }
875
+ /**
876
+ * Apply Ionic-specific token variables alongside the standard NGXSMK theme.
877
+ * Generates `--ion-*` variables derived from the same `ThemeConfig`.
878
+ */
879
+ applyIonicTheme(config) {
880
+ const head = this.document.head;
881
+ let style = this.document.getElementById(IONIC_STYLE_ID);
882
+ if (!style) {
883
+ style = this.document.createElement('style');
884
+ style.id = IONIC_STYLE_ID;
885
+ head.appendChild(style);
886
+ }
887
+ const resolved = resolveTheme(config);
888
+ const vars = ionicVarsAdapter.vars(resolved);
889
+ style.textContent = this.buildAdapterCss('ionic', vars);
890
+ }
891
+ /** Remove a runtime-applied theme, falling back to the build-time CSS. */
892
+ clearTheme() {
893
+ this.document.getElementById(STYLE_ID)?.remove();
894
+ }
895
+ /** Remove the Ionic adapter stylesheet. */
896
+ clearIonicTheme() {
897
+ this.document.getElementById(IONIC_STYLE_ID)?.remove();
898
+ }
899
+ buildAdapterCss(adapterName, vars) {
900
+ const lines = Object.entries(vars).map(([k, v]) => ` ${k}: ${v};`);
901
+ return `/* @ngxsmk/theme — ${adapterName} adapter */\n:root {\n${lines.join('\n')}\n}`;
902
+ }
903
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.0", ngImport: i0, type: NgxThemeInjectorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
904
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.0", ngImport: i0, type: NgxThemeInjectorService, providedIn: 'root' }); }
905
+ }
906
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.0", ngImport: i0, type: NgxThemeInjectorService, decorators: [{
907
+ type: Injectable,
908
+ args: [{ providedIn: 'root' }]
909
+ }] });
910
+
911
+ const STORAGE_KEY = 'ngxsmk-theme-mode';
663
912
  /**
664
913
  * Runtime theme control: dark-mode switching (class strategy) and dynamic
665
914
  * theme application by injecting generated token CSS into the document.
666
915
  *
916
+ * CSS injection is delegated to {@link NgxThemeInjectorService} for
917
+ * consumers who only need token injection without dark-mode logic.
918
+ *
667
919
  * SSR-safe: all DOM access goes through the injected document and degrades
668
920
  * gracefully when `window`/`matchMedia` are unavailable.
669
921
  */
670
922
  class NgxsmkThemeService {
671
923
  constructor() {
672
- // Inject DOCUMENT first so that restoreMode() and matchSystemDark()
673
- // can safely access it when the signals are initialised below.
924
+ this.injector = inject(NgxThemeInjectorService);
674
925
  this.document = inject(DOCUMENT);
675
926
  this.mode = signal(this.restoreMode());
676
927
  this.systemDark = signal(this.matchSystemDark());
@@ -693,23 +944,21 @@ class NgxsmkThemeService {
693
944
  toggle() {
694
945
  this.setMode(this.isDark() ? 'light' : 'dark');
695
946
  }
696
- /**
697
- * Apply a theme at runtime. Generates token CSS from the config and swaps
698
- * it into a dedicated `<style>` element, overriding the build-time theme.
699
- */
700
- applyTheme(config) {
701
- const head = this.document.head;
702
- let style = this.document.getElementById(STYLE_ID);
703
- if (!style) {
704
- style = this.document.createElement('style');
705
- style.id = STYLE_ID;
706
- head.appendChild(style);
707
- }
708
- style.textContent = buildThemeCss(config);
947
+ /** @see NgxThemeInjectorService.applyTheme */
948
+ applyTheme(config, adapter) {
949
+ this.injector.applyTheme(config, adapter);
709
950
  }
710
- /** Remove a runtime-applied theme, falling back to the build-time CSS. */
951
+ /** @see NgxThemeInjectorService.applyIonicTheme */
952
+ applyIonicTheme(config) {
953
+ this.injector.applyIonicTheme(config);
954
+ }
955
+ /** @see NgxThemeInjectorService.clearTheme */
711
956
  clearTheme() {
712
- this.document.getElementById(STYLE_ID)?.remove();
957
+ this.injector.clearTheme();
958
+ }
959
+ /** @see NgxThemeInjectorService.clearIonicTheme */
960
+ clearIonicTheme() {
961
+ this.injector.clearIonicTheme();
713
962
  }
714
963
  restoreMode() {
715
964
  try {
@@ -742,5 +991,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.0", ngImpor
742
991
  * Generated bundle index. Do not edit.
743
992
  */
744
993
 
745
- export { CONTROL_HEIGHT, CONTROL_HEIGHTS, DEFAULT_FONT_FAMILY, DEFAULT_NEUTRAL, DEFAULT_SEMANTIC, DEFAULT_TYPOGRAPHY, DURATION, EASING, ICON_SIZE, NgxsmkThemeService, OPACITY, RADIUS, SHADOW, SPACING, TRACKING, Z_INDEX, buildThemeCss, defineConfig, deriveScale, emeraldPreset, hexToOklch, neutralPreset, oklchToHex, presets, resolveTheme, rosePreset, rotateHue, shiftLightness, violetPreset };
994
+ export { CONTROL_HEIGHT, CONTROL_HEIGHTS, DEFAULT_FONT_FAMILY, DEFAULT_NEUTRAL, DEFAULT_SEMANTIC, DEFAULT_TYPOGRAPHY, DURATION, EASING, FONT_WEIGHT, ICON_SIZE, LEADING, NgxThemeInjectorService, NgxsmkThemeService, OPACITY, RADIUS, SHADOW, SPACING, TRACKING, Z_INDEX, buildThemeCss, cssVarsAdapter, defineConfig, deriveScale, emeraldPreset, hexToOklch, ionicVarsAdapter, neutralPreset, oklchToHex, presets, resolveTheme, rosePreset, rotateHue, shiftLightness, violetPreset };
746
995
  //# sourceMappingURL=ngxsmk-theme.mjs.map