@reforgium/presentia 2.1.1 → 2.1.2
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
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [2.1.2]: 14.04.2026
|
|
2
|
+
### Fix:
|
|
3
|
+
- fixed `presentia-gen-namespaces` skipping spread (`...someRoutes`) and bare-reference (`someRoutes`) items in route arrays — generator now resolves them via imports and recurses into their exports
|
|
4
|
+
- fixed `presentia-gen-namespaces` not handling single-object route exports (`export const route: AppRoute = { ... }`) — added object-literal fallback in `parseRoutesFromExport` alongside the existing array-literal path
|
|
5
|
+
- fixed `LangPipe` NG0100 in Angular dev mode: cache-hit path was returning `existing.value()` directly, bypassing the namespace-loaded guard that cache-miss applied — both paths now go through a shared `applyNamespaceGuard` method
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
1
9
|
## [2.1.1]: 07.04.2026
|
|
2
10
|
### Fix:
|
|
3
11
|
- fixed `presentia-gen-namespaces` tsconfig loading to parse commented `tsconfig` files instead of failing on raw `JSON.parse(...)`
|
|
@@ -863,6 +863,40 @@ function parseRoutes(arrayLiteral, context) {
|
|
|
863
863
|
}
|
|
864
864
|
}
|
|
865
865
|
|
|
866
|
+
// Handle spread (...someRoutes) and bare references (someRoutes) that are not object literals
|
|
867
|
+
for (const item of splitTopLevelItems(arrayLiteral)) {
|
|
868
|
+
const trimmed = item.trim();
|
|
869
|
+
|
|
870
|
+
if (trimmed.startsWith('{')) continue;
|
|
871
|
+
|
|
872
|
+
const spreadMatch = /^\.\.\.\s*(\w+)$/.exec(trimmed);
|
|
873
|
+
const refName = spreadMatch ? spreadMatch[1] : /^\w+$/.test(trimmed) ? trimmed : null;
|
|
874
|
+
|
|
875
|
+
if (!refName) continue;
|
|
876
|
+
|
|
877
|
+
const imported = context.imports.get(refName);
|
|
878
|
+
|
|
879
|
+
if (!imported) continue;
|
|
880
|
+
|
|
881
|
+
const resolved = context.resolver(imported.specifier, context.routesFile);
|
|
882
|
+
|
|
883
|
+
if (!resolved) {
|
|
884
|
+
generatorReport?.addIssue({
|
|
885
|
+
type: 'unresolved-spread-routes',
|
|
886
|
+
filePath: context.routesFile,
|
|
887
|
+
message: `Could not resolve spread routes import "${imported.specifier}" (${refName})`,
|
|
888
|
+
});
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
routes.push(
|
|
893
|
+
...parseRoutesFromExport(resolved, imported.imported, {
|
|
894
|
+
...context,
|
|
895
|
+
routesFile: resolved,
|
|
896
|
+
}),
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
|
|
866
900
|
return routes;
|
|
867
901
|
}
|
|
868
902
|
|
|
@@ -876,20 +910,36 @@ function parseRoutesFromExport(filePath, exportName, context) {
|
|
|
876
910
|
visitedRouteExports.add(visitKey);
|
|
877
911
|
|
|
878
912
|
const source = stripComments(readText(filePath));
|
|
913
|
+
const newContext = {
|
|
914
|
+
...context,
|
|
915
|
+
constObjects: parseConstObjects(filePath, context.resolver),
|
|
916
|
+
imports: parseImports(source),
|
|
917
|
+
routesFile: filePath,
|
|
918
|
+
};
|
|
919
|
+
|
|
879
920
|
const arrayLiteral =
|
|
880
921
|
extractArrayLiteral(source, new RegExp(`\\bexport\\s+const\\s+${exportName}\\s*:\\s*[^=]+=\\s*\\[`, 'g')) ??
|
|
881
922
|
extractArrayLiteral(source, new RegExp(`\\bexport\\s+const\\s+${exportName}\\s*=\\s*\\[`, 'g'));
|
|
882
923
|
|
|
883
|
-
if (
|
|
884
|
-
return
|
|
924
|
+
if (arrayLiteral) {
|
|
925
|
+
return parseRoutes(arrayLiteral, newContext);
|
|
885
926
|
}
|
|
886
927
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
928
|
+
// Fallback: try as a single route object export (e.g. export const analyticsRoutes: AppRoute = { ... })
|
|
929
|
+
const objectMatch =
|
|
930
|
+
new RegExp(`\\bexport\\s+const\\s+${exportName}\\s*:\\s*[^=]+=\\s*\\{`, 'g').exec(source) ??
|
|
931
|
+
new RegExp(`\\bexport\\s+const\\s+${exportName}\\s*=\\s*\\{`, 'g').exec(source);
|
|
932
|
+
|
|
933
|
+
if (objectMatch) {
|
|
934
|
+
const start = source.indexOf('{', objectMatch.index);
|
|
935
|
+
const end = findMatching(source, start, '{', '}');
|
|
936
|
+
|
|
937
|
+
if (start >= 0 && end >= 0) {
|
|
938
|
+
return parseRoutes(`[${source.slice(start, end + 1)}]`, newContext);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
return [];
|
|
893
943
|
}
|
|
894
944
|
|
|
895
945
|
function collectComponentNamespaces(filePath, exportName, context) {
|
|
@@ -73,10 +73,10 @@ const DEVICE_BREAKPOINTS = new InjectionToken('RE_DEVICE_BREAKPOINTS', {
|
|
|
73
73
|
*/
|
|
74
74
|
class AdaptiveService {
|
|
75
75
|
/** @internal Signal of the current device type. */
|
|
76
|
-
#device = signal('desktop', ...(ngDevMode ? [{ debugName: "#device" }] : []));
|
|
76
|
+
#device = signal('desktop', ...(ngDevMode ? [{ debugName: "#device" }] : /* istanbul ignore next */ []));
|
|
77
77
|
/** @internal Signals of the current window width and height. */
|
|
78
|
-
#width = signal(0, ...(ngDevMode ? [{ debugName: "#width" }] : []));
|
|
79
|
-
#height = signal(0, ...(ngDevMode ? [{ debugName: "#height" }] : []));
|
|
78
|
+
#width = signal(0, ...(ngDevMode ? [{ debugName: "#width" }] : /* istanbul ignore next */ []));
|
|
79
|
+
#height = signal(0, ...(ngDevMode ? [{ debugName: "#height" }] : /* istanbul ignore next */ []));
|
|
80
80
|
/**
|
|
81
81
|
* Current device type (reactive signal).
|
|
82
82
|
* Possible values: `'desktop' | 'tablet' | 'mobile'`.
|
|
@@ -99,15 +99,15 @@ class AdaptiveService {
|
|
|
99
99
|
* Computed signal indicating whether the current device is a desktop.
|
|
100
100
|
* Used for conditional rendering or layout configuration.
|
|
101
101
|
*/
|
|
102
|
-
isDesktop = computed(() => this.#device() === 'desktop', ...(ngDevMode ? [{ debugName: "isDesktop" }] : []));
|
|
103
|
-
isMobile = computed(() => this.#device() === 'mobile', ...(ngDevMode ? [{ debugName: "isMobile" }] : []));
|
|
104
|
-
isTablet = computed(() => this.#device() === 'tablet', ...(ngDevMode ? [{ debugName: "isTablet" }] : []));
|
|
105
|
-
isDesktopSmall = computed(() => this.#device() === 'desktop-s', ...(ngDevMode ? [{ debugName: "isDesktopSmall" }] : []));
|
|
102
|
+
isDesktop = computed(() => this.#device() === 'desktop', ...(ngDevMode ? [{ debugName: "isDesktop" }] : /* istanbul ignore next */ []));
|
|
103
|
+
isMobile = computed(() => this.#device() === 'mobile', ...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
|
|
104
|
+
isTablet = computed(() => this.#device() === 'tablet', ...(ngDevMode ? [{ debugName: "isTablet" }] : /* istanbul ignore next */ []));
|
|
105
|
+
isDesktopSmall = computed(() => this.#device() === 'desktop-s', ...(ngDevMode ? [{ debugName: "isDesktopSmall" }] : /* istanbul ignore next */ []));
|
|
106
106
|
/**
|
|
107
107
|
* Computed signal determining whether the screen is in portrait orientation.
|
|
108
108
|
* Returns `true` if window height is greater than width.
|
|
109
109
|
*/
|
|
110
|
-
isPortrait = computed(() => this.#height() > this.#width(), ...(ngDevMode ? [{ debugName: "isPortrait" }] : []));
|
|
110
|
+
isPortrait = computed(() => this.#height() > this.#width(), ...(ngDevMode ? [{ debugName: "isPortrait" }] : /* istanbul ignore next */ []));
|
|
111
111
|
deviceBreakpoints = inject(DEVICE_BREAKPOINTS);
|
|
112
112
|
devicePriority = Object.keys(this.deviceBreakpoints);
|
|
113
113
|
destroyRef = inject(DestroyRef);
|
|
@@ -200,10 +200,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
200
200
|
* the template is automatically added or removed from the DOM.
|
|
201
201
|
*/
|
|
202
202
|
class IfDeviceDirective {
|
|
203
|
-
deviceInput = signal(undefined, ...(ngDevMode ? [{ debugName: "deviceInput" }] : []));
|
|
204
|
-
atLeastInput = signal(undefined, ...(ngDevMode ? [{ debugName: "atLeastInput" }] : []));
|
|
205
|
-
betweenInput = signal(undefined, ...(ngDevMode ? [{ debugName: "betweenInput" }] : []));
|
|
206
|
-
inverseInput = signal(false, ...(ngDevMode ? [{ debugName: "inverseInput" }] : []));
|
|
203
|
+
deviceInput = signal(undefined, ...(ngDevMode ? [{ debugName: "deviceInput" }] : /* istanbul ignore next */ []));
|
|
204
|
+
atLeastInput = signal(undefined, ...(ngDevMode ? [{ debugName: "atLeastInput" }] : /* istanbul ignore next */ []));
|
|
205
|
+
betweenInput = signal(undefined, ...(ngDevMode ? [{ debugName: "betweenInput" }] : /* istanbul ignore next */ []));
|
|
206
|
+
inverseInput = signal(false, ...(ngDevMode ? [{ debugName: "inverseInput" }] : /* istanbul ignore next */ []));
|
|
207
207
|
tpl = inject(TemplateRef);
|
|
208
208
|
vcr = inject(ViewContainerRef);
|
|
209
209
|
adaptive = inject(AdaptiveService);
|
|
@@ -435,14 +435,14 @@ function extractRouteParamKeys(path) {
|
|
|
435
435
|
class RouteWatcher {
|
|
436
436
|
router = inject(Router);
|
|
437
437
|
destroyRef = inject(DestroyRef);
|
|
438
|
-
#params = signal({}, ...(ngDevMode ? [{ debugName: "#params" }] : []));
|
|
439
|
-
#deepestParams = signal({}, ...(ngDevMode ? [{ debugName: "#deepestParams" }] : []));
|
|
440
|
-
#query = signal({}, ...(ngDevMode ? [{ debugName: "#query" }] : []));
|
|
441
|
-
#data = signal({}, ...(ngDevMode ? [{ debugName: "#data" }] : []));
|
|
442
|
-
#mergedData = signal({}, ...(ngDevMode ? [{ debugName: "#mergedData" }] : []));
|
|
443
|
-
#url = signal('', ...(ngDevMode ? [{ debugName: "#url" }] : []));
|
|
444
|
-
#routePattern = signal('', ...(ngDevMode ? [{ debugName: "#routePattern" }] : []));
|
|
445
|
-
#fragment = signal(null, ...(ngDevMode ? [{ debugName: "#fragment" }] : []));
|
|
438
|
+
#params = signal({}, ...(ngDevMode ? [{ debugName: "#params" }] : /* istanbul ignore next */ []));
|
|
439
|
+
#deepestParams = signal({}, ...(ngDevMode ? [{ debugName: "#deepestParams" }] : /* istanbul ignore next */ []));
|
|
440
|
+
#query = signal({}, ...(ngDevMode ? [{ debugName: "#query" }] : /* istanbul ignore next */ []));
|
|
441
|
+
#data = signal({}, ...(ngDevMode ? [{ debugName: "#data" }] : /* istanbul ignore next */ []));
|
|
442
|
+
#mergedData = signal({}, ...(ngDevMode ? [{ debugName: "#mergedData" }] : /* istanbul ignore next */ []));
|
|
443
|
+
#url = signal('', ...(ngDevMode ? [{ debugName: "#url" }] : /* istanbul ignore next */ []));
|
|
444
|
+
#routePattern = signal('', ...(ngDevMode ? [{ debugName: "#routePattern" }] : /* istanbul ignore next */ []));
|
|
445
|
+
#fragment = signal(null, ...(ngDevMode ? [{ debugName: "#fragment" }] : /* istanbul ignore next */ []));
|
|
446
446
|
/** Params merged from root to deepest route. */
|
|
447
447
|
params = this.#params.asReadonly();
|
|
448
448
|
/** Params declared on the deepest route only. */
|
|
@@ -469,7 +469,7 @@ class RouteWatcher {
|
|
|
469
469
|
url: this.#url(),
|
|
470
470
|
routePattern: this.#routePattern(),
|
|
471
471
|
fragment: this.#fragment(),
|
|
472
|
-
}), ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
472
|
+
}), ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
473
473
|
constructor() {
|
|
474
474
|
const read = () => {
|
|
475
475
|
const snapshot = this.deepestSnapshot();
|
|
@@ -725,8 +725,8 @@ class LangService {
|
|
|
725
725
|
...LangService.BUILTIN_LANGS,
|
|
726
726
|
...this.normalizeSupportedLangs(this.config.supportedLangs ?? []),
|
|
727
727
|
]);
|
|
728
|
-
#lang = signal(this.getStoredLang(), ...(ngDevMode ? [{ debugName: "#lang" }] : []));
|
|
729
|
-
#cache = signal({}, ...(ngDevMode ? [{ debugName: "#cache" }] : []));
|
|
728
|
+
#lang = signal(this.getStoredLang(), ...(ngDevMode ? [{ debugName: "#lang" }] : /* istanbul ignore next */ []));
|
|
729
|
+
#cache = signal({}, ...(ngDevMode ? [{ debugName: "#cache" }] : /* istanbul ignore next */ []));
|
|
730
730
|
#loadedNamespaces = new Set();
|
|
731
731
|
#pendingLoads = new Map();
|
|
732
732
|
#pendingBatchLoads = new Map();
|
|
@@ -743,7 +743,7 @@ class LangService {
|
|
|
743
743
|
currentLang = computed(() => {
|
|
744
744
|
const lang = this.#lang();
|
|
745
745
|
return lang === 'kg' ? (this.config?.kgValue ?? 'kg') : lang;
|
|
746
|
-
}, ...(ngDevMode ? [{ debugName: "currentLang" }] : []));
|
|
746
|
+
}, ...(ngDevMode ? [{ debugName: "currentLang" }] : /* istanbul ignore next */ []));
|
|
747
747
|
/**
|
|
748
748
|
* Extracts readonly value from private property `#lang` and assigns it to `innerLangVal`.
|
|
749
749
|
* Expected that property `#lang` has `asReadonly` method that returns immutable representation.
|
|
@@ -1186,19 +1186,19 @@ class LangDirective {
|
|
|
1186
1186
|
* Localization mode: defines which parts of the element will be translated.
|
|
1187
1187
|
* @default 'all'
|
|
1188
1188
|
*/
|
|
1189
|
-
lang = input('all', { ...(ngDevMode ? { debugName: "lang" } : {}), alias: 'reLang' });
|
|
1189
|
+
lang = input('all', { ...(ngDevMode ? { debugName: "lang" } : /* istanbul ignore next */ {}), alias: 'reLang' });
|
|
1190
1190
|
/**
|
|
1191
1191
|
* Explicit key for text content translation.
|
|
1192
1192
|
*/
|
|
1193
|
-
reLangKeySig = signal(undefined, ...(ngDevMode ? [{ debugName: "reLangKeySig" }] : []));
|
|
1193
|
+
reLangKeySig = signal(undefined, ...(ngDevMode ? [{ debugName: "reLangKeySig" }] : /* istanbul ignore next */ []));
|
|
1194
1194
|
/**
|
|
1195
1195
|
* Explicit attribute-to-key map for translation.
|
|
1196
1196
|
*/
|
|
1197
|
-
reLangAttrsSig = signal(undefined, ...(ngDevMode ? [{ debugName: "reLangAttrsSig" }] : []));
|
|
1197
|
+
reLangAttrsSig = signal(undefined, ...(ngDevMode ? [{ debugName: "reLangAttrsSig" }] : /* istanbul ignore next */ []));
|
|
1198
1198
|
/**
|
|
1199
1199
|
* Name of an additional attribute to localize (besides standard `title`, `label`, `placeholder`).
|
|
1200
1200
|
*/
|
|
1201
|
-
langForAttr = input(...(ngDevMode ? [undefined, { debugName: "langForAttr" }] : []));
|
|
1201
|
+
langForAttr = input(...(ngDevMode ? [undefined, { debugName: "langForAttr" }] : /* istanbul ignore next */ []));
|
|
1202
1202
|
el = inject(ElementRef);
|
|
1203
1203
|
renderer = inject(Renderer2);
|
|
1204
1204
|
service = inject(LangService);
|
|
@@ -1452,7 +1452,7 @@ class LangPipe {
|
|
|
1452
1452
|
const existing = this.cache.get(key);
|
|
1453
1453
|
if (existing) {
|
|
1454
1454
|
if (now - existing.ts < this.ttlMs) {
|
|
1455
|
-
return existing.value();
|
|
1455
|
+
return this.applyNamespaceGuard(query, existing.value());
|
|
1456
1456
|
}
|
|
1457
1457
|
this.cache.delete(key);
|
|
1458
1458
|
}
|
|
@@ -1460,7 +1460,9 @@ class LangPipe {
|
|
|
1460
1460
|
this.cache.set(key, { value: this.lang.observe(query, params ?? undefined), ts: now });
|
|
1461
1461
|
this.evictIfNeeded();
|
|
1462
1462
|
}
|
|
1463
|
-
|
|
1463
|
+
return this.applyNamespaceGuard(query, this.cache.get(key).value());
|
|
1464
|
+
}
|
|
1465
|
+
applyNamespaceGuard(query, value) {
|
|
1464
1466
|
const ns = query.split('.', 1)[0];
|
|
1465
1467
|
if (ns && !this.lang.isNamespaceLoaded(ns)) {
|
|
1466
1468
|
const placeholder = this.config.placeholder;
|
|
@@ -1608,7 +1610,7 @@ class ThemeService {
|
|
|
1608
1610
|
persistence = inject(THEME_PERSISTENCE_ADAPTER);
|
|
1609
1611
|
isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
1610
1612
|
document = inject(DOCUMENT);
|
|
1611
|
-
#theme = signal(this.themeDefault, ...(ngDevMode ? [{ debugName: "#theme" }] : []));
|
|
1613
|
+
#theme = signal(this.themeDefault, ...(ngDevMode ? [{ debugName: "#theme" }] : /* istanbul ignore next */ []));
|
|
1612
1614
|
/**
|
|
1613
1615
|
* Current active theme (`light` or `dark`).
|
|
1614
1616
|
*
|
|
@@ -1618,13 +1620,13 @@ class ThemeService {
|
|
|
1618
1620
|
* <div [class]="themeService.theme()"></div>
|
|
1619
1621
|
* ```
|
|
1620
1622
|
*/
|
|
1621
|
-
theme = computed(() => this.#theme(), ...(ngDevMode ? [{ debugName: "theme" }] : []));
|
|
1623
|
+
theme = computed(() => this.#theme(), ...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
|
|
1622
1624
|
/**
|
|
1623
1625
|
* Convenient flag returning `true` if the light theme is active.
|
|
1624
1626
|
* Suitable for conditional style application or resource selection.
|
|
1625
1627
|
*/
|
|
1626
|
-
isLight = computed(() => this.#theme() === themes.light, ...(ngDevMode ? [{ debugName: "isLight" }] : []));
|
|
1627
|
-
isDark = computed(() => this.#theme() === themes.dark, ...(ngDevMode ? [{ debugName: "isDark" }] : []));
|
|
1628
|
+
isLight = computed(() => this.#theme() === themes.light, ...(ngDevMode ? [{ debugName: "isLight" }] : /* istanbul ignore next */ []));
|
|
1629
|
+
isDark = computed(() => this.#theme() === themes.dark, ...(ngDevMode ? [{ debugName: "isDark" }] : /* istanbul ignore next */ []));
|
|
1628
1630
|
constructor() {
|
|
1629
1631
|
effect(() => {
|
|
1630
1632
|
if (!this.isBrowser) {
|
package/package.json
CHANGED
|
@@ -608,6 +608,7 @@ declare class LangPipe implements PipeTransform {
|
|
|
608
608
|
private readonly maxCacheSize;
|
|
609
609
|
constructor();
|
|
610
610
|
transform(query: string | null | undefined, params?: LangParams | null): string;
|
|
611
|
+
private applyNamespaceGuard;
|
|
611
612
|
private makeKey;
|
|
612
613
|
private evictIfNeeded;
|
|
613
614
|
private warnUnresolvedKey;
|