@siemens/element-ng 47.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.
Files changed (105) hide show
  1. package/LICENSE.md +20 -0
  2. package/README.md +27 -0
  3. package/application-header/index.d.ts +17 -0
  4. package/application-header/launchpad/si-launchpad-app.component.d.ts +21 -0
  5. package/application-header/launchpad/si-launchpad-factory.component.d.ts +83 -0
  6. package/application-header/launchpad/si-launchpad.model.d.ts +33 -0
  7. package/application-header/package.json +3 -0
  8. package/application-header/si-application-header.component.d.ts +60 -0
  9. package/application-header/si-header-account-item.component.d.ts +13 -0
  10. package/application-header/si-header-action-item-icon-base.directive.d.ts +26 -0
  11. package/application-header/si-header-action-item.base.d.ts +19 -0
  12. package/application-header/si-header-action-item.component.d.ts +9 -0
  13. package/application-header/si-header-actions.directive.d.ts +5 -0
  14. package/application-header/si-header-brand.directive.d.ts +5 -0
  15. package/application-header/si-header-collapsible-actions.component.d.ts +33 -0
  16. package/application-header/si-header-logo.directive.d.ts +6 -0
  17. package/application-header/si-header-navigation-item.component.d.ts +10 -0
  18. package/application-header/si-header-navigation.component.d.ts +11 -0
  19. package/application-header/si-header-selection-item.component.d.ts +12 -0
  20. package/application-header/si-header-siemens-logo.component.d.ts +20 -0
  21. package/avatar/index.d.ts +6 -0
  22. package/avatar/package.json +3 -0
  23. package/avatar/si-avatar-background-color.directive.d.ts +35 -0
  24. package/avatar/si-avatar.component.d.ts +50 -0
  25. package/common/decorators/index.d.ts +5 -0
  26. package/common/decorators/webcomponent.decorator.d.ts +6 -0
  27. package/common/helpers/animation.helpers.d.ts +10 -0
  28. package/common/helpers/global-events.helpers.d.ts +5 -0
  29. package/common/helpers/index.d.ts +10 -0
  30. package/common/helpers/overlay-helper.d.ts +24 -0
  31. package/common/helpers/positioning.helpers.d.ts +58 -0
  32. package/common/helpers/rtl.d.ts +6 -0
  33. package/common/helpers/track-by.helper.d.ts +27 -0
  34. package/common/index.d.ts +8 -0
  35. package/common/models/color-variant.model.d.ts +8 -0
  36. package/common/models/index.d.ts +8 -0
  37. package/common/models/menu.model.d.ts +85 -0
  38. package/common/models/positions.model.d.ts +18 -0
  39. package/common/models/status-type.model.d.ts +19 -0
  40. package/common/package.json +3 -0
  41. package/common/services/blink.service.d.ts +41 -0
  42. package/common/services/index.d.ts +8 -0
  43. package/common/services/scrollbar-helper.service.d.ts +17 -0
  44. package/common/services/si-uistate.service.d.ts +61 -0
  45. package/common/services/text-measure.service.d.ts +21 -0
  46. package/element-ng.scss +14 -0
  47. package/fesm2022/siemens-element-ng-application-header.mjs +747 -0
  48. package/fesm2022/siemens-element-ng-application-header.mjs.map +1 -0
  49. package/fesm2022/siemens-element-ng-avatar.mjs +185 -0
  50. package/fesm2022/siemens-element-ng-avatar.mjs.map +1 -0
  51. package/fesm2022/siemens-element-ng-common.mjs +946 -0
  52. package/fesm2022/siemens-element-ng-common.mjs.map +1 -0
  53. package/fesm2022/siemens-element-ng-header-dropdown.mjs +384 -0
  54. package/fesm2022/siemens-element-ng-header-dropdown.mjs.map +1 -0
  55. package/fesm2022/siemens-element-ng-icon.mjs +236 -0
  56. package/fesm2022/siemens-element-ng-icon.mjs.map +1 -0
  57. package/fesm2022/siemens-element-ng-link.mjs +233 -0
  58. package/fesm2022/siemens-element-ng-link.mjs.map +1 -0
  59. package/fesm2022/siemens-element-ng-resize-observer.mjs +332 -0
  60. package/fesm2022/siemens-element-ng-resize-observer.mjs.map +1 -0
  61. package/fesm2022/siemens-element-ng-theme.mjs +770 -0
  62. package/fesm2022/siemens-element-ng-theme.mjs.map +1 -0
  63. package/fesm2022/siemens-element-ng-translate.mjs +22 -0
  64. package/fesm2022/siemens-element-ng-translate.mjs.map +1 -0
  65. package/fesm2022/siemens-element-ng.mjs +12 -0
  66. package/fesm2022/siemens-element-ng.mjs.map +1 -0
  67. package/header-dropdown/index.d.ts +9 -0
  68. package/header-dropdown/package.json +3 -0
  69. package/header-dropdown/si-header-dropdown-item.component.d.ts +23 -0
  70. package/header-dropdown/si-header-dropdown-items-factory.component.d.ts +14 -0
  71. package/header-dropdown/si-header-dropdown-trigger.directive.d.ts +57 -0
  72. package/header-dropdown/si-header-dropdown.component.d.ts +20 -0
  73. package/header-dropdown/si-header.model.d.ts +37 -0
  74. package/icon/element-icons.d.ts +12 -0
  75. package/icon/index.d.ts +9 -0
  76. package/icon/package.json +3 -0
  77. package/icon/si-icon-next.component.d.ts +68 -0
  78. package/icon/si-icon.component.d.ts +23 -0
  79. package/icon/si-icon.module.d.ts +7 -0
  80. package/icon/si-icons.d.ts +31 -0
  81. package/index.d.ts +5 -0
  82. package/link/aria-current.model.d.ts +5 -0
  83. package/link/index.d.ts +8 -0
  84. package/link/link.model.d.ts +57 -0
  85. package/link/package.json +3 -0
  86. package/link/si-link-action.service.d.ts +17 -0
  87. package/link/si-link.directive.d.ts +42 -0
  88. package/link/si-link.module.d.ts +7 -0
  89. package/package.json +86 -0
  90. package/public-api.d.ts +5 -0
  91. package/resize-observer/index.d.ts +8 -0
  92. package/resize-observer/package.json +3 -0
  93. package/resize-observer/resize-observer.service.d.ts +41 -0
  94. package/resize-observer/si-resize-observer.directive.d.ts +29 -0
  95. package/resize-observer/si-resize-observer.module.d.ts +8 -0
  96. package/resize-observer/si-responsive-container.directive.d.ts +73 -0
  97. package/theme/index.d.ts +7 -0
  98. package/theme/package.json +3 -0
  99. package/theme/si-theme-store.d.ts +82 -0
  100. package/theme/si-theme.model.d.ts +48 -0
  101. package/theme/si-theme.service.d.ts +129 -0
  102. package/translate/index.d.ts +7 -0
  103. package/translate/package.json +3 -0
  104. package/translate/si-translatable-keys.interface.d.ts +11 -0
  105. package/translate/si-translatable-overrides.provider.d.ts +7 -0
