@reforgium/presentia 1.4.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,144 @@
1
+ ## [2.0.0]: 04.04.2026
2
+ ### Feat:
3
+ - added grouped `providePresentia(...)` v2 entry point and marked `provideReInit(...)` as legacy in dev mode
4
+ - extended `ThemeService` with DOM strategies, theme registry validation, and configurable persistence adapter support
5
+ - extended `AdaptiveService` with named breakpoint helpers (`breakpoint`, `is`, `isAtLeast`, `isBetween`, `isMobile`, `isTablet`, `isDesktopSmall`)
6
+ - extended `IfDeviceDirective` with `reIfDeviceAtLeast` and `reIfDeviceBetween`
7
+ - extended `RouteWatcher` with `deepestParams`, `mergedData`, `routePattern`, `selectParam`, and path matching helpers
8
+ - aligned `SeoRouteListener` with merged route data and normalized route-tree path resolution
9
+
10
+ ### Docs:
11
+ - documented grouped v2 config, theme DOM/persistence options, and adaptive breakpoint helpers
12
+ - documented recommended v2 setup, extended route watcher state, and adaptive range directives
13
+ - promoted `V2-MIGRATION.md` from draft to working migration guide and refreshed `V2-CONFIG.md` / `ROADMAP-2.0.0.md`
14
+ - moved compatibility-only surface out of primary README export lists and into explicit legacy notes
15
+
16
+ ### Test:
17
+ - added provider and runtime coverage for grouped theme config, persistence adapters, theme registry constraints, and adaptive helper methods
18
+ - added route watcher coverage for merged/deepest selectors and adaptive directive coverage for range-based rendering
19
+ - added SEO listener regression coverage for merged route-data application and canonical path normalization
20
+
21
+ ---
22
+
23
+ ## [1.6.0]: 03.04.2026
24
+ ### Feat:
25
+ - added route-aware namespace preload via `LocaleConfig.routeNamespacePreload` with `blocking` / `lazy` modes
26
+ - added strict preload failure handling via `routeNamespacePreload.onError = 'throw'`
27
+ - added route namespace generator CLI `presentia-gen-namespaces`
28
+ - added generator report mode (`--report`, `--print-report`) for unresolved lazy imports and unsupported static-analysis cases
29
+ - extended generator extraction for route data namespaces, `reLangAttrs`, object-style `reLang`, standalone imports, `loadComponent`, and `loadChildren`
30
+ - added route preload diagnostics for late namespace loads after navigation
31
+ - added batch namespace diagnostics and `maxBatchSize` chunking in `LangService.loadNamespaces(...)`
32
+
33
+ ### Docs:
34
+ - documented route preload modes, manifest generation, report mode, and consumer CLI defaults in `README`
35
+ - clarified `LangPipe.placeholder` as the loading-time value while `LocaleConfig.defaultValue` remains a missing-key fallback
36
+
37
+ ### Test:
38
+ - added integration coverage for real router navigation with route preload
39
+ - added regression coverage for custom `dataKey`, `mergeStrategy: 'replace'`, manifest parent/child merge, strict preload failures, batch diagnostics, and `maxBatchSize`
40
+ - extended generator smoke coverage for report output, `reLangAttrs`, and const-based `presentiaNamespaces`
41
+
42
+ ---
43
+
44
+ ## [1.5.0]: 16.03.2026
45
+ ### Fix:
46
+ - `SeoRouteListener.init(...)` no longer duplicates router subscriptions on repeated calls and now reuses normalized `baseUrl`
47
+ - `ThemeService` now normalizes persisted/requested theme values before switching
48
+ - `LangPipe` now warns once in dev mode when a namespace is loaded but the requested key is unresolved
49
+
50
+ ### Feat:
51
+ - added `LangService.has(...)` for explicit translation key existence checks
52
+ - `SeoService` is now provided in root
53
+
54
+ ### Refactor:
55
+ - removed lightweight `rxjs` usage from `AdaptiveService` resize listener and route event listeners (`RouteWatcher`, `SeoRouteListener`) in favor of native subscriptions/cleanup
56
+ - tightened `provideReInit(...)` factories for `CHANGE_LANG` / `CHANGE_THEME`
57
+ - improved presentia package metadata and test setup for storage-backed specs
58
+
59
+ ### Test:
60
+ - added regression coverage for repeated `SeoRouteListener.init(...)`, `ThemeService` contract behavior, init provider wiring, and unresolved lang key warnings
61
+ - kept adaptive/seo regression coverage green after the refactor
62
+
63
+ ---
64
+
65
+ ## [1.4.4]: 05.03.2026
66
+ ### Fix:
67
+ - improved `RouteWatcher.params` to merge params from `pathFromRoot` (parent to deepest child), not only from deepest snapshot
68
+
69
+ ### Test:
70
+ - added a regression test for parent-child params inheritance in nested routes with parent component
71
+
72
+ ---
73
+
74
+ ## [1.4.3]: 27.02.2026
75
+ ### Fix:
76
+ - fixed `RouteWatcher.url` to build a full path from `pathFromRoot` instead of only deepest snapshot segments
77
+
78
+ ### Test:
79
+ - added a regression test for nested route URL composition (`/orgs/:orgId/users/:id`)
80
+
81
+ ---
82
+
83
+ ## [1.4.2]: 16.02.2026
84
+ ### Fix:
85
+ - fixed `LangService.getChainedValue` deep lookup for keys with flat namespace prefix (e.g. `common.some-key.sub-key.target`)
86
+
87
+ ### Test:
88
+ - added a regression test for nested translation resolution via flat namespace prefix key
89
+
90
+ ---
91
+
92
+ ## [1.4.0]: 15.02.2026
93
+ ### Feat:
94
+ - added `LocaleConfig.requestBuilder` and `LocaleConfig.responseAdapter` for custom API url/payload mapping
95
+ - added `LocaleConfig.requestOptionsFactory` to pass `HttpClient` options (`headers`, `params`, `withCredentials`, `responseType`, `context`, `transferCache`)
96
+ - added batch namespace loading support: `loadNamespaces`, `batchRequestBuilder`, `batchResponseAdapter`
97
+ - added namespace cache controls: `namespaceCache.maxNamespaces`, `namespaceCache.ttlMs`
98
+ - added `LangService.evictNamespace` and `LangService.clearNamespaceCache`
99
+ - added typed lang key extension point (`LangKeyRegistry`, `LangKey`) for `LangService.get/observe`
100
+ - added package CLI `presentia-gen-lang-keys` for generating key unions from locale json
101
+
102
+ ### Test:
103
+ - added integration test for language switch across multiple namespaces without page reload
104
+ - added smoke test for CLI generator output
105
+ - extended `LangService` tests for request options, batch mode, stale response protection, and namespace cache policies
106
+
107
+ ### Docs:
108
+ - updated README for new `LocaleConfig` options, service methods, typed keys, and CLI usage
109
+
110
+ ---
111
+
112
+ ## [1.2.0]: 16.01.2026
113
+ ### Feat:
114
+ - added implementation providers: `CHANGE_LANG` and `CHANGE_THEME`
115
+
116
+ ---
117
+
118
+ ## [1.1.1]: 14.01.2026
119
+
120
+ ### Feat:
121
+ - added `translate` method to `LangDirective`
122
+
123
+ ### Fix:
124
+ - fixed `reIfDevice` directive name
125
+
126
+ ---
127
+
128
+ ## [1.1.0]: 14.01.2026
129
+
130
+ ### Feat:
131
+ - added `LangDirective`, auto translate
132
+
133
+ ---
134
+
135
+ ## [1.0.1]: 11.01.2026
136
+
137
+ ### Chore:
138
+ - updated docs
139
+
140
+ ---
141
+
142
+ ## [1.0.0]: 26.12.2025
143
+
144
+ - 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
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4