@memberjunction/ng-explorer-core 5.50.0 → 6.1.0-edge.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/LICENSE +7 -0
- package/dist/app-routing.module.d.ts.map +1 -1
- package/dist/app-routing.module.js +5 -5
- package/dist/app-routing.module.js.map +1 -1
- package/dist/generated/lazy-feature-config.d.ts +11 -2
- package/dist/generated/lazy-feature-config.d.ts.map +1 -1
- package/dist/generated/lazy-feature-config.js +79 -23
- package/dist/generated/lazy-feature-config.js.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js +28 -0
- package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
- package/dist/lib/services/lazy-module-registry.d.ts +48 -8
- package/dist/lib/services/lazy-module-registry.d.ts.map +1 -1
- package/dist/lib/services/lazy-module-registry.js +103 -23
- package/dist/lib/services/lazy-module-registry.js.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts +2 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.js +58 -42
- package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
- package/dist/lib/shell/components/record-open/record-bar.component.d.ts +29 -0
- package/dist/lib/shell/components/record-open/record-bar.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-bar.component.js +72 -0
- package/dist/lib/shell/components/record-open/record-bar.component.js.map +1 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts +26 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js +140 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.d.ts +43 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.js +102 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.js.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.d.ts +21 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.js +30 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.js.map +1 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.d.ts +63 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.js +143 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.js.map +1 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +228 -2
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.js +1073 -113
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.d.ts.map +1 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js +2 -0
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js.map +1 -1
- package/dist/lib/shell/shell.component.d.ts +41 -4
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +400 -240
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/shell/shell.module.d.ts +4 -1
- package/dist/lib/shell/shell.module.d.ts.map +1 -1
- package/dist/lib/shell/shell.module.js +14 -3
- package/dist/lib/shell/shell.module.js.map +1 -1
- package/package.json +62 -59
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-module-registry.js","sourceRoot":"","sources":["../../../src/lib/services/lazy-module-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { MJGlobal } from '@memberjunction/global';\n\n/**\n * Registry that maps compound keys (BaseClassName::Key) to dynamic import() loaders.\n *\n * Wired to ClassFactory via `WireToClassFactory()` so that any `GetRegistrationAsync()`\n * or `CreateInstanceAsync()` call that misses synchronously will automatically trigger\n * the correct lazy chunk load. This replaces the previous pattern where individual\n * components (ResourceContainerComponent, TabContainerComponent, etc.) each had their\n * own retry logic.\n */\n@Injectable({ providedIn: 'root' })\nexport class LazyModuleRegistry {\n private registry = new Map<string, () => Promise<void>>();\n private loadedChunks = new Set<string>();\n private pendingLoads = new Map<string, Promise<void>>();\n\n /**\n * Register a single compound key with its loader function.\n * @param compoundKey Format: \"BaseClassName::Key\" (e.g., \"BaseResourceComponent::HomeDashboard\")\n * @param loader Dynamic import function that loads the chunk containing the class\n */\n Register(compoundKey: string, loader: () => Promise<void>): void {\n this.registry.set(compoundKey, loader);\n }\n\n /**\n * Register multiple compound keys at once (from the generated LAZY_FEATURE_CONFIG).\n */\n RegisterBulk(mappings: Record<string, () => Promise<void>>): void {\n for (const [compoundKey, loader] of Object.entries(mappings)) {\n this.registry.set(compoundKey, loader);\n }\n }\n\n /**\n * Wires this registry to ClassFactory as a lazy loader.\n * After calling this, any `ClassFactory.GetRegistrationAsync()` or `CreateInstanceAsync()`\n * that fails to find a registration synchronously will call back into this registry\n * with the compound key (baseClassName::key) to trigger lazy loading.\n */\n WireToClassFactory(): void {\n MJGlobal.Instance.ClassFactory.RegisterLazyLoader(\n (baseClassName: string, key: string) => this.Load(`${baseClassName}::${key}`)\n );\n // Publish to a well-known global so introspection tools (e.g. the Admin\n // app's \"Lazy Loading\" inspector in ng-dashboards) can read the snapshot\n // without creating a hard package dependency on explorer-core. Dev tools only.\n (globalThis as { __mj_lazy_registry__?: LazyModuleRegistry }).__mj_lazy_registry__ = this;\n }\n\n /**\n * Read-only snapshot of the registry state — for diagnostic tools.\n * Groups compound keys by chunk (the underlying loader function) so\n * inspectors can show \"X chunks, Y loaded\" plus the keys covered by each.\n */\n GetSnapshot(): {\n registered: string[];\n loaded: string[];\n chunks: Array<{ chunkId: string; loaded: boolean; keys: string[] }>;\n chunkCount: number;\n } {\n const byChunk = new Map<string, string[]>();\n for (const [compoundKey, loader] of this.registry.entries()) {\n const id = loader.toString();\n const arr = byChunk.get(id) ?? [];\n arr.push(compoundKey);\n byChunk.set(id, arr);\n }\n\n const chunks = Array.from(byChunk.entries())\n .map(([chunkId, keys]) => ({\n chunkId,\n loaded: this.loadedChunks.has(chunkId),\n keys: keys.sort()\n }))\n .sort((a, b) => b.keys.length - a.keys.length);\n\n const loaded: string[] = [];\n for (const c of chunks) {\n if (c.loaded) loaded.push(...c.keys);\n }\n\n return {\n registered: Array.from(this.registry.keys()).sort(),\n loaded: loaded.sort(),\n chunks,\n chunkCount: this.loadedChunks.size\n };\n }\n\n /**\n * Programmatically trigger a lazy chunk load by compound key. Returns true\n * on success. Useful for dev tools that want to \"preload\" a chunk.\n */\n async ForceLoad(compoundKey: string): Promise<boolean> {\n return this.Load(compoundKey);\n }\n\n /**\n * Attempt to lazy-load the chunk for a given compound key.\n * Returns true if the key was found in the registry and loaded.\n * Deduplicates concurrent loads of the same chunk.\n *\n * @param compoundKey Format: \"BaseClassName::Key\" (e.g., \"BaseResourceComponent::HomeDashboard\")\n */\n async Load(compoundKey: string): Promise<boolean> {\n const loader = this.registry.get(compoundKey);\n if (!loader) return false;\n\n // Use the loader function's string representation as chunk key for deduplication\n // (multiple compound keys can share the same loader/chunk)\n const chunkKey = loader.toString();\n\n if (this.loadedChunks.has(chunkKey)) return true;\n\n // Deduplicate concurrent loads\n const pending = this.pendingLoads.get(chunkKey);\n if (pending) {\n await pending;\n return true;\n }\n\n const loadPromise = loader().then(() => {\n this.loadedChunks.add(chunkKey);\n this.pendingLoads.delete(chunkKey);\n });\n\n this.pendingLoads.set(chunkKey, loadPromise);\n await loadPromise;\n return true;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"lazy-module-registry.js","sourceRoot":"","sources":["../../../src/lib/services/lazy-module-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { MJGlobal } from '@memberjunction/global';\n\n/**\n * One lazy-loadable chunk: a stable identity plus the dynamic import that loads it.\n *\n * Many compound keys map to the same chunk, so `chunkId` is what the registry dedupes\n * loads by — it must uniquely identify the chunk. The generated LAZY_FEATURE_CONFIG\n * uses the dynamic import specifier (e.g. '@memberjunction/ng-dashboards/ai-dashboards.module').\n */\nexport interface LazyFeatureChunk {\n /** Stable, unique chunk identity. Also the label dev tools show for the chunk. */\n readonly chunkId: string;\n /** Imports the chunk, which runs the `@RegisterClass` decorators inside it. */\n readonly load: () => Promise<void>;\n}\n\n/** Extracts the subclass-key half of a `\"BaseClassName::Key\"` compound key. */\nfunction subclassKeyOf(compoundKey: string): string {\n const separator = compoundKey.indexOf('::');\n return separator < 0 ? compoundKey : compoundKey.slice(separator + 2);\n}\n\n/**\n * Registry that maps compound keys (BaseClassName::Key) to the chunk that provides them.\n *\n * Wired to ClassFactory via `WireToClassFactory()` so that any `GetRegistrationAsync()`\n * or `CreateInstanceAsync()` call that misses synchronously will automatically trigger\n * the correct lazy chunk load. This replaces the previous pattern where individual\n * components (ResourceContainerComponent, TabContainerComponent, etc.) each had their\n * own retry logic.\n */\n@Injectable({ providedIn: 'root' })\nexport class LazyModuleRegistry {\n private registry = new Map<string, LazyFeatureChunk>();\n /**\n * Secondary index: subclass key → (compound key → chunk), ignoring the base-class half.\n *\n * Needed because the base-class half of a compound key is NOT stable at runtime. ClassFactory\n * builds the lookup from `baseClass.name`, but the emitted identifier varies by build mode —\n * for the same `BaseResourceComponent` we have observed `_BaseResourceComponent` (unminified,\n * Angular's named class expression) and `BaseResourceComponent2` (minified, esbuild's collision\n * rename) — while the generated LAZY_FEATURE_CONFIG keys use the TypeScript source name. Any\n * mismatch made the primary lookup miss, so NO chunk was ever imported and every lazily\n * provided class silently failed to resolve.\n *\n * The inner map is keyed by compound key (not a flat Set of chunks) so that re-registering a\n * compound key replaces its contribution here too, matching `registry`'s replace semantics.\n */\n private chunksBySubclassKey = new Map<string, Map<string, LazyFeatureChunk>>();\n /** chunkIds that have finished loading. */\n private loadedChunks = new Set<string>();\n /** In-flight loads, keyed by chunkId, so concurrent callers share one import. */\n private pendingLoads = new Map<string, Promise<void>>();\n\n /**\n * Register a single compound key with the chunk that provides it.\n * @param compoundKey Format: \"BaseClassName::Key\" (e.g., \"BaseResourceComponent::HomeDashboard\")\n * @param chunk The chunk descriptor (stable id + dynamic import)\n */\n Register(compoundKey: string, chunk: LazyFeatureChunk): void {\n this.registry.set(compoundKey, chunk);\n const subclassKey = subclassKeyOf(compoundKey);\n const byCompoundKey = this.chunksBySubclassKey.get(subclassKey) ?? new Map<string, LazyFeatureChunk>();\n byCompoundKey.set(compoundKey, chunk);\n this.chunksBySubclassKey.set(subclassKey, byCompoundKey);\n }\n\n /**\n * Register multiple compound keys at once (from the generated LAZY_FEATURE_CONFIG).\n */\n RegisterBulk(mappings: Record<string, LazyFeatureChunk>): void {\n for (const [compoundKey, chunk] of Object.entries(mappings)) {\n this.Register(compoundKey, chunk);\n }\n }\n\n /**\n * Wires this registry to ClassFactory as a lazy loader.\n * After calling this, any `ClassFactory.GetRegistrationAsync()` or `CreateInstanceAsync()`\n * that fails to find a registration synchronously will call back into this registry\n * with the compound key (baseClassName::key) to trigger lazy loading.\n */\n WireToClassFactory(): void {\n MJGlobal.Instance.ClassFactory.RegisterLazyLoader(\n (baseClassName: string, key: string) => this.Load(`${baseClassName}::${key}`)\n );\n // Publish to a well-known global so introspection tools (e.g. the Admin\n // app's \"Lazy Loading\" inspector in ng-dashboards) can read the snapshot\n // without creating a hard package dependency on explorer-core. Dev tools only.\n (globalThis as { __mj_lazy_registry__?: LazyModuleRegistry }).__mj_lazy_registry__ = this;\n }\n\n /**\n * Read-only snapshot of the registry state — for diagnostic tools.\n * Groups compound keys by `chunkId` so inspectors can show \"X chunks, Y loaded\"\n * plus the keys covered by each.\n */\n GetSnapshot(): {\n registered: string[];\n loaded: string[];\n chunks: Array<{ chunkId: string; loaded: boolean; keys: string[] }>;\n /** How many chunks have finished loading. The total is `chunks.length`. */\n loadedChunkCount: number;\n } {\n const byChunk = new Map<string, string[]>();\n for (const [compoundKey, chunk] of this.registry.entries()) {\n const arr = byChunk.get(chunk.chunkId) ?? [];\n arr.push(compoundKey);\n byChunk.set(chunk.chunkId, arr);\n }\n\n const chunks = Array.from(byChunk.entries())\n .map(([chunkId, keys]) => ({\n chunkId,\n loaded: this.loadedChunks.has(chunkId),\n keys: keys.sort()\n }))\n .sort((a, b) => b.keys.length - a.keys.length);\n\n const loaded: string[] = [];\n for (const c of chunks) {\n if (c.loaded) loaded.push(...c.keys);\n }\n\n return {\n registered: Array.from(this.registry.keys()).sort(),\n loaded: loaded.sort(),\n chunks,\n loadedChunkCount: this.loadedChunks.size\n };\n }\n\n /**\n * Programmatically trigger a lazy chunk load by compound key. Returns true\n * on success. Useful for dev tools that want to \"preload\" a chunk.\n */\n async ForceLoad(compoundKey: string): Promise<boolean> {\n return this.Load(compoundKey);\n }\n\n /**\n * Attempt to lazy-load the chunk for a given compound key.\n * Returns true if the key was found in the registry and loaded.\n * Deduplicates concurrent loads of the same chunk.\n *\n * @param compoundKey Format: \"BaseClassName::Key\" (e.g., \"BaseResourceComponent::HomeDashboard\")\n */\n async Load(compoundKey: string): Promise<boolean> {\n // Primary: exact compound key. Correct and precise when the runtime base-class name\n // happens to match the generated config.\n const exact = this.registry.get(compoundKey);\n if (exact) {\n await this.loadChunk(exact);\n return true;\n }\n\n // Fallback: match on the subclass key alone, because the base-class half is not stable\n // across build modes (see `chunksBySubclassKey`). Subclass keys are unique per chunk in\n // practice; if several chunks ever claim one, load them all so resolution can't silently\n // pick wrong — ClassFactory still decides the winner by base class afterwards.\n const candidates = this.candidateChunksFor(subclassKeyOf(compoundKey));\n if (candidates.length === 0) return false;\n\n // One candidate failing must not skip the rest — a later one may hold the class. Only\n // surface a failure if NOTHING loaded, so a partial failure can't mask a success.\n const errors: unknown[] = [];\n let loaded = false;\n for (const chunk of candidates) {\n try {\n await this.loadChunk(chunk);\n loaded = true;\n } catch (error) {\n errors.push(error);\n }\n }\n if (!loaded) throw errors[0];\n return true;\n }\n\n /**\n * The distinct chunks (by `chunkId`) registered under a subclass key, in registration order.\n */\n private candidateChunksFor(subclassKey: string): LazyFeatureChunk[] {\n const byCompoundKey = this.chunksBySubclassKey.get(subclassKey);\n if (!byCompoundKey) return [];\n\n const distinct = new Map<string, LazyFeatureChunk>();\n for (const chunk of byCompoundKey.values()) {\n distinct.set(chunk.chunkId, chunk);\n }\n return Array.from(distinct.values());\n }\n\n /**\n * Imports a chunk once, sharing in-flight loads and remembering completed ones.\n * Resolves when the chunk is loaded; rejects if the import fails.\n */\n private async loadChunk(chunk: LazyFeatureChunk): Promise<void> {\n // Dedupe on the chunk's declared id — multiple compound keys share one chunk.\n // This MUST be a value that differs between chunks; deriving it from the loader\n // function (e.g. Function.toString()) collapses every chunk into one, so the first\n // chunk loaded makes all the others look already-loaded and their classes never register.\n const chunkKey = chunk.chunkId;\n\n if (this.loadedChunks.has(chunkKey)) return;\n\n // Deduplicate concurrent loads\n const pending = this.pendingLoads.get(chunkKey);\n if (pending) {\n await pending;\n return;\n }\n\n const loadPromise = chunk.load().then(\n () => {\n this.loadedChunks.add(chunkKey);\n this.pendingLoads.delete(chunkKey);\n },\n (error: unknown) => {\n // Drop the failed attempt so a later navigation retries. Chunk fetches fail\n // transiently in normal use — a dev-server rebuild renames every lazy chunk, so a\n // page loaded before the rebuild 404s once. Left in `pendingLoads`, the rejected\n // promise would be re-awaited (and re-thrown) forever, killing the chunk for the\n // rest of the page session. The rejection still propagates to this caller.\n this.pendingLoads.delete(chunkKey);\n throw error;\n }\n );\n\n this.pendingLoads.set(chunkKey, loadPromise);\n await loadPromise;\n }\n}\n"]}
|
|
@@ -66,6 +66,7 @@ export declare class AppNavComponent implements OnInit, OnDestroy, AfterViewInit
|
|
|
66
66
|
private _overflowEnabled;
|
|
67
67
|
private navListRef?;
|
|
68
68
|
private moreBtnRef?;
|
|
69
|
+
private trailingSlotRef?;
|
|
69
70
|
navItemClick: EventEmitter<NavItemClickEvent>;
|
|
70
71
|
navItemDismiss: EventEmitter<NavItem>;
|
|
71
72
|
/**
|
|
@@ -171,6 +172,6 @@ export declare class AppNavComponent implements OnInit, OnDestroy, AfterViewInit
|
|
|
171
172
|
*/
|
|
172
173
|
onDismiss(item: NavItem, event: MouseEvent): void;
|
|
173
174
|
static ɵfac: i0.ɵɵFactoryDeclaration<AppNavComponent, never>;
|
|
174
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<AppNavComponent, "mj-app-nav", never, { "OverflowEnabled": { "alias": "OverflowEnabled"; "required": false; }; "app": { "alias": "app"; "required": false; }; }, { "navItemClick": "navItemClick"; "navItemDismiss": "navItemDismiss"; }, never,
|
|
175
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<AppNavComponent, "mj-app-nav", never, { "OverflowEnabled": { "alias": "OverflowEnabled"; "required": false; }; "app": { "alias": "app"; "required": false; }; }, { "navItemClick": "navItemClick"; "navItemDismiss": "navItemDismiss"; }, never, ["*"], false, never>;
|
|
175
176
|
}
|
|
176
177
|
//# sourceMappingURL=app-nav.component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-nav.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/shell/components/header/app-nav.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,iBAAiB,EAA2B,UAAU,EAA2B,MAAM,eAAe,CAAC;AAC1L,OAAO,EAAE,eAAe,EAAkB,OAAO,EAAE,qBAAqB,EAA0B,MAAM,qCAAqC,CAAC;AAC9I,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;;AAG1D;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,qBAOa,eAAgB,YAAW,MAAM,EAAE,SAAS,EAAE,aAAa;
|
|
1
|
+
{"version":3,"file":"app-nav.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/shell/components/header/app-nav.component.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,9 +6,11 @@ import * as i2 from "@memberjunction/ng-shared";
|
|
|
6
6
|
import * as i3 from "@memberjunction/ng-ui-components";
|
|
7
7
|
const _c0 = ["navList"];
|
|
8
8
|
const _c1 = ["moreBtn"];
|
|
9
|
+
const _c2 = ["trailingSlot"];
|
|
10
|
+
const _c3 = ["*"];
|
|
9
11
|
function AppNavComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
10
|
-
i0.ɵɵelementStart(0, "div",
|
|
11
|
-
i0.ɵɵelement(1, "span",
|
|
12
|
+
i0.ɵɵelementStart(0, "div", 4);
|
|
13
|
+
i0.ɵɵelement(1, "span", 9)(2, "span", 10)(3, "span", 11);
|
|
12
14
|
i0.ɵɵelementEnd();
|
|
13
15
|
} }
|
|
14
16
|
function AppNavComponent_For_4_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
@@ -18,7 +20,7 @@ function AppNavComponent_For_4_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
|
18
20
|
i0.ɵɵclassMap(item_r2.Icon);
|
|
19
21
|
} }
|
|
20
22
|
function AppNavComponent_For_4_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
21
|
-
i0.ɵɵelementStart(0, "span",
|
|
23
|
+
i0.ɵɵelementStart(0, "span", 14);
|
|
22
24
|
i0.ɵɵtext(1);
|
|
23
25
|
i0.ɵɵelementEnd();
|
|
24
26
|
} if (rf & 2) {
|
|
@@ -28,21 +30,21 @@ function AppNavComponent_For_4_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
|
28
30
|
} }
|
|
29
31
|
function AppNavComponent_For_4_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
30
32
|
const _r4 = i0.ɵɵgetCurrentView();
|
|
31
|
-
i0.ɵɵelementStart(0, "button",
|
|
33
|
+
i0.ɵɵelementStart(0, "button", 16);
|
|
32
34
|
i0.ɵɵlistener("click", function AppNavComponent_For_4_Conditional_5_Template_button_click_0_listener($event) { i0.ɵɵrestoreView(_r4); const item_r2 = i0.ɵɵnextContext().$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onDismiss(item_r2, $event)); });
|
|
33
|
-
i0.ɵɵelement(1, "i",
|
|
35
|
+
i0.ɵɵelement(1, "i", 17);
|
|
34
36
|
i0.ɵɵelementEnd();
|
|
35
37
|
} }
|
|
36
38
|
function AppNavComponent_For_4_Template(rf, ctx) { if (rf & 1) {
|
|
37
39
|
const _r1 = i0.ɵɵgetCurrentView();
|
|
38
|
-
i0.ɵɵelementStart(0, "div",
|
|
40
|
+
i0.ɵɵelementStart(0, "div", 12);
|
|
39
41
|
i0.ɵɵlistener("click", function AppNavComponent_For_4_Template_div_click_0_listener($event) { const item_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onNavClick(item_r2, $event)); });
|
|
40
|
-
i0.ɵɵconditionalCreate(1, AppNavComponent_For_4_Conditional_1_Template, 1, 2, "i",
|
|
42
|
+
i0.ɵɵconditionalCreate(1, AppNavComponent_For_4_Conditional_1_Template, 1, 2, "i", 13);
|
|
41
43
|
i0.ɵɵelementStart(2, "span");
|
|
42
44
|
i0.ɵɵtext(3);
|
|
43
45
|
i0.ɵɵelementEnd();
|
|
44
|
-
i0.ɵɵconditionalCreate(4, AppNavComponent_For_4_Conditional_4_Template, 2, 1, "span",
|
|
45
|
-
i0.ɵɵconditionalCreate(5, AppNavComponent_For_4_Conditional_5_Template, 2, 0, "button",
|
|
46
|
+
i0.ɵɵconditionalCreate(4, AppNavComponent_For_4_Conditional_4_Template, 2, 1, "span", 14);
|
|
47
|
+
i0.ɵɵconditionalCreate(5, AppNavComponent_For_4_Conditional_5_Template, 2, 0, "button", 15);
|
|
46
48
|
i0.ɵɵelementEnd();
|
|
47
49
|
} if (rf & 2) {
|
|
48
50
|
const item_r2 = ctx.$implicit;
|
|
@@ -61,13 +63,13 @@ function AppNavComponent_For_4_Template(rf, ctx) { if (rf & 1) {
|
|
|
61
63
|
} }
|
|
62
64
|
function AppNavComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
63
65
|
const _r5 = i0.ɵɵgetCurrentView();
|
|
64
|
-
i0.ɵɵelementStart(0, "div",
|
|
66
|
+
i0.ɵɵelementStart(0, "div", 18, 2);
|
|
65
67
|
i0.ɵɵlistener("click", function AppNavComponent_Conditional_5_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.ToggleMore($event)); });
|
|
66
|
-
i0.ɵɵelement(2, "i",
|
|
68
|
+
i0.ɵɵelement(2, "i", 19);
|
|
67
69
|
i0.ɵɵelementStart(3, "span");
|
|
68
70
|
i0.ɵɵtext(4, "More");
|
|
69
71
|
i0.ɵɵelementEnd();
|
|
70
|
-
i0.ɵɵelementStart(5, "span",
|
|
72
|
+
i0.ɵɵelementStart(5, "span", 20);
|
|
71
73
|
i0.ɵɵtext(6);
|
|
72
74
|
i0.ɵɵelementEnd()();
|
|
73
75
|
} if (rf & 2) {
|
|
@@ -77,14 +79,14 @@ function AppNavComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
|
77
79
|
i0.ɵɵadvance(6);
|
|
78
80
|
i0.ɵɵtextInterpolate(ctx_r2.OverflowItems.length);
|
|
79
81
|
} }
|
|
80
|
-
function
|
|
82
|
+
function AppNavComponent_Conditional_9_For_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
81
83
|
i0.ɵɵelement(0, "i");
|
|
82
84
|
} if (rf & 2) {
|
|
83
85
|
const item_r7 = i0.ɵɵnextContext().$implicit;
|
|
84
86
|
i0.ɵɵclassMap(item_r7.Icon);
|
|
85
87
|
} }
|
|
86
|
-
function
|
|
87
|
-
i0.ɵɵelementStart(0, "span",
|
|
88
|
+
function AppNavComponent_Conditional_9_For_2_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
89
|
+
i0.ɵɵelementStart(0, "span", 14);
|
|
88
90
|
i0.ɵɵtext(1);
|
|
89
91
|
i0.ɵɵelementEnd();
|
|
90
92
|
} if (rf & 2) {
|
|
@@ -92,23 +94,23 @@ function AppNavComponent_Conditional_6_For_2_Conditional_4_Template(rf, ctx) { i
|
|
|
92
94
|
i0.ɵɵadvance();
|
|
93
95
|
i0.ɵɵtextInterpolate(item_r7.Badge);
|
|
94
96
|
} }
|
|
95
|
-
function
|
|
97
|
+
function AppNavComponent_Conditional_9_For_2_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
96
98
|
const _r8 = i0.ɵɵgetCurrentView();
|
|
97
|
-
i0.ɵɵelementStart(0, "button",
|
|
98
|
-
i0.ɵɵlistener("click", function
|
|
99
|
-
i0.ɵɵelement(1, "i",
|
|
99
|
+
i0.ɵɵelementStart(0, "button", 16);
|
|
100
|
+
i0.ɵɵlistener("click", function AppNavComponent_Conditional_9_For_2_Conditional_5_Template_button_click_0_listener($event) { i0.ɵɵrestoreView(_r8); const item_r7 = i0.ɵɵnextContext().$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onDismiss(item_r7, $event)); });
|
|
101
|
+
i0.ɵɵelement(1, "i", 17);
|
|
100
102
|
i0.ɵɵelementEnd();
|
|
101
103
|
} }
|
|
102
|
-
function
|
|
104
|
+
function AppNavComponent_Conditional_9_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
103
105
|
const _r6 = i0.ɵɵgetCurrentView();
|
|
104
|
-
i0.ɵɵelementStart(0, "div",
|
|
105
|
-
i0.ɵɵlistener("click", function
|
|
106
|
-
i0.ɵɵconditionalCreate(1,
|
|
107
|
-
i0.ɵɵelementStart(2, "span",
|
|
106
|
+
i0.ɵɵelementStart(0, "div", 23);
|
|
107
|
+
i0.ɵɵlistener("click", function AppNavComponent_Conditional_9_For_2_Template_div_click_0_listener($event) { const item_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onNavClick(item_r7, $event)); });
|
|
108
|
+
i0.ɵɵconditionalCreate(1, AppNavComponent_Conditional_9_For_2_Conditional_1_Template, 1, 2, "i", 13);
|
|
109
|
+
i0.ɵɵelementStart(2, "span", 24);
|
|
108
110
|
i0.ɵɵtext(3);
|
|
109
111
|
i0.ɵɵelementEnd();
|
|
110
|
-
i0.ɵɵconditionalCreate(4,
|
|
111
|
-
i0.ɵɵconditionalCreate(5,
|
|
112
|
+
i0.ɵɵconditionalCreate(4, AppNavComponent_Conditional_9_For_2_Conditional_4_Template, 2, 1, "span", 14);
|
|
113
|
+
i0.ɵɵconditionalCreate(5, AppNavComponent_Conditional_9_For_2_Conditional_5_Template, 2, 0, "button", 15);
|
|
112
114
|
i0.ɵɵelementEnd();
|
|
113
115
|
} if (rf & 2) {
|
|
114
116
|
const item_r7 = ctx.$implicit;
|
|
@@ -125,9 +127,9 @@ function AppNavComponent_Conditional_6_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
|
125
127
|
i0.ɵɵadvance();
|
|
126
128
|
i0.ɵɵconditional(ctx_r2.isDynamic(item_r7) ? 5 : -1);
|
|
127
129
|
} }
|
|
128
|
-
function
|
|
129
|
-
i0.ɵɵelementStart(0, "div",
|
|
130
|
-
i0.ɵɵrepeaterCreate(1,
|
|
130
|
+
function AppNavComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
|
|
131
|
+
i0.ɵɵelementStart(0, "div", 21);
|
|
132
|
+
i0.ɵɵrepeaterCreate(1, AppNavComponent_Conditional_9_For_2_Template, 6, 11, "div", 22, i0.ɵɵcomponentInstance().trackByNavItem, true);
|
|
131
133
|
i0.ɵɵelementEnd();
|
|
132
134
|
} if (rf & 2) {
|
|
133
135
|
const ctx_r2 = i0.ɵɵnextContext();
|
|
@@ -194,6 +196,7 @@ export class AppNavComponent {
|
|
|
194
196
|
_overflowEnabled = true;
|
|
195
197
|
navListRef;
|
|
196
198
|
moreBtnRef;
|
|
199
|
+
trailingSlotRef;
|
|
197
200
|
navItemClick = new EventEmitter();
|
|
198
201
|
navItemDismiss = new EventEmitter();
|
|
199
202
|
/**
|
|
@@ -355,7 +358,13 @@ export class AppNavComponent {
|
|
|
355
358
|
if (this.moreBtnRef?.nativeElement) {
|
|
356
359
|
this.moreBtnWidth = this.moreBtnRef.nativeElement.offsetWidth || this.moreBtnWidth;
|
|
357
360
|
}
|
|
358
|
-
|
|
361
|
+
// Reserve the trailing slot's width (projected shell items, e.g. the
|
|
362
|
+
// Records pill) so nav items yield to More before painting under it.
|
|
363
|
+
// Measured fresh each pass — the slot appears/disappears with open records,
|
|
364
|
+
// and every such change arrives via a configuration$ emission that re-runs
|
|
365
|
+
// this fit anyway.
|
|
366
|
+
const trailingWidth = this.trailingSlotRef?.nativeElement?.offsetWidth ?? 0;
|
|
367
|
+
const fit = this.computeFitCount(hostWidth - trailingWidth);
|
|
359
368
|
this.applyFit(fit.count, fit.tight);
|
|
360
369
|
}
|
|
361
370
|
/**
|
|
@@ -592,20 +601,24 @@ export class AppNavComponent {
|
|
|
592
601
|
}
|
|
593
602
|
static ɵfac = function AppNavComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AppNavComponent)(i0.ɵɵdirectiveInject(i1.WorkspaceStateManager), i0.ɵɵdirectiveInject(i2.SharedService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i0.ElementRef)); };
|
|
594
603
|
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AppNavComponent, selectors: [["mj-app-nav"]], viewQuery: function AppNavComponent_Query(rf, ctx) { if (rf & 1) {
|
|
595
|
-
i0.ɵɵviewQuery(_c0, 5)(_c1, 5);
|
|
604
|
+
i0.ɵɵviewQuery(_c0, 5)(_c1, 5)(_c2, 5);
|
|
596
605
|
} if (rf & 2) {
|
|
597
606
|
let _t;
|
|
598
607
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.navListRef = _t.first);
|
|
599
608
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.moreBtnRef = _t.first);
|
|
609
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.trailingSlotRef = _t.first);
|
|
600
610
|
} }, hostBindings: function AppNavComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
601
611
|
i0.ɵɵlistener("click", function AppNavComponent_click_HostBindingHandler($event) { return ctx.onDocumentClick($event); }, i0.ɵɵresolveDocument)("keydown.escape", function AppNavComponent_keydown_escape_HostBindingHandler() { return ctx.onEscape(); }, i0.ɵɵresolveDocument);
|
|
602
|
-
} }, inputs: { OverflowEnabled: "OverflowEnabled", app: "app" }, outputs: { navItemClick: "navItemClick", navItemDismiss: "navItemDismiss" }, standalone: false, decls:
|
|
603
|
-
i0.ɵɵ
|
|
604
|
-
i0.ɵɵ
|
|
605
|
-
i0.ɵɵ
|
|
606
|
-
i0.ɵɵ
|
|
607
|
-
i0.ɵɵ
|
|
608
|
-
i0.ɵɵ
|
|
612
|
+
} }, inputs: { OverflowEnabled: "OverflowEnabled", app: "app" }, outputs: { navItemClick: "navItemClick", navItemDismiss: "navItemDismiss" }, standalone: false, ngContentSelectors: _c3, decls: 10, vars: 5, consts: [["navList", ""], ["trailingSlot", ""], ["moreBtn", ""], [1, "nav-list"], ["aria-hidden", "true", 1, "nav-skeleton"], ["role", "link", 1, "nav-item", 3, "mjClickable", "testId", "active", "dynamic", "no-icon"], ["mjClickable", "More navigation items", "testId", "nav-more-btn", 1, "nav-item", "nav-more-btn", 3, "active"], [1, "nav-trailing"], [1, "nav-more-dropdown", 3, "left"], [1, "nav-skeleton-pill", 2, "width", "88px"], [1, "nav-skeleton-pill", 2, "width", "72px"], [1, "nav-skeleton-pill", 2, "width", "96px"], ["role", "link", 1, "nav-item", 3, "click", "mjClickable", "testId"], [3, "class"], [1, "badge"], ["title", "Remove", 1, "dismiss-btn"], ["title", "Remove", 1, "dismiss-btn", 3, "click"], [1, "fa-solid", "fa-xmark"], ["mjClickable", "More navigation items", "testId", "nav-more-btn", 1, "nav-item", "nav-more-btn", 3, "click"], [1, "fa-solid", "fa-ellipsis"], [1, "more-count"], [1, "nav-more-dropdown"], ["role", "link", 1, "nav-more-item", 3, "mjClickable", "testId", "active", "dynamic"], ["role", "link", 1, "nav-more-item", 3, "click", "mjClickable", "testId"], [1, "more-item-label"]], template: function AppNavComponent_Template(rf, ctx) { if (rf & 1) {
|
|
613
|
+
i0.ɵɵprojectionDef();
|
|
614
|
+
i0.ɵɵelementStart(0, "nav", 3, 0);
|
|
615
|
+
i0.ɵɵconditionalCreate(2, AppNavComponent_Conditional_2_Template, 4, 0, "div", 4);
|
|
616
|
+
i0.ɵɵrepeaterCreate(3, AppNavComponent_For_4_Template, 6, 13, "div", 5, ctx.trackByNavItem, true);
|
|
617
|
+
i0.ɵɵconditionalCreate(5, AppNavComponent_Conditional_5_Template, 7, 5, "div", 6);
|
|
618
|
+
i0.ɵɵelementStart(6, "span", 7, 1);
|
|
619
|
+
i0.ɵɵprojection(8);
|
|
620
|
+
i0.ɵɵelementEnd()();
|
|
621
|
+
i0.ɵɵconditionalCreate(9, AppNavComponent_Conditional_9_Template, 3, 2, "div", 8);
|
|
609
622
|
} if (rf & 2) {
|
|
610
623
|
i0.ɵɵclassProp("tight", ctx.Tight);
|
|
611
624
|
i0.ɵɵadvance(2);
|
|
@@ -614,19 +627,22 @@ export class AppNavComponent {
|
|
|
614
627
|
i0.ɵɵrepeater(ctx.InlineItems);
|
|
615
628
|
i0.ɵɵadvance(2);
|
|
616
629
|
i0.ɵɵconditional(ctx.OverflowItems.length > 0 ? 5 : -1);
|
|
617
|
-
i0.ɵɵadvance();
|
|
618
|
-
i0.ɵɵconditional(ctx.MoreOpen && ctx.OverflowItems.length > 0 ?
|
|
619
|
-
} }, dependencies: [i3.MJClickableDirective], styles: ["[_nghost-%COMP%] {\n display: block;\n min-width: 0;\n position: relative;\n}\n\n\n\n\n.nav-list[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: nowrap;\n gap: var(--mj-space-1);\n overflow: hidden;\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n\n\n\n.nav-list.tight[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%]:not(.nav-more-btn) {\n flex-shrink: 1;\n min-width: 0;\n}\n.nav-list.tight[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%]:not(.nav-more-btn) > span[_ngcontent-%COMP%]:first-of-type {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-2) var(--mj-space-4);\n border-radius: var(--mj-radius-md);\n text-decoration: none;\n color: var(--mj-text-secondary);\n font-weight: var(--mj-font-medium);\n font-size: var(--mj-text-sm);\n white-space: nowrap;\n transition: all var(--mj-transition-fast);\n position: relative;\n cursor: pointer;\n user-select: none;\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n\n\n\n\n\n.nav-list[_ngcontent-%COMP%] .nav-item.active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: var(--mj-text-base);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item.no-icon[_ngcontent-%COMP%] {\n padding-left: var(--mj-space-3);\n padding-right: var(--mj-space-3);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] .badge[_ngcontent-%COMP%] {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n}\n\n\n\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%]:not(.dynamic) + .nav-item.dynamic[_ngcontent-%COMP%] {\n margin-left: var(--mj-space-1);\n padding-left: var(--mj-space-4);\n border-left: 1px solid var(--mj-border-default);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item.dynamic[_ngcontent-%COMP%]:not(.active) {\n opacity: 0.7;\n}\n.nav-list[_ngcontent-%COMP%] .nav-item.dynamic[_ngcontent-%COMP%]:not(.active):hover {\n opacity: 1;\n}\n\n\n\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] .dismiss-btn[_ngcontent-%COMP%] {\n display: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--mj-text-disabled);\n font-size: 10px;\n cursor: pointer;\n transition: all var(--mj-transition-fast);\n margin-left: calc(-1 * var(--mj-space-0-5));\n}\n.nav-list[_ngcontent-%COMP%] .nav-item.dynamic[_ngcontent-%COMP%]:hover .dismiss-btn[_ngcontent-%COMP%] {\n display: flex;\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] .dismiss-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-secondary);\n}\n\n\n\n.nav-more-btn[_ngcontent-%COMP%] .more-count[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-secondary);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n line-height: var(--mj-leading-none);\n}\n.nav-more-btn.active[_ngcontent-%COMP%] .more-count[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-more-dropdown[_ngcontent-%COMP%] {\n position: absolute;\n top: calc(100% + var(--mj-space-2));\n left: 0; \n\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-lg);\n min-width: 200px;\n width: max-content;\n max-height: calc(100vh - 80px);\n overflow-y: auto;\n padding: var(--mj-space-1) 0;\n z-index: 10000;\n}\n.nav-more-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2-5);\n padding: var(--mj-space-2-5) var(--mj-space-4);\n cursor: pointer;\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-secondary);\n white-space: nowrap;\n transition: all var(--mj-transition-fast);\n}\n.nav-more-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 18px;\n text-align: center;\n font-size: var(--mj-text-base);\n}\n.nav-more-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.nav-more-item[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -2px;\n}\n.nav-more-item.active[_ngcontent-%COMP%] {\n \n\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-more-item[_ngcontent-%COMP%] .badge[_ngcontent-%COMP%] {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n}\n.nav-more-item[_ngcontent-%COMP%] .dismiss-btn[_ngcontent-%COMP%] {\n display: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--mj-text-disabled);\n font-size: 10px;\n cursor: pointer;\n}\n.nav-more-item.dynamic[_ngcontent-%COMP%]:hover .dismiss-btn[_ngcontent-%COMP%] {\n display: flex;\n}\n\n\n\n.nav-skeleton[_ngcontent-%COMP%] {\n display: flex;\n gap: var(--mj-space-1);\n}\n.nav-skeleton-pill[_ngcontent-%COMP%] {\n height: 34px;\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-surface-sunken);\n animation: _ngcontent-%COMP%_nav-skeleton-pulse 1.2s ease-in-out infinite;\n}\n@keyframes _ngcontent-%COMP%_nav-skeleton-pulse {\n 0%, 100% { opacity: 0.55; }\n 50% { opacity: 1; }\n}\n@media (prefers-reduced-motion: reduce) {\n .nav-skeleton-pill[_ngcontent-%COMP%] {\n animation: none;\n opacity: 0.7;\n }\n}\n\n\n\n.mobile-nav-content[_nghost-%COMP%] .nav-list[_ngcontent-%COMP%], .mobile-nav-content [_nghost-%COMP%] .nav-list[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: var(--mj-space-0-5);\n padding: 0 var(--mj-space-3);\n}\n.mobile-nav-content[_nghost-%COMP%] .nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%], .mobile-nav-content [_nghost-%COMP%] .nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] {\n padding: var(--mj-space-3) var(--mj-space-4);\n border-radius: var(--mj-radius-md);\n min-height: 48px; \n\n box-sizing: border-box;\n}\n.mobile-nav-content[_nghost-%COMP%] .nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%], .mobile-nav-content [_nghost-%COMP%] .nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 20px;\n text-align: center;\n}"], changeDetection: 0 });
|
|
630
|
+
i0.ɵɵadvance(4);
|
|
631
|
+
i0.ɵɵconditional(ctx.MoreOpen && ctx.OverflowItems.length > 0 ? 9 : -1);
|
|
632
|
+
} }, dependencies: [i3.MJClickableDirective], styles: ["[_nghost-%COMP%] {\n display: block;\n min-width: 0;\n position: relative;\n}\n\n\n\n\n.nav-list[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: nowrap;\n gap: var(--mj-space-1);\n overflow: hidden;\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n\n\n\n.nav-list[_ngcontent-%COMP%] .nav-trailing[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n flex: 0 0 auto;\n}\n\n\n\n.nav-list.tight[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%]:not(.nav-more-btn) {\n flex-shrink: 1;\n min-width: 0;\n}\n.nav-list.tight[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%]:not(.nav-more-btn) > span[_ngcontent-%COMP%]:first-of-type {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-2) var(--mj-space-4);\n border-radius: var(--mj-radius-md);\n text-decoration: none;\n color: var(--mj-text-secondary);\n font-weight: var(--mj-font-medium);\n font-size: var(--mj-text-sm);\n white-space: nowrap;\n transition: all var(--mj-transition-fast);\n position: relative;\n cursor: pointer;\n user-select: none;\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n\n\n\n\n\n.nav-list[_ngcontent-%COMP%] .nav-item.active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: var(--mj-text-base);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item.no-icon[_ngcontent-%COMP%] {\n padding-left: var(--mj-space-3);\n padding-right: var(--mj-space-3);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] .badge[_ngcontent-%COMP%] {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n}\n\n\n\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%]:not(.dynamic) + .nav-item.dynamic[_ngcontent-%COMP%] {\n margin-left: var(--mj-space-1);\n padding-left: var(--mj-space-4);\n border-left: 1px solid var(--mj-border-default);\n}\n.nav-list[_ngcontent-%COMP%] .nav-item.dynamic[_ngcontent-%COMP%]:not(.active) {\n opacity: 0.7;\n}\n.nav-list[_ngcontent-%COMP%] .nav-item.dynamic[_ngcontent-%COMP%]:not(.active):hover {\n opacity: 1;\n}\n\n\n\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] .dismiss-btn[_ngcontent-%COMP%] {\n display: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--mj-text-disabled);\n font-size: 10px;\n cursor: pointer;\n transition: all var(--mj-transition-fast);\n margin-left: calc(-1 * var(--mj-space-0-5));\n}\n.nav-list[_ngcontent-%COMP%] .nav-item.dynamic[_ngcontent-%COMP%]:hover .dismiss-btn[_ngcontent-%COMP%] {\n display: flex;\n}\n.nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] .dismiss-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-secondary);\n}\n\n\n\n.nav-more-btn[_ngcontent-%COMP%] .more-count[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-secondary);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n line-height: var(--mj-leading-none);\n}\n.nav-more-btn.active[_ngcontent-%COMP%] .more-count[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-more-dropdown[_ngcontent-%COMP%] {\n position: absolute;\n top: calc(100% + var(--mj-space-2));\n left: 0; \n\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-lg);\n min-width: 200px;\n width: max-content;\n max-height: calc(100vh - 80px);\n overflow-y: auto;\n padding: var(--mj-space-1) 0;\n z-index: 10000;\n}\n.nav-more-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2-5);\n padding: var(--mj-space-2-5) var(--mj-space-4);\n cursor: pointer;\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-secondary);\n white-space: nowrap;\n transition: all var(--mj-transition-fast);\n}\n.nav-more-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 18px;\n text-align: center;\n font-size: var(--mj-text-base);\n}\n.nav-more-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.nav-more-item[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -2px;\n}\n.nav-more-item.active[_ngcontent-%COMP%] {\n \n\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-more-item[_ngcontent-%COMP%] .badge[_ngcontent-%COMP%] {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n}\n.nav-more-item[_ngcontent-%COMP%] .dismiss-btn[_ngcontent-%COMP%] {\n display: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--mj-text-disabled);\n font-size: 10px;\n cursor: pointer;\n}\n.nav-more-item.dynamic[_ngcontent-%COMP%]:hover .dismiss-btn[_ngcontent-%COMP%] {\n display: flex;\n}\n\n\n\n.nav-skeleton[_ngcontent-%COMP%] {\n display: flex;\n gap: var(--mj-space-1);\n}\n.nav-skeleton-pill[_ngcontent-%COMP%] {\n height: 34px;\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-surface-sunken);\n animation: _ngcontent-%COMP%_nav-skeleton-pulse 1.2s ease-in-out infinite;\n}\n@keyframes _ngcontent-%COMP%_nav-skeleton-pulse {\n 0%, 100% { opacity: 0.55; }\n 50% { opacity: 1; }\n}\n@media (prefers-reduced-motion: reduce) {\n .nav-skeleton-pill[_ngcontent-%COMP%] {\n animation: none;\n opacity: 0.7;\n }\n}\n\n\n\n.mobile-nav-content[_nghost-%COMP%] .nav-list[_ngcontent-%COMP%], .mobile-nav-content [_nghost-%COMP%] .nav-list[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: var(--mj-space-0-5);\n padding: 0 var(--mj-space-3);\n}\n.mobile-nav-content[_nghost-%COMP%] .nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%], .mobile-nav-content [_nghost-%COMP%] .nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] {\n padding: var(--mj-space-3) var(--mj-space-4);\n border-radius: var(--mj-radius-md);\n min-height: 48px; \n\n box-sizing: border-box;\n}\n.mobile-nav-content[_nghost-%COMP%] .nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%], .mobile-nav-content [_nghost-%COMP%] .nav-list[_ngcontent-%COMP%] .nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 20px;\n text-align: center;\n}"], changeDetection: 0 });
|
|
620
633
|
}
|
|
621
634
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AppNavComponent, [{
|
|
622
635
|
type: Component,
|
|
623
|
-
args: [{ standalone: false, selector: 'mj-app-nav', changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"nav-list\" #navList [class.tight]=\"Tight\">\n @if (Loading && navItems.length === 0) {\n <!-- Skeleton pills while GetNavItems() resolves \u2014 prevents header pop-in -->\n <div class=\"nav-skeleton\" aria-hidden=\"true\">\n <span class=\"nav-skeleton-pill\" style=\"width: 88px\"></span>\n <span class=\"nav-skeleton-pill\" style=\"width: 72px\"></span>\n <span class=\"nav-skeleton-pill\" style=\"width: 96px\"></span>\n </div>\n }\n @for (item of InlineItems; track trackByNavItem($index, item)) {\n <div\n class=\"nav-item\"\n [mjClickable]=\"item.Label\"\n role=\"link\"\n [testId]=\"'nav-item-' + item.Label\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\"\n [class.active]=\"isActive(item)\"\n [class.dynamic]=\"isDynamic(item)\"\n [class.no-icon]=\"!item.Icon\"\n (click)=\"onNavClick(item, $event)\">\n @if (item.Icon) {\n <i [class]=\"item.Icon\"></i>\n }\n <span>{{ item.Label }}</span>\n @if (item.Badge) {\n <span class=\"badge\">{{ item.Badge }}</span>\n }\n @if (isDynamic(item)) {\n <button class=\"dismiss-btn\" title=\"Remove\" (click)=\"onDismiss(item, $event)\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n }\n @if (OverflowItems.length > 0) {\n <div\n #moreBtn\n class=\"nav-item nav-more-btn\"\n mjClickable=\"More navigation items\"\n testId=\"nav-more-btn\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-expanded]=\"MoreOpen\"\n [class.active]=\"OverflowHasActive\"\n (click)=\"ToggleMore($event)\">\n <i class=\"fa-solid fa-ellipsis\"></i>\n <span>More</span>\n <span class=\"more-count\">{{ OverflowItems.length }}</span>\n </div>\n }\n</nav>\n\n<!-- The dropdown lives OUTSIDE the clipped nav row (the row keeps\n overflow: hidden so fit-arithmetic slack can never paint over the\n header's action cluster). Anchored to the More button by measurement. -->\n@if (MoreOpen && OverflowItems.length > 0) {\n <div class=\"nav-more-dropdown\" [style.left.px]=\"MoreDropdownLeft\">\n @for (item of OverflowItems; track trackByNavItem($index, item)) {\n <div\n class=\"nav-more-item\"\n [mjClickable]=\"item.Label\"\n role=\"link\"\n [testId]=\"'nav-more-item-' + item.Label\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\"\n [class.active]=\"isActive(item)\"\n [class.dynamic]=\"isDynamic(item)\"\n (click)=\"onNavClick(item, $event)\">\n @if (item.Icon) {\n <i [class]=\"item.Icon\"></i>\n }\n <span class=\"more-item-label\">{{ item.Label }}</span>\n @if (item.Badge) {\n <span class=\"badge\">{{ item.Badge }}</span>\n }\n @if (isDynamic(item)) {\n <button class=\"dismiss-btn\" title=\"Remove\" (click)=\"onDismiss(item, $event)\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n }\n </div>\n}\n", styles: [":host {\n display: block;\n min-width: 0;\n position: relative;\n}\n/* Always clipped: if the fit arithmetic is ever a few px off (badge widths,\n font fallbacks), the row clips inside its own box instead of painting over\n the header's action cluster. The More dropdown renders outside this row. */\n.nav-list {\n display: flex;\n flex-wrap: nowrap;\n gap: var(--mj-space-1);\n overflow: hidden;\n}\n.nav-list .nav-item {\n flex-shrink: 0;\n}\n/* Tight mode: even one item + More can't fit, so the lone visible pill\n yields with an ellipsis and the More button stays whole. */\n.nav-list.tight .nav-item:not(.nav-more-btn) {\n flex-shrink: 1;\n min-width: 0;\n}\n.nav-list.tight .nav-item:not(.nav-more-btn) > span:first-of-type {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.nav-list .nav-item {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-2) var(--mj-space-4);\n border-radius: var(--mj-radius-md);\n text-decoration: none;\n color: var(--mj-text-secondary);\n font-weight: var(--mj-font-medium);\n font-size: var(--mj-text-sm);\n white-space: nowrap;\n transition: all var(--mj-transition-fast);\n position: relative;\n cursor: pointer;\n user-select: none;\n}\n.nav-list .nav-item:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.nav-list .nav-item:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n/* Active = brand-primary everywhere; the app's metadata color is an identity\n accent (icon glyphs), never a state color. Shared active-tint recipe:\n color-mix(in srgb, var(--mj-brand-primary) 10%, transparent) \u2014 keep in sync\n across nav pills, pinned app buttons, launcher cards, and omnibar rows. */\n.nav-list .nav-item.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-list .nav-item.active i {\n color: var(--mj-brand-primary);\n}\n.nav-list .nav-item i {\n font-size: var(--mj-text-base);\n}\n.nav-list .nav-item.no-icon {\n padding-left: var(--mj-space-3);\n padding-right: var(--mj-space-3);\n}\n.nav-list .nav-item .badge {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n}\n\n/* Dynamic nav items (recent orphan resources) \u2014 separator and dimming */\n.nav-list .nav-item:not(.dynamic) + .nav-item.dynamic {\n margin-left: var(--mj-space-1);\n padding-left: var(--mj-space-4);\n border-left: 1px solid var(--mj-border-default);\n}\n.nav-list .nav-item.dynamic:not(.active) {\n opacity: 0.7;\n}\n.nav-list .nav-item.dynamic:not(.active):hover {\n opacity: 1;\n}\n\n/* Dismiss button for dynamic nav items */\n.nav-list .nav-item .dismiss-btn {\n display: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--mj-text-disabled);\n font-size: 10px;\n cursor: pointer;\n transition: all var(--mj-transition-fast);\n margin-left: calc(-1 * var(--mj-space-0-5));\n}\n.nav-list .nav-item.dynamic:hover .dismiss-btn {\n display: flex;\n}\n.nav-list .nav-item .dismiss-btn:hover {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-secondary);\n}\n\n/* ---- Priority+ overflow: the More button + dropdown ---- */\n.nav-more-btn .more-count {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-secondary);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n line-height: var(--mj-leading-none);\n}\n.nav-more-btn.active .more-count {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-more-dropdown {\n position: absolute;\n top: calc(100% + var(--mj-space-2));\n left: 0; /* Overridden by the measured [style.left.px] anchor */\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-lg);\n min-width: 200px;\n width: max-content;\n max-height: calc(100vh - 80px);\n overflow-y: auto;\n padding: var(--mj-space-1) 0;\n z-index: 10000;\n}\n.nav-more-item {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2-5);\n padding: var(--mj-space-2-5) var(--mj-space-4);\n cursor: pointer;\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-secondary);\n white-space: nowrap;\n transition: all var(--mj-transition-fast);\n}\n.nav-more-item i {\n width: 18px;\n text-align: center;\n font-size: var(--mj-text-base);\n}\n.nav-more-item:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.nav-more-item:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -2px;\n}\n.nav-more-item.active {\n /* Shared active-tint recipe */\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-more-item .badge {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n}\n.nav-more-item .dismiss-btn {\n display: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--mj-text-disabled);\n font-size: 10px;\n cursor: pointer;\n}\n.nav-more-item.dynamic:hover .dismiss-btn {\n display: flex;\n}\n\n/* ---- Skeleton pills shown while nav items load ---- */\n.nav-skeleton {\n display: flex;\n gap: var(--mj-space-1);\n}\n.nav-skeleton-pill {\n height: 34px;\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-surface-sunken);\n animation: nav-skeleton-pulse 1.2s ease-in-out infinite;\n}\n@keyframes nav-skeleton-pulse {\n 0%, 100% { opacity: 0.55; }\n 50% { opacity: 1; }\n}\n@media (prefers-reduced-motion: reduce) {\n .nav-skeleton-pill {\n animation: none;\n opacity: 0.7;\n }\n}\n\n/* Mobile drawer vertical layout */\n:host-context(.mobile-nav-content) .nav-list {\n flex-direction: column;\n gap: var(--mj-space-0-5);\n padding: 0 var(--mj-space-3);\n}\n:host-context(.mobile-nav-content) .nav-list .nav-item {\n padding: var(--mj-space-3) var(--mj-space-4);\n border-radius: var(--mj-radius-md);\n min-height: 48px; /* Touch-target parity with the app switcher's mobile items */\n box-sizing: border-box;\n}\n:host-context(.mobile-nav-content) .nav-list .nav-item i {\n width: 20px;\n text-align: center;\n}\n"] }]
|
|
636
|
+
args: [{ standalone: false, selector: 'mj-app-nav', changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"nav-list\" #navList [class.tight]=\"Tight\">\n @if (Loading && navItems.length === 0) {\n <!-- Skeleton pills while GetNavItems() resolves \u2014 prevents header pop-in -->\n <div class=\"nav-skeleton\" aria-hidden=\"true\">\n <span class=\"nav-skeleton-pill\" style=\"width: 88px\"></span>\n <span class=\"nav-skeleton-pill\" style=\"width: 72px\"></span>\n <span class=\"nav-skeleton-pill\" style=\"width: 96px\"></span>\n </div>\n }\n @for (item of InlineItems; track trackByNavItem($index, item)) {\n <div\n class=\"nav-item\"\n [mjClickable]=\"item.Label\"\n role=\"link\"\n [testId]=\"'nav-item-' + item.Label\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\"\n [class.active]=\"isActive(item)\"\n [class.dynamic]=\"isDynamic(item)\"\n [class.no-icon]=\"!item.Icon\"\n (click)=\"onNavClick(item, $event)\">\n @if (item.Icon) {\n <i [class]=\"item.Icon\"></i>\n }\n <span>{{ item.Label }}</span>\n @if (item.Badge) {\n <span class=\"badge\">{{ item.Badge }}</span>\n }\n @if (isDynamic(item)) {\n <button class=\"dismiss-btn\" title=\"Remove\" (click)=\"onDismiss(item, $event)\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n }\n @if (OverflowItems.length > 0) {\n <div\n #moreBtn\n class=\"nav-item nav-more-btn\"\n mjClickable=\"More navigation items\"\n testId=\"nav-more-btn\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-expanded]=\"MoreOpen\"\n [class.active]=\"OverflowHasActive\"\n (click)=\"ToggleMore($event)\">\n <i class=\"fa-solid fa-ellipsis\"></i>\n <span>More</span>\n <span class=\"more-count\">{{ OverflowItems.length }}</span>\n </div>\n }\n <!-- Trailing slot: shell-level items that sit AFTER the app's own nav\n (the Records pill). Its width is reserved by\n recomputeFit so the priority+ arithmetic never lets nav items paint\n under it. -->\n <span class=\"nav-trailing\" #trailingSlot>\n <ng-content></ng-content>\n </span>\n</nav>\n\n<!-- The dropdown lives OUTSIDE the clipped nav row (the row keeps\n overflow: hidden so fit-arithmetic slack can never paint over the\n header's action cluster). Anchored to the More button by measurement. -->\n@if (MoreOpen && OverflowItems.length > 0) {\n <div class=\"nav-more-dropdown\" [style.left.px]=\"MoreDropdownLeft\">\n @for (item of OverflowItems; track trackByNavItem($index, item)) {\n <div\n class=\"nav-more-item\"\n [mjClickable]=\"item.Label\"\n role=\"link\"\n [testId]=\"'nav-more-item-' + item.Label\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\"\n [class.active]=\"isActive(item)\"\n [class.dynamic]=\"isDynamic(item)\"\n (click)=\"onNavClick(item, $event)\">\n @if (item.Icon) {\n <i [class]=\"item.Icon\"></i>\n }\n <span class=\"more-item-label\">{{ item.Label }}</span>\n @if (item.Badge) {\n <span class=\"badge\">{{ item.Badge }}</span>\n }\n @if (isDynamic(item)) {\n <button class=\"dismiss-btn\" title=\"Remove\" (click)=\"onDismiss(item, $event)\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n }\n </div>\n}\n", styles: [":host {\n display: block;\n min-width: 0;\n position: relative;\n}\n/* Always clipped: if the fit arithmetic is ever a few px off (badge widths,\n font fallbacks), the row clips inside its own box instead of painting over\n the header's action cluster. The More dropdown renders outside this row. */\n.nav-list {\n display: flex;\n flex-wrap: nowrap;\n gap: var(--mj-space-1);\n overflow: hidden;\n}\n.nav-list .nav-item {\n flex-shrink: 0;\n}\n/* Trailing slot for projected shell items (the Records pill) \u2014 rides at the\n end of the row; recomputeFit reserves its width so it never gets clipped. */\n.nav-list .nav-trailing {\n display: flex;\n align-items: center;\n flex: 0 0 auto;\n}\n/* Tight mode: even one item + More can't fit, so the lone visible pill\n yields with an ellipsis and the More button stays whole. */\n.nav-list.tight .nav-item:not(.nav-more-btn) {\n flex-shrink: 1;\n min-width: 0;\n}\n.nav-list.tight .nav-item:not(.nav-more-btn) > span:first-of-type {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.nav-list .nav-item {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-2) var(--mj-space-4);\n border-radius: var(--mj-radius-md);\n text-decoration: none;\n color: var(--mj-text-secondary);\n font-weight: var(--mj-font-medium);\n font-size: var(--mj-text-sm);\n white-space: nowrap;\n transition: all var(--mj-transition-fast);\n position: relative;\n cursor: pointer;\n user-select: none;\n}\n.nav-list .nav-item:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.nav-list .nav-item:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n/* Active = brand-primary everywhere; the app's metadata color is an identity\n accent (icon glyphs), never a state color. Shared active-tint recipe:\n color-mix(in srgb, var(--mj-brand-primary) 10%, transparent) \u2014 keep in sync\n across nav pills, pinned app buttons, launcher cards, and omnibar rows. */\n.nav-list .nav-item.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-list .nav-item.active i {\n color: var(--mj-brand-primary);\n}\n.nav-list .nav-item i {\n font-size: var(--mj-text-base);\n}\n.nav-list .nav-item.no-icon {\n padding-left: var(--mj-space-3);\n padding-right: var(--mj-space-3);\n}\n.nav-list .nav-item .badge {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n}\n\n/* Dynamic nav items (recent orphan resources) \u2014 separator and dimming */\n.nav-list .nav-item:not(.dynamic) + .nav-item.dynamic {\n margin-left: var(--mj-space-1);\n padding-left: var(--mj-space-4);\n border-left: 1px solid var(--mj-border-default);\n}\n.nav-list .nav-item.dynamic:not(.active) {\n opacity: 0.7;\n}\n.nav-list .nav-item.dynamic:not(.active):hover {\n opacity: 1;\n}\n\n/* Dismiss button for dynamic nav items */\n.nav-list .nav-item .dismiss-btn {\n display: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--mj-text-disabled);\n font-size: 10px;\n cursor: pointer;\n transition: all var(--mj-transition-fast);\n margin-left: calc(-1 * var(--mj-space-0-5));\n}\n.nav-list .nav-item.dynamic:hover .dismiss-btn {\n display: flex;\n}\n.nav-list .nav-item .dismiss-btn:hover {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-secondary);\n}\n\n/* ---- Priority+ overflow: the More button + dropdown ---- */\n.nav-more-btn .more-count {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-secondary);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n line-height: var(--mj-leading-none);\n}\n.nav-more-btn.active .more-count {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-more-dropdown {\n position: absolute;\n top: calc(100% + var(--mj-space-2));\n left: 0; /* Overridden by the measured [style.left.px] anchor */\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-lg);\n min-width: 200px;\n width: max-content;\n max-height: calc(100vh - 80px);\n overflow-y: auto;\n padding: var(--mj-space-1) 0;\n z-index: 10000;\n}\n.nav-more-item {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2-5);\n padding: var(--mj-space-2-5) var(--mj-space-4);\n cursor: pointer;\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-secondary);\n white-space: nowrap;\n transition: all var(--mj-transition-fast);\n}\n.nav-more-item i {\n width: 18px;\n text-align: center;\n font-size: var(--mj-text-base);\n}\n.nav-more-item:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.nav-more-item:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -2px;\n}\n.nav-more-item.active {\n /* Shared active-tint recipe */\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n.nav-more-item .badge {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n border-radius: var(--mj-radius-full);\n padding: var(--mj-space-0-5) var(--mj-space-1-5);\n font-size: 11px;\n font-weight: var(--mj-font-semibold);\n min-width: 18px;\n text-align: center;\n}\n.nav-more-item .dismiss-btn {\n display: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--mj-text-disabled);\n font-size: 10px;\n cursor: pointer;\n}\n.nav-more-item.dynamic:hover .dismiss-btn {\n display: flex;\n}\n\n/* ---- Skeleton pills shown while nav items load ---- */\n.nav-skeleton {\n display: flex;\n gap: var(--mj-space-1);\n}\n.nav-skeleton-pill {\n height: 34px;\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-surface-sunken);\n animation: nav-skeleton-pulse 1.2s ease-in-out infinite;\n}\n@keyframes nav-skeleton-pulse {\n 0%, 100% { opacity: 0.55; }\n 50% { opacity: 1; }\n}\n@media (prefers-reduced-motion: reduce) {\n .nav-skeleton-pill {\n animation: none;\n opacity: 0.7;\n }\n}\n\n/* Mobile drawer vertical layout */\n:host-context(.mobile-nav-content) .nav-list {\n flex-direction: column;\n gap: var(--mj-space-0-5);\n padding: 0 var(--mj-space-3);\n}\n:host-context(.mobile-nav-content) .nav-list .nav-item {\n padding: var(--mj-space-3) var(--mj-space-4);\n border-radius: var(--mj-radius-md);\n min-height: 48px; /* Touch-target parity with the app switcher's mobile items */\n box-sizing: border-box;\n}\n:host-context(.mobile-nav-content) .nav-list .nav-item i {\n width: 20px;\n text-align: center;\n}\n"] }]
|
|
624
637
|
}], () => [{ type: i1.WorkspaceStateManager }, { type: i2.SharedService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }], { navListRef: [{
|
|
625
638
|
type: ViewChild,
|
|
626
639
|
args: ['navList']
|
|
627
640
|
}], moreBtnRef: [{
|
|
628
641
|
type: ViewChild,
|
|
629
642
|
args: ['moreBtn']
|
|
643
|
+
}], trailingSlotRef: [{
|
|
644
|
+
type: ViewChild,
|
|
645
|
+
args: ['trailingSlot']
|
|
630
646
|
}], navItemClick: [{
|
|
631
647
|
type: Output
|
|
632
648
|
}], navItemDismiss: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-nav.component.js","sourceRoot":"","sources":["../../../../../src/lib/shell/components/header/app-nav.component.ts","../../../../../src/lib/shell/components/header/app-nav.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, AfterViewInit, ChangeDetectorRef, ChangeDetectionStrategy, ElementRef, ViewChild, HostListener } from '@angular/core';\nimport { BaseApplication, DynamicNavItem, NavItem, WorkspaceStateManager, WorkspaceConfiguration } from '@memberjunction/ng-base-application';\nimport { SharedService } from '@memberjunction/ng-shared';\nimport { Subject, takeUntil } from 'rxjs';\n\n/**\n * Event emitted when a nav item is clicked\n */\nexport interface NavItemClickEvent {\n item: NavItem;\n shiftKey: boolean;\n}\n\n/**\n * Horizontal navigation items for the current app.\n * Uses OnPush change detection and reactive state management for optimal performance.\n *\n * Overflow behavior (priority+ pattern): when the header can't fit every nav item,\n * trailing items collapse into a \"More\" dropdown instead of squeezing the header's\n * action cluster off-screen. Item widths are measured once per nav-items change\n * (all items render for one frame, widths are cached), then every resize recomputes\n * the fit from the cache — no re-measure, no layout thrash.\n */\n@Component({\n standalone: false,\n selector: 'mj-app-nav',\n templateUrl: './app-nav.component.html',\n styleUrls: ['./app-nav.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class AppNavComponent implements OnInit, OnDestroy, AfterViewInit {\n private destroy$ = new Subject<void>();\n private _app: BaseApplication | null = null;\n private _cachedNavItems: NavItem[] = [];\n private _servicesInjected = false;\n\n /**\n * Monotonically increasing counter used to detect and discard stale async results.\n *\n * Because GetNavItems() is async (HomeApplication does a DB lookup for record names),\n * and RxJS subscribe() does NOT serialize async callbacks, multiple calls to\n * updateCachedData() can overlap. Without this guard, a slow call (e.g., Home app\n * doing a DB lookup) that started BEFORE a fast call (e.g., switching to App B)\n * could resolve AFTER the fast call and overwrite the correct nav items with stale ones.\n *\n * How it works:\n * 1. Each updateCachedData() call increments this counter and captures it as `gen`\n * 2. After the await, it checks: does `gen` still match `_updateGeneration`?\n * 3. If not, a newer call started while we were waiting — discard our stale results\n */\n private _updateGeneration = 0;\n\n // Map of nav item key (Route or Label) to active state\n private activeStateMap = new Map<string, boolean>();\n\n // ---- Overflow (priority+) state ----\n /** Cached natural pixel width of each nav item, parallel to navItems order */\n private itemWidths: number[] = [];\n /** Measured width of the More button (fallback used until first render) */\n private moreBtnWidth = 120;\n /** Left offset (px, relative to host) where the More dropdown anchors */\n public MoreDropdownLeft = 0;\n /** True when even one item + More can't fit — the lone visible pill\n * shrinks with an ellipsis instead of clipping the More button. */\n public Tight = false;\n /** Flex gap between nav items — measured from the rendered row (falls back\n * to --mj-space-1's default of 4px until first measurement) */\n private itemGap = 4;\n /** How many leading nav items are currently visible inline */\n public VisibleCount = Number.MAX_SAFE_INTEGER;\n /** Whether the More dropdown is open */\n public MoreOpen = false;\n /** True while nav items are being (re)loaded for skeleton display */\n public Loading = false;\n\n private resizeObserver: ResizeObserver | null = null;\n private _overflowEnabled = true;\n\n @ViewChild('navList') private navListRef?: ElementRef<HTMLElement>;\n @ViewChild('moreBtn') private moreBtnRef?: ElementRef<HTMLElement>;\n\n @Output() navItemClick = new EventEmitter<NavItemClickEvent>();\n @Output() navItemDismiss = new EventEmitter<NavItem>();\n\n /**\n * Disable the priority+ overflow behavior. Used by the mobile drawer instance,\n * where items stack vertically and can never overflow horizontally.\n */\n @Input()\n set OverflowEnabled(value: boolean) {\n this._overflowEnabled = value;\n if (!value) {\n this.VisibleCount = Number.MAX_SAFE_INTEGER;\n }\n }\n get OverflowEnabled(): boolean {\n return this._overflowEnabled;\n }\n\n constructor(\n private workspaceManager: WorkspaceStateManager,\n private sharedService: SharedService,\n private cdr: ChangeDetectorRef,\n private host: ElementRef<HTMLElement>\n ) {}\n\n /**\n * Input setter for app - triggers cache update when app changes\n */\n @Input()\n set app(value: BaseApplication | null) {\n if (this._app !== value) {\n this._app = value;\n this._cachedNavItems = []; // Clear stale items immediately so previous app's items don't flash\n this.activeStateMap.clear();\n this._servicesInjected = false; // Reset injection flag\n this.Loading = value != null; // Skeleton until GetNavItems resolves\n this.updateCachedData();\n this.cdr.markForCheck();\n }\n }\n\n get app(): BaseApplication | null {\n return this._app;\n }\n\n ngOnInit(): void {\n // Subscribe to workspace configuration changes.\n // Must rebuild nav items (not just active states) because dynamic nav items\n // are generated based on the currently active tab - when a user navigates\n // from one record to another (e.g., via OpenEntityRecord), the active tab\n // changes and the dynamic nav item needs to reflect the new record.\n this.workspaceManager.Configuration\n .pipe(takeUntil(this.destroy$))\n .subscribe(async () => {\n await this.updateCachedData();\n });\n }\n\n ngAfterViewInit(): void {\n if (this._overflowEnabled && typeof ResizeObserver !== 'undefined') {\n this.resizeObserver = new ResizeObserver(() => this.recomputeFit());\n this.resizeObserver.observe(this.host.nativeElement);\n }\n // Widths measured before webfonts arrive are stale (Explorer's Montserrat\n // loads late over the fallback stack) — invalidate the cache once fonts\n // settle so the fit is computed against real glyph metrics.\n if (this._overflowEnabled && typeof document !== 'undefined' && document.fonts?.ready) {\n document.fonts.ready.then(() => {\n this.itemWidths = [];\n this.recomputeFit();\n });\n }\n }\n\n ngOnDestroy(): void {\n this.resizeObserver?.disconnect();\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n /**\n * Update cached nav items and app color when app changes\n */\n private async updateCachedData(): Promise<void> {\n // Capture the current generation before any async work.\n // See _updateGeneration JSDoc for full explanation of the race condition this prevents.\n const gen = ++this._updateGeneration;\n\n if (this._app) {\n // Inject services once for apps that need them (e.g., HomeApplication for dynamic nav items)\n if (!this._servicesInjected) {\n const appWithServices = this._app as BaseApplication & {\n SetWorkspaceManager?: (manager: WorkspaceStateManager) => void;\n SetSharedService?: (service: SharedService) => void;\n };\n\n if (typeof appWithServices.SetWorkspaceManager === 'function') {\n appWithServices.SetWorkspaceManager(this.workspaceManager);\n }\n if (typeof appWithServices.SetSharedService === 'function') {\n appWithServices.SetSharedService(this.sharedService);\n }\n this._servicesInjected = true;\n }\n\n const items = await this._app.GetNavItems() || [];\n\n // If a newer call started while we were awaiting, our results are stale — bail out\n // so we don't overwrite the newer call's (correct) results.\n if (gen !== this._updateGeneration) {\n return;\n }\n\n // Only show items with Status 'Active' or undefined (default to Active)\n this._cachedNavItems = items.filter(item => !item.Status || item.Status === 'Active');\n } else {\n this._cachedNavItems = [];\n }\n\n this.Loading = false;\n\n // Update active states after nav items change\n const config = this.workspaceManager.GetConfiguration();\n this.updateActiveStates(config);\n\n // New item set: show everything for one frame so natural widths can be\n // measured (the row clips, so the temporary full row is invisible),\n // then collapse to fit. Tight mode MUST be off during measurement — its\n // flex-shrink would record squeezed widths and poison the fit arithmetic.\n this.VisibleCount = Number.MAX_SAFE_INTEGER;\n this.MoreOpen = false;\n this.Tight = false;\n this.syncChangeDetection();\n\n if (this._overflowEnabled) {\n requestAnimationFrame(() => {\n this.measureItemWidths();\n this.recomputeFit();\n });\n }\n }\n\n /**\n * Cache the natural width of every nav item. Called in the frame after a new\n * item set renders (all items visible, flex-shrink: 0, so widths are natural\n * even if the row is clipped by overflow: hidden).\n */\n private measureItemWidths(): void {\n const list = this.navListRef?.nativeElement;\n if (!list) {\n return;\n }\n const els = Array.from(list.querySelectorAll<HTMLElement>('.nav-item:not(.nav-more-btn)'));\n if (els.length !== this._cachedNavItems.length) {\n return; // View out of sync (mid-CD) — the next update cycle re-measures\n }\n this.itemWidths = els.map(el => el.offsetWidth);\n // Measure the real flex gap (rem-based tokens can differ from the 4px\n // fallback if the root font-size changes)\n const gap = parseFloat(getComputedStyle(list).columnGap);\n if (!Number.isNaN(gap)) {\n this.itemGap = gap;\n }\n }\n\n /**\n * Decide how many items fit in the host's current width; the rest collapse\n * into the More dropdown. Pure arithmetic over cached widths — safe to call\n * on every resize.\n */\n private recomputeFit(): void {\n if (!this._overflowEnabled || this._cachedNavItems.length === 0) {\n return;\n }\n const hostWidth = this.host.nativeElement.clientWidth;\n if (hostWidth === 0) {\n return; // Hidden (mobile breakpoint) — leave state alone\n }\n if (!this.ensureFreshWidthCache()) {\n return; // Cache still stale — a retry frame is already scheduled\n }\n if (this.moreBtnRef?.nativeElement) {\n this.moreBtnWidth = this.moreBtnRef.nativeElement.offsetWidth || this.moreBtnWidth;\n }\n const fit = this.computeFitCount(hostWidth);\n this.applyFit(fit.count, fit.tight);\n }\n\n /**\n * Self-heal for the item-width cache: if it's stale or missing (the\n * measurement frame can race the first render after a reload/app switch),\n * expand to the full item set, re-measure, and retry next frame until it\n * converges. Returns true when the cache matches the current item set.\n */\n private ensureFreshWidthCache(): boolean {\n if (this.itemWidths.length === this._cachedNavItems.length) {\n return true;\n }\n if (this.VisibleCount < this._cachedNavItems.length || this.Tight) {\n this.VisibleCount = Number.MAX_SAFE_INTEGER;\n this.Tight = false; // Never measure with tight-mode shrink applied\n this.syncChangeDetection();\n }\n this.measureItemWidths();\n if (this.itemWidths.length !== this._cachedNavItems.length) {\n requestAnimationFrame(() => this.recomputeFit());\n return false;\n }\n return true;\n }\n\n /** Pure fit arithmetic: how many leading items fit beside the More button */\n private computeFitCount(hostWidth: number): { count: number; tight: boolean } {\n const gap = this.itemGap;\n const n = this.itemWidths.length;\n const totalAll = this.itemWidths.reduce((a, b) => a + b, 0) + gap * (n - 1);\n if (totalAll <= hostWidth) {\n return { count: Number.MAX_SAFE_INTEGER, tight: false }; // Everything fits\n }\n // Reserve room for the More button, then fit as many leading items as possible\n let used = this.moreBtnWidth;\n let fit = 0;\n for (let i = 0; i < n; i++) {\n const next = used + gap + this.itemWidths[i];\n if (next > hostWidth) {\n break;\n }\n used = next;\n fit++;\n }\n // Never collapse below one visible item; tight = even the lone item + More overflows\n return { count: Math.max(1, fit), tight: fit === 0 };\n }\n\n /** Commit a fit result, closing More if nothing overflows; CD only on change */\n private applyFit(count: number, tight: boolean): void {\n if (count === this.VisibleCount && tight === this.Tight) {\n return;\n }\n this.VisibleCount = count;\n this.Tight = tight;\n if (this.OverflowItems.length === 0) {\n this.MoreOpen = false;\n }\n this.syncChangeDetection();\n }\n\n /**\n * markForCheck + synchronous detectChanges. In Angular 21 zoneless mode,\n * markForCheck() alone is unreliable when the trigger is an RxJS\n * subscription or rAF callback not tracked by the zoneless scheduler — the\n * dirty flag is set but no follow-up tick is scheduled. detectChanges()\n * throws if invoked re-entrantly during an in-flight CD pass; that's\n * harmless (the in-flight pass picks up our markForCheck), hence the catch.\n */\n private syncChangeDetection(): void {\n this.cdr.markForCheck();\n try {\n this.cdr.detectChanges();\n } catch {\n // Re-entrant CD — harmless.\n }\n }\n\n /** Items rendered inline in the header row */\n get InlineItems(): NavItem[] {\n return this._cachedNavItems.slice(0, this.VisibleCount);\n }\n\n /** Items collapsed into the More dropdown */\n get OverflowItems(): NavItem[] {\n return this.VisibleCount >= this._cachedNavItems.length\n ? []\n : this._cachedNavItems.slice(this.VisibleCount);\n }\n\n /** True when any overflowed item is the active one (More button shows active tint) */\n get OverflowHasActive(): boolean {\n return this.OverflowItems.some(item => this.isActive(item));\n }\n\n /** Open/close the More dropdown, anchoring it under the More button and clamping it inside the host */\n ToggleMore(event: MouseEvent): void {\n event.stopPropagation();\n this.MoreOpen = !this.MoreOpen;\n if (this.MoreOpen) {\n // Anchor the dropdown under the More button; once the dropdown has\n // rendered, clamp it so its right edge stays inside the host.\n this.MoreDropdownLeft = this.moreBtnRef?.nativeElement?.offsetLeft ?? 0;\n requestAnimationFrame(() => {\n const dropdown = this.host.nativeElement.querySelector<HTMLElement>('.nav-more-dropdown');\n if (dropdown) {\n const maxLeft = this.host.nativeElement.clientWidth - dropdown.offsetWidth;\n this.MoreDropdownLeft = Math.max(0, Math.min(this.MoreDropdownLeft, maxLeft));\n this.cdr.markForCheck();\n try {\n this.cdr.detectChanges();\n } catch {\n // Re-entrant CD — harmless.\n }\n }\n });\n }\n this.cdr.markForCheck();\n }\n\n /** Close the More dropdown on any outside click or Escape */\n @HostListener('document:click', ['$event'])\n onDocumentClick(event: MouseEvent): void {\n if (this.MoreOpen && !this.host.nativeElement.contains(event.target as Node)) {\n this.MoreOpen = false;\n this.cdr.markForCheck();\n }\n }\n\n /** Close the More dropdown on Escape */\n @HostListener('document:keydown.escape')\n onEscape(): void {\n if (this.MoreOpen) {\n this.MoreOpen = false;\n this.cdr.markForCheck();\n }\n }\n\n /**\n * Update active state map based on current workspace configuration\n */\n private updateActiveStates(config: WorkspaceConfiguration | null): void {\n this.activeStateMap.clear();\n\n if (!config || !this._app) {\n return;\n }\n\n const activeTab = config.tabs.find(t => t.id === config.activeTabId);\n if (!activeTab || activeTab.applicationId !== this._app.ID) {\n return;\n }\n\n // Compute active state for each nav item once\n for (const item of this._cachedNavItems) {\n const key = this.getItemKey(item);\n const isActive = this.computeIsActive(item, activeTab);\n this.activeStateMap.set(key, isActive);\n }\n }\n\n /**\n * Get unique key for nav item (used for tracking and active state).\n * Prefers RecordID for dynamic items to avoid label collisions.\n */\n private getItemKey(item: NavItem): string {\n return item.RecordID || item.Route || item.Label || '';\n }\n\n /**\n * Check if a nav item is dynamic (generated from recent orphan resources)\n */\n isDynamic(item: NavItem): boolean {\n return (item as DynamicNavItem).isDynamic === true;\n }\n\n /**\n * Compute if nav item is active based on active tab\n */\n private computeIsActive(item: NavItem, activeTab: any): boolean {\n // Check if nav item has a custom matching function (for dynamic items)\n const dynamicItem = item as NavItem & { isActiveMatch?: (tab: unknown) => boolean };\n if (dynamicItem.isActiveMatch && typeof dynamicItem.isActiveMatch === 'function') {\n return dynamicItem.isActiveMatch(activeTab);\n }\n\n const config = activeTab.configuration || {};\n\n // Match by DriverClass (most reliable for Custom resource types — always set correctly)\n if (item.DriverClass && (config['driverClass'] === item.DriverClass || config['resourceTypeDriverClass'] === item.DriverClass)) {\n return true;\n }\n\n // Match by navItemName from config (reliable — set when nav item opens)\n if (config['navItemName'] && config['navItemName'] === item.Label) {\n return true;\n }\n\n // Match by route (for route-based nav items)\n if (item.Route && config['route'] === item.Route) {\n return true;\n }\n\n // NOTE: We intentionally do NOT match by activeTab.title here.\n // Tab titles can be stale (updated asynchronously by DisplayNameChangedEvent\n // from cached components) and cause double-matches where two nav items\n // both appear active. DriverClass and navItemName are sufficient.\n return false;\n }\n\n /**\n * Get cached navigation items (no computation in getter)\n */\n get navItems(): NavItem[] {\n return this._cachedNavItems;\n }\n\n /**\n * Check if nav item is active (uses cached state from Map)\n */\n isActive(item: NavItem): boolean {\n const key = this.getItemKey(item);\n return this.activeStateMap.get(key) || false;\n }\n\n /**\n * Track function for @for to optimize rendering\n */\n trackByNavItem(_index: number, item: NavItem): string {\n return this.getItemKey(item);\n }\n\n /**\n * Handle nav item click\n */\n onNavClick(item: NavItem, event?: MouseEvent): void {\n this.MoreOpen = false;\n this.navItemClick.emit({\n item,\n shiftKey: event?.shiftKey || false\n });\n }\n\n /**\n * Handle dismiss click on a dynamic nav item.\n * Removes from the app's recent stack and refreshes nav items immediately.\n * Stops propagation so the nav click handler doesn't fire.\n */\n onDismiss(item: NavItem, event: MouseEvent): void {\n event.stopPropagation();\n\n // Remove from the app's recent stack directly so we can refresh immediately\n if (this._app) {\n const appWithRemove = this._app as BaseApplication & {\n RemoveDynamicNavItem?: (navItem: NavItem) => void;\n };\n if (typeof appWithRemove.RemoveDynamicNavItem === 'function') {\n appWithRemove.RemoveDynamicNavItem(item);\n this.updateCachedData();\n }\n }\n\n this.navItemDismiss.emit(item);\n }\n\n}\n","<nav class=\"nav-list\" #navList [class.tight]=\"Tight\">\n @if (Loading && navItems.length === 0) {\n <!-- Skeleton pills while GetNavItems() resolves — prevents header pop-in -->\n <div class=\"nav-skeleton\" aria-hidden=\"true\">\n <span class=\"nav-skeleton-pill\" style=\"width: 88px\"></span>\n <span class=\"nav-skeleton-pill\" style=\"width: 72px\"></span>\n <span class=\"nav-skeleton-pill\" style=\"width: 96px\"></span>\n </div>\n }\n @for (item of InlineItems; track trackByNavItem($index, item)) {\n <div\n class=\"nav-item\"\n [mjClickable]=\"item.Label\"\n role=\"link\"\n [testId]=\"'nav-item-' + item.Label\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\"\n [class.active]=\"isActive(item)\"\n [class.dynamic]=\"isDynamic(item)\"\n [class.no-icon]=\"!item.Icon\"\n (click)=\"onNavClick(item, $event)\">\n @if (item.Icon) {\n <i [class]=\"item.Icon\"></i>\n }\n <span>{{ item.Label }}</span>\n @if (item.Badge) {\n <span class=\"badge\">{{ item.Badge }}</span>\n }\n @if (isDynamic(item)) {\n <button class=\"dismiss-btn\" title=\"Remove\" (click)=\"onDismiss(item, $event)\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n }\n @if (OverflowItems.length > 0) {\n <div\n #moreBtn\n class=\"nav-item nav-more-btn\"\n mjClickable=\"More navigation items\"\n testId=\"nav-more-btn\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-expanded]=\"MoreOpen\"\n [class.active]=\"OverflowHasActive\"\n (click)=\"ToggleMore($event)\">\n <i class=\"fa-solid fa-ellipsis\"></i>\n <span>More</span>\n <span class=\"more-count\">{{ OverflowItems.length }}</span>\n </div>\n }\n</nav>\n\n<!-- The dropdown lives OUTSIDE the clipped nav row (the row keeps\n overflow: hidden so fit-arithmetic slack can never paint over the\n header's action cluster). Anchored to the More button by measurement. -->\n@if (MoreOpen && OverflowItems.length > 0) {\n <div class=\"nav-more-dropdown\" [style.left.px]=\"MoreDropdownLeft\">\n @for (item of OverflowItems; track trackByNavItem($index, item)) {\n <div\n class=\"nav-more-item\"\n [mjClickable]=\"item.Label\"\n role=\"link\"\n [testId]=\"'nav-more-item-' + item.Label\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\"\n [class.active]=\"isActive(item)\"\n [class.dynamic]=\"isDynamic(item)\"\n (click)=\"onNavClick(item, $event)\">\n @if (item.Icon) {\n <i [class]=\"item.Icon\"></i>\n }\n <span class=\"more-item-label\">{{ item.Label }}</span>\n @if (item.Badge) {\n <span class=\"badge\">{{ item.Badge }}</span>\n }\n @if (isDynamic(item)) {\n <button class=\"dismiss-btn\" title=\"Remove\" (click)=\"onDismiss(item, $event)\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n }\n </div>\n}\n"]}
|
|
1
|
+
{"version":3,"file":"app-nav.component.js","sourceRoot":"","sources":["../../../../../src/lib/shell/components/header/app-nav.component.ts","../../../../../src/lib/shell/components/header/app-nav.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, AfterViewInit, ChangeDetectorRef, ChangeDetectionStrategy, ElementRef, ViewChild, HostListener } from '@angular/core';\nimport { BaseApplication, DynamicNavItem, NavItem, WorkspaceStateManager, WorkspaceConfiguration } from '@memberjunction/ng-base-application';\nimport { SharedService } from '@memberjunction/ng-shared';\nimport { Subject, takeUntil } from 'rxjs';\n\n/**\n * Event emitted when a nav item is clicked\n */\nexport interface NavItemClickEvent {\n item: NavItem;\n shiftKey: boolean;\n}\n\n/**\n * Horizontal navigation items for the current app.\n * Uses OnPush change detection and reactive state management for optimal performance.\n *\n * Overflow behavior (priority+ pattern): when the header can't fit every nav item,\n * trailing items collapse into a \"More\" dropdown instead of squeezing the header's\n * action cluster off-screen. Item widths are measured once per nav-items change\n * (all items render for one frame, widths are cached), then every resize recomputes\n * the fit from the cache — no re-measure, no layout thrash.\n */\n@Component({\n standalone: false,\n selector: 'mj-app-nav',\n templateUrl: './app-nav.component.html',\n styleUrls: ['./app-nav.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class AppNavComponent implements OnInit, OnDestroy, AfterViewInit {\n private destroy$ = new Subject<void>();\n private _app: BaseApplication | null = null;\n private _cachedNavItems: NavItem[] = [];\n private _servicesInjected = false;\n\n /**\n * Monotonically increasing counter used to detect and discard stale async results.\n *\n * Because GetNavItems() is async (HomeApplication does a DB lookup for record names),\n * and RxJS subscribe() does NOT serialize async callbacks, multiple calls to\n * updateCachedData() can overlap. Without this guard, a slow call (e.g., Home app\n * doing a DB lookup) that started BEFORE a fast call (e.g., switching to App B)\n * could resolve AFTER the fast call and overwrite the correct nav items with stale ones.\n *\n * How it works:\n * 1. Each updateCachedData() call increments this counter and captures it as `gen`\n * 2. After the await, it checks: does `gen` still match `_updateGeneration`?\n * 3. If not, a newer call started while we were waiting — discard our stale results\n */\n private _updateGeneration = 0;\n\n // Map of nav item key (Route or Label) to active state\n private activeStateMap = new Map<string, boolean>();\n\n // ---- Overflow (priority+) state ----\n /** Cached natural pixel width of each nav item, parallel to navItems order */\n private itemWidths: number[] = [];\n /** Measured width of the More button (fallback used until first render) */\n private moreBtnWidth = 120;\n /** Left offset (px, relative to host) where the More dropdown anchors */\n public MoreDropdownLeft = 0;\n /** True when even one item + More can't fit — the lone visible pill\n * shrinks with an ellipsis instead of clipping the More button. */\n public Tight = false;\n /** Flex gap between nav items — measured from the rendered row (falls back\n * to --mj-space-1's default of 4px until first measurement) */\n private itemGap = 4;\n /** How many leading nav items are currently visible inline */\n public VisibleCount = Number.MAX_SAFE_INTEGER;\n /** Whether the More dropdown is open */\n public MoreOpen = false;\n /** True while nav items are being (re)loaded for skeleton display */\n public Loading = false;\n\n private resizeObserver: ResizeObserver | null = null;\n private _overflowEnabled = true;\n\n @ViewChild('navList') private navListRef?: ElementRef<HTMLElement>;\n @ViewChild('moreBtn') private moreBtnRef?: ElementRef<HTMLElement>;\n @ViewChild('trailingSlot') private trailingSlotRef?: ElementRef<HTMLElement>;\n\n @Output() navItemClick = new EventEmitter<NavItemClickEvent>();\n @Output() navItemDismiss = new EventEmitter<NavItem>();\n\n /**\n * Disable the priority+ overflow behavior. Used by the mobile drawer instance,\n * where items stack vertically and can never overflow horizontally.\n */\n @Input()\n set OverflowEnabled(value: boolean) {\n this._overflowEnabled = value;\n if (!value) {\n this.VisibleCount = Number.MAX_SAFE_INTEGER;\n }\n }\n get OverflowEnabled(): boolean {\n return this._overflowEnabled;\n }\n\n constructor(\n private workspaceManager: WorkspaceStateManager,\n private sharedService: SharedService,\n private cdr: ChangeDetectorRef,\n private host: ElementRef<HTMLElement>\n ) {}\n\n /**\n * Input setter for app - triggers cache update when app changes\n */\n @Input()\n set app(value: BaseApplication | null) {\n if (this._app !== value) {\n this._app = value;\n this._cachedNavItems = []; // Clear stale items immediately so previous app's items don't flash\n this.activeStateMap.clear();\n this._servicesInjected = false; // Reset injection flag\n this.Loading = value != null; // Skeleton until GetNavItems resolves\n this.updateCachedData();\n this.cdr.markForCheck();\n }\n }\n\n get app(): BaseApplication | null {\n return this._app;\n }\n\n ngOnInit(): void {\n // Subscribe to workspace configuration changes.\n // Must rebuild nav items (not just active states) because dynamic nav items\n // are generated based on the currently active tab - when a user navigates\n // from one record to another (e.g., via OpenEntityRecord), the active tab\n // changes and the dynamic nav item needs to reflect the new record.\n this.workspaceManager.Configuration\n .pipe(takeUntil(this.destroy$))\n .subscribe(async () => {\n await this.updateCachedData();\n });\n }\n\n ngAfterViewInit(): void {\n if (this._overflowEnabled && typeof ResizeObserver !== 'undefined') {\n this.resizeObserver = new ResizeObserver(() => this.recomputeFit());\n this.resizeObserver.observe(this.host.nativeElement);\n }\n // Widths measured before webfonts arrive are stale (Explorer's Montserrat\n // loads late over the fallback stack) — invalidate the cache once fonts\n // settle so the fit is computed against real glyph metrics.\n if (this._overflowEnabled && typeof document !== 'undefined' && document.fonts?.ready) {\n document.fonts.ready.then(() => {\n this.itemWidths = [];\n this.recomputeFit();\n });\n }\n }\n\n ngOnDestroy(): void {\n this.resizeObserver?.disconnect();\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n /**\n * Update cached nav items and app color when app changes\n */\n private async updateCachedData(): Promise<void> {\n // Capture the current generation before any async work.\n // See _updateGeneration JSDoc for full explanation of the race condition this prevents.\n const gen = ++this._updateGeneration;\n\n if (this._app) {\n // Inject services once for apps that need them (e.g., HomeApplication for dynamic nav items)\n if (!this._servicesInjected) {\n const appWithServices = this._app as BaseApplication & {\n SetWorkspaceManager?: (manager: WorkspaceStateManager) => void;\n SetSharedService?: (service: SharedService) => void;\n };\n\n if (typeof appWithServices.SetWorkspaceManager === 'function') {\n appWithServices.SetWorkspaceManager(this.workspaceManager);\n }\n if (typeof appWithServices.SetSharedService === 'function') {\n appWithServices.SetSharedService(this.sharedService);\n }\n this._servicesInjected = true;\n }\n\n const items = await this._app.GetNavItems() || [];\n\n // If a newer call started while we were awaiting, our results are stale — bail out\n // so we don't overwrite the newer call's (correct) results.\n if (gen !== this._updateGeneration) {\n return;\n }\n\n // Only show items with Status 'Active' or undefined (default to Active)\n this._cachedNavItems = items.filter(item => !item.Status || item.Status === 'Active');\n } else {\n this._cachedNavItems = [];\n }\n\n this.Loading = false;\n\n // Update active states after nav items change\n const config = this.workspaceManager.GetConfiguration();\n this.updateActiveStates(config);\n\n // New item set: show everything for one frame so natural widths can be\n // measured (the row clips, so the temporary full row is invisible),\n // then collapse to fit. Tight mode MUST be off during measurement — its\n // flex-shrink would record squeezed widths and poison the fit arithmetic.\n this.VisibleCount = Number.MAX_SAFE_INTEGER;\n this.MoreOpen = false;\n this.Tight = false;\n this.syncChangeDetection();\n\n if (this._overflowEnabled) {\n requestAnimationFrame(() => {\n this.measureItemWidths();\n this.recomputeFit();\n });\n }\n }\n\n /**\n * Cache the natural width of every nav item. Called in the frame after a new\n * item set renders (all items visible, flex-shrink: 0, so widths are natural\n * even if the row is clipped by overflow: hidden).\n */\n private measureItemWidths(): void {\n const list = this.navListRef?.nativeElement;\n if (!list) {\n return;\n }\n const els = Array.from(list.querySelectorAll<HTMLElement>('.nav-item:not(.nav-more-btn)'));\n if (els.length !== this._cachedNavItems.length) {\n return; // View out of sync (mid-CD) — the next update cycle re-measures\n }\n this.itemWidths = els.map(el => el.offsetWidth);\n // Measure the real flex gap (rem-based tokens can differ from the 4px\n // fallback if the root font-size changes)\n const gap = parseFloat(getComputedStyle(list).columnGap);\n if (!Number.isNaN(gap)) {\n this.itemGap = gap;\n }\n }\n\n /**\n * Decide how many items fit in the host's current width; the rest collapse\n * into the More dropdown. Pure arithmetic over cached widths — safe to call\n * on every resize.\n */\n private recomputeFit(): void {\n if (!this._overflowEnabled || this._cachedNavItems.length === 0) {\n return;\n }\n const hostWidth = this.host.nativeElement.clientWidth;\n if (hostWidth === 0) {\n return; // Hidden (mobile breakpoint) — leave state alone\n }\n if (!this.ensureFreshWidthCache()) {\n return; // Cache still stale — a retry frame is already scheduled\n }\n if (this.moreBtnRef?.nativeElement) {\n this.moreBtnWidth = this.moreBtnRef.nativeElement.offsetWidth || this.moreBtnWidth;\n }\n // Reserve the trailing slot's width (projected shell items, e.g. the\n // Records pill) so nav items yield to More before painting under it.\n // Measured fresh each pass — the slot appears/disappears with open records,\n // and every such change arrives via a configuration$ emission that re-runs\n // this fit anyway.\n const trailingWidth = this.trailingSlotRef?.nativeElement?.offsetWidth ?? 0;\n const fit = this.computeFitCount(hostWidth - trailingWidth);\n this.applyFit(fit.count, fit.tight);\n }\n\n /**\n * Self-heal for the item-width cache: if it's stale or missing (the\n * measurement frame can race the first render after a reload/app switch),\n * expand to the full item set, re-measure, and retry next frame until it\n * converges. Returns true when the cache matches the current item set.\n */\n private ensureFreshWidthCache(): boolean {\n if (this.itemWidths.length === this._cachedNavItems.length) {\n return true;\n }\n if (this.VisibleCount < this._cachedNavItems.length || this.Tight) {\n this.VisibleCount = Number.MAX_SAFE_INTEGER;\n this.Tight = false; // Never measure with tight-mode shrink applied\n this.syncChangeDetection();\n }\n this.measureItemWidths();\n if (this.itemWidths.length !== this._cachedNavItems.length) {\n requestAnimationFrame(() => this.recomputeFit());\n return false;\n }\n return true;\n }\n\n /** Pure fit arithmetic: how many leading items fit beside the More button */\n private computeFitCount(hostWidth: number): { count: number; tight: boolean } {\n const gap = this.itemGap;\n const n = this.itemWidths.length;\n const totalAll = this.itemWidths.reduce((a, b) => a + b, 0) + gap * (n - 1);\n if (totalAll <= hostWidth) {\n return { count: Number.MAX_SAFE_INTEGER, tight: false }; // Everything fits\n }\n // Reserve room for the More button, then fit as many leading items as possible\n let used = this.moreBtnWidth;\n let fit = 0;\n for (let i = 0; i < n; i++) {\n const next = used + gap + this.itemWidths[i];\n if (next > hostWidth) {\n break;\n }\n used = next;\n fit++;\n }\n // Never collapse below one visible item; tight = even the lone item + More overflows\n return { count: Math.max(1, fit), tight: fit === 0 };\n }\n\n /** Commit a fit result, closing More if nothing overflows; CD only on change */\n private applyFit(count: number, tight: boolean): void {\n if (count === this.VisibleCount && tight === this.Tight) {\n return;\n }\n this.VisibleCount = count;\n this.Tight = tight;\n if (this.OverflowItems.length === 0) {\n this.MoreOpen = false;\n }\n this.syncChangeDetection();\n }\n\n /**\n * markForCheck + synchronous detectChanges. In Angular 21 zoneless mode,\n * markForCheck() alone is unreliable when the trigger is an RxJS\n * subscription or rAF callback not tracked by the zoneless scheduler — the\n * dirty flag is set but no follow-up tick is scheduled. detectChanges()\n * throws if invoked re-entrantly during an in-flight CD pass; that's\n * harmless (the in-flight pass picks up our markForCheck), hence the catch.\n */\n private syncChangeDetection(): void {\n this.cdr.markForCheck();\n try {\n this.cdr.detectChanges();\n } catch {\n // Re-entrant CD — harmless.\n }\n }\n\n /** Items rendered inline in the header row */\n get InlineItems(): NavItem[] {\n return this._cachedNavItems.slice(0, this.VisibleCount);\n }\n\n /** Items collapsed into the More dropdown */\n get OverflowItems(): NavItem[] {\n return this.VisibleCount >= this._cachedNavItems.length\n ? []\n : this._cachedNavItems.slice(this.VisibleCount);\n }\n\n /** True when any overflowed item is the active one (More button shows active tint) */\n get OverflowHasActive(): boolean {\n return this.OverflowItems.some(item => this.isActive(item));\n }\n\n /** Open/close the More dropdown, anchoring it under the More button and clamping it inside the host */\n ToggleMore(event: MouseEvent): void {\n event.stopPropagation();\n this.MoreOpen = !this.MoreOpen;\n if (this.MoreOpen) {\n // Anchor the dropdown under the More button; once the dropdown has\n // rendered, clamp it so its right edge stays inside the host.\n this.MoreDropdownLeft = this.moreBtnRef?.nativeElement?.offsetLeft ?? 0;\n requestAnimationFrame(() => {\n const dropdown = this.host.nativeElement.querySelector<HTMLElement>('.nav-more-dropdown');\n if (dropdown) {\n const maxLeft = this.host.nativeElement.clientWidth - dropdown.offsetWidth;\n this.MoreDropdownLeft = Math.max(0, Math.min(this.MoreDropdownLeft, maxLeft));\n this.cdr.markForCheck();\n try {\n this.cdr.detectChanges();\n } catch {\n // Re-entrant CD — harmless.\n }\n }\n });\n }\n this.cdr.markForCheck();\n }\n\n /** Close the More dropdown on any outside click or Escape */\n @HostListener('document:click', ['$event'])\n onDocumentClick(event: MouseEvent): void {\n if (this.MoreOpen && !this.host.nativeElement.contains(event.target as Node)) {\n this.MoreOpen = false;\n this.cdr.markForCheck();\n }\n }\n\n /** Close the More dropdown on Escape */\n @HostListener('document:keydown.escape')\n onEscape(): void {\n if (this.MoreOpen) {\n this.MoreOpen = false;\n this.cdr.markForCheck();\n }\n }\n\n /**\n * Update active state map based on current workspace configuration\n */\n private updateActiveStates(config: WorkspaceConfiguration | null): void {\n this.activeStateMap.clear();\n\n if (!config || !this._app) {\n return;\n }\n\n const activeTab = config.tabs.find(t => t.id === config.activeTabId);\n if (!activeTab || activeTab.applicationId !== this._app.ID) {\n return;\n }\n\n // Compute active state for each nav item once\n for (const item of this._cachedNavItems) {\n const key = this.getItemKey(item);\n const isActive = this.computeIsActive(item, activeTab);\n this.activeStateMap.set(key, isActive);\n }\n }\n\n /**\n * Get unique key for nav item (used for tracking and active state).\n * Prefers RecordID for dynamic items to avoid label collisions.\n */\n private getItemKey(item: NavItem): string {\n return item.RecordID || item.Route || item.Label || '';\n }\n\n /**\n * Check if a nav item is dynamic (generated from recent orphan resources)\n */\n isDynamic(item: NavItem): boolean {\n return (item as DynamicNavItem).isDynamic === true;\n }\n\n /**\n * Compute if nav item is active based on active tab\n */\n private computeIsActive(item: NavItem, activeTab: any): boolean {\n // Check if nav item has a custom matching function (for dynamic items)\n const dynamicItem = item as NavItem & { isActiveMatch?: (tab: unknown) => boolean };\n if (dynamicItem.isActiveMatch && typeof dynamicItem.isActiveMatch === 'function') {\n return dynamicItem.isActiveMatch(activeTab);\n }\n\n const config = activeTab.configuration || {};\n\n // Match by DriverClass (most reliable for Custom resource types — always set correctly)\n if (item.DriverClass && (config['driverClass'] === item.DriverClass || config['resourceTypeDriverClass'] === item.DriverClass)) {\n return true;\n }\n\n // Match by navItemName from config (reliable — set when nav item opens)\n if (config['navItemName'] && config['navItemName'] === item.Label) {\n return true;\n }\n\n // Match by route (for route-based nav items)\n if (item.Route && config['route'] === item.Route) {\n return true;\n }\n\n // NOTE: We intentionally do NOT match by activeTab.title here.\n // Tab titles can be stale (updated asynchronously by DisplayNameChangedEvent\n // from cached components) and cause double-matches where two nav items\n // both appear active. DriverClass and navItemName are sufficient.\n return false;\n }\n\n /**\n * Get cached navigation items (no computation in getter)\n */\n get navItems(): NavItem[] {\n return this._cachedNavItems;\n }\n\n /**\n * Check if nav item is active (uses cached state from Map)\n */\n isActive(item: NavItem): boolean {\n const key = this.getItemKey(item);\n return this.activeStateMap.get(key) || false;\n }\n\n /**\n * Track function for @for to optimize rendering\n */\n trackByNavItem(_index: number, item: NavItem): string {\n return this.getItemKey(item);\n }\n\n /**\n * Handle nav item click\n */\n onNavClick(item: NavItem, event?: MouseEvent): void {\n this.MoreOpen = false;\n this.navItemClick.emit({\n item,\n shiftKey: event?.shiftKey || false\n });\n }\n\n /**\n * Handle dismiss click on a dynamic nav item.\n * Removes from the app's recent stack and refreshes nav items immediately.\n * Stops propagation so the nav click handler doesn't fire.\n */\n onDismiss(item: NavItem, event: MouseEvent): void {\n event.stopPropagation();\n\n // Remove from the app's recent stack directly so we can refresh immediately\n if (this._app) {\n const appWithRemove = this._app as BaseApplication & {\n RemoveDynamicNavItem?: (navItem: NavItem) => void;\n };\n if (typeof appWithRemove.RemoveDynamicNavItem === 'function') {\n appWithRemove.RemoveDynamicNavItem(item);\n this.updateCachedData();\n }\n }\n\n this.navItemDismiss.emit(item);\n }\n\n}\n","<nav class=\"nav-list\" #navList [class.tight]=\"Tight\">\n @if (Loading && navItems.length === 0) {\n <!-- Skeleton pills while GetNavItems() resolves — prevents header pop-in -->\n <div class=\"nav-skeleton\" aria-hidden=\"true\">\n <span class=\"nav-skeleton-pill\" style=\"width: 88px\"></span>\n <span class=\"nav-skeleton-pill\" style=\"width: 72px\"></span>\n <span class=\"nav-skeleton-pill\" style=\"width: 96px\"></span>\n </div>\n }\n @for (item of InlineItems; track trackByNavItem($index, item)) {\n <div\n class=\"nav-item\"\n [mjClickable]=\"item.Label\"\n role=\"link\"\n [testId]=\"'nav-item-' + item.Label\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\"\n [class.active]=\"isActive(item)\"\n [class.dynamic]=\"isDynamic(item)\"\n [class.no-icon]=\"!item.Icon\"\n (click)=\"onNavClick(item, $event)\">\n @if (item.Icon) {\n <i [class]=\"item.Icon\"></i>\n }\n <span>{{ item.Label }}</span>\n @if (item.Badge) {\n <span class=\"badge\">{{ item.Badge }}</span>\n }\n @if (isDynamic(item)) {\n <button class=\"dismiss-btn\" title=\"Remove\" (click)=\"onDismiss(item, $event)\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n }\n @if (OverflowItems.length > 0) {\n <div\n #moreBtn\n class=\"nav-item nav-more-btn\"\n mjClickable=\"More navigation items\"\n testId=\"nav-more-btn\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-expanded]=\"MoreOpen\"\n [class.active]=\"OverflowHasActive\"\n (click)=\"ToggleMore($event)\">\n <i class=\"fa-solid fa-ellipsis\"></i>\n <span>More</span>\n <span class=\"more-count\">{{ OverflowItems.length }}</span>\n </div>\n }\n <!-- Trailing slot: shell-level items that sit AFTER the app's own nav\n (the Records pill). Its width is reserved by\n recomputeFit so the priority+ arithmetic never lets nav items paint\n under it. -->\n <span class=\"nav-trailing\" #trailingSlot>\n <ng-content></ng-content>\n </span>\n</nav>\n\n<!-- The dropdown lives OUTSIDE the clipped nav row (the row keeps\n overflow: hidden so fit-arithmetic slack can never paint over the\n header's action cluster). Anchored to the More button by measurement. -->\n@if (MoreOpen && OverflowItems.length > 0) {\n <div class=\"nav-more-dropdown\" [style.left.px]=\"MoreDropdownLeft\">\n @for (item of OverflowItems; track trackByNavItem($index, item)) {\n <div\n class=\"nav-more-item\"\n [mjClickable]=\"item.Label\"\n role=\"link\"\n [testId]=\"'nav-more-item-' + item.Label\"\n [attr.aria-current]=\"isActive(item) ? 'page' : null\"\n [class.active]=\"isActive(item)\"\n [class.dynamic]=\"isDynamic(item)\"\n (click)=\"onNavClick(item, $event)\">\n @if (item.Icon) {\n <i [class]=\"item.Icon\"></i>\n }\n <span class=\"more-item-label\">{{ item.Label }}</span>\n @if (item.Badge) {\n <span class=\"badge\">{{ item.Badge }}</span>\n }\n @if (isDynamic(item)) {\n <button class=\"dismiss-btn\" title=\"Remove\" (click)=\"onDismiss(item, $event)\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n }\n </div>\n}\n"]}
|