@ngxsmk/theme 2.0.0 → 3.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.
- package/README.md +1 -1
- package/esm2022/lib/css.mjs +17 -1
- package/esm2022/lib/ionic-provider.mjs +30 -0
- package/esm2022/lib/theme-injector.service.mjs +19 -8
- package/esm2022/lib/token-adapter.mjs +154 -49
- package/esm2022/lib/tokens.mjs +10 -1
- package/esm2022/public-api.mjs +2 -1
- package/fesm2022/ngxsmk-theme.mjs +225 -57
- package/fesm2022/ngxsmk-theme.mjs.map +1 -1
- package/lib/css.d.ts.map +1 -1
- package/lib/ionic-provider.d.ts +18 -0
- package/lib/ionic-provider.d.ts.map +1 -0
- package/lib/theme-injector.service.d.ts.map +1 -1
- package/lib/token-adapter.d.ts +9 -7
- package/lib/token-adapter.d.ts.map +1 -1
- package/lib/tokens.d.ts +9 -0
- package/lib/tokens.d.ts.map +1 -1
- package/package.json +6 -2
- package/public-api.d.ts +1 -0
- package/public-api.d.ts.map +1 -1
- package/styles/ngxsmk.css +15 -0
- package/styles/ngxsmk.emerald.css +15 -0
- package/styles/ngxsmk.neutral.css +15 -0
- package/styles/ngxsmk.rose.css +15 -0
- package/styles/ngxsmk.violet.css +15 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DOCUMENT } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { inject, Injectable, signal, computed, effect } from '@angular/core';
|
|
3
|
+
import { inject, Injectable, signal, computed, effect, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Design token model for the NGXSMK universal theme engine.
|
|
@@ -303,6 +303,15 @@ const EASING = {
|
|
|
303
303
|
'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
304
304
|
emphasized: 'cubic-bezier(0.2, 0, 0, 1)',
|
|
305
305
|
};
|
|
306
|
+
/** Safe-area inset defaults (fallback to CSS env() variables for iOS/Android/Ionic). */
|
|
307
|
+
const SAFE_AREA = {
|
|
308
|
+
top: 'env(safe-area-inset-top, 0px)',
|
|
309
|
+
bottom: 'env(safe-area-inset-bottom, 0px)',
|
|
310
|
+
left: 'env(safe-area-inset-left, 0px)',
|
|
311
|
+
right: 'env(safe-area-inset-right, 0px)',
|
|
312
|
+
};
|
|
313
|
+
/** Minimum touch target size for mobile accessibility (Apple HIG & Android Material). */
|
|
314
|
+
const TOUCH_TARGET_MIN = '44px';
|
|
306
315
|
|
|
307
316
|
/**
|
|
308
317
|
* Identity helper that gives `ngxsmk.config.ts` files full type inference:
|
|
@@ -454,6 +463,12 @@ function staticVars(theme) {
|
|
|
454
463
|
'--ngxsmk-focus-ring': 'var(--ngxsmk-shadow-focus)',
|
|
455
464
|
'--ngxsmk-focus-ring-error': '0 0 0 2px color-mix(in srgb, var(--ngxsmk-color-error) 15%, transparent), ' +
|
|
456
465
|
'0 0 0 4px color-mix(in srgb, var(--ngxsmk-color-error) 45%, transparent)',
|
|
466
|
+
// Mobile viewport & safe-area tokens (iOS / Android / Ionic)
|
|
467
|
+
'--ngxsmk-safe-area-top': 'env(safe-area-inset-top, 0px)',
|
|
468
|
+
'--ngxsmk-safe-area-bottom': 'env(safe-area-inset-bottom, 0px)',
|
|
469
|
+
'--ngxsmk-safe-area-left': 'env(safe-area-inset-left, 0px)',
|
|
470
|
+
'--ngxsmk-safe-area-right': 'env(safe-area-inset-right, 0px)',
|
|
471
|
+
'--ngxsmk-touch-target-min': '44px',
|
|
457
472
|
// Component-specific token defaults
|
|
458
473
|
...Object.values(COMPONENT_TOKEN_DEFAULTS).reduce((merged, tokens) => ({ ...merged, ...tokens }), {}),
|
|
459
474
|
};
|
|
@@ -532,12 +547,17 @@ function lightVars(theme) {
|
|
|
532
547
|
'--ngxsmk-color-on-surface': '#0A1317',
|
|
533
548
|
'--ngxsmk-color-surface-variant': theme.neutral[100],
|
|
534
549
|
'--ngxsmk-color-on-surface-variant': theme.neutral[500],
|
|
550
|
+
'--ngxsmk-color-surface-muted': theme.neutral[50],
|
|
551
|
+
'--ngxsmk-color-surface-elevated': '#FFFFFF',
|
|
552
|
+
'--ngxsmk-color-surface-overlay': '#FFFFFF',
|
|
553
|
+
'--ngxsmk-color-on-surface-muted': theme.neutral[400],
|
|
535
554
|
'--ngxsmk-color-surface-hover': 'rgb(5 54 89 / 0.05)',
|
|
536
555
|
'--ngxsmk-color-surface-active': 'rgb(5 54 89 / 0.1)',
|
|
537
556
|
'--ngxsmk-color-surface-container': '#FFFFFF',
|
|
538
557
|
'--ngxsmk-color-outline': 'rgb(5 54 89 / 0.1)',
|
|
539
558
|
'--ngxsmk-color-outline-strong': theme.neutral[200],
|
|
540
559
|
'--ngxsmk-color-outline-variant': 'rgb(5 54 89 / 0.06)',
|
|
560
|
+
'--ngxsmk-color-outline-subtle': 'rgb(5 54 89 / 0.05)',
|
|
541
561
|
// Elevation ladder
|
|
542
562
|
'--ngxsmk-color-surface-1': '#FFFFFF',
|
|
543
563
|
'--ngxsmk-color-surface-2': theme.neutral[50],
|
|
@@ -599,12 +619,17 @@ function darkVars(theme) {
|
|
|
599
619
|
'--ngxsmk-color-on-surface': '#DFE2E5',
|
|
600
620
|
'--ngxsmk-color-surface-variant': '#28292C',
|
|
601
621
|
'--ngxsmk-color-on-surface-variant': '#AAAFB5',
|
|
622
|
+
'--ngxsmk-color-surface-muted': '#141416',
|
|
623
|
+
'--ngxsmk-color-surface-elevated': '#28292C',
|
|
624
|
+
'--ngxsmk-color-surface-overlay': '#2F3136',
|
|
625
|
+
'--ngxsmk-color-on-surface-muted': '#777C85',
|
|
602
626
|
'--ngxsmk-color-surface-hover': 'rgb(255 255 255 / 0.05)',
|
|
603
627
|
'--ngxsmk-color-surface-active': 'rgb(255 255 255 / 0.1)',
|
|
604
628
|
'--ngxsmk-color-surface-container': '#28292C',
|
|
605
629
|
'--ngxsmk-color-outline': 'rgb(242 244 246 / 0.1)',
|
|
606
630
|
'--ngxsmk-color-outline-strong': '#494D53',
|
|
607
631
|
'--ngxsmk-color-outline-variant': 'rgb(242 244 246 / 0.06)',
|
|
632
|
+
'--ngxsmk-color-outline-subtle': 'rgb(242 244 246 / 0.05)',
|
|
608
633
|
// Elevation ladder
|
|
609
634
|
'--ngxsmk-color-surface-1': theme.neutral[800],
|
|
610
635
|
'--ngxsmk-color-surface-2': theme.neutral[700],
|
|
@@ -755,51 +780,127 @@ const presets = {
|
|
|
755
780
|
};
|
|
756
781
|
|
|
757
782
|
/**
|
|
758
|
-
* Maps NGXSMK semantic color roles to Ionic
|
|
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.
|
|
783
|
+
* Maps NGXSMK semantic color roles to Ionic 7 & 8 color variable specifications.
|
|
763
784
|
*
|
|
764
|
-
*
|
|
785
|
+
* Emits full color suites (base, rgb, contrast, contrast-rgb, shade, tint),
|
|
786
|
+
* 19-step contrast ladder (--ion-color-step-50..950), safe area mappings, and
|
|
787
|
+
* surface variables for seamless Ionic framework interoperability.
|
|
765
788
|
*/
|
|
766
789
|
const ionicVarsAdapter = {
|
|
767
790
|
name: 'ionic',
|
|
768
|
-
vars(theme) {
|
|
769
|
-
|
|
770
|
-
|
|
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);
|
|
791
|
+
vars(theme, options) {
|
|
792
|
+
if (options?.isDark) {
|
|
793
|
+
return this.darkVars ? this.darkVars(theme) : buildLightVars(theme);
|
|
790
794
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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;
|
|
795
|
+
return buildLightVars(theme);
|
|
796
|
+
},
|
|
797
|
+
darkVars(theme) {
|
|
798
|
+
return buildDarkVars(theme);
|
|
801
799
|
},
|
|
802
800
|
};
|
|
801
|
+
function buildColorSuite(prefix, baseHex, vars) {
|
|
802
|
+
const contrastHex = getContrastHex(baseHex);
|
|
803
|
+
vars[`--ion-color-${prefix}`] = baseHex;
|
|
804
|
+
vars[`--ion-color-${prefix}-rgb`] = hexToRgbStr(baseHex);
|
|
805
|
+
vars[`--ion-color-${prefix}-contrast`] = contrastHex;
|
|
806
|
+
vars[`--ion-color-${prefix}-contrast-rgb`] = hexToRgbStr(contrastHex);
|
|
807
|
+
vars[`--ion-color-${prefix}-shade`] = shiftLightnessHex(baseHex, -12);
|
|
808
|
+
vars[`--ion-color-${prefix}-tint`] = shiftLightnessHex(baseHex, 10);
|
|
809
|
+
}
|
|
810
|
+
function buildLightVars(theme) {
|
|
811
|
+
const vars = {};
|
|
812
|
+
// Primary & Secondary
|
|
813
|
+
buildColorSuite('primary', theme.brandBase, vars);
|
|
814
|
+
buildColorSuite('secondary', theme.secondaryBase, vars);
|
|
815
|
+
// Tertiary (derived from brand hue + 64)
|
|
816
|
+
const tertiary = deriveScale(rotateHue(theme.brandBase, 64));
|
|
817
|
+
buildColorSuite('tertiary', tertiary[500], vars);
|
|
818
|
+
// Status & Feedback colors
|
|
819
|
+
const successHex = theme.semantic['success'] ?? '#0D8626';
|
|
820
|
+
const warningHex = theme.semantic['warning'] ?? '#E9AF08';
|
|
821
|
+
const dangerHex = theme.semantic['error'] ?? '#E3193B';
|
|
822
|
+
const infoHex = theme.semantic['info'] ?? '#2563EB';
|
|
823
|
+
buildColorSuite('success', successHex, vars);
|
|
824
|
+
buildColorSuite('warning', warningHex, vars);
|
|
825
|
+
buildColorSuite('danger', dangerHex, vars);
|
|
826
|
+
buildColorSuite('info', infoHex, vars);
|
|
827
|
+
// Medium / Light / Dark baseline scales
|
|
828
|
+
buildColorSuite('light', theme.neutral[100] ?? '#F1F4F7', vars);
|
|
829
|
+
buildColorSuite('medium', theme.neutral[500] ?? '#778593', vars);
|
|
830
|
+
buildColorSuite('dark', theme.neutral[900] ?? '#18181A', vars);
|
|
831
|
+
// Background / Surfaces
|
|
832
|
+
const bgHex = theme.neutral[50] ?? '#FFFFFF';
|
|
833
|
+
const textHex = '#0A1317';
|
|
834
|
+
vars['--ion-background-color'] = bgHex;
|
|
835
|
+
vars['--ion-background-color-rgb'] = hexToRgbStr(bgHex);
|
|
836
|
+
vars['--ion-text-color'] = textHex;
|
|
837
|
+
vars['--ion-text-color-rgb'] = hexToRgbStr(textHex);
|
|
838
|
+
vars['--ion-card-background'] = '#FFFFFF';
|
|
839
|
+
vars['--ion-item-background'] = '#FFFFFF';
|
|
840
|
+
vars['--ion-item-color'] = textHex;
|
|
841
|
+
vars['--ion-toolbar-background'] = bgHex;
|
|
842
|
+
vars['--ion-toolbar-color'] = textHex;
|
|
843
|
+
vars['--ion-tab-bar-background'] = '#FFFFFF';
|
|
844
|
+
vars['--ion-border-color'] = theme.neutral[200] ?? '#CCD3DB';
|
|
845
|
+
// Typography & Radius
|
|
846
|
+
vars['--ion-font-family'] = theme.fontFamily.sans;
|
|
847
|
+
vars['--ion-border-radius'] = theme.radius[theme.radiusBase];
|
|
848
|
+
// Stepped colors for light mode (interpolated between bg and text)
|
|
849
|
+
interpolateSteppedColors(bgHex, textHex, vars);
|
|
850
|
+
// Safe area insets
|
|
851
|
+
vars['--ion-safe-area-top'] = 'var(--ngxsmk-safe-area-top, env(safe-area-inset-top, 0px))';
|
|
852
|
+
vars['--ion-safe-area-bottom'] =
|
|
853
|
+
'var(--ngxsmk-safe-area-bottom, env(safe-area-inset-bottom, 0px))';
|
|
854
|
+
vars['--ion-safe-area-left'] = 'var(--ngxsmk-safe-area-left, env(safe-area-inset-left, 0px))';
|
|
855
|
+
vars['--ion-safe-area-right'] = 'var(--ngxsmk-safe-area-right, env(safe-area-inset-right, 0px))';
|
|
856
|
+
return vars;
|
|
857
|
+
}
|
|
858
|
+
function buildDarkVars(theme) {
|
|
859
|
+
const vars = {};
|
|
860
|
+
// Primary & Secondary dark adjustments
|
|
861
|
+
const primaryDark = theme.brand[400] ?? theme.brandBase;
|
|
862
|
+
const secondaryDark = theme.secondary[400] ?? theme.secondaryBase;
|
|
863
|
+
buildColorSuite('primary', primaryDark, vars);
|
|
864
|
+
buildColorSuite('secondary', secondaryDark, vars);
|
|
865
|
+
// Tertiary
|
|
866
|
+
const tertiary = deriveScale(rotateHue(theme.brandBase, 64));
|
|
867
|
+
buildColorSuite('tertiary', tertiary[400] ?? tertiary[500], vars);
|
|
868
|
+
// Status & Feedback
|
|
869
|
+
buildColorSuite('success', shiftLightnessHex(theme.semantic['success'] ?? '#0D8626', 10), vars);
|
|
870
|
+
buildColorSuite('warning', shiftLightnessHex(theme.semantic['warning'] ?? '#E9AF08', 8), vars);
|
|
871
|
+
buildColorSuite('danger', shiftLightnessHex(theme.semantic['error'] ?? '#E3193B', 10), vars);
|
|
872
|
+
buildColorSuite('info', shiftLightnessHex(theme.semantic['info'] ?? '#2563EB', 10), vars);
|
|
873
|
+
buildColorSuite('light', theme.neutral[800] ?? '#1F1F22', vars);
|
|
874
|
+
buildColorSuite('medium', theme.neutral[400] ?? '#778593', vars);
|
|
875
|
+
buildColorSuite('dark', theme.neutral[100] ?? '#F1F4F7', vars);
|
|
876
|
+
// Dark backgrounds & surfaces
|
|
877
|
+
const darkBgHex = '#111112';
|
|
878
|
+
const darkTextHex = '#DFE2E5';
|
|
879
|
+
const darkSurface = '#1F1F22';
|
|
880
|
+
vars['--ion-background-color'] = darkBgHex;
|
|
881
|
+
vars['--ion-background-color-rgb'] = hexToRgbStr(darkBgHex);
|
|
882
|
+
vars['--ion-text-color'] = darkTextHex;
|
|
883
|
+
vars['--ion-text-color-rgb'] = hexToRgbStr(darkTextHex);
|
|
884
|
+
vars['--ion-card-background'] = darkSurface;
|
|
885
|
+
vars['--ion-item-background'] = darkSurface;
|
|
886
|
+
vars['--ion-item-color'] = darkTextHex;
|
|
887
|
+
vars['--ion-toolbar-background'] = '#18181A';
|
|
888
|
+
vars['--ion-toolbar-color'] = darkTextHex;
|
|
889
|
+
vars['--ion-tab-bar-background'] = '#18181A';
|
|
890
|
+
vars['--ion-border-color'] = 'rgba(242, 244, 246, 0.1)';
|
|
891
|
+
// Typography & Radius
|
|
892
|
+
vars['--ion-font-family'] = theme.fontFamily.sans;
|
|
893
|
+
vars['--ion-border-radius'] = theme.radius[theme.radiusBase];
|
|
894
|
+
// Stepped colors for dark mode
|
|
895
|
+
interpolateSteppedColors(darkBgHex, darkTextHex, vars);
|
|
896
|
+
// Safe area insets
|
|
897
|
+
vars['--ion-safe-area-top'] = 'var(--ngxsmk-safe-area-top, env(safe-area-inset-top, 0px))';
|
|
898
|
+
vars['--ion-safe-area-bottom'] =
|
|
899
|
+
'var(--ngxsmk-safe-area-bottom, env(safe-area-inset-bottom, 0px))';
|
|
900
|
+
vars['--ion-safe-area-left'] = 'var(--ngxsmk-safe-area-left, env(safe-area-inset-left, 0px))';
|
|
901
|
+
vars['--ion-safe-area-right'] = 'var(--ngxsmk-safe-area-right, env(safe-area-inset-right, 0px))';
|
|
902
|
+
return vars;
|
|
903
|
+
}
|
|
803
904
|
/** Default NGXSMK adapter — emits `--ngxsmk-*` variables. */
|
|
804
905
|
const cssVarsAdapter = {
|
|
805
906
|
name: 'ngxsmk',
|
|
@@ -807,18 +908,26 @@ const cssVarsAdapter = {
|
|
|
807
908
|
};
|
|
808
909
|
// ─── Helpers ──────────────────────────────────────────────
|
|
809
910
|
function hexToRgbStr(hex) {
|
|
810
|
-
const
|
|
811
|
-
const r = parseInt(
|
|
812
|
-
const g = parseInt(
|
|
813
|
-
const b = parseInt(
|
|
911
|
+
const clean = hex.replace('#', '');
|
|
912
|
+
const r = parseInt(clean.substring(0, 2), 16) || 0;
|
|
913
|
+
const g = parseInt(clean.substring(2, 4), 16) || 0;
|
|
914
|
+
const b = parseInt(clean.substring(4, 6), 16) || 0;
|
|
814
915
|
return `${r}, ${g}, ${b}`;
|
|
815
916
|
}
|
|
917
|
+
function getContrastHex(hex) {
|
|
918
|
+
const clean = hex.replace('#', '');
|
|
919
|
+
const r = parseInt(clean.substring(0, 2), 16) || 0;
|
|
920
|
+
const g = parseInt(clean.substring(2, 4), 16) || 0;
|
|
921
|
+
const b = parseInt(clean.substring(4, 6), 16) || 0;
|
|
922
|
+
// Standard WCAG relative luminance approximation
|
|
923
|
+
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
|
|
924
|
+
return yiq >= 150 ? '#000000' : '#FFFFFF';
|
|
925
|
+
}
|
|
816
926
|
function shiftLightnessHex(hex, amount) {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
let
|
|
820
|
-
let
|
|
821
|
-
let b = parseInt(h.substring(4, 6), 16);
|
|
927
|
+
const clean = hex.replace('#', '');
|
|
928
|
+
let r = parseInt(clean.substring(0, 2), 16) || 0;
|
|
929
|
+
let g = parseInt(clean.substring(2, 4), 16) || 0;
|
|
930
|
+
let b = parseInt(clean.substring(4, 6), 16) || 0;
|
|
822
931
|
if (amount > 0) {
|
|
823
932
|
r = Math.min(255, r + Math.round((255 - r) * (amount / 100)));
|
|
824
933
|
g = Math.min(255, g + Math.round((255 - g) * (amount / 100)));
|
|
@@ -830,7 +939,28 @@ function shiftLightnessHex(hex, amount) {
|
|
|
830
939
|
g = Math.max(0, Math.round(g * factor));
|
|
831
940
|
b = Math.max(0, Math.round(b * factor));
|
|
832
941
|
}
|
|
833
|
-
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}
|
|
942
|
+
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
|
|
943
|
+
}
|
|
944
|
+
function interpolateSteppedColors(bgHex, textHex, vars) {
|
|
945
|
+
const cleanBg = bgHex.replace('#', '');
|
|
946
|
+
const cleanText = textHex.replace('#', '');
|
|
947
|
+
const bgR = parseInt(cleanBg.substring(0, 2), 16) || 0;
|
|
948
|
+
const bgG = parseInt(cleanBg.substring(2, 4), 16) || 0;
|
|
949
|
+
const bgB = parseInt(cleanBg.substring(4, 6), 16) || 0;
|
|
950
|
+
const textR = parseInt(cleanText.substring(0, 2), 16) || 0;
|
|
951
|
+
const textG = parseInt(cleanText.substring(2, 4), 16) || 0;
|
|
952
|
+
const textB = parseInt(cleanText.substring(4, 6), 16) || 0;
|
|
953
|
+
const steps = [
|
|
954
|
+
50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950,
|
|
955
|
+
];
|
|
956
|
+
for (const step of steps) {
|
|
957
|
+
const weight = step / 1000;
|
|
958
|
+
const r = Math.round(bgR * (1 - weight) + textR * weight);
|
|
959
|
+
const g = Math.round(bgG * (1 - weight) + textG * weight);
|
|
960
|
+
const b = Math.round(bgB * (1 - weight) + textB * weight);
|
|
961
|
+
const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
|
|
962
|
+
vars[`--ion-color-step-${step}`] = hex;
|
|
963
|
+
}
|
|
834
964
|
}
|
|
835
965
|
|
|
836
966
|
const STYLE_ID = 'ngxsmk-theme';
|
|
@@ -862,7 +992,7 @@ class NgxThemeInjectorService {
|
|
|
862
992
|
if (!style) {
|
|
863
993
|
style = this.document.createElement('style');
|
|
864
994
|
style.id = STYLE_ID;
|
|
865
|
-
head
|
|
995
|
+
head?.appendChild(style);
|
|
866
996
|
}
|
|
867
997
|
let css = buildThemeCss(config);
|
|
868
998
|
if (adapter) {
|
|
@@ -870,7 +1000,9 @@ class NgxThemeInjectorService {
|
|
|
870
1000
|
const platformVars = adapter.vars(resolved);
|
|
871
1001
|
css += '\n' + this.buildAdapterCss(adapter.name, platformVars);
|
|
872
1002
|
}
|
|
873
|
-
style
|
|
1003
|
+
if (style) {
|
|
1004
|
+
style.textContent = css;
|
|
1005
|
+
}
|
|
874
1006
|
}
|
|
875
1007
|
/**
|
|
876
1008
|
* Apply Ionic-specific token variables alongside the standard NGXSMK theme.
|
|
@@ -882,11 +1014,14 @@ class NgxThemeInjectorService {
|
|
|
882
1014
|
if (!style) {
|
|
883
1015
|
style = this.document.createElement('style');
|
|
884
1016
|
style.id = IONIC_STYLE_ID;
|
|
885
|
-
head
|
|
1017
|
+
head?.appendChild(style);
|
|
886
1018
|
}
|
|
887
1019
|
const resolved = resolveTheme(config);
|
|
888
|
-
const
|
|
889
|
-
|
|
1020
|
+
const lightVars = ionicVarsAdapter.vars(resolved);
|
|
1021
|
+
const darkVars = ionicVarsAdapter.darkVars ? ionicVarsAdapter.darkVars(resolved) : undefined;
|
|
1022
|
+
if (style) {
|
|
1023
|
+
style.textContent = this.buildAdapterCss('ionic', lightVars, darkVars);
|
|
1024
|
+
}
|
|
890
1025
|
}
|
|
891
1026
|
/** Remove a runtime-applied theme, falling back to the build-time CSS. */
|
|
892
1027
|
clearTheme() {
|
|
@@ -896,9 +1031,15 @@ class NgxThemeInjectorService {
|
|
|
896
1031
|
clearIonicTheme() {
|
|
897
1032
|
this.document.getElementById(IONIC_STYLE_ID)?.remove();
|
|
898
1033
|
}
|
|
899
|
-
buildAdapterCss(adapterName, vars) {
|
|
1034
|
+
buildAdapterCss(adapterName, vars, darkVars) {
|
|
900
1035
|
const lines = Object.entries(vars).map(([k, v]) => ` ${k}: ${v};`);
|
|
901
|
-
|
|
1036
|
+
let result = `/* @ngxsmk/theme — ${adapterName} adapter */\n:root {\n${lines.join('\n')}\n}`;
|
|
1037
|
+
if (darkVars && Object.keys(darkVars).length > 0) {
|
|
1038
|
+
const darkLines = Object.entries(darkVars).map(([k, v]) => ` ${k}: ${v};`);
|
|
1039
|
+
result += `\n\n:root.dark, body.dark, html.dark, [data-theme="dark"] {\n${darkLines.join('\n')}\n}`;
|
|
1040
|
+
result += `\n\n@media (prefers-color-scheme: dark) {\n :root:not(.light) {\n ${darkLines.join('\n ')}\n }\n}`;
|
|
1041
|
+
}
|
|
1042
|
+
return result;
|
|
902
1043
|
}
|
|
903
1044
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.0", ngImport: i0, type: NgxThemeInjectorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
904
1045
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.0", ngImport: i0, type: NgxThemeInjectorService, providedIn: 'root' }); }
|
|
@@ -983,6 +1124,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.0", ngImpor
|
|
|
983
1124
|
args: [{ providedIn: 'root' }]
|
|
984
1125
|
}], ctorParameters: () => [] });
|
|
985
1126
|
|
|
1127
|
+
/**
|
|
1128
|
+
* Standalone provider for Angular 18-22+ applications that automatically
|
|
1129
|
+
* synchronizes NGXSMK design tokens with Ionic 7/8 CSS variables on startup.
|
|
1130
|
+
*
|
|
1131
|
+
* @example
|
|
1132
|
+
* ```ts
|
|
1133
|
+
* export const appConfig: ApplicationConfig = {
|
|
1134
|
+
* providers: [
|
|
1135
|
+
* provideIonicAngular(),
|
|
1136
|
+
* provideNgxsmkIonicTheme(),
|
|
1137
|
+
* ]
|
|
1138
|
+
* };
|
|
1139
|
+
* ```
|
|
1140
|
+
*/
|
|
1141
|
+
function provideNgxsmkIonicTheme(config) {
|
|
1142
|
+
return makeEnvironmentProviders([
|
|
1143
|
+
{
|
|
1144
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
1145
|
+
multi: true,
|
|
1146
|
+
useValue: () => {
|
|
1147
|
+
const theme = inject(NgxsmkThemeService);
|
|
1148
|
+
theme.applyIonicTheme(config ?? violetPreset);
|
|
1149
|
+
},
|
|
1150
|
+
},
|
|
1151
|
+
]);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
986
1154
|
/*
|
|
987
1155
|
* Public API Surface of @ngxsmk/theme
|
|
988
1156
|
*/
|
|
@@ -991,5 +1159,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.0", ngImpor
|
|
|
991
1159
|
* Generated bundle index. Do not edit.
|
|
992
1160
|
*/
|
|
993
1161
|
|
|
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 };
|
|
1162
|
+
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, SAFE_AREA, SHADOW, SPACING, TOUCH_TARGET_MIN, TRACKING, Z_INDEX, buildThemeCss, cssVarsAdapter, defineConfig, deriveScale, emeraldPreset, hexToOklch, ionicVarsAdapter, neutralPreset, oklchToHex, presets, provideNgxsmkIonicTheme, resolveTheme, rosePreset, rotateHue, shiftLightness, violetPreset };
|
|
995
1163
|
//# sourceMappingURL=ngxsmk-theme.mjs.map
|