@reforgium/presentia 1.5.0 → 2.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,163 @@
1
+ ## [2.1.0]: 05.04.2026
2
+ ### Refactor:
3
+ - replaced presentia-specific persistence adapter behavior with storage contracts and strategies backed by `@reforgium/internal`
4
+ - `LangService` and `ThemeService` now persist through storage interface bindings instead of direct localStorage-specific adapter semantics
5
+
6
+ ### Feat:
7
+ - added grouped `storage` / `persistence` options for `providePresentia(...)` theme and lang config
8
+ - kept `persistenceAdapter` as a compatibility alias during the transition to storage-based persistence
9
+
10
+ ### Fix:
11
+ - aligned `persistence: 'none'` with a real no-op storage instead of pseudo-persistent in-memory behavior
12
+ - preserved raw `lang` / `theme` browser storage keys while moving persistence onto shared storage contracts
13
+
14
+ ### Test:
15
+ - updated provider and theme tests for the storage-based persistence contract
16
+ - verified `presentia` typecheck, Vitest suite, and Angular package build after the persistence migration
17
+
18
+ ---
19
+
20
+ ## [2.0.0]: 04.04.2026
21
+ ### Feat:
22
+ - added grouped `providePresentia(...)` v2 entry point and marked `provideReInit(...)` as legacy in dev mode
23
+ - extended `ThemeService` with DOM strategies, theme registry validation, and configurable persistence adapter support
24
+ - extended `AdaptiveService` with named breakpoint helpers (`breakpoint`, `is`, `isAtLeast`, `isBetween`, `isMobile`, `isTablet`, `isDesktopSmall`)
25
+ - extended `IfDeviceDirective` with `reIfDeviceAtLeast` and `reIfDeviceBetween`
26
+ - extended `RouteWatcher` with `deepestParams`, `mergedData`, `routePattern`, `selectParam`, and path matching helpers
27
+ - aligned `SeoRouteListener` with merged route data and normalized route-tree path resolution
28
+
29
+ ### Docs:
30
+ - documented grouped v2 config, theme DOM/persistence options, and adaptive breakpoint helpers
31
+ - documented recommended v2 setup, extended route watcher state, and adaptive range directives
32
+ - promoted `V2-MIGRATION.md` from draft to working migration guide and refreshed `V2-CONFIG.md` / `ROADMAP-2.0.0.md`
33
+ - moved compatibility-only surface out of primary README export lists and into explicit legacy notes
34
+
35
+ ### Test:
36
+ - added provider and runtime coverage for grouped theme config, persistence adapters, theme registry constraints, and adaptive helper methods
37
+ - added route watcher coverage for merged/deepest selectors and adaptive directive coverage for range-based rendering
38
+ - added SEO listener regression coverage for merged route-data application and canonical path normalization
39
+
40
+ ---
41
+
42
+ ## [1.6.0]: 03.04.2026
43
+ ### Feat:
44
+ - added route-aware namespace preload via `LocaleConfig.routeNamespacePreload` with `blocking` / `lazy` modes
45
+ - added strict preload failure handling via `routeNamespacePreload.onError = 'throw'`
46
+ - added route namespace generator CLI `presentia-gen-namespaces`
47
+ - added generator report mode (`--report`, `--print-report`) for unresolved lazy imports and unsupported static-analysis cases
48
+ - extended generator extraction for route data namespaces, `reLangAttrs`, object-style `reLang`, standalone imports, `loadComponent`, and `loadChildren`
49
+ - added route preload diagnostics for late namespace loads after navigation
50
+ - added batch namespace diagnostics and `maxBatchSize` chunking in `LangService.loadNamespaces(...)`
51
+
52
+ ### Docs:
53
+ - documented route preload modes, manifest generation, report mode, and consumer CLI defaults in `README`
54
+ - clarified `LangPipe.placeholder` as the loading-time value while `LocaleConfig.defaultValue` remains a missing-key fallback
55
+
56
+ ### Test:
57
+ - added integration coverage for real router navigation with route preload
58
+ - added regression coverage for custom `dataKey`, `mergeStrategy: 'replace'`, manifest parent/child merge, strict preload failures, batch diagnostics, and `maxBatchSize`
59
+ - extended generator smoke coverage for report output, `reLangAttrs`, and const-based `presentiaNamespaces`
60
+
61
+ ---
62
+
63
+ ## [1.5.0]: 16.03.2026
64
+ ### Fix:
65
+ - `SeoRouteListener.init(...)` no longer duplicates router subscriptions on repeated calls and now reuses normalized `baseUrl`
66
+ - `ThemeService` now normalizes persisted/requested theme values before switching
67
+ - `LangPipe` now warns once in dev mode when a namespace is loaded but the requested key is unresolved
68
+
69
+ ### Feat:
70
+ - added `LangService.has(...)` for explicit translation key existence checks
71
+ - `SeoService` is now provided in root
72
+
73
+ ### Refactor:
74
+ - removed lightweight `rxjs` usage from `AdaptiveService` resize listener and route event listeners (`RouteWatcher`, `SeoRouteListener`) in favor of native subscriptions/cleanup
75
+ - tightened `provideReInit(...)` factories for `CHANGE_LANG` / `CHANGE_THEME`
76
+ - improved presentia package metadata and test setup for storage-backed specs
77
+
78
+ ### Test:
79
+ - added regression coverage for repeated `SeoRouteListener.init(...)`, `ThemeService` contract behavior, init provider wiring, and unresolved lang key warnings
80
+ - kept adaptive/seo regression coverage green after the refactor
81
+
82
+ ---
83
+
84
+ ## [1.4.4]: 05.03.2026
85
+ ### Fix:
86
+ - improved `RouteWatcher.params` to merge params from `pathFromRoot` (parent to deepest child), not only from deepest snapshot
87
+
88
+ ### Test:
89
+ - added a regression test for parent-child params inheritance in nested routes with parent component
90
+
91
+ ---
92
+
93
+ ## [1.4.3]: 27.02.2026
94
+ ### Fix:
95
+ - fixed `RouteWatcher.url` to build a full path from `pathFromRoot` instead of only deepest snapshot segments
96
+
97
+ ### Test:
98
+ - added a regression test for nested route URL composition (`/orgs/:orgId/users/:id`)
99
+
100
+ ---
101
+
102
+ ## [1.4.2]: 16.02.2026
103
+ ### Fix:
104
+ - fixed `LangService.getChainedValue` deep lookup for keys with flat namespace prefix (e.g. `common.some-key.sub-key.target`)
105
+
106
+ ### Test:
107
+ - added a regression test for nested translation resolution via flat namespace prefix key
108
+
109
+ ---
110
+
111
+ ## [1.4.0]: 15.02.2026
112
+ ### Feat:
113
+ - added `LocaleConfig.requestBuilder` and `LocaleConfig.responseAdapter` for custom API url/payload mapping
114
+ - added `LocaleConfig.requestOptionsFactory` to pass `HttpClient` options (`headers`, `params`, `withCredentials`, `responseType`, `context`, `transferCache`)
115
+ - added batch namespace loading support: `loadNamespaces`, `batchRequestBuilder`, `batchResponseAdapter`
116
+ - added namespace cache controls: `namespaceCache.maxNamespaces`, `namespaceCache.ttlMs`
117
+ - added `LangService.evictNamespace` and `LangService.clearNamespaceCache`
118
+ - added typed lang key extension point (`LangKeyRegistry`, `LangKey`) for `LangService.get/observe`
119
+ - added package CLI `presentia-gen-lang-keys` for generating key unions from locale json
120
+
121
+ ### Test:
122
+ - added integration test for language switch across multiple namespaces without page reload
123
+ - added smoke test for CLI generator output
124
+ - extended `LangService` tests for request options, batch mode, stale response protection, and namespace cache policies
125
+
126
+ ### Docs:
127
+ - updated README for new `LocaleConfig` options, service methods, typed keys, and CLI usage
128
+
129
+ ---
130
+
131
+ ## [1.2.0]: 16.01.2026
132
+ ### Feat:
133
+ - added implementation providers: `CHANGE_LANG` and `CHANGE_THEME`
134
+
135
+ ---
136
+
137
+ ## [1.1.1]: 14.01.2026
138
+
139
+ ### Feat:
140
+ - added `translate` method to `LangDirective`
141
+
142
+ ### Fix:
143
+ - fixed `reIfDevice` directive name
144
+
145
+ ---
146
+
147
+ ## [1.1.0]: 14.01.2026
148
+
149
+ ### Feat:
150
+ - added `LangDirective`, auto translate
151
+
152
+ ---
153
+
154
+ ## [1.0.1]: 11.01.2026
155
+
156
+ ### Chore:
157
+ - updated docs
158
+
159
+ ---
160
+
161
+ ## [1.0.0]: 26.12.2025
162
+
163
+ - Init
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rtommievich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -10,19 +10,13 @@ Infrastructure package for Angular applications:
10
10
  - route state helper (`RouteWatcher`),
