@breadstone/mosaik-elements-foundation 0.0.221 → 0.0.222

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 (111) hide show
  1. package/Controls/Behaviors/Themeable.js +2 -2
  2. package/Controls/Behaviors/Themeable.js.map +1 -1
  3. package/Controls/Components/Buttons/Button/Themes/ButtonElement.Cosmopolitan.js +26 -26
  4. package/Controls/Components/Buttons/Button/Themes/ButtonElement.Joy.js +27 -27
  5. package/Controls/Components/Buttons/Button/Themes/ButtonElement.Memphis.js +26 -26
  6. package/Controls/Components/Buttons/CompoundButton/Themes/CompoundButtonElement.Cosmopolitan.js +8 -8
  7. package/Controls/Components/Buttons/CompoundButton/Themes/CompoundButtonElement.Joy.js +10 -10
  8. package/Controls/Components/Buttons/CompoundButton/Themes/CompoundButtonElement.Memphis.js +9 -9
  9. package/Controls/Components/Buttons/DropDownButton/Themes/DropDownButtonElement.Cosmopolitan.js +9 -9
  10. package/Controls/Components/Buttons/DropDownButton/Themes/DropDownButtonElement.Joy.js +10 -10
  11. package/Controls/Components/Buttons/DropDownButton/Themes/DropDownButtonElement.Memphis.js +8 -8
  12. package/Controls/Components/Buttons/FloatingActionButton/Themes/FloatingActionButtonElement.Cosmopolitan.js +8 -8
  13. package/Controls/Components/Buttons/FloatingActionButton/Themes/FloatingActionButtonElement.Joy.js +9 -9
  14. package/Controls/Components/Buttons/FloatingActionButton/Themes/FloatingActionButtonElement.Memphis.js +32 -32
  15. package/Controls/Components/Buttons/RepeatButton/Themes/RepeatButtonElement.Cosmopolitan.js +8 -8
  16. package/Controls/Components/Buttons/RepeatButton/Themes/RepeatButtonElement.Joy.js +10 -10
  17. package/Controls/Components/Buttons/RepeatButton/Themes/RepeatButtonElement.Memphis.js +9 -9
  18. package/Controls/Components/Buttons/SplitButton/Themes/SplitButtonElement.Cosmopolitan.js +8 -8
  19. package/Controls/Components/Buttons/SplitButton/Themes/SplitButtonElement.Joy.js +12 -12
  20. package/Controls/Components/Buttons/SplitButton/Themes/SplitButtonElement.Memphis.js +9 -9
  21. package/Controls/Components/Buttons/ToggleButton/Themes/ToggleButtonElement.Cosmopolitan.js +9 -9
  22. package/Controls/Components/Buttons/ToggleButton/Themes/ToggleButtonElement.Joy.js +10 -10
  23. package/Controls/Components/Buttons/ToggleButton/Themes/ToggleButtonElement.Memphis.js +9 -9
  24. package/Controls/Components/Grouping/BannerGroup/Themes/BannerGroupElement.Cosmopolitan.js +1 -1
  25. package/Controls/Components/Grouping/BannerGroup/Themes/BannerGroupElement.Joy.js +1 -1
  26. package/Controls/Components/Grouping/BannerGroup/Themes/BannerGroupElement.Memphis.js +1 -1
  27. package/Controls/Components/Grouping/Expander/ExpanderGroupElement.d.ts +0 -3
  28. package/Controls/Components/Grouping/Expander/ExpanderGroupElement.d.ts.map +1 -1
  29. package/Controls/Components/Grouping/Expander/Themes/ExpanderElement.Cosmopolitan.d.ts.map +1 -1
  30. package/Controls/Components/Grouping/Expander/Themes/ExpanderElement.Cosmopolitan.js +0 -4
  31. package/Controls/Components/Grouping/Expander/Themes/ExpanderElement.Cosmopolitan.js.map +1 -1
  32. package/Controls/Components/Grouping/Expander/Themes/ExpanderElement.Joy.d.ts.map +1 -1
  33. package/Controls/Components/Grouping/Expander/Themes/ExpanderElement.Joy.js +0 -4
  34. package/Controls/Components/Grouping/Expander/Themes/ExpanderElement.Joy.js.map +1 -1
  35. package/Controls/Components/Grouping/Expander/Themes/ExpanderElement.Memphis.d.ts.map +1 -1
  36. package/Controls/Components/Grouping/Expander/Themes/ExpanderElement.Memphis.js +0 -4
  37. package/Controls/Components/Grouping/Expander/Themes/ExpanderElement.Memphis.js.map +1 -1
  38. package/Controls/Components/Inputs/Calendar/Themes/CalendarSubHeaderElement.Cosmopolitan.js +1 -1
  39. package/Controls/Components/Inputs/Calendar/Themes/CalendarSubHeaderElement.Joy.js +1 -1
  40. package/Controls/Components/Inputs/Calendar/Themes/CalendarSubHeaderElement.Memphis.js +1 -1
  41. package/Controls/Components/Inputs/CheckBox/Themes/CheckBoxElement.Joy.js +1 -1
  42. package/Controls/Components/Inputs/Choice/Themes/ChoiceElement.Joy.js +1 -1
  43. package/Controls/Components/Inputs/Radio/Themes/RadioElement.Joy.js +1 -1
  44. package/Controls/Components/Inputs/Radio/Themes/RadioElement.Memphis.js +1 -1
  45. package/Controls/Components/Inputs/ToggleSwitch/Themes/ToggleSwitchElement.Joy.js +1 -1
  46. package/Controls/Components/Layouts/TileManager/Themes/TileManagerTileElement.Cosmopolitan.js +2 -2
  47. package/Controls/Components/Layouts/TileManager/Themes/TileManagerTileElement.Joy.js +2 -2
  48. package/Controls/Components/Layouts/TileManager/Themes/TileManagerTileElement.Memphis.js +2 -2
  49. package/Controls/Components/Media/Avatar/Themes/AvatarElement.Joy.js +3 -3
  50. package/Controls/Components/Media/Avatar/Themes/AvatarElement.Memphis.js +1 -1
  51. package/Controls/Components/Media/Badge/Themes/BadgeElement.Joy.js +4 -4
  52. package/Controls/Components/Media/Chat/Themes/ChatMessageAvatarElement.Joy.js +1 -1
  53. package/Controls/Components/Media/Chat/Themes/ChatMessageAvatarElement.Memphis.js +1 -1
  54. package/Controls/Components/Media/Chip/Themes/ChipElement.Joy.js +4 -4
  55. package/Controls/Components/Media/ColorSwatch/Themes/ColorSwatchElement.Cosmopolitan.js +4 -4
  56. package/Controls/Components/Media/ColorSwatch/Themes/ColorSwatchElement.Joy.js +4 -4
  57. package/Controls/Components/Media/ColorSwatch/Themes/ColorSwatchElement.Memphis.js +4 -4
  58. package/Controls/Components/Media/Persona/Themes/PersonaElement.Joy.js +1 -1
  59. package/Controls/Components/Overlays/Toast/Themes/ToastElement.Memphis.js +1 -1
  60. package/Controls/Components/Primitives/Floating/Themes/FloatingElement.Joy.js +1 -1
  61. package/Controls/Components/Primitives/Floating/Themes/FloatingElement.Memphis.js +1 -1
  62. package/Controls/Components/Primitives/Popup/Themes/PopupElement.Memphis.js +1 -1
  63. package/Controls/Components/Primitives/TickBar/Themes/TickBarElement.Cosmopolitan.js +5 -5
  64. package/Controls/Components/Primitives/TickBar/Themes/TickBarElement.Joy.js +5 -5
  65. package/Controls/Components/Primitives/TickBar/Themes/TickBarElement.Memphis.js +5 -5
  66. package/Controls/Components/Ranges/MeterRing/Themes/MeterRingElement.Cosmopolitan.js +2 -2
  67. package/Controls/Components/Ranges/MeterRing/Themes/MeterRingElement.Joy.js +2 -2
  68. package/Controls/Components/Ranges/MeterRing/Themes/MeterRingElement.Memphis.js +2 -2
  69. package/Controls/Components/Ranges/ProgressBar/Themes/ProgressBarElement.Joy.d.ts.map +1 -1
  70. package/Controls/Components/Ranges/ProgressBar/Themes/ProgressBarElement.Joy.js +7 -4
  71. package/Controls/Components/Ranges/ProgressBar/Themes/ProgressBarElement.Joy.js.map +1 -1
  72. package/Controls/Components/Ranges/ProgressRing/Themes/ProgressRingElement.Cosmopolitan.js +3 -3
  73. package/Controls/Components/Ranges/ProgressRing/Themes/ProgressRingElement.Joy.js +3 -3
  74. package/Controls/Components/Ranges/ProgressRing/Themes/ProgressRingElement.Memphis.js +3 -3
  75. package/Controls/Components/Ranges/ScrubSlider/Themes/ScrubSliderElement.Cosmopolitan.js +2 -2
  76. package/Controls/Components/Ranges/ScrubSlider/Themes/ScrubSliderElement.Joy.js +2 -2
  77. package/Controls/Components/Ranges/ScrubSlider/Themes/ScrubSliderElement.Memphis.js +2 -2
  78. package/Controls/Components/Selectors/ElectronicProgramGuide/Themes/EpgProgramElement.Joy.js +1 -1
  79. package/Controls/Components/Selectors/ElectronicProgramGuide/Themes/EpgProgramElement.Memphis.js +1 -1
  80. package/Controls/Components/Selectors/Menu/Themes/MenuItemElement.Cosmopolitan.js +1 -1
  81. package/Controls/Components/Selectors/Menu/Themes/MenuItemElement.Joy.js +1 -1
  82. package/Controls/Components/Selectors/Menu/Themes/MenuItemElement.Memphis.js +1 -1
  83. package/Controls/Components/Selectors/Segment/Themes/SegmentElement.Joy.js +1 -1
  84. package/Controls/Components/Selectors/Segment/Themes/SegmentElement.Memphis.js +1 -1
  85. package/Controls/Components/Selectors/Tab/Themes/TabElement.Cosmopolitan.js +1 -1
  86. package/Controls/Components/Selectors/Tab/Themes/TabElement.Joy.js +1 -1
  87. package/Controls/Components/Selectors/Tab/Themes/TabElement.Memphis.js +1 -1
  88. package/Controls/Components/Selectors/TabStrip/Themes/TabStripItemElement.Joy.js +1 -1
  89. package/Index.d.ts +1 -1
  90. package/Index.d.ts.map +1 -1
  91. package/Reactivity/Rx/Directives/AsyncDirective.d.ts +1 -1
  92. package/Routing/PathToRegexp.d.ts +1 -1
  93. package/Theming/IThemeElementProps.d.ts +16 -1
  94. package/Theming/IThemeElementProps.d.ts.map +1 -1
  95. package/Theming/IThemeService.d.ts +57 -154
  96. package/Theming/IThemeService.d.ts.map +1 -1
  97. package/Theming/IThemeService.js +3 -1
  98. package/Theming/IThemeService.js.map +1 -1
  99. package/Theming/Theme2Element.d.ts +76 -9
  100. package/Theming/Theme2Element.d.ts.map +1 -1
  101. package/Theming/Theme2Element.js +105 -54
  102. package/Theming/Theme2Element.js.map +1 -1
  103. package/Theming/Theme2ElementTemplate.d.ts +1 -1
  104. package/Theming/Theme2ElementTemplate.d.ts.map +1 -1
  105. package/Theming/Theme2ElementTemplate.js +1 -1
  106. package/Theming/Theme2ElementTemplate.js.map +1 -1
  107. package/Theming/ThemeService.d.ts +118 -67
  108. package/Theming/ThemeService.d.ts.map +1 -1
  109. package/Theming/ThemeService.js +453 -195
  110. package/Theming/ThemeService.js.map +1 -1
  111. package/package.json +3 -3
