@reforgium/presentia 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, signal, computed, inject, afterRenderEffect, Injectable, input, TemplateRef, ViewContainerRef, effect, Directive, Pipe, makeEnvironmentProviders, LOCALE_ID, DestroyRef } from '@angular/core';
|
|
3
|
-
import { SELECTED_LANG, SELECTED_THEME, CURRENT_DEVICE, deepEqual } from '@reforgium/internal';
|
|
2
|
+
import { InjectionToken, signal, computed, inject, afterRenderEffect, Injectable, input, TemplateRef, ViewContainerRef, effect, Directive, ElementRef, Renderer2, afterNextRender, Pipe, makeEnvironmentProviders, LOCALE_ID, DestroyRef } from '@angular/core';
|
|
3
|
+
import { TRANSLATION, SELECTED_LANG, SELECTED_THEME, CURRENT_DEVICE, deepEqual } from '@reforgium/internal';
|
|
4
4
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
5
5
|
import { fromEvent, debounceTime, startWith, filter as filter$1, map } from 'rxjs';
|
|
6
6
|
import { HttpClient } from '@angular/common/http';
|
|
@@ -9,67 +9,100 @@ import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
|
|
|
9
9
|
import { filter } from 'rxjs/operators';
|
|
10
10
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Default breakpoints for device type detection.
|
|
14
|
+
*
|
|
15
|
+
* Defines media queries for each device category:
|
|
16
|
+
* - `mobile` — screens up to 719px wide
|
|
17
|
+
* - `tablet` — screens from 720px to 1399px wide
|
|
18
|
+
* - `desktop-s` — small desktop screens from 1400px to 1919px wide
|
|
19
|
+
* - `desktop` — large desktop screens 1920px and wider
|
|
20
|
+
*
|
|
21
|
+
* These breakpoints are used by `AdaptiveService` to determine the current device type.
|
|
22
|
+
*/
|
|
12
23
|
const defaultBreakpoints = {
|
|
13
24
|
'mobile': '(max-width: 719px)',
|
|
14
25
|
'tablet': '(min-width: 720px) and (max-width: 1399px)',
|
|
15
26
|
'desktop-s': '(min-width: 1400px) and (max-width: 1919px)',
|
|
16
27
|
'desktop': '(min-width: 1920px)',
|
|
17
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* Injection token for providing custom device breakpoints.
|
|
31
|
+
*
|
|
32
|
+
* By default, provides `defaultBreakpoints`, but can be overridden
|
|
33
|
+
* at any level of the dependency injection tree to customize
|
|
34
|
+
* the device detection behavior.
|
|
35
|
+
*
|
|
36
|
+
* Example of overriding:
|
|
37
|
+
* ```typescript
|
|
38
|
+
* {
|
|
39
|
+
* provide: DEVICE_BREAKPOINTS,
|
|
40
|
+
* useValue: {
|
|
41
|
+
* mobile: '(max-width: 599px)',
|
|
42
|
+
* tablet: '(min-width: 600px) and (max-width: 1199px)',
|
|
43
|
+
* 'desktop-s': '(min-width: 1200px) and (max-width: 1799px)',
|
|
44
|
+
* desktop: '(min-width: 1800px)'
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Used by `AdaptiveService` to observe media query changes.
|
|
50
|
+
*/
|
|
18
51
|
const DEVICE_BREAKPOINTS = new InjectionToken('RE_DEVICE_BREAKPOINTS', {
|
|
19
52
|
providedIn: 'root',
|
|
20
53
|
factory: () => defaultBreakpoints,
|
|
21
54
|
});
|
|
22
55
|
|
|
23
56
|
/**
|
|
24
|
-
*
|
|
57
|
+
* The `AdaptiveService` is responsible for determining the device type, window dimensions, and screen orientation.
|
|
25
58
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
59
|
+
* Used for building adaptive interfaces, changing component behavior and styles
|
|
60
|
+
* depending on the current device or screen size.
|
|
28
61
|
*
|
|
29
|
-
*
|
|
30
|
-
* -
|
|
31
|
-
* -
|
|
32
|
-
* -
|
|
62
|
+
* Main features:
|
|
63
|
+
* - Reactive tracking of the current device (`desktop`, `tablet`, `mobile`).
|
|
64
|
+
* - Support for computed properties (`isDesktop`, `isPortrait`).
|
|
65
|
+
* - Automatic updates when the window is resized and breakpoints are crossed.
|
|
33
66
|
*
|
|
34
|
-
*
|
|
35
|
-
* - `BreakpointObserver`
|
|
36
|
-
* - `signal`
|
|
37
|
-
* - `fromEvent(window, 'resize')` —
|
|
67
|
+
* Implementation is based on:
|
|
68
|
+
* - `BreakpointObserver` from Angular CDK — for observing media queries.
|
|
69
|
+
* - `signal` and `computed` — for reactive state without zones.
|
|
70
|
+
* - `fromEvent(window, 'resize')` — for updating window dimensions with debouncing.
|
|
38
71
|
*
|
|
39
|
-
*
|
|
72
|
+
* The service is registered as `providedIn: 'root'` and is available throughout the application.
|
|
40
73
|
*/
|
|
41
74
|
class AdaptiveService {
|
|
42
|
-
/** @internal
|
|
75
|
+
/** @internal Signal of the current device type. */
|
|
43
76
|
#device = signal('desktop', ...(ngDevMode ? [{ debugName: "#device" }] : []));
|
|
44
|
-
/** @internal
|
|
77
|
+
/** @internal Signals of the current window width and height. */
|
|
45
78
|
#width = signal(0, ...(ngDevMode ? [{ debugName: "#width" }] : []));
|
|
46
79
|
#height = signal(0, ...(ngDevMode ? [{ debugName: "#height" }] : []));
|
|
47
80
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
81
|
+
* Current device type (reactive signal).
|
|
82
|
+
* Possible values: `'desktop' | 'tablet' | 'mobile'`.
|
|
50
83
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
84
|
+
* Updates automatically when screen width changes
|
|
85
|
+
* or when specified breakpoints are crossed (`DEVICE_BREAKPOINTS`).
|
|
53
86
|
*/
|
|
54
87
|
device = this.#device.asReadonly();
|
|
55
88
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
89
|
+
* Current browser window width in pixels.
|
|
90
|
+
* Updates reactively on `resize` event.
|
|
58
91
|
*/
|
|
59
92
|
width = this.#width.asReadonly();
|
|
60
93
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
94
|
+
* Current browser window height in pixels.
|
|
95
|
+
* Updates reactively on `resize` event.
|
|
63
96
|
*/
|
|
64
97
|
height = this.#height.asReadonly();
|
|
65
98
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
99
|
+
* Computed signal indicating whether the current device is a desktop.
|
|
100
|
+
* Used for conditional rendering or layout configuration.
|
|
68
101
|
*/
|
|
69
102
|
isDesktop = computed(() => this.#device() === 'desktop', ...(ngDevMode ? [{ debugName: "isDesktop" }] : []));
|
|
70
103
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
104
|
+
* Computed signal determining whether the screen is in portrait orientation.
|
|
105
|
+
* Returns `true` if window height is greater than width.
|
|
73
106
|
*/
|
|
74
107
|
isPortrait = computed(() => this.#height() > this.#width(), ...(ngDevMode ? [{ debugName: "isPortrait" }] : []));
|
|
75
108
|
deviceBreakpoints = inject(DEVICE_BREAKPOINTS);
|
|
@@ -102,24 +135,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
|
|
|
102
135
|
}], ctorParameters: () => [] });
|
|
103
136
|
|
|
104
137
|
/**
|
|
105
|
-
*
|
|
138
|
+
* Structural directive `*ssIfDevice`.
|
|
106
139
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
140
|
+
* Shows or hides an element based on the current device type,
|
|
141
|
+
* as determined by `AdaptiveService`.
|
|
109
142
|
*
|
|
110
|
-
*
|
|
143
|
+
* Example:
|
|
111
144
|
* ```html
|
|
112
|
-
* <div *ssIfDevice="'desktop'"
|
|
113
|
-
* <div *ssIfDevice="['mobile', 'tablet']"
|
|
114
|
-
* <div *ssIfDevice="'mobile'; inverse: true"
|
|
145
|
+
* <div *ssIfDevice="'desktop'">Desktop only</div>
|
|
146
|
+
* <div *ssIfDevice="['mobile', 'tablet']">For mobile and tablets</div>
|
|
147
|
+
* <div *ssIfDevice="'mobile'; inverse: true"> Hide on mobile</div>
|
|
115
148
|
* ```
|
|
116
149
|
*
|
|
117
|
-
*
|
|
118
|
-
* - `ssIfDevice` —
|
|
119
|
-
* - `inverse` —
|
|
150
|
+
* Parameters:
|
|
151
|
+
* - `ssIfDevice` — one or more `Devices` values (`'desktop' | 'tablet' | 'mobile'`)
|
|
152
|
+
* - `inverse` — inverts the display condition
|
|
120
153
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
154
|
+
* Works reactively: when the device type changes in `AdaptiveService`,
|
|
155
|
+
* the template is automatically added or removed from the DOM.
|
|
123
156
|
*/
|
|
124
157
|
class IfDeviceDirective {
|
|
125
158
|
device = input(undefined, { ...(ngDevMode ? { debugName: "device" } : {}), alias: 'ssIfDevice' });
|
|
@@ -162,10 +195,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
|
|
|
162
195
|
}]
|
|
163
196
|
}], ctorParameters: () => [], propDecorators: { device: [{ type: i0.Input, args: [{ isSignal: true, alias: "ssIfDevice", required: false }] }], inverse: [{ type: i0.Input, args: [{ isSignal: true, alias: "inverse", required: false }] }] } });
|
|
164
197
|
|
|
165
|
-
const LANG_CONFIG = new InjectionToken('RE_LANG_CONFIG');
|
|
166
|
-
|
|
167
198
|
const innerLangVal = Symbol('reInnerLangVal');
|
|
168
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Injection token for providing locale configuration to the language module.
|
|
202
|
+
*
|
|
203
|
+
* Used to inject `LocaleConfig` into services and components that require
|
|
204
|
+
* internationalization settings (e.g., locale, date formats, translations).
|
|
205
|
+
*
|
|
206
|
+
* Example:
|
|
207
|
+
* ```typescript
|
|
208
|
+
* providers: [
|
|
209
|
+
* { provide: LANG_CONFIG, useValue: { locale: 'ru', ... } }
|
|
210
|
+
* ]
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* The token can be injected using Angular's DI system:
|
|
214
|
+
* ```typescript
|
|
215
|
+
* private config = inject(LANG_CONFIG);
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
const LANG_CONFIG = new InjectionToken('RE_LANG_CONFIG');
|
|
219
|
+
|
|
169
220
|
/**
|
|
170
221
|
* LangService provides functionality for managing and tracking language settings
|
|
171
222
|
* and translations in the application. It is designed to handle localization needs,
|
|
@@ -185,11 +236,11 @@ class LangService {
|
|
|
185
236
|
* - If private method `#lang` returns 'ru', this property will return 'ru'.
|
|
186
237
|
* - If `#lang` returns another value, the `config.kgValue` property is checked:
|
|
187
238
|
* - If `config.kgValue` is defined, the property will return its value.
|
|
188
|
-
* - If `config.kgValue` is not defined, the property will return default value 'kg'.
|
|
239
|
+
* - If `config.kgValue` is not defined, the property will return the default value 'kg'.
|
|
189
240
|
*/
|
|
190
241
|
currentLang = computed(() => {
|
|
191
242
|
const lang = this.#lang();
|
|
192
|
-
return lang === 'ru' ? 'ru' : (this.config
|
|
243
|
+
return lang === 'ru' ? 'ru' : (this.config?.kgValue ?? 'kg');
|
|
193
244
|
}, ...(ngDevMode ? [{ debugName: "currentLang" }] : []));
|
|
194
245
|
/**
|
|
195
246
|
* Extracts readonly value from private property `#lang` and assigns it to `innerLangVal`.
|
|
@@ -214,12 +265,12 @@ class LangService {
|
|
|
214
265
|
}
|
|
215
266
|
}
|
|
216
267
|
/**
|
|
217
|
-
* Gets value based on provided query and optionally applies specified parameters.
|
|
268
|
+
* Gets value based on a provided query and optionally applies specified parameters.
|
|
218
269
|
*
|
|
219
270
|
* @param {string} query - Query string used to retrieve desired value.
|
|
220
271
|
* @param {LangParams} [params] - Optional parameters to apply to retrieved value.
|
|
221
272
|
* @return {string} Retrieved value after optional parameter application,
|
|
222
|
-
* or default value if query is not found.
|
|
273
|
+
* or default value if a query is not found.
|
|
223
274
|
*/
|
|
224
275
|
get(query, params) {
|
|
225
276
|
const value = this.getChainedValue(query);
|
|
@@ -229,13 +280,13 @@ class LangService {
|
|
|
229
280
|
return value ?? this.config.defaultValue ?? query;
|
|
230
281
|
}
|
|
231
282
|
/**
|
|
232
|
-
* Observes changes to specified translation key and dynamically computes its value.
|
|
283
|
+
* Observes changes to a specified translation key and dynamically computes its value.
|
|
233
284
|
*
|
|
234
285
|
* @param {string} query - Translation key to observe, typically in format "namespace.key".
|
|
235
286
|
* @param {LangParams} [params] - Optional parameters for interpolation or
|
|
236
287
|
* dynamic content replacement in translation value.
|
|
237
288
|
* @return {Signal<string>} Computed value that dynamically updates
|
|
238
|
-
* with translation matching provided query and parameters.
|
|
289
|
+
* with translation matching a provided query and parameters.
|
|
239
290
|
*/
|
|
240
291
|
observe(query, params) {
|
|
241
292
|
const [ns] = query.split('.');
|
|
@@ -248,8 +299,8 @@ class LangService {
|
|
|
248
299
|
* Loads specified namespace, ensuring its caching and availability for use.
|
|
249
300
|
*
|
|
250
301
|
* @param {string} ns - Namespace name to load.
|
|
251
|
-
* @return {Promise<void>} Promise that resolves on successful namespace load,
|
|
252
|
-
* or rejects when error occurs during process.
|
|
302
|
+
* @return {Promise<void>} Promise that resolves on a successful namespace load,
|
|
303
|
+
* or rejects when error occurs during a process.
|
|
253
304
|
*/
|
|
254
305
|
async loadNamespace(ns) {
|
|
255
306
|
const key = this.makeNamespaceKey(ns);
|
|
@@ -318,16 +369,138 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
|
|
|
318
369
|
}] });
|
|
319
370
|
|
|
320
371
|
/**
|
|
321
|
-
*
|
|
372
|
+
* **LangDirective** — directive for automatic localization of attributes and text content.
|
|
322
373
|
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
374
|
+
* Replaces localization keys (format `namespace.key`) with translated strings via `LangService`.
|
|
375
|
+
* Supports `title`, `label`, `placeholder` attributes and text nodes.
|
|
325
376
|
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
377
|
+
* ### Usage
|
|
378
|
+
* ```html
|
|
379
|
+
* <!-- Localize all attributes and content -->
|
|
380
|
+
* <button reLang title="common.save">common.save</button>
|
|
328
381
|
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
382
|
+
* <!-- Localize only title -->
|
|
383
|
+
* <button [reLang]="'only-title'" title="common.cancel">Cancel</button>
|
|
384
|
+
*
|
|
385
|
+
* <!-- Localize custom attribute -->
|
|
386
|
+
* <input reLang [langForAttr]="'aria-label'" aria-label="forms.username" />
|
|
387
|
+
* ```
|
|
388
|
+
*
|
|
389
|
+
* @publicApi
|
|
390
|
+
*/
|
|
391
|
+
class LangDirective {
|
|
392
|
+
/**
|
|
393
|
+
* Localization mode: defines which parts of the element will be translated.
|
|
394
|
+
* @default 'all'
|
|
395
|
+
*/
|
|
396
|
+
lang = input('all', { ...(ngDevMode ? { debugName: "lang" } : {}), alias: 'reLang' });
|
|
397
|
+
/**
|
|
398
|
+
* Name of an additional attribute to localize (besides standard `title`, `label`, `placeholder`).
|
|
399
|
+
*/
|
|
400
|
+
langForAttr = input(...(ngDevMode ? [undefined, { debugName: "langForAttr" }] : []));
|
|
401
|
+
el = inject(ElementRef);
|
|
402
|
+
renderer = inject(Renderer2);
|
|
403
|
+
service = inject(LangService);
|
|
404
|
+
constructor() {
|
|
405
|
+
afterNextRender(() => this.setAttributes());
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Applies localization to the element according to `lang` and `langForAttr` settings.
|
|
409
|
+
* Called automatically after the component renders.
|
|
410
|
+
*/
|
|
411
|
+
setAttributes() {
|
|
412
|
+
const native = this.el.nativeElement;
|
|
413
|
+
const langFor = this.lang() || 'all';
|
|
414
|
+
const langForAttr = this.langForAttr();
|
|
415
|
+
if (['all', 'only-title'].includes(langFor) && native.hasAttribute('title')) {
|
|
416
|
+
this.replaceAttrValue('title');
|
|
417
|
+
}
|
|
418
|
+
if (['all', 'only-label'].includes(langFor) && native.hasAttribute('label')) {
|
|
419
|
+
this.replaceAttrValue('label');
|
|
420
|
+
}
|
|
421
|
+
if (['all', 'only-placeholder'].includes(langFor) && native.hasAttribute('placeholder')) {
|
|
422
|
+
this.replaceAttrValue('placeholder');
|
|
423
|
+
}
|
|
424
|
+
if (['all', 'only-content'].includes(langFor)) {
|
|
425
|
+
this.replaceTextNodeValue();
|
|
426
|
+
}
|
|
427
|
+
if (langForAttr) {
|
|
428
|
+
this.replaceAttrValue(langForAttr);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Replaces attribute value with a localized string.
|
|
433
|
+
* If the value doesn't look like a localization key, outputs a warning.
|
|
434
|
+
*
|
|
435
|
+
* @param attributeName name of the attribute to replace
|
|
436
|
+
*/
|
|
437
|
+
replaceAttrValue(attributeName) {
|
|
438
|
+
const native = this.el.nativeElement;
|
|
439
|
+
const title = native.getAttribute(attributeName)?.trim() || '';
|
|
440
|
+
if (!this.looksLikeKey(title)) {
|
|
441
|
+
console.warn(`Lang: Attribute ${attributeName} is not a lang key`, title);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
this.getLangValue(title).then((langed) => this.renderer.setAttribute(native, attributeName, langed));
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Replaces element's text node with a localized string.
|
|
448
|
+
* Works only if the element contains no child elements (text only).
|
|
449
|
+
* If the content doesn't look like a localization key, outputs a warning.
|
|
450
|
+
*/
|
|
451
|
+
replaceTextNodeValue() {
|
|
452
|
+
const native = this.el.nativeElement;
|
|
453
|
+
if (native.children.length > 0) {
|
|
454
|
+
console.warn('Lang: Element has children, text node replacement is skipped');
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const textNode = Array.from(native.childNodes).find((n) => n.nodeType === Node.TEXT_NODE);
|
|
458
|
+
const key = textNode?.nodeValue?.trim() || '';
|
|
459
|
+
if (!this.looksLikeKey(key)) {
|
|
460
|
+
console.warn('Lang: Text node is not a lang key', key);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
this.getLangValue(key).then((langed) => this.renderer.setValue(textNode, langed));
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Checks if a string looks like a localization key (contains `.` and no spaces).
|
|
467
|
+
*
|
|
468
|
+
* @param value string to check
|
|
469
|
+
* @returns `true` if the string looks like a localization key
|
|
470
|
+
*/
|
|
471
|
+
looksLikeKey(value) {
|
|
472
|
+
return !!value && value.includes('.') && !value.includes(' ');
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Asynchronously loads namespace and retrieves localized value by key.
|
|
476
|
+
*
|
|
477
|
+
* @param key localization key in format `namespace.path.to.key`
|
|
478
|
+
* @returns localized string
|
|
479
|
+
*/
|
|
480
|
+
async getLangValue(key) {
|
|
481
|
+
const [ns] = key.split('.', 1);
|
|
482
|
+
await this.service.loadNamespace(ns);
|
|
483
|
+
return this.service.get(key);
|
|
484
|
+
}
|
|
485
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: LangDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
486
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.3", type: LangDirective, isStandalone: true, selector: "[reLang]", inputs: { lang: { classPropertyName: "lang", publicName: "reLang", isSignal: true, isRequired: false, transformFunction: null }, langForAttr: { classPropertyName: "langForAttr", publicName: "langForAttr", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
487
|
+
}
|
|
488
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: LangDirective, decorators: [{
|
|
489
|
+
type: Directive,
|
|
490
|
+
args: [{ selector: '[reLang]', standalone: true }]
|
|
491
|
+
}], ctorParameters: () => [], propDecorators: { lang: [{ type: i0.Input, args: [{ isSignal: true, alias: "reLang", required: false }] }], langForAttr: [{ type: i0.Input, args: [{ isSignal: true, alias: "langForAttr", required: false }] }] } });
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Custom Angular pipe that transforms a language key and additional parameters into a localized string.
|
|
495
|
+
*
|
|
496
|
+
* The pipe is declared as standalone and impure — meaning it can be used without importing a module
|
|
497
|
+
* and will be recalculated when the state changes (for example, when the language is switched).
|
|
498
|
+
*
|
|
499
|
+
* In its operation, the pipe observes and caches language keys to improve performance,
|
|
500
|
+
* using LangService to retrieve translated strings based on the current application language.
|
|
501
|
+
*
|
|
502
|
+
* The transformation involves accepting a key string and optional parameters,
|
|
503
|
+
* forming a cache key, and returning the localized value corresponding to that request.
|
|
331
504
|
*
|
|
332
505
|
* @implements {PipeTransform}
|
|
333
506
|
*/
|
|
@@ -349,22 +522,76 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
|
|
|
349
522
|
args: [{ name: 'lang', standalone: true, pure: false }]
|
|
350
523
|
}] });
|
|
351
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Type-safe mapping of available theme names.
|
|
527
|
+
*
|
|
528
|
+
* Provides a constant record that maps theme identifiers to their corresponding string values.
|
|
529
|
+
* This ensures compile-time safety when referencing theme names throughout the application.
|
|
530
|
+
*
|
|
531
|
+
* Available themes:
|
|
532
|
+
* - `light` - Light theme variant
|
|
533
|
+
* - `dark` - Dark theme variant
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```typescript
|
|
537
|
+
* const currentTheme = themes.light; // 'light'
|
|
538
|
+
* const isDarkTheme = theme === themes.dark;
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
352
541
|
const themes = {
|
|
353
542
|
light: 'light',
|
|
354
543
|
dark: 'dark',
|
|
355
544
|
};
|
|
545
|
+
/**
|
|
546
|
+
* CSS class prefix used for dark theme styling.
|
|
547
|
+
*
|
|
548
|
+
* This constant defines the prefix applied to HTML elements when the dark theme is active.
|
|
549
|
+
* It is typically added to the root element or specific components to enable dark theme styles.
|
|
550
|
+
*
|
|
551
|
+
* @example
|
|
552
|
+
* ```typescript
|
|
553
|
+
* document.body.classList.add(darkThemePrefix); // Applies 're-dark' class
|
|
554
|
+
* ```
|
|
555
|
+
*/
|
|
356
556
|
const darkThemePrefix = 're-dark';
|
|
357
557
|
|
|
558
|
+
/**
|
|
559
|
+
* Default theme configuration object.
|
|
560
|
+
*
|
|
561
|
+
* Defines the initial theme settings for the application.
|
|
562
|
+
* By default, sets the light theme as the active theme.
|
|
563
|
+
*
|
|
564
|
+
* This configuration can be overridden when providing `THEME_CONFIG` token
|
|
565
|
+
* at the module or application level.
|
|
566
|
+
* ```
|
|
567
|
+
*/
|
|
358
568
|
const defaultThemeConfig = {
|
|
359
569
|
defaultTheme: themes.light,
|
|
360
570
|
};
|
|
571
|
+
/**
|
|
572
|
+
* Injection token for theme configuration.
|
|
573
|
+
*
|
|
574
|
+
* Used to provide custom theme settings via Angular's dependency injection system.
|
|
575
|
+
* The token expects a value of type `ThemeConfig`.
|
|
576
|
+
*
|
|
577
|
+
* Example usage:
|
|
578
|
+
* ```typescript
|
|
579
|
+
* // In providers array:
|
|
580
|
+
* { provide: THEME_CONFIG, useValue: { defaultTheme: themes.dark } }
|
|
581
|
+
*
|
|
582
|
+
* // In service or component:
|
|
583
|
+
* private themeConfig = inject(THEME_CONFIG);
|
|
584
|
+
* ```
|
|
585
|
+
*
|
|
586
|
+
* If not provided, `defaultThemeConfig` will be used as fallback.
|
|
587
|
+
*/
|
|
361
588
|
const THEME_CONFIG = new InjectionToken('RE_THEME_CONFIG');
|
|
362
589
|
|
|
363
590
|
/**
|
|
364
591
|
* Service for managing application theme.
|
|
365
592
|
*
|
|
366
593
|
* Allows getting the current theme and switching between light and dark.
|
|
367
|
-
* Automatically saves selected theme to `localStorage` and applies CSS class to `<html>` element.
|
|
594
|
+
* Automatically saves the selected theme to `localStorage` and applies CSS class to `<html>` element.
|
|
368
595
|
*
|
|
369
596
|
* Example:
|
|
370
597
|
* ```ts
|
|
@@ -402,8 +629,8 @@ class ThemeService {
|
|
|
402
629
|
/**
|
|
403
630
|
* Switches theme.
|
|
404
631
|
*
|
|
405
|
-
* If parameter is not provided — performs toggle between `light` and `dark`.
|
|
406
|
-
* Also automatically updates `<html>` class and saves selection to `localStorage`.
|
|
632
|
+
* If a parameter is not provided — performs toggle between `light` and `dark`.
|
|
633
|
+
* Also, automatically updates `<html>` class and saves selection to `localStorage`.
|
|
407
634
|
*
|
|
408
635
|
* @param theme — explicit theme value (`'light'` or `'dark'`).
|
|
409
636
|
*/
|
|
@@ -424,8 +651,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
|
|
|
424
651
|
}]
|
|
425
652
|
}], ctorParameters: () => [] });
|
|
426
653
|
|
|
654
|
+
/**
|
|
655
|
+
* Provides environment-level providers for Presentia application initialization.
|
|
656
|
+
*
|
|
657
|
+
* This function configures essential application services:
|
|
658
|
+
* - Language and localization settings
|
|
659
|
+
* - Theme configuration
|
|
660
|
+
* - Adaptive/responsive breakpoints
|
|
661
|
+
* - Current device detection
|
|
662
|
+
*
|
|
663
|
+
* @param {AppConfig} config - Configuration object containing locale, theme, and breakpoints settings.
|
|
664
|
+
* @returns {EnvironmentProviders} A collection of Angular providers for the application environment.
|
|
665
|
+
*
|
|
666
|
+
* @example
|
|
667
|
+
* ```typescript
|
|
668
|
+
* export const appConfig: ApplicationConfig = {
|
|
669
|
+
* providers: [
|
|
670
|
+
* provideReInit({
|
|
671
|
+
* locale: { defaultLang: 'en', supportedLangs: ['en', 'ru'] },
|
|
672
|
+
* theme: { defaultTheme: 'dark' },
|
|
673
|
+
* breakpoints: { mobile: 768, tablet: 1024 }
|
|
674
|
+
* })
|
|
675
|
+
* ]
|
|
676
|
+
* };
|
|
677
|
+
* ```
|
|
678
|
+
*/
|
|
427
679
|
function provideReInit(config) {
|
|
428
680
|
return makeEnvironmentProviders([
|
|
681
|
+
{ provide: TRANSLATION, deps: [LangService], useFactory: (ls) => ls },
|
|
429
682
|
{ provide: SELECTED_LANG, deps: [LangService], useFactory: (ls) => ls[innerLangVal] },
|
|
430
683
|
{ provide: SELECTED_THEME, deps: [ThemeService], useFactory: (ls) => ls.theme },
|
|
431
684
|
{ provide: CURRENT_DEVICE, deps: [AdaptiveService], useFactory: (ls) => ls.device },
|
|
@@ -546,11 +799,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
|
|
|
546
799
|
type: Injectable
|
|
547
800
|
}] });
|
|
548
801
|
|
|
802
|
+
/**
|
|
803
|
+
* Service that listens to Angular route changes and automatically updates SEO metadata
|
|
804
|
+
* (title, description, Open Graph, Twitter cards, JSON-LD, canonical URL, robots meta tag)
|
|
805
|
+
* based on route data configuration.
|
|
806
|
+
*
|
|
807
|
+
* @example
|
|
808
|
+
* ```typescript
|
|
809
|
+
* const routes: Routes = [{
|
|
810
|
+
* path: 'about',
|
|
811
|
+
* component: AboutComponent,
|
|
812
|
+
* data: {
|
|
813
|
+
* title: 'About Us',
|
|
814
|
+
* description: 'Learn more about our company',
|
|
815
|
+
* robots: 'index,follow'
|
|
816
|
+
* }
|
|
817
|
+
* }];
|
|
818
|
+
* ```
|
|
819
|
+
*/
|
|
549
820
|
class SeoRouteListener {
|
|
550
821
|
router = inject(Router);
|
|
551
822
|
seo = inject(SeoService);
|
|
552
823
|
ar = inject(ActivatedRoute);
|
|
553
824
|
destroyRef = inject(DestroyRef);
|
|
825
|
+
/**
|
|
826
|
+
* Initializes the route listener to monitor navigation events and update SEO metadata.
|
|
827
|
+
* Subscribes to router NavigationEnd events and automatically unsubscribes on component destruction.
|
|
828
|
+
*
|
|
829
|
+
* @param baseUrl - The base URL of the application used for constructing canonical URLs.
|
|
830
|
+
* Trailing slashes will be removed automatically.
|
|
831
|
+
*/
|
|
554
832
|
init(baseUrl) {
|
|
555
833
|
const sub = this.router.events.pipe(filter((e) => e instanceof NavigationEnd)).subscribe(() => {
|
|
556
834
|
const route = this.deepest(this.ar);
|
|
@@ -571,6 +849,13 @@ class SeoRouteListener {
|
|
|
571
849
|
});
|
|
572
850
|
this.destroyRef.onDestroy(() => sub.unsubscribe());
|
|
573
851
|
}
|
|
852
|
+
/**
|
|
853
|
+
* Recursively finds the deepest (most nested) activated route in the route tree.
|
|
854
|
+
* This is used to extract route data from the currently active leaf route.
|
|
855
|
+
*
|
|
856
|
+
* @param r - The root activated route to start traversing from.
|
|
857
|
+
* @returns The deepest child route in the hierarchy.
|
|
858
|
+
*/
|
|
574
859
|
deepest(r) {
|
|
575
860
|
let cur = r;
|
|
576
861
|
while (cur.firstChild) {
|
|
@@ -667,5 +952,5 @@ function joinUrl(segments) {
|
|
|
667
952
|
* Generated bundle index. Do not edit.
|
|
668
953
|
*/
|
|
669
954
|
|
|
670
|
-
export { AdaptiveService, DEVICE_BREAKPOINTS, IfDeviceDirective, LANG_CONFIG, LangPipe, LangService, RouteWatcher, SeoRouteListener, SeoService, THEME_CONFIG, ThemeService, darkThemePrefix, defaultBreakpoints, defaultThemeConfig, provideReInit, themes };
|
|
955
|
+
export { AdaptiveService, DEVICE_BREAKPOINTS, IfDeviceDirective, LANG_CONFIG, LangDirective, LangPipe, LangService, RouteWatcher, SeoRouteListener, SeoService, THEME_CONFIG, ThemeService, darkThemePrefix, defaultBreakpoints, defaultThemeConfig, innerLangVal, provideReInit, themes };
|
|
671
956
|
//# sourceMappingURL=reforgium-presentia.mjs.map
|