11
11
  - SEO automation (`SeoService`, `SeoRouteListener`).
12
12
 
13
- ## Release Highlights (1.4.0)
14
-
15
- - Extended localization HTTP pipeline:
16
- `requestBuilder`, `requestOptionsFactory`, `responseAdapter`.
17
- - Batch namespace loading:
18
- `loadNamespaces`, `batchRequestBuilder`, `batchResponseAdapter`.
19
- - Namespace cache policy:
20
- `namespaceCache.maxNamespaces`, `namespaceCache.ttlMs`,
21
- plus `evictNamespace` and `clearNamespaceCache`.
22
- - Typed localization keys support:
23
- `LangKeyRegistry` extension point for `get()` and `observe()`.
24
- - Consumer CLI for key generation:
25
- `presentia-gen-lang-keys`.
13
+ ## Release Highlights (2.0.0)
14
+
15
+ - Grouped `v2` provider via `providePresentia(...)`.
16
+ - Route-aware namespace preload with generated manifests, diagnostics, and strict failure handling.
17
+ - Extended `ThemeService` with theme registry, configurable DOM strategy, and persistence adapter.
18
+ - Breakpoint-first adaptive helpers with `reIfDeviceAtLeast` and `reIfDeviceBetween`.
19
+ - Richer `RouteWatcher` state with merged/deepest selectors and route matching helpers.
26
20
 