@@ -21,9 +21,11 @@ import { THEME_SERVICE_DEFAULT_CONFIG } from './IThemeService';
21
21
  * // Use the singleton instance
22
22
  * ThemeService.instance.applyTheme(myTheme);
23
23
  *
24
- * // Or apply individual parts
25
- * ThemeService.instance.applyScheme(theme.scheme, 'joy');
26
- * ThemeService.instance.applyPalette(theme.palette, 'joy');
24
+ * // Or apply individual parts (theme name is derived from stored state)
25
+ * ThemeService.instance.applyScheme(theme.scheme); // global scope
26
+ * ThemeService.instance.applyPalette(theme.palette); // global scope
27
+ * ThemeService.instance.applyLayout(theme.layout); // global scope
28
+ * ThemeService.instance.applyFontFamily('Arial'); // global scope
27
29
  *
28
30
  * // Subscribe to theme changes
29
31
  * ThemeService.instance.themeChanged.subscribe((detail) => {
@@ -39,11 +41,11 @@ export class ThemeService {
39
41
  static _config = { ...THEME_SERVICE_DEFAULT_CONFIG };
40
42
  _globalStyleSheet;
41
43
  _providers;
44
+ _themeStates;
42
45
  _themeChanged;
43
46
  _schemeChanged;
44
47
  _paletteChanged;
45
48
  _initialized;
46
- _currentTheme;
47
49
  // #endregion
48
50
  // #region Ctor
49
51
  /**
@@ -56,11 +58,11 @@ export class ThemeService {
56
58
  const config = ThemeService._config;
57
59
  this._globalStyleSheet = new GlobalStyleSheet(config.styleId ?? THEME_SERVICE_DEFAULT_CONFIG.styleId);
58
60
  this._providers = [];
61
+ this._themeStates = new Map();
59
62
  this._themeChanged = new PureEventEmitter();
60
63
  this._schemeChanged = new PureEventEmitter();
61
64
  this._paletteChanged = new PureEventEmitter();
62
65
  this._initialized = false;
63
- this._currentTheme = null;
64
66
  // Auto-initialize if enabled
65
67
  if (config.autoInitialize ?? THEME_SERVICE_DEFAULT_CONFIG.autoInitialize) {
66
68
  this.initialize();
@@ -89,15 +91,6 @@ export class ThemeService {
89
91
  get isInitialized() {
90
92
  return this._initialized;
91
93
  }
92
- /**
93
- * Gets the currently applied theme, if any.
94
- *
95
- * @public
96
- * @readonly
97
- */
98
- get currentTheme() {
99
- return this._currentTheme;
100
- }
101
94
  /**
102
95
  * Event emitter that fires when the theme changes.
103
96
  *
@@ -133,7 +126,7 @@ export class ThemeService {
133
126
  *
134
127
  * @public
135
128
  * @static
136
- * @param config - Configuration options for the theme service.
129
+ * @param config Configuration options for the theme service.
137
130
  * @returns The ThemeService class for chaining.
138
131
  * @throws Error if called after the instance has already been created.
139
132
  *
@@ -182,6 +175,15 @@ export class ThemeService {
182
175
  }
183
176
  this._globalStyleSheet.initialize();
184
177
  this._initialized = true;
178
+ const config = ThemeService._config;
179
+ // Apply default theme mode to document
180
+ const themeMode = config.defaultThemeMode ?? THEME_SERVICE_DEFAULT_CONFIG.defaultThemeMode ?? 'light';
181
+ document.documentElement.setAttribute('theme-mode', themeMode);
182
+ // Apply default theme if configured
183
+ if (config.defaultTheme) {
184
+ document.documentElement.setAttribute('theme', config.defaultTheme.name);
185
+ this.applyTheme(config.defaultTheme);
186
+ }
185
187
  }
186
188
  /**
187
189
  * Disposes of the service and cleans up resources.
@@ -191,25 +193,38 @@ export class ThemeService {
191
193
  dispose() {
192
194
  this._globalStyleSheet.dispose();
193
195
  this._providers.length = 0;
196
+ this._themeStates.clear();
194
197
  this._initialized = false;
195
- this._currentTheme = null;
196
198
  }
197
199
  /**
198
200
  * Registers a theme provider with the service.
201
+ * If a theme state already exists for the provider's name, the provider
202
+ * will be synchronized with that state. Otherwise, if the provider has a theme,
203
+ * it will be used to initialize the state for that name.
199
204
  *
200
205
  * @public
201
- * @param provider - The theme provider to register.
206
+ * @param provider The theme provider to register.
202
207
  */
203
208
  registerProvider(provider) {
204
209
  if (!this._providers.includes(provider)) {
205
210
  this._providers.push(provider);
211
+ const name = provider.name;
212
+ const existingState = this._themeStates.get(name);
213
+ if (existingState) {
214
+ // Sync provider with existing state
215
+ provider.applyTheme(existingState);
216
+ }
217
+ else if (provider.theme) {
218
+ // Initialize state from provider's theme (deep clone)
219
+ this._themeStates.set(name, this.cloneTheme(provider.theme));
220
+ }
206
221
  }
207
222
  }
208
223
  /**
209
224
  * Unregisters a theme provider from the service.
210
225
  *
211
226
  * @public
212
- * @param provider - The theme provider to unregister.
227
+ * @param provider The theme provider to unregister.
213
228
  */
214
229
  unregisterProvider(provider) {
215
230
  const index = this._providers.indexOf(provider);
@@ -218,18 +233,62 @@ export class ThemeService {
218
233
  }
219
234
  }
220
235
  /**
221
- * Gets the current theme from the first registered provider.
236
+ * Checks whether a theme exists for the specified name.
237
+ * For the global scope (no name), this also returns true if a default theme is configured.
222
238
  *
223
239
  * @public
240
+ * @param name Optional name for scoped themes. Use `null` or omit for global scope.
241
+ * @returns `true` if a theme is set for the specified scope, `false` otherwise.
242
+ */
243
+ hasTheme(name) {
244
+ const normalizedName = name === undefined ? null : name;
245
+ // Check state map first
246
+ if (this._themeStates.has(normalizedName)) {
247
+ return true;
248
+ }
249
+ // Check providers for this scope
250
+ const provider = this._providers.find((p) => p.name === normalizedName && p.theme !== null);
251
+ if (provider) {
252
+ return true;
253
+ }
254
+ // For global scope, check default theme
255
+ if (normalizedName === null && ThemeService._config.defaultTheme) {
256
+ return true;
257
+ }
258
+ return false;
259
+ }
260
+ /**
261
+ * Gets the current theme for the specified name.
262
+ * If no name is provided (undefined), returns the theme for the global/default scope (null key).
263
+ *
264
+ * The method uses the following fallback chain:
265
+ * 1. Theme state from `_themeStates` map
266
+ * 2. Theme from the first matching provider
267
+ * 3. Default theme from configuration (global scope only)
268
+ *
269
+ * @public
270
+ * @param name Optional name for scoped themes. Use `null` or omit for global scope.
224
271
  * @returns The current theme.
225
- * @throws Error if no theme is set.
226
- */
227
- getTheme() {
228
- const current = this._providers.at(0)?.theme ?? this._currentTheme;
229
- if (!current) {
230
- throw new Error('[ThemeService] No theme set.');
272
+ * @throws Error if no theme is set for the specified scope and no default theme is configured.
273
+ */
274
+ getTheme(name) {
275
+ // Normalize undefined to null for the global scope
276
+ const normalizedName = name === undefined ? null : name;
277
+ // 1. Check state map first
278
+ const state = this._themeStates.get(normalizedName);
279
+ if (state) {
280
+ return state;
281
+ }
282
+ // 2. Check providers for this scope
283
+ const provider = this._providers.find((p) => p.name === normalizedName && p.theme !== null);
284
+ if (provider?.theme) {
285
+ return provider.theme;
286
+ }
287
+ // 3. For global scope, use default theme as fallback
288
+ if (normalizedName === null && ThemeService._config.defaultTheme) {
289
+ return ThemeService._config.defaultTheme;
231
290
  }
232
- return current;
291
+ throw new Error(`[ThemeService] No theme set for name: ${normalizedName ?? 'global'}.`);
233
292
  }
234
293
  /**
235
294
  * Gets the current theme name from the document element.
@@ -251,135 +310,257 @@ export class ThemeService {
251
310
  }
252
311
  /**
253
312
  * Applies the complete theme.
313
+ * If name is provided, only providers with that name are notified.
314
+ * If name is not provided (undefined), only providers without a name (null) are notified.
254
315
  *
255
316
  * @public
256
- * @param theme - The theme to apply.
317
+ * @param theme The theme to apply.
318
+ * @param name Optional name for scoped application.
319
+ * @param options Optional settings to control notification behavior.
257
320
  */
258
- applyTheme(theme) {
321
+ applyTheme(theme, name, options) {
259
322
  this.ensureInitialized();
323
+ // Normalize undefined to null for the global scope
324
+ const normalizedName = name === undefined ? null : name;
325
+ const notifyProviders = options?.notifyProviders ?? true;
260
326
  const themeName = theme.name;
261
- const typographyKeys = Object.keys(theme.typography);
327
+ const currentMode = this.getCurrentThemeMode();
262
328
  const fontFamily = theme.fontFamily;
263
- this.applySchemeInternal(theme.scheme, themeName);
264
- this.applyPaletteInternal(theme.palette, themeName);
265
- this.applyFontFamily(theme.fontFamily, themeName, typographyKeys);
266
- this.applyTypography(theme.typography, themeName, fontFamily);
267
- this.applyLayout(theme.layout, themeName);
268
- this.applyElevation(theme.elevation, themeName);
269
- this._currentTheme = theme;
270
- this._themeChanged.emit({ theme });
329
+ const providers = this.getProvidersForName(normalizedName);
330
+ // For global scope (null), always apply globally. For named scopes, only if a provider has global=true.
331
+ const shouldApplyGlobally = normalizedName === null || providers.some((p) => p.global);
332
+ // Apply scheme
333
+ Object.entries(theme.scheme).forEach(([mode, value]) => {
334
+ Object.entries(value).forEach(([property, cssValue]) => {
335
+ const kebabProperty = this.toKebabCase(property);
336
+ this.setCssVariableWithProviders(`--${themeName}-scheme-${mode}-${kebabProperty}`, cssValue, providers, shouldApplyGlobally);
337
+ if (mode === currentMode) {
338
+ this.setCssVariableWithProviders(`--${themeName}-scheme-${kebabProperty}`, cssValue, providers, shouldApplyGlobally);
339
+ this.setCssVariableWithProviders(`--mosaik-scheme-${kebabProperty}`, `var(--${themeName}-scheme-${kebabProperty}, ${cssValue})`, providers, shouldApplyGlobally);
340
+ }
341
+ });
342
+ });
343
+ // Apply palette
344
+ Object.entries(theme.palette).forEach(([mode, modeValues]) => {
345
+ Object.entries(modeValues).forEach(([paletteName, paletteValues]) => {
346
+ if (!ThemePalette.isThemePalette(paletteValues)) {
347
+ this.setCssVariableWithProviders(`--${themeName}-color-${mode}-${paletteName}`, paletteValues, providers, shouldApplyGlobally);
348
+ if (mode === currentMode) {
349
+ this.setCssVariableWithProviders(`--${themeName}-color-${paletteName}`, paletteValues, providers, shouldApplyGlobally);
350
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
351
+ this.setCssVariableWithProviders(`--mosaik-color-${paletteName}`, `var(--${themeName}-color-${paletteName}, ${paletteValues})`, providers, shouldApplyGlobally);
352
+ }
353
+ return;
354
+ }
355
+ Object.entries(paletteValues).forEach(([shade, colorValue]) => {
356
+ const kebabShade = this.toKebabCase(shade);
357
+ this.setCssVariableWithProviders(`--${themeName}-color-${mode}-${paletteName}-${kebabShade}`, colorValue, providers, shouldApplyGlobally);
358
+ if (mode === currentMode) {
359
+ this.setCssVariableWithProviders(`--${themeName}-color-${paletteName}-${kebabShade}`, colorValue, providers, shouldApplyGlobally);
360
+ this.setCssVariableWithProviders(`--mosaik-color-${paletteName}-${kebabShade}`, `var(--${themeName}-color-${paletteName}-${kebabShade}, ${colorValue})`, providers, shouldApplyGlobally);
361
+ }
362
+ });
363
+ });
364
+ });
365
+ // Apply font family
366
+ this.setCssVariableWithProviders(`--${themeName}-font-family`, fontFamily, providers, shouldApplyGlobally);
367
+ this.setCssVariableWithProviders('--mosaik-font-family', `var(--${themeName}-font-family, ${fontFamily})`, providers, shouldApplyGlobally);
368
+ Object.keys(theme.typography).forEach((typographyName) => {
369
+ this.setCssVariableWithProviders(`--${themeName}-typography-${typographyName}-font-family`, fontFamily, providers, shouldApplyGlobally);
370
+ this.setCssVariableWithProviders(`--mosaik-typography-${typographyName}-font-family`, fontFamily, providers, shouldApplyGlobally);
371
+ });
372
+ // Apply typography
373
+ Object.entries(theme.typography).forEach(([typeName, value]) => {
374
+ Object.entries(value).forEach(([property, cssValue]) => {
375
+ const kebabProperty = this.toKebabCase(property);
376
+ this.setCssVariableWithProviders(`--${themeName}-typography-${typeName}-${kebabProperty}`, cssValue, providers, shouldApplyGlobally);
377
+ this.setCssVariableWithProviders(`--mosaik-typography-${typeName}-${kebabProperty}`, `var(--${themeName}-typography-${typeName}-${kebabProperty}, ${cssValue})`, providers, shouldApplyGlobally);
378
+ });
379
+ const shorthand = `${value.fontWeight} ${value.fontSize}/${value.lineHeight} ${fontFamily}`;
380
+ this.setCssVariableWithProviders(`--${themeName}-typography-${typeName}`, shorthand, providers, shouldApplyGlobally);
381
+ this.setCssVariableWithProviders(`--mosaik-typography-${typeName}`, `var(--${themeName}-typography-${typeName}, ${shorthand})`, providers, shouldApplyGlobally);
382
+ });
383
+ // Apply layout
384
+ Object.entries(theme.layout).forEach(([key, value]) => {
385
+ const normalizedValue = this.normalizeCssLength(value);
386
+ const kebabKey = this.toKebabCase(key);
387
+ this.setCssVariableWithProviders(`--${themeName}-layout-${kebabKey}`, normalizedValue, providers, shouldApplyGlobally);
388
+ this.setCssVariableWithProviders(`--mosaik-layout-${kebabKey}`, `var(--${themeName}-layout-${kebabKey}, ${normalizedValue})`, providers, shouldApplyGlobally);
389
+ });
390
+ // Apply elevation
391
+ Object.entries(theme.elevation).forEach(([level, value]) => {
392
+ Object.entries(value).forEach(([property, cssValue]) => {
393
+ const kebabProperty = this.toKebabCase(property);
394
+ this.setCssVariableWithProviders(`--${themeName}-elevation-${level}-${kebabProperty}`, cssValue, providers, shouldApplyGlobally);
395
+ this.setCssVariableWithProviders(`--mosaik-elevation-${level}-${kebabProperty}`, `var(--${themeName}-elevation-${level}-${kebabProperty}, ${cssValue})`, providers, shouldApplyGlobally);
396
+ });
397
+ });
398
+ // Store the theme state (deep clone to prevent mutations)
399
+ this._themeStates.set(normalizedName, this.cloneTheme(theme));
400
+ // Notify providers with matching name (only update their internal state, CSS is already set)
401
+ if (notifyProviders) {
402
+ providers.forEach((provider) => {
403
+ provider.applyTheme(theme);
404
+ });
405
+ }
406
+ this._themeChanged.emit({
407
+ theme,
408
+ name: normalizedName
409
+ });
271
410
  }
272
411
  /**
273
412
  * Applies the scheme (light/dark mode color roles) from the theme.
413
+ * Uses the theme name from the stored state for the specified scope.
274
414
  *
275
415
  * @public
276
- * @param scheme - The scheme to apply.
277
- * @param themeName - The name of the theme for CSS variable prefixing.
416
+ * @param scheme The scheme to apply.
417
+ * @param name Optional name for scoped application.
278
418
  */
279
- applyScheme(scheme, themeName) {
280
- this.applySchemeInternal(scheme, themeName);
281
- this._schemeChanged.emit({ scheme });
419
+ applyScheme(scheme, name) {
420
+ this.ensureInitialized();
421
+ const normalizedName = name === undefined ? null : name;
422
+ const themeName = this.getThemeNameForScope(normalizedName);
423
+ const currentMode = this.getCurrentThemeMode();
424
+ Object.entries(scheme).forEach(([mode, value]) => {
425
+ Object.entries(value).forEach(([property, cssValue]) => {
426
+ const kebabProperty = this.toKebabCase(property);
427
+ this.setCssVariable(`--${themeName}-scheme-${mode}-${kebabProperty}`, cssValue);
428
+ if (mode === currentMode) {
429
+ this.setCssVariable(`--${themeName}-scheme-${kebabProperty}`, cssValue);
430
+ this.setCssVariable(`--mosaik-scheme-${kebabProperty}`, `var(--${themeName}-scheme-${kebabProperty}, ${cssValue})`);
431
+ }
432
+ });
433
+ });
434
+ this._schemeChanged.emit({
435
+ scheme,
436
+ name: normalizedName
437
+ });
282
438
  }
283
439
  /**
284
440
  * Applies the color palette from the theme.
441
+ * Uses the theme name from the stored state for the specified scope.
285
442
  *
286
443
  * @public
287
- * @param palette - The palette to apply.
288
- * @param themeName - The name of the theme for CSS variable prefixing.
444
+ * @param palette The palette to apply.
445
+ * @param name Optional name for scoped application.
289
446
  */
290
- applyPalette(palette, themeName) {
291
- this.applyPaletteInternal(palette, themeName);
292
- this._paletteChanged.emit({ palette });
447
+ applyPalette(palette, name) {
448
+ this.ensureInitialized();
449
+ const normalizedName = name === undefined ? null : name;
450
+ const themeName = this.getThemeNameForScope(normalizedName);
451
+ const currentMode = this.getCurrentThemeMode();
452
+ Object.entries(palette).forEach(([mode, modeValues]) => {
453
+ Object.entries(modeValues).forEach(([paletteName, paletteValues]) => {
454
+ if (!ThemePalette.isThemePalette(paletteValues)) {
455
+ this.setCssVariable(`--${themeName}-color-${mode}-${paletteName}`, paletteValues);
456
+ if (mode === currentMode) {
457
+ this.setCssVariable(`--${themeName}-color-${paletteName}`, paletteValues);
458
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
459
+ this.setCssVariable(`--mosaik-color-${paletteName}`, `var(--${themeName}-color-${paletteName}, ${paletteValues})`);
460
+ }
461
+ return;
462
+ }
463
+ Object.entries(paletteValues).forEach(([shade, colorValue]) => {
464
+ const kebabShade = this.toKebabCase(shade);
465
+ this.setCssVariable(`--${themeName}-color-${mode}-${paletteName}-${kebabShade}`, colorValue);
466
+ if (mode === currentMode) {
467
+ this.setCssVariable(`--${themeName}-color-${paletteName}-${kebabShade}`, colorValue);
468
+ this.setCssVariable(`--mosaik-color-${paletteName}-${kebabShade}`, `var(--${themeName}-color-${paletteName}-${kebabShade}, ${colorValue})`);
469
+ }
470
+ });
471
+ });
472
+ });
473
+ this._paletteChanged.emit({
474
+ palette,
475
+ name: normalizedName
476
+ });
293
477
  }
294
478
  /**
295
479
  * Applies the font family from the theme.
480
+ * Uses the theme name and typography keys from the stored state for the specified scope.
296
481
  *
297
482
  * @public
298
- * @param fontFamily - The font family to apply.
299
- * @param themeName - The name of the theme for CSS variable prefixing. If not provided, uses the current theme name.
300
- * @param typographyKeys - The typography keys to apply the font family to. If not provided, uses the current theme typography keys.
301
- */
302
- applyFontFamily(fontFamily, themeName, typographyKeys) {
483
+ * @param fontFamily The font family to apply.
484
+ * @param name Optional name for scoped application.
485
+ */
486
+ applyFontFamily(fontFamily, name) {
487
+ const normalizedName = name === undefined ? null : name;
488
+ const state = this._themeStates.get(normalizedName);
489
+ const themeName = state?.name ?? 'joy';
490
+ const typographyKeys = state ? Object.keys(state.typography) : [];
303
491
  this.ensureInitialized();
304
- const resolvedThemeName = themeName ?? this._currentTheme?.name;
305
- if (!resolvedThemeName) {
306
- throw new Error('[ThemeService] No theme name provided and no current theme set.');
307
- }
308
- const resolvedTypographyKeys = typographyKeys ?? Object.keys(this._currentTheme?.typography ?? {});
309
492
  // legacy variable style
310
- this.setCssVariable(`--${resolvedThemeName}-font-family`, fontFamily);
493
+ this.setCssVariable(`--${themeName}-font-family`, fontFamily);
311
494
  // new variable style
312
- this.setCssVariable('--mosaik-font-family', `var(--${resolvedThemeName}-font-family, ${fontFamily})`);
313
- resolvedTypographyKeys.forEach((typographyName) => {
495
+ this.setCssVariable('--mosaik-font-family', `var(--${themeName}-font-family, ${fontFamily})`);
496
+ typographyKeys.forEach((typographyName) => {
314
497
  // legacy variable style
315
- this.setCssVariable(`--${resolvedThemeName}-typography-${typographyName}-font-family`, fontFamily);
498
+ this.setCssVariable(`--${themeName}-typography-${typographyName}-font-family`, fontFamily);
316
499
  // new variable style
317
500
  this.setCssVariable(`--mosaik-typography-${typographyName}-font-family`, fontFamily);
318
501
  });
319
502
  }
320
503
  /**
321
504
  * Applies the typography settings from the theme.
505
+ * Uses the theme name and font family from the stored state for the specified scope.
322
506
  *
323
507
  * @public
324
- * @param typography - The typography settings to apply.
325
- * @param themeName - The name of the theme for CSS variable prefixing. If not provided, uses the current theme name.
326
- * @param fontFamily - The font family to use in shorthand typography values. If not provided, uses the current theme font family.
327
- */
328
- applyTypography(typography, themeName, fontFamily) {
508
+ * @param typography The typography settings to apply.
509
+ * @param name Optional name for scoped application.
510
+ */
511
+ applyTypography(typography, name) {
512
+ const normalizedName = name === undefined ? null : name;
513
+ const state = this._themeStates.get(normalizedName);
514
+ const themeName = state?.name ?? 'joy';
515
+ const fontFamily = state?.fontFamily ?? '';
329
516
  this.ensureInitialized();
330
- const resolvedThemeName = themeName ?? this._currentTheme?.name;
331
- if (!resolvedThemeName) {
332
- throw new Error('[ThemeService] No theme name provided and no current theme set.');
333
- }
334
- const resolvedFontFamily = fontFamily ?? this._currentTheme?.fontFamily;
335
- if (!resolvedFontFamily) {
336
- throw new Error('[ThemeService] No font family provided and no current theme set.');
337
- }
338
- Object.entries(typography).forEach(([name, value]) => {
517
+ Object.entries(typography).forEach(([typoName, value]) => {
339
518
  Object.entries(value).forEach(([property, cssValue]) => {
340
519
  const kebabProperty = this.toKebabCase(property);
341
520
  // legacy variable style
342
- this.setCssVariable(`--${resolvedThemeName}-typography-${name}-${kebabProperty}`, cssValue);
521
+ this.setCssVariable(`--${themeName}-typography-${typoName}-${kebabProperty}`, cssValue);
343
522
  // new variable style
344
- this.setCssVariable(`--mosaik-typography-${name}-${kebabProperty}`, `var(--${resolvedThemeName}-typography-${name}-${kebabProperty}, ${cssValue})`);
523
+ this.setCssVariable(`--mosaik-typography-${typoName}-${kebabProperty}`, `var(--${themeName}-typography-${typoName}-${kebabProperty}, ${cssValue})`);
345
524
  });
346
- const shorthand = `${value.fontWeight} ${value.fontSize}/${value.lineHeight} ${resolvedFontFamily}`;
525
+ const shorthand = `${value.fontWeight} ${value.fontSize}/${value.lineHeight} ${fontFamily}`;
347
526
  // legacy variable style
348
- this.setCssVariable(`--${resolvedThemeName}-typography-${name}`, shorthand);
527
+ this.setCssVariable(`--${themeName}-typography-${typoName}`, shorthand);
349
528
  // new variable style
350
- this.setCssVariable(`--mosaik-typography-${name}`, `var(--${resolvedThemeName}-typography-${name}, ${shorthand})`);
529
+ this.setCssVariable(`--mosaik-typography-${typoName}`, `var(--${themeName}-typography-${typoName}, ${shorthand})`);
351
530
  });
352
531
  }
353
532
  /**
354
533
  * Applies the layout settings from the theme.
534
+ * Uses the theme name from the stored state for the specified scope.
355
535
  *
356
536
  * @public
357
- * @param layout - The layout settings to apply.
358
- * @param themeName - The name of the theme for CSS variable prefixing. If not provided, uses the current theme name.
537
+ * @param layout The layout settings to apply.
538
+ * @param name Optional name for scoped application.
359
539
  */
360
- applyLayout(layout, themeName) {
540
+ applyLayout(layout, name) {
541
+ const normalizedName = name === undefined ? null : name;
542
+ const themeName = this.getThemeNameForScope(normalizedName);
361
543
  this.ensureInitialized();
362
- const resolvedThemeName = themeName ?? this._currentTheme?.name;
363
- if (!resolvedThemeName) {
364
- throw new Error('Theme name must be provided or a theme must be applied first.');
365
- }
366
544
  Object.entries(layout).forEach(([key, value]) => {
367
545
  const normalizedValue = this.normalizeCssLength(value);
368
546
  const kebabKey = this.toKebabCase(key);
369
547
  // legacy variable style
370
- this.setCssVariable(`--${resolvedThemeName}-layout-${kebabKey}`, normalizedValue);
548
+ this.setCssVariable(`--${themeName}-layout-${kebabKey}`, normalizedValue);
371
549
  // new variable style
372
- this.setCssVariable(`--mosaik-layout-${kebabKey}`, `var(--${resolvedThemeName}-layout-${kebabKey}, ${normalizedValue})`);
550
+ this.setCssVariable(`--mosaik-layout-${kebabKey}`, `var(--${themeName}-layout-${kebabKey}, ${normalizedValue})`);
373
551
  });
374
552
  }
375
553
  /**
376
554
  * Applies the elevation (shadow) settings from the theme.
555
+ * Uses the theme name from the stored state for the specified scope.
377
556
  *
378
557
  * @public
379
- * @param elevation - The elevation settings to apply.
380
- * @param themeName - The name of the theme for CSS variable prefixing.
558
+ * @param elevation The elevation settings to apply.
559
+ * @param name Optional name for scoped application.
381
560
  */
382
- applyElevation(elevation, themeName) {
561
+ applyElevation(elevation, name) {
562
+ const normalizedName = name === undefined ? null : name;
563
+ const themeName = this.getThemeNameForScope(normalizedName);
383
564
  this.ensureInitialized();
384
565
  Object.entries(elevation).forEach(([level, value]) => {
385
566
  Object.entries(value).forEach(([property, cssValue]) => {
@@ -391,27 +572,6 @@ export class ThemeService {
391
572
  });
392
573
  });
393
574
  }
394
- /**
395
- * Sets a CSS variable.
396
- *
397
- * @public
398
- * @param property - The CSS variable name (e.g., `--my-var`).
399
- * @param value - The value to set.
400
- * @param options - Optional settings for where to apply the variable.
401
- */
402
- setCssVariable(property, value, options) {
403
- const applyGlobally = options?.applyGlobally ?? true;
404
- const element = options?.element;
405
- if (element) {
406
- // eslint-disable-next-line no-restricted-syntax -- Setting CSS variables is the correct approach for theming
407
- element.style.setProperty(property, value);
408
- }
409
- if (applyGlobally) {
410
- this._globalStyleSheet.setVariable(property, value);
411
- // eslint-disable-next-line no-restricted-syntax -- Setting CSS variables is the correct approach for theming
412
- document.documentElement.style.setProperty(property, value);
413
- }
414
- }
415
575
  /**
416
576
  * Gets the current theme mode from the document.
417
577
  *
@@ -425,121 +585,187 @@ export class ThemeService {
425
585
  /**
426
586
  * Replaces the scheme with the specified key.
427
587
  * This is useful when you want to replace a specific scheme in the theme.
588
+ * If the name is provided, only providers with that name are notified.
589
+ * If name is not provided (undefined), only providers without a name (null) are notified.
428
590
  *
429
591
  * @public
430
- * @param mode - The theme mode to replace.
431
- * @param scheme - The scheme color roles to replace.
592
+ * @param mode The theme mode to replace.
593
+ * @param scheme The scheme color roles to replace.
594
+ * @param name Optional name for scoped replacement.
595
+ * @param options Optional settings to control notification behavior.
432
596
  */
433
- replaceScheme(mode, scheme) {
434
- this._providers.forEach((provider) => {
435
- if (!provider.theme?.scheme) {
436
- return;
437
- }
438
- const currentScheme = provider.theme.scheme;
439
- const currentMode = currentScheme[mode];
440
- const mergedMode = Object.assign({}, currentMode, scheme);
597
+ replaceScheme(mode, scheme, name, options) {
598
+ this.ensureInitialized();
599
+ // Normalize undefined to null for the global scope
600
+ const normalizedName = name === undefined ? null : name;
601
+ const notifyProviders = options?.notifyProviders ?? true;
602
+ const currentMode = this.getCurrentThemeMode();
603
+ const providers = this.getProvidersForName(normalizedName);
604
+ // For global scope (null), always apply globally. For named scopes, only if a provider has global=true.
605
+ const shouldApplyGlobally = normalizedName === null || providers.some((p) => p.global);
606
+ // Update the theme state
607
+ const currentState = this._themeStates.get(normalizedName);
608
+ if (currentState) {
609
+ const currentScheme = currentState.scheme;
610
+ const currentModeValue = currentScheme[mode];
611
+ const mergedMode = Object.assign({}, currentModeValue, scheme);
441
612
  const updatedScheme = Object.assign({}, currentScheme, { [mode]: mergedMode });
442
- provider.applyScheme(updatedScheme);
443
- });
613
+ // Update state
614
+ currentState.scheme = updatedScheme;
615
+ // Apply CSS variables
616
+ const themeName = currentState.name;
617
+ Object.entries(updatedScheme).forEach(([schemeMode, value]) => {
618
+ Object.entries(value).forEach(([property, cssValue]) => {
619
+ const kebabProperty = this.toKebabCase(property);
620
+ this.setCssVariableWithProviders(`--${themeName}-scheme-${schemeMode}-${kebabProperty}`, cssValue, providers, shouldApplyGlobally);
621
+ if (schemeMode === currentMode) {
622
+ this.setCssVariableWithProviders(`--${themeName}-scheme-${kebabProperty}`, cssValue, providers, shouldApplyGlobally);
623
+ this.setCssVariableWithProviders(`--mosaik-scheme-${kebabProperty}`, `var(--${themeName}-scheme-${kebabProperty}, ${cssValue})`, providers, shouldApplyGlobally);
624
+ }
625
+ });
626
+ });
627
+ }
628
+ // Notify providers with matching name (only update their internal state)
629
+ if (notifyProviders) {
630
+ providers.forEach((provider) => {
631
+ if (!provider.theme?.scheme) {
632
+ return;
633
+ }
634
+ const currentScheme = provider.theme.scheme;
635
+ const currentModeValue = currentScheme[mode];
636
+ const mergedMode = Object.assign({}, currentModeValue, scheme);
637
+ const updatedScheme = Object.assign({}, currentScheme, { [mode]: mergedMode });
638
+ provider.applyScheme(updatedScheme);
639
+ });
640
+ }
444
641
  this._schemeChanged.emit({
445
642
  mode,
446
- scheme
643
+ scheme,
644
+ name: normalizedName
447
645
  });
448
646
  }
449
647
  /**
450
648
  * Replaces the palette with the specified key.
451
649
  * This is useful when you want to replace a specific palette in the theme.
650
+ * If themeId is provided, only providers with that ID are notified.
651
+ * If themeId is not provided (undefined), only providers without an ID (null) are notified.
452
652
  *
453
653
  * @public
454
- * @param mode - The theme mode to replace.
455
- * @param semantic - The semantic color roles to replace.
654
+ * @param mode The theme mode to replace.
655
+ * @param semantic The semantic color roles to replace.
656
+ * @param name Optional name for scoped replacement.
657
+ * @param options Optional settings to control notification behavior.
456
658
  */
457
- replacePalette(mode, semantic) {
458
- this._providers.forEach((provider) => {
459
- if (!provider.theme?.palette) {
460
- return;
461
- }
462
- const currentPalette = provider.theme.palette;
463
- const currentMode = currentPalette[mode];
464
- const mergedMode = Object.assign({}, currentMode, semantic);
659
+ replacePalette(mode, semantic, name, options) {
660
+ this.ensureInitialized();
661
+ // Normalize undefined to null for the global scope
662
+ const normalizedName = name === undefined ? null : name;
663
+ const notifyProviders = options?.notifyProviders ?? true;
664
+ const currentMode = this.getCurrentThemeMode();
665
+ const providers = this.getProvidersForName(normalizedName);
666
+ // For global scope (null), always apply globally. For named scopes, only if a provider has global=true.
667
+ const shouldApplyGlobally = normalizedName === null || providers.some((p) => p.global);
668
+ // Update the theme state
669
+ const currentState = this._themeStates.get(normalizedName);
670
+ if (currentState) {
671
+ const currentPalette = currentState.palette;
672
+ const currentModeValue = currentPalette[mode];
673
+ const mergedMode = Object.assign({}, currentModeValue, semantic);
465
674
  const updatedPalette = Object.assign({}, currentPalette, { [mode]: mergedMode });
466
- provider.applyPalette(updatedPalette);
467
- });
675
+ // Update state
676
+ currentState.palette = updatedPalette;
677
+ // Apply CSS variables
678
+ const themeName = currentState.name;
679
+ Object.entries(updatedPalette).forEach(([paletteMode, modeValues]) => {
680
+ Object.entries(modeValues).forEach(([paletteName, paletteValues]) => {
681
+ if (!ThemePalette.isThemePalette(paletteValues)) {
682
+ this.setCssVariableWithProviders(`--${themeName}-color-${paletteMode}-${paletteName}`, paletteValues, providers, shouldApplyGlobally);
683
+ if (paletteMode === currentMode) {
684
+ this.setCssVariableWithProviders(`--${themeName}-color-${paletteName}`, paletteValues, providers, shouldApplyGlobally);
685
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
686
+ this.setCssVariableWithProviders(`--mosaik-color-${paletteName}`, `var(--${themeName}-color-${paletteName}, ${paletteValues})`, providers, shouldApplyGlobally);
687
+ }
688
+ return;
689
+ }
690
+ Object.entries(paletteValues).forEach(([shade, colorValue]) => {
691
+ const kebabShade = this.toKebabCase(shade);
692
+ this.setCssVariableWithProviders(`--${themeName}-color-${paletteMode}-${paletteName}-${kebabShade}`, colorValue, providers, shouldApplyGlobally);
693
+ if (paletteMode === currentMode) {
694
+ this.setCssVariableWithProviders(`--${themeName}-color-${paletteName}-${kebabShade}`, colorValue, providers, shouldApplyGlobally);
695
+ this.setCssVariableWithProviders(`--mosaik-color-${paletteName}-${kebabShade}`, `var(--${themeName}-color-${paletteName}-${kebabShade}, ${colorValue})`, providers, shouldApplyGlobally);
696
+ }
697
+ });
698
+ });
699
+ });
700
+ }
701
+ // Notify providers with matching name (only update their internal state)
702
+ if (notifyProviders) {
703
+ providers.forEach((provider) => {
704
+ if (!provider.theme?.palette) {
705
+ return;
706
+ }
707
+ const currentPalette = provider.theme.palette;
708
+ const currentModeValue = currentPalette[mode];
709
+ const mergedMode = Object.assign({}, currentModeValue, semantic);
710
+ const updatedPalette = Object.assign({}, currentPalette, { [mode]: mergedMode });
711
+ provider.applyPalette(updatedPalette);
712
+ });
713
+ }
468
714
  this._paletteChanged.emit({
469
715
  mode,
470
- palette: semantic
716
+ palette: semantic,
717
+ name: normalizedName
471
718
  });
472
719
  }
473
720
  /**
474
- * Internal method to apply the scheme without emitting events.
721
+ * Sets a CSS variable.
475
722
  *
476
723
  * @private
477
- * @param scheme - The scheme to apply.
478
- * @param themeName - The name of the theme for CSS variable prefixing.
724
+ * @param property The CSS variable name (e.g., `--my-var`).
725
+ * @param value The value to set.
726
+ * @param options Optional settings for where to apply the variable.
479
727
  */
480
- applySchemeInternal(scheme, themeName) {
481
- this.ensureInitialized();
482
- const currentMode = this.getCurrentThemeMode();
483
- Object.entries(scheme).forEach(([mode, value]) => {
484
- Object.entries(value).forEach(([property, cssValue]) => {
485
- const kebabProperty = this.toKebabCase(property);
486
- // legacy variable style with mode (e.g., --joy-scheme-light-background)
487
- this.setCssVariable(`--${themeName}-scheme-${mode}-${kebabProperty}`, cssValue);
488
- // If this is the current mode, also set the mode-agnostic variables
489
- if (mode === currentMode) {
490
- // legacy variable style without mode (e.g., --joy-scheme-background)
491
- this.setCssVariable(`--${themeName}-scheme-${kebabProperty}`, cssValue);
492
- // new variable style
493
- this.setCssVariable(`--mosaik-scheme-${kebabProperty}`, `var(--${themeName}-scheme-${kebabProperty}, ${cssValue})`);
494
- }
495
- });
496
- });
728
+ setCssVariable(property, value, options) {
729
+ const applyGlobally = options?.applyGlobally ?? true;
730
+ const element = options?.element;
731
+ if (element) {
732
+ // eslint-disable-next-line no-restricted-syntax -- Setting CSS variables is the correct approach for theming
733
+ element.style.setProperty(property, value);
734
+ }
735
+ if (applyGlobally) {
736
+ this._globalStyleSheet.setVariable(property, value);
737
+ // eslint-disable-next-line no-restricted-syntax -- Setting CSS variables is the correct approach for theming
738
+ document.documentElement.style.setProperty(property, value);
739
+ }
497
740
  }
498
741
  /**
499
- * Internal method to apply the palette without emitting events.
742
+ * Sets a CSS variable globally and on all provider elements.
743
+ * For providers with global=false, only sets locally on the element.
500
744
  *
501
745
  * @private
502
- * @param palette - The palette to apply.
503
- * @param themeName - The name of the theme for CSS variable prefixing.
746
+ * @param property The CSS variable name.
747
+ * @param value The value to set.
748
+ * @param providers The providers to set the variable on.
749
+ * @param applyGlobally Whether to apply the variable globally.
504
750
  */
505
- applyPaletteInternal(palette, themeName) {
506
- this.ensureInitialized();
507
- const currentMode = this.getCurrentThemeMode();
508
- Object.entries(palette).forEach(([mode, modeValues]) => {
509
- Object.entries(modeValues).forEach(([paletteName, paletteValues]) => {
510
- if (!ThemePalette.isThemePalette(paletteValues)) {
511
- // legacy variable style with mode
512
- this.setCssVariable(`--${themeName}-color-${mode}-${paletteName}`, paletteValues);
513
- // If this is the current mode, also set the mode-agnostic variables
514
- if (mode === currentMode) {
515
- // legacy variable style without mode (e.g., --joy-color-primary)
516
- this.setCssVariable(`--${themeName}-color-${paletteName}`, paletteValues);
517
- // new variable style
518
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
519
- this.setCssVariable(`--mosaik-color-${paletteName}`, `var(--${themeName}-color-${paletteName}, ${paletteValues})`);
520
- }
521
- return;
522
- }
523
- Object.entries(paletteValues).forEach(([shade, colorValue]) => {
524
- const kebabShade = this.toKebabCase(shade);
525
- // legacy variable style with mode (e.g., --joy-color-light-primary-500)
526
- this.setCssVariable(`--${themeName}-color-${mode}-${paletteName}-${kebabShade}`, colorValue);
527
- // If this is the current mode, also set the mode-agnostic variables
528
- if (mode === currentMode) {
529
- // legacy variable style without mode (e.g., --joy-color-primary-500)
530
- this.setCssVariable(`--${themeName}-color-${paletteName}-${kebabShade}`, colorValue);
531
- // new variable style
532
- this.setCssVariable(`--mosaik-color-${paletteName}-${kebabShade}`, `var(--${themeName}-color-${paletteName}-${kebabShade}, ${colorValue})`);
533
- }
534
- });
535
- });
751
+ setCssVariableWithProviders(property, value, providers, applyGlobally) {
752
+ // Apply globally if needed
753
+ if (applyGlobally) {
754
+ this._globalStyleSheet.setVariable(property, value);
755
+ // eslint-disable-next-line no-restricted-syntax -- Setting CSS variables is the correct approach for theming
756
+ document.documentElement.style.setProperty(property, value);
757
+ }
758
+ // Apply to all provider elements (they are HTMLElements)
759
+ providers.forEach((provider) => {
760
+ // eslint-disable-next-line no-restricted-syntax -- Setting CSS variables is the correct approach for theming
761
+ provider.style.setProperty(property, value);
536
762
  });
537
763
  }
538
764
  /**
539
765
  * Converts a camelCase string to kebab-case.
540
766
  *
541
767
  * @private
542
- * @param value - The value to convert.
768
+ * @param value The value to convert.
543
769
  * @returns The kebab-case string.
544
770
  */
545
771
  toKebabCase(value) {
@@ -552,7 +778,7 @@ export class ThemeService {
552
778
  * Normalizes a CssLength value to a px string.
553
779
  *
554
780
  * @private
555
- * @param value - The value to normalize.
781
+ * @param value The value to normalize.
556
782
  * @returns The normalized px string.
557
783
  */
558
784
  normalizeCssLength(value) {
@@ -577,5 +803,37 @@ export class ThemeService {
577
803
  throw new Error('[ThemeService] The service must be initialized before use. Call initialize() first or enable autoInitialize in the configuration.');
578
804
  }
579
805
  }
806
+ /**
807
+ * Gets all providers that match the specified theme ID.
808
+ * If themeId is null, returns providers without an ID.
809
+ *
810
+ * @private
811
+ * @param themeId The theme ID to filter by.
812
+ * @returns Array of matching providers.
813
+ */
814
+ getProvidersForName(name) {
815
+ return this._providers.filter((provider) => provider.name === name);
816
+ }
817
+ /**
818
+ * Gets the theme name for CSS variable prefixing from the stored state.
819
+ * Falls back to the default theme name from configuration if no state exists for the given scope.
820
+ *
821
+ * @private
822
+ * @param name The scope name (null for global scope).
823
+ * @returns The theme name for CSS variable prefixing.
824
+ */
825
+ getThemeNameForScope(name) {
826
+ return this._themeStates.get(name)?.name ?? ThemeService._config.defaultTheme?.name ?? 'joy';
827
+ }
828
+ /**
829
+ * Creates a deep clone of a theme object.
830
+ *
831
+ * @private
832
+ * @param theme The theme to clone.
833
+ * @returns A deep clone of the theme.
834
+ */
835
+ cloneTheme(theme) {
836
+ return JSON.parse(JSON.stringify(theme));
837
+ }
580
838
  }
581
839
  //# sourceMappingURL=ThemeService.js.map