@@ -0,0 +1,770 @@
1
+ import { of, ReplaySubject, throwError } from 'rxjs';
2
+ import { isPlatformBrowser, DOCUMENT } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { EventEmitter, signal, inject, PLATFORM_ID, Injectable } from '@angular/core';
5
+ import { Meta, DomSanitizer } from '@angular/platform-browser';
6
+ import { take, tap, map, switchMap } from 'rxjs/operators';
7
+
8
+ /**
9
+ * Copyright Siemens 2016 - 2025.
10
+ * SPDX-License-Identifier: MIT
11
+ */
12
+ /**
13
+ * SiThemeStore object is used by the theme service to load and
14
+ * store the themes. You can inject your own implementation to provide
15
+ * a backend implementation. Otherwise a localStorage based implementation
16
+ * is used.
17
+ */
18
+ class SiThemeStore {
19
+ }
20
+ const SI_THEME_LOCAL_STORAGE_KEY = 'si-themes';
21
+ class SiDefaultThemeStore extends SiThemeStore {
22
+ isBrowser;
23
+ constructor(isBrowser) {
24
+ super();
25
+ this.isBrowser = isBrowser;
26
+ }
27
+ loadActiveTheme() {
28
+ if (!this.isBrowser) {
29
+ return of(undefined);
30
+ }
31
+ const store = this.loadStore();
32
+ if (store.activeTheme) {
33
+ return of(store.themes[store.activeTheme]);
34
+ }
35
+ else {
36
+ return of(undefined);
37
+ }
38
+ }
39
+ activateTheme(name) {
40
+ if (!this.isBrowser) {
41
+ return of(false);
42
+ }
43
+ const store = this.loadStore();
44
+ if (store.themes[name]) {
45
+ store.activeTheme = name;
46
+ this.saveStore(store);
47
+ return of(true);
48
+ }
49
+ else {
50
+ return of(false);
51
+ }
52
+ }
53
+ deactivateTheme() {
54
+ if (!this.isBrowser) {
55
+ return of(false);
56
+ }
57
+ const store = this.loadStore();
58
+ store.activeTheme = undefined;
59
+ this.saveStore(store);
60
+ return of(true);
61
+ }
62
+ loadThemeNames() {
63
+ if (!this.isBrowser) {
64
+ return of([]);
65
+ }
66
+ const store = this.loadStore();
67
+ return of(Array.from(Object.keys(store.themes)));
68
+ }
69
+ saveTheme(theme) {
70
+ if (!this.isBrowser) {
71
+ return of(false);
72
+ }
73
+ const store = this.loadStore();
74
+ store.themes[theme.name] = theme;
75
+ this.saveStore(store);
76
+ return of(true);
77
+ }
78
+ loadTheme(name) {
79
+ if (!this.isBrowser) {
80
+ return of(undefined);
81
+ }
82
+ const store = this.loadStore();
83
+ return of(store.themes[name]);
84
+ }
85
+ deleteTheme(name) {
86
+ if (!this.isBrowser) {
87
+ return of(false);
88
+ }
89
+ const store = this.loadStore();
90
+ delete store.themes[name];
91
+ if (store.activeTheme === name) {
92
+ store.activeTheme = undefined;
93
+ }
94
+ this.saveStore(store);
95
+ return of(true);
96
+ }
97
+ loadStore() {
98
+ const storeStr = localStorage.getItem(SI_THEME_LOCAL_STORAGE_KEY);
99
+ if (storeStr) {
100
+ return JSON.parse(storeStr);
101
+ }
102
+ else {
103
+ return { activeTheme: undefined, themes: {} };
104
+ }
105
+ }
106
+ saveStore(store) {
107
+ localStorage.setItem(SI_THEME_LOCAL_STORAGE_KEY, JSON.stringify(store));
108
+ }
109
+ }
110
+
111
+ const ELEMENT_THEME_NAME = 'element';
112
+ const elementTheme = {
113
+ name: 'element',
114
+ groups: [
115
+ {
116
+ name: 'Branding',
117
+ description: 'Configure the brand logo.',
118
+ properties: [
119
+ {
120
+ name: '--element-brand-logo',
121
+ usage: 'Enter a brand logo in the format: \'url("https://company/logo.svg")\'',
122
+ type: 'text'
123
+ },
124
+ {
125
+ name: '--element-brand-logo-width',
126
+ usage: "Enter the width of the logo as CSS width value. Example: '100px'",
127
+ type: 'text'
128
+ },
129
+ {
130
+ name: '--element-brand-logo-height',
131
+ usage: "Enter the height of the logo as CSS width value. Max: 48px; Example: '16px'",
132
+ type: 'text'
133
+ },
134
+ {
135
+ name: '--element-brand-logo-text',
136
+ usage: 'Enter a textual representation of the logo. Example: \'"Siemens logo"\'. The text must be wrapped in double quotes.',
137
+ type: 'text'
138
+ }
139
+ ]
140
+ },
141
+ {
142
+ name: 'Basic definitions',
143
+ description: 'Basic definitions',
144
+ properties: [
145
+ {
146
+ name: '--element-body-font-family',
147
+ usage: 'The theme only uses one font everywhere.',
148
+ type: 'font'
149
+ },
150
+ { name: '--element-button-radius', usage: 'Button border radius', type: 'text' },
151
+ { name: '--element-button-focus-width', usage: 'Focus width', type: 'text' },
152
+ {
153
+ name: '--element-button-focus-overlay-width',
154
+ usage: 'Focus overlay width',
155
+ type: 'text'
156
+ },
157
+ {
158
+ name: '--element-button-focus-overlay-color',
159
+ usage: 'Focus overlay color',
160
+ type: 'text'
161
+ },
162
+ { name: '--element-input-radius', usage: 'Input field border radius', type: 'text' },
163
+ { name: '--element-logo-color', usage: 'Logo color', type: 'text' },
164
+ { name: '--element-radius-1', usage: 'Border radius-1', type: 'text' },
165
+ { name: '--element-radius-2', usage: 'Border radius-2', type: 'text' },
166
+ { name: '--element-radius-3', usage: 'Border radius-3', type: 'text' }
167
+ ]
168
+ },
169
+ {
170
+ name: 'UI',
171
+ description: 'UI colors are used on structural properties and icons and provide good contrast when used over any background.',
172
+ properties: [
173
+ { name: '--element-ui-0', usage: 'Logo, selected (active) elements', type: 'color' },
174
+ { name: '--element-ui-0-hover', usage: 'Selected hover', type: 'color' },
175
+ { name: '--element-ui-1', usage: 'Primary icons', type: 'color' },
176
+ { name: '--element-ui-2', usage: 'Secondary icons', type: 'color' },
177
+ { name: '--element-ui-3', usage: 'Disabled', type: 'color' },
178
+ { name: '--element-ui-4', usage: 'Borders', type: 'color' },
179
+ { name: '--element-ui-5', usage: 'Inverse', type: 'color' },
180
+ { name: '--element-ui-6', usage: 'Shadows', type: 'color' },
181
+ { name: '--element-box-shadow-color-1', usage: 'Box shadow color 1', type: 'color' },
182
+ { name: '--element-box-shadow-color-2', usage: 'Box shadow color 2', type: 'color' },
183
+ { name: '--element-focus-default', usage: 'Default focus color', type: 'color' }
184
+ ]
185
+ },
186
+ {
187
+ name: 'Base',
188
+ description: 'Base colors are used as backgrounds of containers.',
189
+ properties: [
190
+ { name: '--element-base-0', usage: 'Page background, row background', type: 'color' },
191
+ {
192
+ name: '--element-base-1',
193
+ usage: 'Header, navigation, card, table background',
194
+ type: 'color'
195
+ },
196
+ {
197
+ name: '--element-base-1-hover',
198
+ usage: 'Hover on base-1 backgrounds, like table, tree, or menu',
199
+ type: 'color'
200
+ },
201
+ {
202
+ name: '--element-base-1-selected',
203
+ usage: 'Selected on base-1 backgrounds, like table, tree, or menu',
204
+ type: 'color'
205
+ },
206
+ {
207
+ name: '--element-base-2',
208
+ usage: 'Page background with higher contrast pages in dark mode',
209
+ type: 'color'
210
+ },
211
+ {
212
+ name: '--element-base-information',
213
+ usage: 'Informational component background for e.g. badges',
214
+ type: 'gradient'
215
+ },
216
+ {
217
+ name: '--element-base-success',
218
+ usage: 'Success component background for e.g. badges',
219
+ type: 'gradient'
220
+ },
221
+ {
222
+ name: '--element-base-caution',
223
+ usage: 'Caution component background for e.g. badges',
224
+ type: 'gradient'
225
+ },
226
+ {
227
+ name: '--element-base-warning',
228
+ usage: 'Warning component background for e.g. badges',
229
+ type: 'gradient'
230
+ },
231
+ {
232
+ name: '--element-base-danger',
233
+ usage: 'Danger component background for e.g. badges',
234
+ type: 'gradient'
235
+ },
236
+ {
237
+ name: '--element-base-translucent-1',
238
+ usage: 'Translucent background, e.g. backdrop',
239
+ type: 'gradient'
240
+ },
241
+ {
242
+ name: '--element-base-translucent-2',
243
+ usage: 'Slightly translucent background, e.g. toasts',
244
+ type: 'gradient'
245
+ }
246
+ ]
247
+ },
248
+ {
249
+ name: 'Actions',
250
+ description: 'Action colors are used to indicate actions that users can perform.',
251
+ properties: [
252
+ { name: '--element-action-primary', usage: 'Primary interaction', type: 'gradient' },
253
+ {
254
+ name: '--element-action-primary-hover',
255
+ usage: 'Primary action background on hover',
256
+ type: 'gradient'
257
+ },
258
+ { name: '--element-action-primary-text', usage: 'Primary text color', type: 'color' },
259
+ { name: '--element-action-secondary', usage: 'Secondary interaction', type: 'gradient' },
260
+ {
261
+ name: '--element-action-secondary-hover',
262
+ usage: 'Secondary action background on hover',
263
+ type: 'gradient'
264
+ },
265
+ { name: '--element-action-secondary-text', usage: 'Secondary text color', type: 'color' },
266
+ {
267
+ name: '--element-action-secondary-text-hover',
268
+ usage: 'Secondary text color on hover',
269
+ type: 'color'
270
+ },
271
+ {
272
+ name: '--element-action-secondary-border',
273
+ usage: 'Secondary border color',
274
+ type: 'color'
275
+ },
276
+ {
277
+ name: '--element-action-secondary-border-hover',
278
+ usage: 'Secondary border color on hover',
279
+ type: 'color'
280
+ },
281
+ { name: '--element-action-warning', usage: 'Warning', type: 'gradient' },
282
+ {
283
+ name: '--element-action-warning-hover',
284
+ usage: 'Warning action background on hover',
285
+ type: 'gradient'
286
+ },
287
+ { name: '--element-action-warning-text', usage: 'Warning text color', type: 'color' },
288
+ { name: '--element-action-danger', usage: 'Danger', type: 'gradient' },
289
+ {
290
+ name: '--element-action-danger-hover',
291
+ usage: 'Danger action background on hover',
292
+ type: 'gradient'
293
+ },
294
+ { name: '--element-action-danger-text', usage: 'Danger text color', type: 'color' },
295
+ {
296
+ name: '--element-action-disabled-opacity',
297
+ usage: 'Disabled action opacity',
298
+ type: 'number'
299
+ }
300
+ ]
301
+ },
302
+ {
303
+ name: 'Text',
304
+ description: 'Similarly, categories for typography colors are also defined in this system.',
305
+ properties: [
306
+ { name: '--element-text-primary', usage: 'Primary', type: 'color' },
307
+ { name: '--element-text-secondary', usage: 'Secondary', type: 'color' },
308
+ { name: '--element-text-disabled', usage: 'Disabled', type: 'color' },
309
+ { name: '--element-text-inverse', usage: 'Inverse', type: 'color' },
310
+ { name: '--element-text-active', usage: 'Active', type: 'color' },
311
+ { name: '--element-text-information', usage: 'Informational', type: 'color' },
312
+ { name: '--element-text-success', usage: 'Success', type: 'color' },
313
+ { name: '--element-text-caution', usage: 'Caution', type: 'color' },
314
+ { name: '--element-text-warning', usage: 'Warning', type: 'color' },
315
+ { name: '--element-text-danger', usage: 'Danger', type: 'color' }
316
+ ]
317
+ },
318
+ {
319
+ name: 'Status',
320
+ description: 'Status colors are used to describe and/or report on the status of different things.',
321
+ properties: [
322
+ { name: '--element-status-information', usage: 'Informational', type: 'color' },
323
+ {
324
+ name: '--element-status-information-contrast',
325
+ usage: 'Informational contrast',
326
+ type: 'color'
327
+ },
328
+ { name: '--element-status-success', usage: 'Success', type: 'color' },
329
+ { name: '--element-status-success-contrast', usage: 'Success contrast', type: 'color' },
330
+ { name: '--element-status-caution', usage: 'Caution', type: 'color' },
331
+ { name: '--element-status-caution-contrast', usage: 'Caution contrast', type: 'color' },
332
+ { name: '--element-status-warning', usage: 'Warning', type: 'color' },
333
+ { name: '--element-status-warning-contrast', usage: 'Warning contrast', type: 'color' },
334
+ { name: '--element-status-danger', usage: 'Danger', type: 'color' },
335
+ { name: '--element-status-danger-contrast', usage: 'Danger contrast', type: 'color' }
336
+ ]
337
+ },
338
+ {
339
+ name: 'Form feedback icons',
340
+ description: 'Icons that are used to indicate a form item status',
341
+ properties: [{ name: '--element-invalid-feedback-icon', usage: 'Danger', type: 'text' }]
342
+ }
343
+ ]
344
+ };
345
+
346
+ /**
347
+ * Copyright Siemens 2016 - 2025.
348
+ * SPDX-License-Identifier: MIT
349
+ */
350
+ class SiThemeService {
351
+ /**
352
+ * The current color scheme. (e.g. light or dark).
353
+ */
354
+ _resolvedColorScheme = 'light';
355
+ resolvedColorSchemeSub = new ReplaySubject(1);
356
+ /**
357
+ * Emits events when the color scheme changes.
358
+ *
359
+ * @defaultValue this.resolvedColorSchemeSub.asObservable()
360
+ */
361
+ resolvedColorScheme$ = this.resolvedColorSchemeSub.asObservable();
362
+ /**
363
+ * The current color scheme. (e.g. light or dark).
364
+ */
365
+ get resolvedColorScheme() {
366
+ return this._resolvedColorScheme;
367
+ }
368
+ /**
369
+ * All available theme names, including element theme name.
370
+ */
371
+ _themeNames = [ELEMENT_THEME_NAME];
372
+ themeNamesSub = new ReplaySubject(1);
373
+ /**
374
+ * Emits events when the list of available theme names changes.
375
+ *
376
+ * @defaultValue this.themeNamesSub.asObservable()
377
+ */
378
+ themeNames$ = this.themeNamesSub.asObservable();
379
+ /**
380
+ * All available theme names, including element theme name.
381
+ */
382
+ get themeNames() {
383
+ return this._themeNames;
384
+ }
385
+ /**
386
+ * Emits events when the currently applied theme changes. Either by
387
+ * changing to another theme or by re-applying a theme with updated
388
+ * properties. When switching to default theme element, `undefined`
389
+ * is emitted.
390
+ */
391
+ themeChange = new EventEmitter();
392
+ /**
393
+ * The name of the theme that is active. Theme name `element` is the default.
394
+ */
395
+ activeThemeName;
396
+ /**
397
+ * Icon overrides by the currently activeTheme.
398
+ * @defaultValue
399
+ * ```
400
+ * {}
401
+ * ```
402
+ */
403
+ themeIcons = signal({});
404
+ themes = new Map();
405
+ darkMediaQuery;
406
+ mediaQueryListener;
407
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
408
+ previewTheme;
409
+ themeStore = inject(SiThemeStore, { optional: true }) ?? new SiDefaultThemeStore(this.isBrowser);
410
+ meta = inject(Meta);
411
+ document = inject(DOCUMENT);
412
+ domSanitizer = inject(DomSanitizer);
413
+ constructor() {
414
+ this.resolvedColorScheme$.subscribe(scheme => (this._resolvedColorScheme = scheme));
415
+ this.themeNames$.subscribe(names => (this._themeNames = names));
416
+ if (this.isBrowser) {
417
+ this.darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
418
+ this.mediaQueryListener = () => this.toggleDark(this.darkMediaQuery.matches);
419
+ }
420
+ this.getActiveTheme()
421
+ .pipe(take(1))
422
+ .subscribe({
423
+ next: theme => this.applyThemeStyle(theme, this._resolvedColorScheme),
424
+ error: error => console.error('Cannot load active theme', error)
425
+ });
426
+ this.themeStore
427
+ .loadThemeNames()
428
+ .pipe(take(1))
429
+ .subscribe({
430
+ next: names => {
431
+ names.push(ELEMENT_THEME_NAME);
432
+ this.themeNamesSub.next(names);
433
+ },
434
+ error: error => console.error('Cannot load theme names', error)
435
+ });
436
+ }
437
+ /**
438
+ * Returns `true` if a theme with the given name is available.
439
+ */
440
+ hasTheme(name) {
441
+ return this._themeNames.includes(name);
442
+ }
443
+ /**
444
+ * Returns a clone of the theme with the given name or `undefined` if not
445
+ * available or name is `element`.
446
+ * @param name - The name of the theme to be returned.
447
+ * @returns The theme with the given name and `undefined` if name is `element`.
448
+ */
449
+ getTheme(name) {
450
+ if (name === ELEMENT_THEME_NAME || !this.hasTheme(name)) {
451
+ return of(undefined);
452
+ }
453
+ if (this.themes.has(name)) {
454
+ const theme = this.themes.get(name);
455
+ // return a clone to avoid changes as a side effect
456
+ return of(this.cloneTheme(theme));
457
+ }
458
+ return this.themeStore.loadTheme(name).pipe(tap(theme => {
459
+ if (theme) {
460
+ this.themes.set(theme.name, this.cloneTheme(theme));
461
+ }
462
+ }));
463
+ }
464
+ /**
465
+ * Loads and returns the currently active theme. Returns undefined if no custom theme is used.
466
+ * @returns A custom theme or `undefined` if the default element theme is used.
467
+ */
468
+ getActiveTheme() {
469
+ if (this.activeThemeName) {
470
+ return of(this.themes.get(this.activeThemeName));
471
+ }
472
+ return this.themeStore.loadActiveTheme().pipe(map(theme => {
473
+ if (theme) {
474
+ this.activeThemeName = theme.name;
475
+ this.themes.set(theme.name, theme);
476
+ this.applyTheme(theme, this._resolvedColorScheme);
477
+ return theme;
478
+ }
479
+ this.activeThemeName = ELEMENT_THEME_NAME;
480
+ return undefined;
481
+ }));
482
+ }
483
+ /**
484
+ * Adds or updates the given theme in the theme store.
485
+ * @param theme - The theme to be saved.
486
+ * @returns `true` if the theme was saved successfully.
487
+ */
488
+ addOrUpdateTheme(theme) {
489
+ const result = new ReplaySubject(1);
490
+ this.themeStore.saveTheme(theme).subscribe({
491
+ next: saveResult => {
492
+ if (saveResult) {
493
+ this.themes.set(theme.name, this.cloneTheme(theme));
494
+ // Update list of theme names when this is a new one
495
+ if (!this.hasTheme(theme.name)) {
496
+ const names = this._themeNames.concat(theme.name);
497
+ this.themeNamesSub.next(names);
498
+ }
499
+ }
500
+ result.next(saveResult);
501
+ result.complete();
502
+ },
503
+ error: error => result.error(error),
504
+ complete: () => result.complete()
505
+ });
506
+ return result;
507
+ }
508
+ /**
509
+ * Deletes the theme with the given name from the theme store.
510
+ */
511
+ deleteTheme(name) {
512
+ const result = new ReplaySubject(1);
513
+ if (!this.hasTheme(name) || name === ELEMENT_THEME_NAME) {
514
+ result.next(false);
515
+ result.complete();
516
+ }
517
+ else {
518
+ this.removeThemeCSS(name);
519
+ this.themeStore.deleteTheme(name).subscribe({
520
+ next: deleteResult => {
521
+ if (deleteResult) {
522
+ this.themes.delete(name);
523
+ const names = this._themeNames.filter(themeName => themeName !== name);
524
+ this.themeNamesSub.next(names);
525
+ }
526
+ result.next(deleteResult);
527
+ result.complete();
528
+ },
529
+ error: error => result.error(error),
530
+ complete: () => result.complete()
531
+ });
532
+ }
533
+ return result;
534
+ }
535
+ /**
536
+ * Resets the preview theme to the default element theme.
537
+ */
538
+ resetPreview() {
539
+ this.previewTheme = { name: '__preview', schemes: { dark: {}, light: {} } };
540
+ this.removeThemeCSS(this.previewTheme.name);
541
+ }
542
+ /**
543
+ * Sets the active theme to the given name. If no name is given, the default element theme is used.
544
+ */
545
+ setActiveTheme(name, type) {
546
+ const theType = type ?? this._resolvedColorScheme ?? 'auto';
547
+ const theName = name ?? this.activeThemeName ?? ELEMENT_THEME_NAME;
548
+ if (theName === this.activeThemeName) {
549
+ return of(true);
550
+ }
551
+ if (!this.hasTheme(theName)) {
552
+ return of(false);
553
+ }
554
+ const result = new ReplaySubject(1);
555
+ this.activeThemeName = theName;
556
+ // Make the change in the remove store
557
+ const store = theName !== ELEMENT_THEME_NAME
558
+ ? this.themeStore.activateTheme(theName)
559
+ : this.themeStore.deactivateTheme();
560
+ store
561
+ .pipe(
562
+ // On success, load the theme
563
+ switchMap(storeResult => {
564
+ if (storeResult) {
565
+ return this.getTheme(theName);
566
+ }
567
+ else {
568
+ return throwError(() => 'Cannot change active theme in theme store.');
569
+ }
570
+ }),
571
+ // Only take one to avoid unsubscription issues
572
+ take(1))
573
+ .subscribe({
574
+ next: theme => {
575
+ this.applyTheme(theme, theType);
576
+ result.next(true);
577
+ },
578
+ error: error => {
579
+ console.error(`Activating theme ${name} failed`, error);
580
+ result.next(false);
581
+ },
582
+ complete: () => result.complete()
583
+ });
584
+ return result;
585
+ }
586
+ /**
587
+ * Apply `light` or `dark` theme to the document.
588
+ */
589
+ applyThemeType(type) {
590
+ if (type === this._resolvedColorScheme || !this.darkMediaQuery || !this.mediaQueryListener) {
591
+ return;
592
+ }
593
+ this.darkMediaQuery.removeEventListener('change', this.mediaQueryListener);
594
+ if (type === 'light') {
595
+ this.toggleDark(false);
596
+ }
597
+ else if (type === 'dark') {
598
+ this.toggleDark(true);
599
+ }
600
+ else {
601
+ this.mediaQueryListener();
602
+ this.darkMediaQuery.addEventListener('change', this.mediaQueryListener);
603
+ }
604
+ }
605
+ /**
606
+ * Applies the given theme to the document. If no theme is given, the active theme is applied.
607
+ */
608
+ applyTheme(theme, type, overwrite) {
609
+ if (theme) {
610
+ this.applyThemeStyle(theme, type, overwrite);
611
+ }
612
+ else {
613
+ this.getActiveTheme()
614
+ .pipe(take(1))
615
+ .subscribe({
616
+ next: loadedTheme => this.applyThemeStyle(loadedTheme, type),
617
+ error: error => console.error('Cannot load active theme', error)
618
+ });
619
+ }
620
+ }
621
+ /**
622
+ * Updates the given property of the preview theme.
623
+ */
624
+ updateProperty(name, value, type) {
625
+ if (!this.previewTheme) {
626
+ this.resetPreview();
627
+ }
628
+ this.previewTheme.schemes[type][name] = value;
629
+ this.createThemeCSS(this.previewTheme);
630
+ this.document.documentElement.classList.add('theme-__preview');
631
+ this.dispatchThemeSwitchEvent();
632
+ }
633
+ /**
634
+ * Checks if the given theme JSON object is a valid theme.
635
+ */
636
+ isThemeValid(data) {
637
+ const theme = data;
638
+ return (!!data &&
639
+ typeof data === 'object' &&
640
+ 'name' in data &&
641
+ 'schemes' in data &&
642
+ typeof theme.name === 'string' &&
643
+ typeof theme.schemes === 'object' &&
644
+ this.isThemeTypeValid(theme, 'light') &&
645
+ this.isThemeTypeValid(theme, 'dark'));
646
+ }
647
+ isThemeTypeValid(theme, type) {
648
+ return (!(type in theme.schemes) ||
649
+ (typeof theme.schemes[type] === 'object' &&
650
+ Object.values(theme.schemes[type]).every(item => typeof item === 'string') &&
651
+ Object.keys(theme.schemes[type]).every(item => typeof item === 'string')));
652
+ }
653
+ applyThemeStyle(theme, type, overwrite) {
654
+ if (!this.isBrowser) {
655
+ return;
656
+ }
657
+ this.resetPreview();
658
+ if (theme && theme.name !== ELEMENT_THEME_NAME && (overwrite || !this.hasThemeCSS(theme))) {
659
+ this.createThemeCSS(theme);
660
+ }
661
+ this.activateTheme(theme);
662
+ if (type && type !== this._resolvedColorScheme) {
663
+ this.applyThemeType(type);
664
+ }
665
+ this.themeIcons.set(Object.fromEntries(Object.entries(theme?.icons ?? {}).map(([key, icon]) => [key, this.parseDataSvgIcon(icon)])));
666
+ this.themeChange.emit(theme);
667
+ }
668
+ activateTheme(theme) {
669
+ const classList = this.document.documentElement.classList;
670
+ classList.forEach(c => {
671
+ if (c.startsWith('theme-')) {
672
+ classList.remove(c);
673
+ }
674
+ });
675
+ if (theme) {
676
+ classList.add(`theme-${theme.name}`);
677
+ }
678
+ }
679
+ hasThemeCSS(theme) {
680
+ const id = `__theme-${theme.name}`;
681
+ return !!this.document.getElementById(id);
682
+ }
683
+ createThemeCSS(theme) {
684
+ let css = '';
685
+ const themeSelector = `:root.theme-${theme.name}`;
686
+ if (theme.schemes.light) {
687
+ css = this.createThemeVariantCSS(css, themeSelector, theme.schemes.light);
688
+ }
689
+ if (theme.schemes.dark) {
690
+ css = this.createThemeVariantCSS(css, themeSelector + '.app--dark', theme.schemes.dark);
691
+ }
692
+ this.removeThemeCSS(theme.name);
693
+ const cssElement = this.document.createElement('style');
694
+ cssElement.id = `__theme-${theme.name}`;
695
+ cssElement.textContent = css;
696
+ this.document.body.appendChild(cssElement);
697
+ }
698
+ createThemeVariantCSS(css, selector, scheme) {
699
+ css += `${selector} {\n`;
700
+ for (const key of Object.keys(scheme)) {
701
+ css += `${key}: ${scheme[key]};\n`;
702
+ }
703
+ css += '}\n';
704
+ return css;
705
+ }
706
+ removeThemeCSS(name) {
707
+ const id = `__theme-${name}`;
708
+ this.document.getElementById(id)?.remove();
709
+ this.document.documentElement.classList.remove(`theme-${name}`);
710
+ }
711
+ toggleDark(dark) {
712
+ if (!this.isBrowser) {
713
+ return;
714
+ }
715
+ this.document.documentElement.classList.toggle('app--dark', dark);
716
+ const colorScheme = dark ? 'dark' : 'light';
717
+ this.meta.updateTag({ name: 'color-scheme', content: colorScheme });
718
+ this.resolvedColorSchemeSub.next(colorScheme);
719
+ this.dispatchThemeSwitchEvent();
720
+ }
721
+ cloneTheme(theme) {
722
+ const clone = { ...theme, schemes: {} };
723
+ if (theme.schemes.light) {
724
+ clone.schemes.light = { ...theme.schemes.light };
725
+ }
726
+ if (theme.schemes.dark) {
727
+ clone.schemes.dark = { ...theme.schemes.dark };
728
+ }
729
+ return clone;
730
+ }
731
+ dispatchThemeSwitchEvent() {
732
+ window.dispatchEvent(new CustomEvent('theme-switch', {
733
+ detail: {
734
+ dark: this._resolvedColorScheme === 'dark',
735
+ colorScheme: this._resolvedColorScheme
736
+ }
737
+ }));
738
+ }
739
+ parseDataSvgIcon(icon) {
740
+ // This method is currently a copy of parseDataSvgIcon in si-icon.registry.ts.
741
+ // Those are likely to diverge in the future, as this version will get support for other formats like:
742
+ // - URLs
743
+ // - Plain SVG data
744
+ // - Promises to enable lazy icon loading using import()
745
+ const parsed = /^data:image\/svg\+xml;utf8,(.*)$/.exec(icon);
746
+ if (!parsed) {
747
+ console.error('Failed to parse icon', icon);
748
+ return '';
749
+ }
750
+ return this.domSanitizer.bypassSecurityTrustHtml(parsed[1]);
751
+ }
752
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
753
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiThemeService, providedIn: 'root' });
754
+ }
755
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiThemeService, decorators: [{
756
+ type: Injectable,
757
+ args: [{ providedIn: 'root' }]
758
+ }], ctorParameters: () => [] });
759
+
760
+ /**
761
+ * Copyright Siemens 2016 - 2025.
762
+ * SPDX-License-Identifier: MIT
763
+ */
764
+
765
+ /**
766
+ * Generated bundle index. Do not edit.
767
+ */
768
+
769
+ export { ELEMENT_THEME_NAME, SI_THEME_LOCAL_STORAGE_KEY, SiDefaultThemeStore, SiThemeService, SiThemeStore, elementTheme };
770
+ //# sourceMappingURL=siemens-element-ng-theme.mjs.map