27
21
  ## Install
28
22
 
@@ -30,30 +24,45 @@ plus `evictNamespace` and `clearNamespaceCache`.
30
24
  npm i @reforgium/presentia
31
25
  ```
32
26
 
27
+ ## Recommended Setup
28
+
29
+ For new integrations prefer:
30
+ - `providePresentia(...)`
31
+ - route namespace preload in `blocking` mode
32
+ - `theme.registry` even if you currently use only `light` / `dark`
33
+ - explicit `adaptive.breakpoints` when app breakpoints differ from defaults
34
+
35
+ Legacy integrations can keep `provideReInit(...)` temporarily, but it is now the compatibility path, not the recommended one.
36
+
33
37
  ## Quick Start
34
38
 
35
39
  ```ts
36
40
  import { bootstrapApplication } from '@angular/platform-browser';
37
- import { provideReInit } from '@reforgium/presentia';
41
+ import { providePresentia } from '@reforgium/presentia';
38
42
 
39
43
  bootstrapApplication(AppComponent, {
40
44
  providers: [
41
- provideReInit({
42
- locale: {
43
- url: '/assets/i18n',
44
- isFromAssets: true,
45
- defaultLang: 'ru',
46
- fallbackLang: 'en',
47
- supportedLangs: ['de'],
48
- preloadNamespaces: ['layout', 'common'],
45
+ providePresentia({
46
+ lang: {
47
+ source: {
48
+ url: '/assets/i18n',
49
+ fromAssets: true,
50
+ defaultLang: 'ru',
51
+ fallbackLang: 'en',
52
+ supportedLangs: ['de'],
53
+ },
54
+ preload: {
55
+ global: ['layout', 'common'],
56
+ },
57
+ rendering: {
58
+ placeholder: '...',
59
+ },
60
+ missingKeyHandler: (key, ctx) => `[${ctx.lang}] ${key}`,
49
61
  },
50
- langPipe: {
51
- ttlMs: 300_000,
52
- maxCacheSize: 500,
53
- placeholder: '...',
62
+ theme: {
63
+ defaultTheme: 'light',
64
+ dom: { darkClassName: 'dark' },
54
65
  },
55
- langMissingKeyHandler: (key, ctx) => `[${ctx.lang}] ${key}`,
56
- theme: { defaultTheme: 'light', darkThemePrefix: 'dark' },
57
66
  }),
58
67
  ],
59
68
  });
@@ -62,7 +71,58 @@ bootstrapApplication(AppComponent, {
62
71
  ## What You Get
63
72
 
64
73
  Main provider:
65
- - `provideReInit(config: AppConfig)`
74
+ - `providePresentia(config: PresentiaConfig)`
75
+
76
+ `providePresentia(...)` is the new grouped config entry point.
77
+ `provideReInit(...)` remains available as the legacy `1.x` style provider.
78
+
79
+ ## Migration Snapshot
80
+
81
+ Typical `1.x -> v2` provider migration:
82
+
83
+ ```ts
84
+ // 1.x
85
+ provideReInit({
86
+ locale: {
87
+ url: '/assets/i18n',
88
+ isFromAssets: true,
89
+ preloadNamespaces: ['layout', 'common'],
90
+ },
91
+ theme: {
92
+ defaultTheme: 'light',
93
+ darkThemePrefix: 're-dark',
94
+ },
95
+ breakpoints: {
96
+ mobile: '(max-width: 719px)',
97
+ },
98
+ });
99
+
100
+ // v2
101
+ providePresentia({
102
+ lang: {
103
+ source: {
104
+ url: '/assets/i18n',
105
+ fromAssets: true,
106
+ },
107
+ preload: {
108
+ global: ['layout', 'common'],
109
+ },
110
+ },
111
+ theme: {
112
+ defaultTheme: 'light',
113
+ dom: {
114
+ darkClassName: 're-dark',
115
+ },
116
+ },
117
+ adaptive: {
118
+ breakpoints: {
119
+ mobile: '(max-width: 719px)',
120
+ },
121
+ },
122
+ });
123
+ ```
124
+
125
+ For a fuller mapping see [`V2-MIGRATION.md`](./V2-MIGRATION.md).
66
126
 
67
127
  Localization exports:
68
128
  - `LangService`
@@ -71,12 +131,14 @@ Localization exports:
71
131
  - `LANG_CONFIG`
72
132
  - `LANG_PIPE_CONFIG`
73
133
  - `LANG_MISSING_KEY_HANDLER`
134
+ - `PRESENTIA_ROUTE_NAMESPACES_DATA_KEY`
74
135
  - Types: `LocaleConfig`, `LangModel`, `LangDto`, `LangParams`, `LangKey`, `LangKeyRegistry`, etc.
75
136
 
76
137
  Theme exports:
77
138
  - `ThemeService`
139
+ - `Theme`
78
140
  - `THEME_CONFIG`
79
- - `themes`, `darkThemePrefix`
141
+ - `themes`
80
142
 
81
143
  Adaptive exports:
82
144
  - `AdaptiveService`
@@ -88,7 +150,63 @@ Routes and SEO exports:
88
150
  - `SeoService`
89
151
  - `SeoRouteListener`
90
152
 
91
- ## `provideReInit` Configuration
153
+ Legacy compatibility surface:
154
+ - `provideReInit(config: AppConfig)`
155
+ - `darkThemePrefix`
156
+ - `defaultThemeConfig`
157
+ - `defaultThemePersistenceAdapter`
158
+ - `RouteNamespaceDiagnosticsService`
159
+
160
+ ## Provider Configuration
161
+
162
+ ### `providePresentia`
163
+
164
+ ```ts
165
+ providePresentia({
166
+ lang: {
167
+ source: {
168
+ url: '/assets/i18n',
169
+ fromAssets: true,
170
+ defaultLang: 'ru',
171
+ fallbackLang: 'en',
172
+ },
173
+ rendering: {
174
+ placeholder: '...',
175
+ missingValue: '--',
176
+ },
177
+ preload: {
178
+ global: ['layout', 'common'],
179
+ routes: {
180
+ mode: 'blocking',
181
+ },
182
+ },
183
+ },
184
+ theme: {
185
+ registry: ['light', 'dark'],
186
+ defaultTheme: 'dark',
187
+ persistence: 'localStorage',
188
+ dom: {
189
+ strategy: 'root-class',
190
+ darkClassName: 're-dark',
191
+ },
192
+ },
193
+ adaptive: {
194
+ breakpoints: {
195
+ mobile: '(max-width: 719px)',
196
+ tablet: '(min-width: 720px) and (max-width: 1399px)',
197
+ 'desktop-s': '(min-width: 1400px) and (max-width: 1919px)',
198
+ desktop: '(min-width: 1920px)',
199
+ },
200
+ },
201
+ });
202
+ ```
203
+
204
+ Use `providePresentia(...)` for new integrations.
205
+ Use `provideReInit(...)` only when you need the legacy flat config shape.
206
+
207
+ ### `provideReInit`
208
+
209
+ Legacy compatibility provider. Prefer `providePresentia(...)` for new integrations.
92
210
 
93
211
  `AppConfig` includes:
94
212
  - `locale: LocaleConfig` (required)
@@ -158,6 +276,14 @@ type LocaleConfig = {
158
276
  maxNamespaces?: number;
159
277
  ttlMs?: number;
160
278
  };
279
+ routeNamespacePreload?: {
280
+ mode?: 'blocking' | 'lazy';
281
+ dataKey?: string;
282
+ manifest?: Record<string, readonly string[]>;
283
+ mergeStrategy?: 'append' | 'replace';
284
+ onError?: 'continue' | 'throw';
285
+ diagnostics?: boolean;
286
+ };
161
287
  };
162
288
  ```
163
289
 
@@ -167,6 +293,8 @@ type LocaleConfig = {
167
293
  - Alias `ky` is accepted and normalized to internal `kg`.
168
294
  - `supportedLangs` lets you add custom language codes.
169
295
  - If key is missing, `defaultValue` is used (or missing key handler result).
296
+ - `defaultValue` is a missing-key fallback, not a loading placeholder.
297
+ - `LangPipe.placeholder` is used while a namespace is still loading.
170
298
  - Stale HTTP responses are ignored when language changes mid-flight.
171
299
 
172
300
  ### Basic Usage
@@ -216,6 +344,135 @@ const lang = inject(LangService);
216
344
  await lang.loadNamespaces(['layout', 'common', 'breadcrumbs']);
217
345
  ```
218
346
 
347
+ ### Route Namespace Preload
348
+
349
+ Use this when first-paint localization must be ready before page activation.
350
+ `presentia` patches router config automatically via `providePresentia(...)` or `provideReInit(...)`,
351
+ so no extra resolver wiring is required in the consumer app.
352
+
353
+ Behavior summary:
354
+ - `mode: 'blocking'` waits for route namespaces before route activation.
355
+ - `mode: 'lazy'` starts namespace loading without delaying navigation.
356
+ - `onError: 'continue'` keeps navigation alive if preload fails.
357
+ - `onError: 'throw'` cancels navigation by rethrowing the preload error.
358
+
359
+ Route data mode:
360
+
361
+ ```ts
362
+ providePresentia({
363
+ lang: {
364
+ source: {
365
+ url: '/assets/i18n',
366
+ fromAssets: true,
367
+ },
368
+ preload: {
369
+ routes: {},
370
+ },
371
+ },
372
+ });
373
+
374
+ export const routes: Routes = [
375
+ {
376
+ path: 'profile',
377
+ component: ProfilePage,
378
+ data: {
379
+ presentiaNamespaces: ['profile', 'layout', 'common'],
380
+ },
381
+ },
382
+ ];
383
+ ```
384
+
385
+ Manifest mode:
386
+
387
+ ```ts
388
+ const PRESENTIA_ROUTE_NAMESPACES = {
389
+ '/': ['home', 'layout', 'common'],
390
+ '/profile': ['profile', 'layout', 'common'],
391
+ '/users/:id': ['users', 'layout', 'common'],
392
+ } as const;
393
+
394
+ providePresentia({
395
+ lang: {
396
+ source: {
397
+ url: '/assets/i18n',
398
+ fromAssets: true,
399
+ },
400
+ preload: {
401
+ routes: {
402
+ manifest: PRESENTIA_ROUTE_NAMESPACES,
403
+ mode: 'blocking',
404
+ },
405
+ },
406
+ diagnostics: {
407
+ lateNamespaceLoads: true,
408
+ },
409
+ },
410
+ });
411
+ ```
412
+
413
+ Legacy `1.x` projects can keep using `provideReInit(...)`,
414
+ but new integrations should prefer `providePresentia(...)`.
415
+
416
+ Generate the manifest in a consumer app:
417
+
418
+ ```bash
419
+ npx presentia-gen-namespaces
420
+ ```
421
+
422
+ Default paths:
423
+ - `--routes src/app/app.routes.ts`
424
+ - `--project src`
425
+ - `--tsconfig tsconfig.json`
426
+ - `--out src/app/presentia-route-namespaces.ts`
427
+
428
+ Explicit paths:
429
+
430
+ ```bash
431
+ npx presentia-gen-namespaces \
432
+ --routes src/app/app.routes.ts \
433
+ --project src \
434
+ --tsconfig tsconfig.json \
435
+ --locales src/assets/locales \
436
+ --out src/app/presentia-route-namespaces.ts
437
+ ```
438
+
439
+ The public CLI is `presentia-gen-namespaces`.
440
+ Repository scripts such as `libs/presentia/scripts/generate-sandbox-namespaces.mjs`
441
+ are internal helpers for local demo verification only.
442
+
443
+ Best-effort extraction sources:
444
+ - route `data` lang keys such as `title: 'profile.title'`
445
+ - route `data.presentiaNamespaces`
446
+ - `{{ 'namespace.key' | lang }}`
447
+ - `reLang` / `reLangKey`
448
+ - `reLangAttrs`
449
+ - `lang.get('namespace.key')`
450
+ - `lang.observe('namespace.key')`
451
+ - imported local standalone components from component `imports: [...]`
452
+
453
+ When `--locales` is provided, generator validates discovered keys and namespaces
454
+ against real locale JSON files to reduce false positives.
455
+
456
+ Optional report mode:
457
+
458
+ ```bash
459
+ npx presentia-gen-namespaces \
460
+ --locales src/assets/locales \
461
+ --report src/app/presentia-route-namespaces.report.json \
462
+ --print-report
463
+ ```
464
+
465
+ This emits a JSON report with static-analysis gaps such as unresolved lazy imports
466
+ or unsupported `presentiaNamespaces` expressions.
467
+
468
+ Options:
469
+ - `mode: 'blocking' | 'lazy'` - waits for namespaces before route activation, or starts loading without blocking.
470
+ - `dataKey` - custom route-data field name instead of default `presentiaNamespaces`.
471
+ - `manifest` - generated or handwritten `route -> namespaces[]` map.
472
+ - `mergeStrategy: 'append' | 'replace'` - merge manifest and route-data namespaces, or let route data override manifest entries.
473
+ - `onError: 'continue' | 'throw'` - in blocking mode either keep navigation alive on preload failure, or cancel it by rethrowing the load error.
474
+ - `diagnostics` - in dev mode warns when a namespace starts loading only after route activation.
475
+
219
476
  ### Namespace Cache Policy
220
477
 
221
478
  ```ts
@@ -387,8 +644,10 @@ After generation, invalid keys in `get()`/`observe()` are compile-time errors.
387
644
  ## Theme
388
645
 
389
646
  `ThemeService`:
390
- - `theme()` current theme (`'light' | 'dark'`)
647
+ - `theme()` current theme (`Theme = BaseTheme | string`)
391
648
  - `isLight()`
649
+ - `isDark()`
650
+ - `is(theme | theme[])`
392
651
  - `switch(theme?)` explicit switch or toggle
393
652
 
394
653
  ```ts
@@ -397,17 +656,60 @@ theme.switch('dark');
397
656
  ```
398
657
 
399
658
  `ThemeConfig`:
400
- - `defaultTheme?: 'light' | 'dark'`
659
+ - `registry?: readonly Theme[]`
660
+ - `defaultTheme?: Theme`
401
661
  - `darkThemePrefix?: string`
662
+ - `dom?: { strategy?: 'root-class' | 'data-attribute'; rootSelector?: string; darkThemePrefix?: string; attributeName?: string; themeClassPrefix?: string; classNameBuilder?: (theme) => string }`
663
+
664
+ `providePresentia(...).theme` additionally supports:
665
+ - `persistence?: 'localStorage' | 'none'`
666
+ - `persistenceAdapter?: PersistenceAdapter`
667
+ - `dom.darkClassName` as a consumer-friendly alias for `darkThemePrefix`
668
+ - `dom.classPrefix` as a consumer-friendly alias for `themeClassPrefix`
669
+
670
+ Example:
671
+
672
+ ```ts
673
+ providePresentia({
674
+ lang: {
675
+ source: {
676
+ url: '/assets/i18n',
677
+ fromAssets: true,
678
+ },
679
+ },
680
+ theme: {
681
+ registry: ['light', 'dark', 'sepia'],
682
+ defaultTheme: 'light',
683
+ persistence: 'localStorage',
684
+ dom: {
685
+ strategy: 'root-class',
686
+ darkClassName: 're-dark',
687
+ classPrefix: 're-theme-',
688
+ },
689
+ },
690
+ });
691
+ ```
692
+
693
+ With that config:
694
+ - `dark` still applies the legacy dark class (`re-dark`)
695
+ - every registered theme also gets a prefixed class such as `re-theme-light`, `re-theme-dark`, `re-theme-sepia`
696
+ - for fully custom naming, use `dom.classNameBuilder`
402
697
 
403
698
  ## Adaptive
404
699
 
405
700
  `AdaptiveService` provides reactive signals:
406
- - `device()` -> `'desktop' | 'tablet' | 'mobile'`
701
+ - `device()` -> `'desktop' | 'desktop-s' | 'tablet' | 'mobile'`
702
+ - `breakpoint()` alias for the current device bucket
407
703
  - `width()`
408
704
  - `height()`
705
+ - `isMobile()`
706
+ - `isTablet()`
707
+ - `isDesktopSmall()`
409
708
  - `isDesktop()`
410
709
  - `isPortrait()`
710
+ - `is(device | device[])`
711
+ - `isAtLeast(device)`
712
+ - `isBetween(min, max)`
411
713
 
412
714
  `*reIfDevice` structural directive:
413
715
 
@@ -415,20 +717,29 @@ theme.switch('dark');
415
717
  <div *reIfDevice="'desktop'">Desktop only</div>
416
718
  <div *reIfDevice="['mobile', 'tablet']">Mobile and tablet</div>
417
719
  <div *reIfDevice="'mobile'; inverse: true">Hidden on mobile</div>
720
+ <div *reIfDeviceAtLeast="'tablet'">Tablet and larger</div>
721
+ <div *reIfDeviceBetween="['tablet', 'desktop']">Tablet to desktop</div>
418
722
  ```
419
723
 
420
724
  Breakpoints are configurable via `DeviceBreakpoints`.
421
725
 
422
726
  ## Route State Helper
423
727
 
424
- `RouteWatcher` gives reactive deepest-route snapshot:
728
+ `RouteWatcher` gives reactive route state:
425
729
  - `params()`
730
+ - `deepestParams()`
426
731
  - `query()`
427
732
  - `data()`
733
+ - `mergedData()`
428
734
  - `url()`
735
+ - `routePattern()`
429
736
  - `fragment()`
430
737
  - `state()`
431
738
  - `selectData<T>(key)`
739
+ - `selectData<T>(key, 'merged')`
740
+ - `selectParam(key)`
741
+ - `selectParam(key, 'deepest')`
742
+ - `matchesPath(path | regexp)`
432
743
 
433
744
  Use when you want route-derived UI state without manual router subscriptions.
434
745