@memberjunction/ng-explorer-core 6.1.0-edge.5 → 6.1.0-edge.7
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/README.md +19 -0
- package/dist/generated/lazy-feature-config.js +3 -3
- package/dist/generated/lazy-feature-config.js.map +1 -1
- package/dist/lib/about/about-dialog.component.js +4 -4
- package/dist/lib/about/about-dialog.component.js.map +1 -1
- package/dist/lib/command-palette/command-palette.component.js +1 -1
- package/dist/lib/conversation-feedback/conversation-feedback.js +1 -1
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js +1 -1
- package/dist/lib/oauth/oauth-callback.component.js +1 -1
- package/dist/lib/omnibar/omnibar-palette.component.d.ts.map +1 -1
- package/dist/lib/omnibar/omnibar-palette.component.js +9 -3
- package/dist/lib/omnibar/omnibar-palette.component.js.map +1 -1
- package/dist/lib/omnibar/providers/omnibar-record.provider.d.ts.map +1 -1
- package/dist/lib/omnibar/providers/omnibar-record.provider.js +9 -3
- package/dist/lib/omnibar/providers/omnibar-record.provider.js.map +1 -1
- package/dist/lib/profile/profile-dialog.component.js +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js +6 -14
- package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/list-detail-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/list-detail-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/query-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/query-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/record-resource.component.d.ts +3 -0
- package/dist/lib/resource-wrappers/record-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/record-resource.component.js +18 -4
- package/dist/lib/resource-wrappers/record-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.js +6 -2
- package/dist/lib/resource-wrappers/search-results-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js +6 -3
- package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
- package/dist/lib/shell/components/dialogs/app-access-dialog.component.js +2 -2
- package/dist/lib/shell/components/dialogs/app-access-dialog.component.js.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.js +1 -1
- package/dist/lib/shell/components/header/app-switcher.component.js +82 -83
- package/dist/lib/shell/components/header/app-switcher.component.js.map +1 -1
- package/dist/lib/shell/components/record-open/record-bar.component.js +1 -1
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js +1 -1
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.js +1 -1
- package/dist/lib/shell/components/record-open/records-hub-pill.component.js +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +25 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.js +88 -4
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js +2 -2
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js.map +1 -1
- package/dist/lib/shell/shell-chrome-policy.d.ts +54 -0
- package/dist/lib/shell/shell-chrome-policy.d.ts.map +1 -0
- package/dist/lib/shell/shell-chrome-policy.js +62 -0
- package/dist/lib/shell/shell-chrome-policy.js.map +1 -0
- package/dist/lib/shell/shell.component.d.ts +19 -0
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +190 -119
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.js +1 -1
- package/dist/lib/single-dashboard/single-dashboard.component.js +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.d.ts +9 -0
- package/dist/lib/single-list-detail/single-list-detail.component.d.ts.map +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.js +34 -15
- package/dist/lib/single-list-detail/single-list-detail.component.js.map +1 -1
- package/dist/lib/single-record/single-record.component.d.ts +10 -0
- package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
- package/dist/lib/single-record/single-record.component.js +23 -3
- package/dist/lib/single-record/single-record.component.js.map +1 -1
- package/dist/lib/single-search-result/single-search-result.component.d.ts.map +1 -1
- package/dist/lib/single-search-result/single-search-result.component.js +5 -2
- package/dist/lib/single-search-result/single-search-result.component.js.map +1 -1
- package/dist/lib/user-notifications/user-notifications.component.js +11 -12
- package/dist/lib/user-notifications/user-notifications.component.js.map +1 -1
- package/dist/lib/user-profile/user-profile.component.js +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +62 -62
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-switcher.component.js","sourceRoot":"","sources":["../../../../../src/lib/shell/components/header/app-switcher.component.ts","../../../../../src/lib/shell/components/header/app-switcher.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';\nimport { ApplicationManager, BaseApplication } from '@memberjunction/ng-base-application';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { UserAppConfigContentComponent } from '@memberjunction/ng-explorer-settings';\nimport { UUIDsEqual, NormalizeUUID } from '@memberjunction/global';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\nimport { Subject, takeUntil } from 'rxjs';\n\n/** UserInfoEngine setting key holding the recently-switched-to apps */\nconst RECENT_APPS_KEY = 'mj.shell.recentApps.v1';\n/** UserInfoEngine setting key for the launcher's All-apps sort preference */\nconst LAUNCHER_SORT_KEY = 'mj.shell.launcherSort.v1';\n/** How many recent entries to persist (display shows fewer) */\nconst RECENT_STORE_MAX = 8;\n/** How many recent cards to display */\nconst RECENT_DISPLAY_MAX = 3;\n/** 'auto' style: below this many apps the compact anchored panel is used */\nconst COMPACT_AUTO_THRESHOLD = 8;\n/** Compact mode: the filter input appears only at or above this many apps */\nconst COMPACT_FILTER_THRESHOLD = 10;\n\n/**\n * Presentation style for the app switcher, resolved from the\n * `Shell.AppSwitcher.Style` instance-config key:\n * - 'launcher': always the centered card launcher\n * - 'compact': always the anchored compact panel (dropdown-like)\n * - 'auto' (default): compact below {@link COMPACT_AUTO_THRESHOLD} apps\n */\nexport type AppSwitcherStyle = 'launcher' | 'compact' | 'auto';\n\ninterface RecentAppEntry {\n id: string;\n ts: number;\n}\n\n/**\n * App switcher in the header: the trigger shows the active app's identity\n * glyph; activating it opens a centered launcher overlay — a filterable grid\n * of app cards (identity glyph + name + Description summary), with the user's\n * most recently used apps surfaced first.\n *\n * Rendering model: the panel is a native <dialog> opened with showModal(),\n * so it lives in the browser's TOP LAYER — above every stacking context in\n * the app (the shell header's z-index:500 context, chat FAB, toasts). The\n * modal state also makes the rest of the document inert (focus cannot\n * escape) and the browser restores focus to the trigger on close.\n *\n * Accessibility model: every app card is a real focusable control (via\n * mjClickable), so Tab walks filter → close → cards → Configure, and\n * Enter/Space activate the focused card. Arrow keys are the fast path:\n * ArrowDown from the filter focuses the first card; arrows move between\n * cards; ArrowUp from the first card returns to the filter. Escape layers:\n * discard-bar → config view → filter → close. Exiting the config view with\n * unsaved changes shows an inline discard bar (a body-appended confirm\n * dialog would be inert/beneath the top layer).\n */\n@Component({\n standalone: false,\n selector: 'mj-app-switcher',\n templateUrl: './app-switcher.component.html',\n styleUrls: ['./app-switcher.component.css']\n})\nexport class AppSwitcherComponent extends BaseAngularComponent implements OnInit, OnDestroy {\n private destroy$ = new Subject<void>();\n @Input() activeApp: BaseApplication | null = null;\n @Input() isViewingSystemTab = false;\n /** ID of the app currently being loaded (shows loading indicator) */\n @Input() loadingAppId: string | null = null;\n /** Presentation style (from Shell.AppSwitcher.Style instance config) */\n @Input() SwitcherStyle: AppSwitcherStyle = 'auto';\n @Output() appSelected = new EventEmitter<string>();\n\n @ViewChild('filterInput') private filterInputRef?: ElementRef<HTMLInputElement>;\n @ViewChild('trigger') private triggerRef?: ElementRef<HTMLElement>;\n @ViewChild('panel') private panelRef?: ElementRef<HTMLDialogElement>;\n @ViewChild(UserAppConfigContentComponent) private configContent?: UserAppConfigContentComponent;\n\n showDropdown = false;\n /** When true, the launcher body shows the app-configuration view instead of the card grid */\n public ConfigMode = false;\n /** Pending action awaiting the inline unsaved-changes discard bar ('exit' = back to grid, 'close' = close launcher) */\n public PendingDiscard: 'exit' | 'close' | null = null;\n /** True when the panel renders as the compact anchored dropdown (few apps) */\n public CompactMode = false;\n /** All-apps sort: the user's configured order ('custom') or A–Z ('alpha').\n * Per-user preference, persisted via UserInfoEngine. */\n public SortMode: 'custom' | 'alpha' = 'custom';\n /** Anchor position for the compact panel (px, from the trigger's rect) */\n public AnchorLeft = 0;\n public AnchorTop = 0;\n\n /** Live filter text — matches app names AND Description summaries */\n public FilterText = '';\n\n private recentEntries: RecentAppEntry[] = [];\n\n constructor(private appManager: ApplicationManager, private cdr: ChangeDetectorRef) {\n super();\n }\n\n /**\n * UserInfoEngine scoped to this component's provider (multi-provider rule:\n * never reach for the global singleton in per-provider code paths). For the\n * default single-provider shell this resolves to the already-loaded engine\n * registered under the default connection. When no provider is available at\n * all (test environments), fall back to the global singleton rather than\n * letting GetProviderInstance construct a fresh unloaded engine.\n */\n private get userInfoEngine(): UserInfoEngine {\n const provider = this.ProviderToUse;\n return provider\n ? UserInfoEngine.GetProviderInstance<UserInfoEngine>(provider, UserInfoEngine) as UserInfoEngine\n : UserInfoEngine.Instance;\n }\n\n ngOnInit(): void {\n // The launcher's presentation (compact vs full, filter visibility) is\n // resolved from the app COUNT — which can change while the panel is open\n // (the user saves config changes that cross the auto threshold). React to\n // the same stream the app list uses so the presentation can't go stale.\n // Optional-chained: lightweight test stubs may not provide the observable.\n this.appManager.Applications?.pipe(takeUntil(this.destroy$)).subscribe(() => {\n if (this.showDropdown) {\n this.RefreshPresentation();\n }\n });\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n /**\n * Re-resolve the presentation for the CURRENT app count. No-op while the\n * config view is open (the panel is promoted to full size there anyway);\n * called when the app stream emits and when the config view exits.\n */\n RefreshPresentation(): void {\n if (this.ConfigMode) {\n return;\n }\n const wasCompact = this.CompactMode;\n this.CompactMode = this.resolveCompactMode();\n if (this.CompactMode && !wasCompact) {\n this.computeCompactAnchor();\n }\n // Mode flips can add/remove the filter input — if the focused element got\n // torn out of the DOM, focus would drop into the inert background. Re-home\n // it after the view settles.\n // Zoneless: an RxJS-driven mutation needs an explicit CD kick (same\n // pattern as AppNavComponent's workspace subscription).\n this.cdr.markForCheck();\n try {\n this.cdr.detectChanges();\n } catch {\n // Re-entrant CD — harmless.\n }\n if (wasCompact !== this.CompactMode) {\n requestAnimationFrame(() => {\n const panel = this.panelRef?.nativeElement;\n if (panel && !panel.contains(document.activeElement)) {\n if (this.ShowFilter) {\n this.filterInputRef?.nativeElement?.focus();\n } else {\n panel.querySelector<HTMLElement>('.app-card')?.focus();\n }\n }\n });\n }\n }\n\n /** Template handler for the filter input's input event */\n OnFilterInput(event: Event): void {\n this.FilterText = (event.target as HTMLInputElement).value;\n this.OnFilterChange();\n }\n\n /**\n * Check if the app switcher should show loading state\n */\n get isLoading(): boolean {\n return this.loadingAppId !== null;\n }\n\n /**\n * Visible trigger label: the current app's name, or \"Apps\" when there's no\n * app context (system tabs, nothing active yet).\n */\n get TriggerLabel(): string {\n return this.activeApp && !this.isViewingSystemTab ? this.activeApp.Name : 'Apps';\n }\n\n /** Accessible name for the trigger — names the control AND the current app */\n get TriggerAriaLabel(): string {\n return this.activeApp && !this.isViewingSystemTab\n ? `Switch application — current: ${this.activeApp.Name}`\n : 'Switch application';\n }\n\n /**\n * Get applications that should appear in the app switcher\n * (NavigationStyle = 'App Switcher' or 'Both')\n */\n get apps(): BaseApplication[] {\n return this.appManager.GetAppSwitcherApps();\n }\n\n /** Recently-used apps (persisted per user), newest first, capped for display.\n * Compact mode skips the Recent section — with a handful of apps it's noise. */\n get RecentApps(): BaseApplication[] {\n if (this.CompactMode) {\n return [];\n }\n const byId = new Map(this.apps.map(a => [NormalizeUUID(a.ID), a]));\n const result: BaseApplication[] = [];\n for (const entry of this.recentEntries) {\n const app = byId.get(NormalizeUUID(entry.id));\n if (app) {\n result.push(app);\n if (result.length >= RECENT_DISPLAY_MAX) {\n break;\n }\n }\n }\n return this.applyFilter(result);\n }\n\n /** All switcher apps in the user's chosen sort (custom Sequence order or A–Z) */\n private get sortedApps(): BaseApplication[] {\n return this.SortMode === 'alpha'\n ? [...this.apps].sort((a, b) => a.Name.localeCompare(b.Name))\n : this.apps;\n }\n\n /** The complete \"All apps\" section, in the user's chosen sort. Apps shown in\n * Recent appear here TOO — being recently used must not make an app vanish\n * from its expected place in the full list. */\n get OtherApps(): BaseApplication[] {\n return this.applyFilter(this.sortedApps);\n }\n\n /** Flat visible list in RENDERED card order — the keyboard-navigation index\n * space. Filtering collapses the sections into one deduplicated list;\n * otherwise it's Recent followed by the full All-apps list (a recent app\n * intentionally appears twice, matching the two cards on screen). */\n get VisibleApps(): BaseApplication[] {\n if (this.IsFiltering) {\n return this.applyFilter(this.sortedApps);\n }\n return [...this.RecentApps, ...this.OtherApps];\n }\n\n /** Distinct app count for the screen-reader status (VisibleApps can contain\n * the same app twice when it's in both Recent and All apps) */\n get AnnouncedAppCount(): number {\n return this.IsFiltering ? this.VisibleApps.length : this.applyFilter(this.apps).length;\n }\n\n /** True when a filter is active (sections collapse into one result list) */\n get IsFiltering(): boolean {\n return this.FilterText.trim().length > 0;\n }\n\n /** Compact mode hides the filter under a handful of apps — it's noise there */\n get ShowFilter(): boolean {\n return !this.CompactMode || this.apps.length >= COMPACT_FILTER_THRESHOLD;\n }\n\n private applyFilter(apps: BaseApplication[]): BaseApplication[] {\n const q = this.FilterText.trim().toLowerCase();\n if (!q) {\n return apps;\n }\n return apps.filter(a =>\n a.Name.toLowerCase().includes(q) || (a.Description || '').toLowerCase().includes(q));\n }\n\n /** Stable DOM id for a card (arrow-key focus target) */\n OptionId(index: number): string {\n return `mj-launcher-opt-${index}`;\n }\n\n /** Trigger handler: open the launcher panel, or close it if already open */\n ToggleLauncher(): void {\n if (this.showDropdown) {\n this.closeLauncher();\n } else {\n this.openLauncher();\n }\n }\n\n /** Flip the All-apps sort and persist it (fail-silent, like recents) */\n ToggleSortMode(): void {\n this.SortMode = this.SortMode === 'alpha' ? 'custom' : 'alpha';\n try {\n this.userInfoEngine.SetSettingDebounced(LAUNCHER_SORT_KEY, this.SortMode);\n } catch {\n // Preference persistence must never break the launcher\n }\n }\n\n private loadSortMode(): void {\n try {\n this.SortMode = this.userInfoEngine.GetSetting(LAUNCHER_SORT_KEY) === 'alpha' ? 'alpha' : 'custom';\n } catch {\n this.SortMode = 'custom';\n }\n }\n\n private openLauncher(): void {\n this.loadRecents();\n this.loadSortMode();\n this.FilterText = '';\n this.ConfigMode = false;\n this.PendingDiscard = null;\n this.CompactMode = this.resolveCompactMode();\n if (this.CompactMode) {\n this.computeCompactAnchor();\n }\n this.showDropdown = true;\n // The dialog element renders under @if next frame: put it in the top\n // layer via showModal() (jsdom fallback: plain open attribute), then\n // focus the filter (or the first card when compact mode hides it).\n requestAnimationFrame(() => {\n const dialog = this.panelRef?.nativeElement;\n if (dialog && !dialog.open) {\n if (typeof dialog.showModal === 'function') {\n dialog.showModal();\n } else {\n dialog.setAttribute('open', '');\n }\n }\n if (this.ShowFilter) {\n this.filterInputRef?.nativeElement?.focus();\n } else {\n dialog?.querySelector<HTMLElement>('.app-card')?.focus();\n }\n });\n }\n\n /** Resolve the presentation for this open (auto = compact under the threshold) */\n private resolveCompactMode(): boolean {\n if (this.SwitcherStyle === 'compact') {\n return true;\n }\n if (this.SwitcherStyle === 'launcher') {\n return false;\n }\n return this.apps.length < COMPACT_AUTO_THRESHOLD;\n }\n\n /** Anchor the compact panel under the trigger, clamped to the viewport */\n private computeCompactAnchor(): void {\n const rect = this.triggerRef?.nativeElement?.getBoundingClientRect();\n if (!rect) {\n return;\n }\n const panelWidth = 320; // Keep in sync with .launcher-panel--compact width\n const margin = 8;\n this.AnchorLeft = Math.max(margin, Math.min(rect.left, window.innerWidth - panelWidth - margin));\n this.AnchorTop = rect.bottom + margin;\n }\n\n private closeLauncher(restoreFocus = true): void {\n const dialog = this.panelRef?.nativeElement;\n if (dialog?.open && typeof dialog.close === 'function') {\n dialog.close(); // Browser restores focus to the trigger automatically\n }\n this.showDropdown = false;\n this.FilterText = '';\n this.ConfigMode = false;\n this.PendingDiscard = null;\n if (restoreFocus) {\n requestAnimationFrame(() => {\n this.triggerRef?.nativeElement?.focus();\n });\n }\n }\n\n /** Guarded close for template paths (backdrop / close button): unsaved\n * config changes surface the inline discard bar instead of closing. */\n CloseLauncher(): void {\n if (this.ConfigMode && this.configContent?.HasChanges()) {\n this.PendingDiscard = 'close';\n return;\n }\n this.closeLauncher();\n }\n\n /** Backdrop clicks arrive as clicks on the <dialog> element itself */\n OnDialogClick(event: MouseEvent): void {\n if (event.target === this.panelRef?.nativeElement) {\n this.CloseLauncher();\n }\n }\n\n /** Native cancel (Esc while the browser owns the dialog) — route through\n * our layered Escape handling instead of closing unconditionally. */\n OnDialogCancel(event: Event): void {\n event.preventDefault();\n this.handleEscape();\n }\n\n /** Inline discard bar: confirm — perform the pending action, dropping changes */\n ConfirmDiscard(): void {\n const action = this.PendingDiscard;\n this.PendingDiscard = null;\n if (action === 'close') {\n this.closeLauncher();\n } else if (action === 'exit') {\n this.ExitConfigMode();\n }\n }\n\n /** Inline discard bar: keep editing */\n CancelDiscard(): void {\n this.PendingDiscard = null;\n }\n\n OnFilterChange(): void {\n // Filtering re-renders the card set; nothing else to sync — focus stays\n // in the input and Tab/arrows reach the new result set naturally.\n }\n\n /** Keyboard model on the filter input */\n OnFilterKeydown(event: KeyboardEvent): void {\n const visible = this.VisibleApps;\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n this.focusCard(0);\n break;\n case 'Enter':\n event.preventDefault();\n // While filtering, Enter opens the top result (omnibar semantics —\n // the footer advertises \"↵ open\"); unfiltered it needs an unambiguous\n // single app.\n if (this.IsFiltering && visible.length > 0) {\n this.selectApp(visible[0]);\n } else if (visible.length === 1) {\n this.selectApp(visible[0]);\n }\n break;\n case 'Escape':\n event.preventDefault();\n // Stop the bubble: the panel's own Escape handler would otherwise\n // re-evaluate AFTER we've mutated state and double-handle the key.\n event.stopPropagation();\n this.handleEscape();\n break;\n }\n }\n\n /** Arrow-key navigation between focused cards */\n OnCardKeydown(index: number, event: KeyboardEvent): void {\n switch (event.key) {\n case 'ArrowDown':\n case 'ArrowRight':\n event.preventDefault();\n this.focusCard(index + 1);\n break;\n case 'ArrowUp':\n case 'ArrowLeft':\n event.preventDefault();\n if (index === 0) {\n this.filterInputRef?.nativeElement?.focus();\n } else {\n this.focusCard(index - 1);\n }\n break;\n case 'Home':\n event.preventDefault();\n this.focusCard(0);\n break;\n case 'End':\n event.preventDefault();\n this.focusCard(this.VisibleApps.length - 1);\n break;\n }\n }\n\n /** Move DOM focus to the card at the given VisibleApps index (clamped) */\n private focusCard(index: number): void {\n const count = this.VisibleApps.length;\n if (count === 0) {\n return;\n }\n const clamped = Math.min(count - 1, Math.max(0, index));\n const el = document.getElementById(this.OptionId(clamped));\n if (el) {\n el.focus();\n // jsdom (unit tests) doesn't implement scrollIntoView — guard it.\n if (typeof el.scrollIntoView === 'function') {\n el.scrollIntoView({ block: 'nearest' });\n }\n }\n }\n\n /** Keep Tab cycling within the launcher's real focusables (filter, close, Configure) */\n /** Escape layering: discard bar → config view (guarded) → filter → close.\n * Tab is NOT manually trapped — showModal() makes the background inert,\n * which is a real trap (no leak when focus sits on a non-focusable). */\n OnPanelKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n this.handleEscape();\n }\n }\n\n private handleEscape(): void {\n if (this.PendingDiscard) {\n this.CancelDiscard();\n } else if (this.ConfigMode) {\n this.RequestExitConfigMode();\n } else if (this.IsFiltering) {\n this.FilterText = '';\n this.filterInputRef?.nativeElement?.focus();\n } else {\n this.closeLauncher();\n }\n }\n\n /**\n * Select an application.\n * When viewing a system tab, always emit to allow returning to the app.\n */\n selectApp(app: BaseApplication): void {\n this.recordRecent(app);\n this.closeLauncher(false); // Focus moves into the app, not back to the trigger\n if (!UUIDsEqual(app.ID, this.activeApp?.ID) || this.isViewingSystemTab) {\n this.appSelected.emit(app.ID);\n }\n }\n\n /** Case-insensitive UUID check whether an app is the currently active app. */\n IsActiveApp(app: BaseApplication): boolean {\n return UUIDsEqual(app.ID, this.activeApp?.ID);\n }\n\n /** Case-insensitive UUID check whether an app is the one currently loading. */\n IsLoadingApp(app: BaseApplication): boolean {\n return UUIDsEqual(app.ID, this.loadingAppId);\n }\n\n // ---- Recents persistence (MJ: User Settings via UserInfoEngine) ----\n\n private loadRecents(): void {\n try {\n const raw = this.userInfoEngine.GetSetting(RECENT_APPS_KEY);\n const parsed = raw ? JSON.parse(raw) : [];\n this.recentEntries = Array.isArray(parsed)\n ? parsed.filter((e: RecentAppEntry) => e && typeof e.id === 'string' && typeof e.ts === 'number')\n : [];\n } catch {\n this.recentEntries = [];\n }\n }\n\n private recordRecent(app: BaseApplication): void {\n const id = NormalizeUUID(app.ID);\n this.recentEntries = [\n { id, ts: Date.now() },\n ...this.recentEntries.filter(e => NormalizeUUID(e.id) !== id)\n ].slice(0, RECENT_STORE_MAX);\n try {\n this.userInfoEngine.SetSettingDebounced(RECENT_APPS_KEY, JSON.stringify(this.recentEntries));\n } catch {\n // Settings persistence must never block app switching (e.g. engine not\n // configured in tests or permission-constrained sessions).\n }\n }\n\n /**\n * Swap the launcher body to the app-configuration view (in-panel, seamless —\n * no separate dialog). The embedded mj-user-app-config-content loads on\n * creation.\n */\n EnterConfigMode(): void {\n this.ConfigMode = true;\n this.FilterText = '';\n this.PendingDiscard = null;\n // Move focus into the config view (the back button is its first focusable)\n requestAnimationFrame(() => {\n this.panelRef?.nativeElement\n ?.querySelector<HTMLElement>('.launcher-back')?.focus();\n });\n }\n\n /** Guarded exit from the config view: unsaved changes surface the inline\n * discard bar instead of silently dropping the user's edits. */\n RequestExitConfigMode(): void {\n if (this.configContent?.HasChanges()) {\n this.PendingDiscard = 'exit';\n return;\n }\n this.ExitConfigMode();\n }\n\n /**\n * Return from the configuration view to the app grid.\n * The app list refreshes reactively after a save: each UserApplication save\n * fires a BaseEntity event, UserInfoEngine's debounced refresh emits\n * DataChange$, and ApplicationManager.syncFromEngine() pushes the new list\n * to applications$, which the `apps` getter reads. No explicit reload needed.\n */\n ExitConfigMode(): void {\n this.ConfigMode = false;\n // The config session may have changed the app count across the compact\n // threshold — re-resolve the presentation for the view we're returning to.\n // (The Applications stream also triggers this when the post-save engine\n // refresh lands, whichever comes last wins with the freshest count.)\n this.RefreshPresentation();\n requestAnimationFrame(() => {\n if (this.ShowFilter) {\n this.filterInputRef?.nativeElement?.focus();\n } else {\n this.panelRef?.nativeElement?.querySelector<HTMLElement>('.app-card')?.focus();\n }\n });\n }\n}\n","<div class=\"app-switcher-container\" [class.loading]=\"isLoading\">\n <div class=\"app-switcher-button\"\n #trigger\n [mjClickable]=\"TriggerAriaLabel\"\n testId=\"app-switcher-trigger\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"showDropdown\"\n (click)=\"ToggleLauncher()\">\n <!-- Show spinner when loading, otherwise show app icon -->\n @if (isLoading) {\n <i class=\"app-icon loading-spinner fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n }\n @if (!isLoading) {\n <i class=\"app-icon\" [class]=\"activeApp?.Icon || 'fa-solid fa-cube'\"\n [style.color]=\"activeApp?.GetColor()\" aria-hidden=\"true\"></i>\n }\n <!-- Current app name; hidden at the mobile breakpoint (icon-only trigger) -->\n <span class=\"app-label\">{{ TriggerLabel }}</span>\n <i class=\"dropdown-arrow fa-solid fa-chevron-down\" aria-hidden=\"true\"></i>\n </div>\n\n @if (showDropdown) {\n <!-- Native <dialog> + showModal(): lives in the browser TOP LAYER (above\n every app stacking context — chat FAB, toasts), background is inert\n (real focus trap), and focus restores to the trigger on close. The\n backdrop is ::backdrop; clicks on it arrive as clicks on the dialog\n element itself (OnDialogClick). -->\n <dialog class=\"launcher-panel\"\n #panel\n role=\"dialog\"\n aria-modal=\"true\"\n [class.launcher-panel--compact]=\"CompactMode && !ConfigMode\"\n [style.--anchor-left]=\"AnchorLeft + 'px'\"\n [style.--anchor-top]=\"AnchorTop + 'px'\"\n [attr.aria-label]=\"ConfigMode ? null : 'Application launcher'\"\n [attr.aria-labelledby]=\"ConfigMode ? 'mj-launcher-config-title' : null\"\n (click)=\"OnDialogClick($event)\"\n (cancel)=\"OnDialogCancel($event)\"\n (keydown)=\"OnPanelKeydown($event)\">\n\n @if (ConfigMode) {\n <!-- Configuration view: same panel, body swapped -->\n <div class=\"launcher-head launcher-head--config\">\n <button class=\"launcher-back\" type=\"button\" aria-label=\"Back to apps\" (click)=\"RequestExitConfigMode()\">\n <i class=\"fa-solid fa-arrow-left\" aria-hidden=\"true\"></i>\n </button>\n <h2 class=\"launcher-title\" id=\"mj-launcher-config-title\">Configure apps</h2>\n <button class=\"launcher-close\" type=\"button\" aria-label=\"Close application launcher\" (click)=\"CloseLauncher()\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n @if (PendingDiscard) {\n <div class=\"launcher-discard-bar\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n <span class=\"discard-msg\">You have unsaved changes.</span>\n <button mjButton variant=\"danger\" size=\"sm\" (click)=\"ConfirmDiscard()\">Discard</button>\n <button mjButton size=\"sm\" (click)=\"CancelDiscard()\">Keep editing</button>\n </div>\n }\n <mj-user-app-config-content\n class=\"launcher-config-body\"\n [Provider]=\"Provider\"\n (Saved)=\"ExitConfigMode()\"\n (Cancelled)=\"RequestExitConfigMode()\">\n </mj-user-app-config-content>\n } @else {\n @if (ShowFilter) {\n <div class=\"launcher-head\">\n <div class=\"launcher-filter\">\n <i class=\"fa-solid fa-magnifying-glass\" aria-hidden=\"true\"></i>\n <input\n #filterInput\n type=\"text\"\n placeholder=\"Find an app…\"\n aria-label=\"Find an app\"\n [value]=\"FilterText\"\n (input)=\"OnFilterInput($event)\"\n (keydown)=\"OnFilterKeydown($event)\"\n autocomplete=\"off\"\n spellcheck=\"false\" />\n </div>\n <button class=\"launcher-close\" type=\"button\" aria-label=\"Close application launcher\" (click)=\"CloseLauncher()\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n <div class=\"launcher-scroll\">\n @if (IsFiltering) {\n <div class=\"launcher-grid\">\n @for (app of VisibleApps; track app.ID; let i = $index) {\n <ng-container *ngTemplateOutlet=\"appCard; context: { $implicit: app, idx: i }\"></ng-container>\n }\n </div>\n @if (VisibleApps.length === 0) {\n <div class=\"launcher-empty\">No apps match \"{{ FilterText }}\"</div>\n }\n } @else {\n @if (RecentApps.length > 0) {\n <div class=\"launcher-sect\">Recent</div>\n <div class=\"launcher-grid\" role=\"group\" aria-label=\"Recent applications\">\n @for (app of RecentApps; track app.ID; let i = $index) {\n <ng-container *ngTemplateOutlet=\"appCard; context: { $implicit: app, idx: i }\"></ng-container>\n }\n </div>\n }\n <div class=\"launcher-sect launcher-sect--row\">\n <span>All apps</span>\n <button class=\"launcher-sort-toggle\" type=\"button\"\n [attr.aria-pressed]=\"SortMode === 'alpha'\"\n [attr.aria-label]=\"SortMode === 'alpha' ? 'Sorted alphabetically — switch to your custom order' : 'Sorted by your custom order — switch to alphabetical'\"\n [title]=\"SortMode === 'alpha' ? 'Sorted A–Z — click for your custom order' : 'Custom order — click for A–Z'\"\n (click)=\"ToggleSortMode()\">\n <i [class]=\"SortMode === 'alpha' ? 'fa-solid fa-arrow-down-a-z' : 'fa-solid fa-bars-staggered'\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"launcher-grid\" role=\"group\" aria-label=\"All applications\">\n @for (app of OtherApps; track app.ID; let i = $index) {\n <ng-container *ngTemplateOutlet=\"appCard; context: { $implicit: app, idx: i + RecentApps.length }\"></ng-container>\n }\n </div>\n @if (VisibleApps.length === 0) {\n <div class=\"launcher-empty\">\n No applications are enabled for you yet — use Configure… below to add some.\n </div>\n }\n }\n </div>\n\n <div class=\"launcher-foot\">\n <span class=\"launcher-kbd-hints\" aria-hidden=\"true\">\n <span><kbd>↑</kbd><kbd>↓</kbd> navigate</span>\n <span><kbd>↵</kbd> open</span>\n <span><kbd>esc</kbd> close</span>\n </span>\n <button class=\"launcher-configure\" type=\"button\" testId=\"app-switcher-configure\" (click)=\"EnterConfigMode()\">\n <i class=\"fa-solid fa-gear\" aria-hidden=\"true\"></i>\n <span>Configure…</span>\n </button>\n </div>\n\n <span class=\"sr-only\" role=\"status\">{{ AnnouncedAppCount }} applications available</span>\n }\n </dialog>\n }\n</div>\n\n<!-- Shared app-card template: each card is a real focusable control (mjClickable\n adds role, tabindex 0, aria-label, and Enter/Space activation), so Tab walks\n the cards and arrow keys are the fast path. -->\n<ng-template #appCard let-app let-idx=\"idx\">\n <div class=\"app-card\"\n [id]=\"OptionId(idx)\"\n [mjClickable]=\"app.Name\"\n role=\"link\"\n [testId]=\"'app-switcher-item-' + app.Name\"\n [attr.aria-current]=\"IsActiveApp(app) ? 'true' : null\"\n [attr.aria-describedby]=\"app.Description ? OptionId(idx) + '-desc' : null\"\n [class.active]=\"IsActiveApp(app)\"\n [class.loading]=\"IsLoadingApp(app)\"\n [style.--app-color]=\"app.GetColor()\"\n (click)=\"selectApp(app)\"\n (keydown)=\"OnCardKeydown(idx, $event)\">\n <div class=\"app-card-glyph\">\n @if (IsLoadingApp(app)) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i [class]=\"app.Icon || 'fa-solid fa-cube'\" aria-hidden=\"true\"></i>\n }\n </div>\n <div class=\"app-card-text\">\n <div class=\"app-card-name\">\n <span class=\"app-card-name-label\">{{ app.Name }}</span>\n </div>\n @if (app.Description) {\n <div class=\"app-card-desc\" [id]=\"OptionId(idx) + '-desc'\">{{ app.Description }}</div>\n }\n </div>\n </div>\n</ng-template>\n\n"]}
|
|
1
|
+
{"version":3,"file":"app-switcher.component.js","sourceRoot":"","sources":["../../../../../src/lib/shell/components/header/app-switcher.component.ts","../../../../../src/lib/shell/components/header/app-switcher.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';\nimport { ApplicationManager, BaseApplication } from '@memberjunction/ng-base-application';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { UserAppConfigContentComponent } from '@memberjunction/ng-explorer-settings';\nimport { UUIDsEqual, NormalizeUUID } from '@memberjunction/global';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\nimport { Subject, takeUntil } from 'rxjs';\n\n/** UserInfoEngine setting key holding the recently-switched-to apps */\nconst RECENT_APPS_KEY = 'mj.shell.recentApps.v1';\n/** UserInfoEngine setting key for the launcher's All-apps sort preference */\nconst LAUNCHER_SORT_KEY = 'mj.shell.launcherSort.v1';\n/** How many recent entries to persist (display shows fewer) */\nconst RECENT_STORE_MAX = 8;\n/** How many recent cards to display */\nconst RECENT_DISPLAY_MAX = 3;\n/** 'auto' style: below this many apps the compact anchored panel is used */\nconst COMPACT_AUTO_THRESHOLD = 8;\n/** Compact mode: the filter input appears only at or above this many apps */\nconst COMPACT_FILTER_THRESHOLD = 10;\n\n/**\n * Presentation style for the app switcher, resolved from the\n * `Shell.AppSwitcher.Style` instance-config key:\n * - 'launcher': always the centered card launcher\n * - 'compact': always the anchored compact panel (dropdown-like)\n * - 'auto' (default): compact below {@link COMPACT_AUTO_THRESHOLD} apps\n */\nexport type AppSwitcherStyle = 'launcher' | 'compact' | 'auto';\n\ninterface RecentAppEntry {\n id: string;\n ts: number;\n}\n\n/**\n * App switcher in the header: the trigger shows the active app's identity\n * glyph; activating it opens a centered launcher overlay — a filterable grid\n * of app cards (identity glyph + name + Description summary), with the user's\n * most recently used apps surfaced first.\n *\n * Rendering model: the panel is a native <dialog> opened with showModal(),\n * so it lives in the browser's TOP LAYER — above every stacking context in\n * the app (the shell header's z-index:500 context, chat FAB, toasts). The\n * modal state also makes the rest of the document inert (focus cannot\n * escape) and the browser restores focus to the trigger on close.\n *\n * Accessibility model: every app card is a real focusable control (via\n * mjClickable), so Tab walks filter → close → cards → Configure, and\n * Enter/Space activate the focused card. Arrow keys are the fast path:\n * ArrowDown from the filter focuses the first card; arrows move between\n * cards; ArrowUp from the first card returns to the filter. Escape layers:\n * discard-bar → config view → filter → close. Exiting the config view with\n * unsaved changes shows an inline discard bar (a body-appended confirm\n * dialog would be inert/beneath the top layer).\n */\n@Component({\n standalone: false,\n selector: 'mj-app-switcher',\n templateUrl: './app-switcher.component.html',\n styleUrls: ['./app-switcher.component.css']\n})\nexport class AppSwitcherComponent extends BaseAngularComponent implements OnInit, OnDestroy {\n private destroy$ = new Subject<void>();\n @Input() activeApp: BaseApplication | null = null;\n @Input() isViewingSystemTab = false;\n /** ID of the app currently being loaded (shows loading indicator) */\n @Input() loadingAppId: string | null = null;\n /** Presentation style (from Shell.AppSwitcher.Style instance config) */\n @Input() SwitcherStyle: AppSwitcherStyle = 'auto';\n @Output() appSelected = new EventEmitter<string>();\n\n @ViewChild('filterInput') private filterInputRef?: ElementRef<HTMLInputElement>;\n @ViewChild('trigger') private triggerRef?: ElementRef<HTMLElement>;\n @ViewChild('panel') private panelRef?: ElementRef<HTMLDialogElement>;\n @ViewChild(UserAppConfigContentComponent) private configContent?: UserAppConfigContentComponent;\n\n showDropdown = false;\n /** When true, the launcher body shows the app-configuration view instead of the card grid */\n public ConfigMode = false;\n /** Pending action awaiting the inline unsaved-changes discard bar ('exit' = back to grid, 'close' = close launcher) */\n public PendingDiscard: 'exit' | 'close' | null = null;\n /** True when the panel renders as the compact anchored dropdown (few apps) */\n public CompactMode = false;\n /** All-apps sort: the user's configured order ('custom') or A–Z ('alpha').\n * Per-user preference, persisted via UserInfoEngine. */\n public SortMode: 'custom' | 'alpha' = 'custom';\n /** Anchor position for the compact panel (px, from the trigger's rect) */\n public AnchorLeft = 0;\n public AnchorTop = 0;\n\n /** Live filter text — matches app names AND Description summaries */\n public FilterText = '';\n\n private recentEntries: RecentAppEntry[] = [];\n\n constructor(private appManager: ApplicationManager, private cdr: ChangeDetectorRef) {\n super();\n }\n\n /**\n * UserInfoEngine scoped to this component's provider (multi-provider rule:\n * never reach for the global singleton in per-provider code paths). For the\n * default single-provider shell this resolves to the already-loaded engine\n * registered under the default connection. When no provider is available at\n * all (test environments), fall back to the global singleton rather than\n * letting GetProviderInstance construct a fresh unloaded engine.\n */\n private get userInfoEngine(): UserInfoEngine {\n const provider = this.ProviderToUse;\n return provider\n ? UserInfoEngine.GetProviderInstance<UserInfoEngine>(provider, UserInfoEngine) as UserInfoEngine\n : UserInfoEngine.Instance;\n }\n\n ngOnInit(): void {\n // The launcher's presentation (compact vs full, filter visibility) is\n // resolved from the app COUNT — which can change while the panel is open\n // (the user saves config changes that cross the auto threshold). React to\n // the same stream the app list uses so the presentation can't go stale.\n // Optional-chained: lightweight test stubs may not provide the observable.\n this.appManager.Applications?.pipe(takeUntil(this.destroy$)).subscribe(() => {\n if (this.showDropdown) {\n this.RefreshPresentation();\n }\n });\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n /**\n * Re-resolve the presentation for the CURRENT app count. No-op while the\n * config view is open (the panel is promoted to full size there anyway);\n * called when the app stream emits and when the config view exits.\n */\n RefreshPresentation(): void {\n if (this.ConfigMode) {\n return;\n }\n const wasCompact = this.CompactMode;\n this.CompactMode = this.resolveCompactMode();\n if (this.CompactMode && !wasCompact) {\n this.computeCompactAnchor();\n }\n // Mode flips can add/remove the filter input — if the focused element got\n // torn out of the DOM, focus would drop into the inert background. Re-home\n // it after the view settles.\n // Zoneless: an RxJS-driven mutation needs an explicit CD kick (same\n // pattern as AppNavComponent's workspace subscription).\n this.cdr.markForCheck();\n try {\n this.cdr.detectChanges();\n } catch {\n // Re-entrant CD — harmless.\n }\n if (wasCompact !== this.CompactMode) {\n requestAnimationFrame(() => {\n const panel = this.panelRef?.nativeElement;\n if (panel && !panel.contains(document.activeElement)) {\n if (this.ShowFilter) {\n this.filterInputRef?.nativeElement?.focus();\n } else {\n panel.querySelector<HTMLElement>('.app-card')?.focus();\n }\n }\n });\n }\n }\n\n /** Template handler for the filter input's input event */\n OnFilterInput(event: Event): void {\n this.FilterText = (event.target as HTMLInputElement).value;\n this.OnFilterChange();\n }\n\n /**\n * Check if the app switcher should show loading state\n */\n get isLoading(): boolean {\n return this.loadingAppId !== null;\n }\n\n /**\n * Visible trigger label: the current app's name, or \"Apps\" when there's no\n * app context (system tabs, nothing active yet).\n */\n get TriggerLabel(): string {\n return this.activeApp && !this.isViewingSystemTab ? this.activeApp.Name : 'Apps';\n }\n\n /** Accessible name for the trigger — names the control AND the current app */\n get TriggerAriaLabel(): string {\n return this.activeApp && !this.isViewingSystemTab\n ? `Switch application — current: ${this.activeApp.Name}`\n : 'Switch application';\n }\n\n /**\n * Get applications that should appear in the app switcher\n * (NavigationStyle = 'App Switcher' or 'Both')\n */\n get apps(): BaseApplication[] {\n return this.appManager.GetAppSwitcherApps();\n }\n\n /** Recently-used apps (persisted per user), newest first, capped for display.\n * Compact mode skips the Recent section — with a handful of apps it's noise. */\n get RecentApps(): BaseApplication[] {\n if (this.CompactMode) {\n return [];\n }\n const byId = new Map(this.apps.map(a => [NormalizeUUID(a.ID), a]));\n const result: BaseApplication[] = [];\n for (const entry of this.recentEntries) {\n const app = byId.get(NormalizeUUID(entry.id));\n if (app) {\n result.push(app);\n if (result.length >= RECENT_DISPLAY_MAX) {\n break;\n }\n }\n }\n return this.applyFilter(result);\n }\n\n /** All switcher apps in the user's chosen sort (custom Sequence order or A–Z) */\n private get sortedApps(): BaseApplication[] {\n return this.SortMode === 'alpha'\n ? [...this.apps].sort((a, b) => a.Name.localeCompare(b.Name))\n : this.apps;\n }\n\n /** The complete \"All apps\" section, in the user's chosen sort. Apps shown in\n * Recent appear here TOO — being recently used must not make an app vanish\n * from its expected place in the full list. */\n get OtherApps(): BaseApplication[] {\n return this.applyFilter(this.sortedApps);\n }\n\n /** Flat visible list in RENDERED card order — the keyboard-navigation index\n * space. Filtering collapses the sections into one deduplicated list;\n * otherwise it's Recent followed by the full All-apps list (a recent app\n * intentionally appears twice, matching the two cards on screen). */\n get VisibleApps(): BaseApplication[] {\n if (this.IsFiltering) {\n return this.applyFilter(this.sortedApps);\n }\n return [...this.RecentApps, ...this.OtherApps];\n }\n\n /** Distinct app count for the screen-reader status (VisibleApps can contain\n * the same app twice when it's in both Recent and All apps) */\n get AnnouncedAppCount(): number {\n return this.IsFiltering ? this.VisibleApps.length : this.applyFilter(this.apps).length;\n }\n\n /** True when a filter is active (sections collapse into one result list) */\n get IsFiltering(): boolean {\n return this.FilterText.trim().length > 0;\n }\n\n /** Compact mode hides the filter under a handful of apps — it's noise there */\n get ShowFilter(): boolean {\n return !this.CompactMode || this.apps.length >= COMPACT_FILTER_THRESHOLD;\n }\n\n private applyFilter(apps: BaseApplication[]): BaseApplication[] {\n const q = this.FilterText.trim().toLowerCase();\n if (!q) {\n return apps;\n }\n return apps.filter(a =>\n a.Name.toLowerCase().includes(q) || (a.Description || '').toLowerCase().includes(q));\n }\n\n /** Stable DOM id for a card (arrow-key focus target) */\n OptionId(index: number): string {\n return `mj-launcher-opt-${index}`;\n }\n\n /** Trigger handler: open the launcher panel, or close it if already open */\n ToggleLauncher(): void {\n if (this.showDropdown) {\n this.closeLauncher();\n } else {\n this.openLauncher();\n }\n }\n\n /** Flip the All-apps sort and persist it (fail-silent, like recents) */\n ToggleSortMode(): void {\n this.SortMode = this.SortMode === 'alpha' ? 'custom' : 'alpha';\n try {\n this.userInfoEngine.SetSettingDebounced(LAUNCHER_SORT_KEY, this.SortMode);\n } catch {\n // Preference persistence must never break the launcher\n }\n }\n\n private loadSortMode(): void {\n try {\n this.SortMode = this.userInfoEngine.GetSetting(LAUNCHER_SORT_KEY) === 'alpha' ? 'alpha' : 'custom';\n } catch {\n this.SortMode = 'custom';\n }\n }\n\n private openLauncher(): void {\n this.loadRecents();\n this.loadSortMode();\n this.FilterText = '';\n this.ConfigMode = false;\n this.PendingDiscard = null;\n this.CompactMode = this.resolveCompactMode();\n if (this.CompactMode) {\n this.computeCompactAnchor();\n }\n this.showDropdown = true;\n // The dialog element renders under @if next frame: put it in the top\n // layer via showModal() (jsdom fallback: plain open attribute), then\n // focus the filter (or the first card when compact mode hides it).\n requestAnimationFrame(() => {\n const dialog = this.panelRef?.nativeElement;\n if (dialog && !dialog.open) {\n if (typeof dialog.showModal === 'function') {\n dialog.showModal();\n } else {\n dialog.setAttribute('open', '');\n }\n }\n if (this.ShowFilter) {\n this.filterInputRef?.nativeElement?.focus();\n } else {\n dialog?.querySelector<HTMLElement>('.app-card')?.focus();\n }\n });\n }\n\n /** Resolve the presentation for this open (auto = compact under the threshold) */\n private resolveCompactMode(): boolean {\n if (this.SwitcherStyle === 'compact') {\n return true;\n }\n if (this.SwitcherStyle === 'launcher') {\n return false;\n }\n return this.apps.length < COMPACT_AUTO_THRESHOLD;\n }\n\n /** Anchor the compact panel under the trigger, clamped to the viewport */\n private computeCompactAnchor(): void {\n const rect = this.triggerRef?.nativeElement?.getBoundingClientRect();\n if (!rect) {\n return;\n }\n const panelWidth = 320; // Keep in sync with .launcher-panel--compact width\n const margin = 8;\n this.AnchorLeft = Math.max(margin, Math.min(rect.left, window.innerWidth - panelWidth - margin));\n this.AnchorTop = rect.bottom + margin;\n }\n\n private closeLauncher(restoreFocus = true): void {\n const dialog = this.panelRef?.nativeElement;\n if (dialog?.open && typeof dialog.close === 'function') {\n dialog.close(); // Browser restores focus to the trigger automatically\n }\n this.showDropdown = false;\n this.FilterText = '';\n this.ConfigMode = false;\n this.PendingDiscard = null;\n if (restoreFocus) {\n requestAnimationFrame(() => {\n this.triggerRef?.nativeElement?.focus();\n });\n }\n }\n\n /** Guarded close for template paths (backdrop / close button): unsaved\n * config changes surface the inline discard bar instead of closing. */\n CloseLauncher(): void {\n if (this.ConfigMode && this.configContent?.HasChanges()) {\n this.PendingDiscard = 'close';\n return;\n }\n this.closeLauncher();\n }\n\n /** Backdrop clicks arrive as clicks on the <dialog> element itself */\n OnDialogClick(event: MouseEvent): void {\n if (event.target === this.panelRef?.nativeElement) {\n this.CloseLauncher();\n }\n }\n\n /** Native cancel (Esc while the browser owns the dialog) — route through\n * our layered Escape handling instead of closing unconditionally. */\n OnDialogCancel(event: Event): void {\n event.preventDefault();\n this.handleEscape();\n }\n\n /** Inline discard bar: confirm — perform the pending action, dropping changes */\n ConfirmDiscard(): void {\n const action = this.PendingDiscard;\n this.PendingDiscard = null;\n if (action === 'close') {\n this.closeLauncher();\n } else if (action === 'exit') {\n this.ExitConfigMode();\n }\n }\n\n /** Inline discard bar: keep editing */\n CancelDiscard(): void {\n this.PendingDiscard = null;\n }\n\n OnFilterChange(): void {\n // Filtering re-renders the card set; nothing else to sync — focus stays\n // in the input and Tab/arrows reach the new result set naturally.\n }\n\n /** Keyboard model on the filter input */\n OnFilterKeydown(event: KeyboardEvent): void {\n const visible = this.VisibleApps;\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n this.focusCard(0);\n break;\n case 'Enter':\n event.preventDefault();\n // While filtering, Enter opens the top result (omnibar semantics —\n // the footer advertises \"↵ open\"); unfiltered it needs an unambiguous\n // single app.\n if (this.IsFiltering && visible.length > 0) {\n this.selectApp(visible[0]);\n } else if (visible.length === 1) {\n this.selectApp(visible[0]);\n }\n break;\n case 'Escape':\n event.preventDefault();\n // Stop the bubble: the panel's own Escape handler would otherwise\n // re-evaluate AFTER we've mutated state and double-handle the key.\n event.stopPropagation();\n this.handleEscape();\n break;\n }\n }\n\n /** Arrow-key navigation between focused cards */\n OnCardKeydown(index: number, event: KeyboardEvent): void {\n switch (event.key) {\n case 'ArrowDown':\n case 'ArrowRight':\n event.preventDefault();\n this.focusCard(index + 1);\n break;\n case 'ArrowUp':\n case 'ArrowLeft':\n event.preventDefault();\n if (index === 0) {\n this.filterInputRef?.nativeElement?.focus();\n } else {\n this.focusCard(index - 1);\n }\n break;\n case 'Home':\n event.preventDefault();\n this.focusCard(0);\n break;\n case 'End':\n event.preventDefault();\n this.focusCard(this.VisibleApps.length - 1);\n break;\n }\n }\n\n /** Move DOM focus to the card at the given VisibleApps index (clamped) */\n private focusCard(index: number): void {\n const count = this.VisibleApps.length;\n if (count === 0) {\n return;\n }\n const clamped = Math.min(count - 1, Math.max(0, index));\n const el = document.getElementById(this.OptionId(clamped));\n if (el) {\n el.focus();\n // jsdom (unit tests) doesn't implement scrollIntoView — guard it.\n if (typeof el.scrollIntoView === 'function') {\n el.scrollIntoView({ block: 'nearest' });\n }\n }\n }\n\n /** Keep Tab cycling within the launcher's real focusables (filter, close, Configure) */\n /** Escape layering: discard bar → config view (guarded) → filter → close.\n * Tab is NOT manually trapped — showModal() makes the background inert,\n * which is a real trap (no leak when focus sits on a non-focusable). */\n OnPanelKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n this.handleEscape();\n }\n }\n\n private handleEscape(): void {\n if (this.PendingDiscard) {\n this.CancelDiscard();\n } else if (this.ConfigMode) {\n this.RequestExitConfigMode();\n } else if (this.IsFiltering) {\n this.FilterText = '';\n this.filterInputRef?.nativeElement?.focus();\n } else {\n this.closeLauncher();\n }\n }\n\n /**\n * Select an application.\n * When viewing a system tab, always emit to allow returning to the app.\n */\n selectApp(app: BaseApplication): void {\n this.recordRecent(app);\n this.closeLauncher(false); // Focus moves into the app, not back to the trigger\n if (!UUIDsEqual(app.ID, this.activeApp?.ID) || this.isViewingSystemTab) {\n this.appSelected.emit(app.ID);\n }\n }\n\n /** Case-insensitive UUID check whether an app is the currently active app. */\n IsActiveApp(app: BaseApplication): boolean {\n return UUIDsEqual(app.ID, this.activeApp?.ID);\n }\n\n /** Case-insensitive UUID check whether an app is the one currently loading. */\n IsLoadingApp(app: BaseApplication): boolean {\n return UUIDsEqual(app.ID, this.loadingAppId);\n }\n\n // ---- Recents persistence (MJ: User Settings via UserInfoEngine) ----\n\n private loadRecents(): void {\n try {\n const raw = this.userInfoEngine.GetSetting(RECENT_APPS_KEY);\n const parsed = raw ? JSON.parse(raw) : [];\n this.recentEntries = Array.isArray(parsed)\n ? parsed.filter((e: RecentAppEntry) => e && typeof e.id === 'string' && typeof e.ts === 'number')\n : [];\n } catch {\n this.recentEntries = [];\n }\n }\n\n private recordRecent(app: BaseApplication): void {\n const id = NormalizeUUID(app.ID);\n this.recentEntries = [\n { id, ts: Date.now() },\n ...this.recentEntries.filter(e => NormalizeUUID(e.id) !== id)\n ].slice(0, RECENT_STORE_MAX);\n try {\n this.userInfoEngine.SetSettingDebounced(RECENT_APPS_KEY, JSON.stringify(this.recentEntries));\n } catch {\n // Settings persistence must never block app switching (e.g. engine not\n // configured in tests or permission-constrained sessions).\n }\n }\n\n /**\n * Swap the launcher body to the app-configuration view (in-panel, seamless —\n * no separate dialog). The embedded mj-user-app-config-content loads on\n * creation.\n */\n EnterConfigMode(): void {\n this.ConfigMode = true;\n this.FilterText = '';\n this.PendingDiscard = null;\n // Move focus into the config view (the back button is its first focusable)\n requestAnimationFrame(() => {\n this.panelRef?.nativeElement\n ?.querySelector<HTMLElement>('.launcher-back')?.focus();\n });\n }\n\n /** Guarded exit from the config view: unsaved changes surface the inline\n * discard bar instead of silently dropping the user's edits. */\n RequestExitConfigMode(): void {\n if (this.configContent?.HasChanges()) {\n this.PendingDiscard = 'exit';\n return;\n }\n this.ExitConfigMode();\n }\n\n /**\n * Return from the configuration view to the app grid.\n * The app list refreshes reactively after a save: each UserApplication save\n * fires a BaseEntity event, UserInfoEngine's debounced refresh emits\n * DataChange$, and ApplicationManager.syncFromEngine() pushes the new list\n * to applications$, which the `apps` getter reads. No explicit reload needed.\n */\n ExitConfigMode(): void {\n this.ConfigMode = false;\n // The config session may have changed the app count across the compact\n // threshold — re-resolve the presentation for the view we're returning to.\n // (The Applications stream also triggers this when the post-save engine\n // refresh lands, whichever comes last wins with the freshest count.)\n this.RefreshPresentation();\n requestAnimationFrame(() => {\n if (this.ShowFilter) {\n this.filterInputRef?.nativeElement?.focus();\n } else {\n this.panelRef?.nativeElement?.querySelector<HTMLElement>('.app-card')?.focus();\n }\n });\n }\n}\n","<div class=\"app-switcher-container\" [class.loading]=\"isLoading\">\n <div class=\"app-switcher-button\"\n #trigger\n [mjClickable]=\"TriggerAriaLabel\"\n testId=\"app-switcher-trigger\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"showDropdown\"\n (click)=\"ToggleLauncher()\">\n <!-- Show spinner when loading, otherwise show app icon -->\n @if (isLoading) {\n <i class=\"app-icon loading-spinner fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n }\n @if (!isLoading) {\n <i class=\"app-icon\" [class]=\"activeApp?.Icon || 'fa-solid fa-cube'\"\n [style.color]=\"activeApp?.GetColor()\" aria-hidden=\"true\"></i>\n }\n <!-- Current app name; hidden at the mobile breakpoint (icon-only trigger) -->\n <span class=\"app-label\">{{ TriggerLabel }}</span>\n <i class=\"dropdown-arrow fa-solid fa-chevron-down\" aria-hidden=\"true\"></i>\n </div>\n\n @if (showDropdown) {\n <!-- Native <dialog> + showModal(): lives in the browser TOP LAYER (above\n every app stacking context — chat FAB, toasts), background is inert\n (real focus trap), and focus restores to the trigger on close. The\n backdrop is ::backdrop; clicks on it arrive as clicks on the dialog\n element itself (OnDialogClick). -->\n <dialog class=\"launcher-panel\"\n #panel\n role=\"dialog\"\n aria-modal=\"true\"\n [class.launcher-panel--compact]=\"CompactMode && !ConfigMode\"\n [style.--anchor-left]=\"AnchorLeft + 'px'\"\n [style.--anchor-top]=\"AnchorTop + 'px'\"\n [attr.aria-label]=\"ConfigMode ? null : 'Application launcher'\"\n [attr.aria-labelledby]=\"ConfigMode ? 'mj-launcher-config-title' : null\"\n (click)=\"OnDialogClick($event)\"\n (cancel)=\"OnDialogCancel($event)\"\n (keydown)=\"OnPanelKeydown($event)\">\n\n @if (ConfigMode) {\n <!-- Configuration view: same panel, body swapped -->\n <div class=\"launcher-head launcher-head--config\">\n <button class=\"launcher-back\" type=\"button\" aria-label=\"Back to apps\" (click)=\"RequestExitConfigMode()\">\n <i class=\"fa-solid fa-arrow-left\" aria-hidden=\"true\"></i>\n </button>\n <h2 class=\"launcher-title\" id=\"mj-launcher-config-title\">Configure apps</h2>\n <button class=\"launcher-close\" type=\"button\" aria-label=\"Close application launcher\" (click)=\"CloseLauncher()\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n @if (PendingDiscard) {\n <div class=\"launcher-discard-bar\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n <span class=\"discard-msg\">You have unsaved changes.</span>\n <button mjButton variant=\"danger\" size=\"sm\" (click)=\"ConfirmDiscard()\">Discard</button>\n <button mjButton size=\"sm\" (click)=\"CancelDiscard()\">Keep editing</button>\n </div>\n }\n <mj-user-app-config-content\n class=\"launcher-config-body\"\n [Provider]=\"Provider\"\n (Saved)=\"ExitConfigMode()\"\n (Cancelled)=\"RequestExitConfigMode()\">\n </mj-user-app-config-content>\n } @else {\n @if (ShowFilter) {\n <div class=\"launcher-head\">\n <div class=\"launcher-filter\">\n <i class=\"fa-solid fa-magnifying-glass\" aria-hidden=\"true\"></i>\n <input\n #filterInput\n type=\"text\"\n placeholder=\"Find an app…\"\n aria-label=\"Find an app\"\n [value]=\"FilterText\"\n (input)=\"OnFilterInput($event)\"\n (keydown)=\"OnFilterKeydown($event)\"\n autocomplete=\"off\"\n spellcheck=\"false\" />\n </div>\n <button class=\"launcher-close\" type=\"button\" aria-label=\"Close application launcher\" (click)=\"CloseLauncher()\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n <div class=\"launcher-scroll\">\n @if (IsFiltering) {\n <div class=\"launcher-grid\">\n @for (app of VisibleApps; track app.ID; let i = $index) {\n <ng-container *ngTemplateOutlet=\"appCard; context: { $implicit: app, idx: i }\"></ng-container>\n }\n </div>\n @if (VisibleApps.length === 0) {\n <div class=\"launcher-empty\">No apps match \"{{ FilterText }}\"</div>\n }\n } @else {\n @if (RecentApps.length > 0) {\n <div class=\"launcher-sect\">Recent</div>\n <div class=\"launcher-grid\" role=\"group\" aria-label=\"Recent applications\">\n @for (app of RecentApps; track app.ID; let i = $index) {\n <ng-container *ngTemplateOutlet=\"appCard; context: { $implicit: app, idx: i }\"></ng-container>\n }\n </div>\n }\n <div class=\"launcher-sect launcher-sect--row\">\n <span>All apps</span>\n <button class=\"launcher-sort-toggle\" type=\"button\"\n [attr.aria-pressed]=\"SortMode === 'alpha'\"\n [attr.aria-label]=\"SortMode === 'alpha' ? 'Sorted alphabetically — switch to your custom order' : 'Sorted by your custom order — switch to alphabetical'\"\n [title]=\"SortMode === 'alpha' ? 'Sorted A–Z — click for your custom order' : 'Custom order — click for A–Z'\"\n (click)=\"ToggleSortMode()\">\n <i [class]=\"SortMode === 'alpha' ? 'fa-solid fa-arrow-down-a-z' : 'fa-solid fa-bars-staggered'\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"launcher-grid\" role=\"group\" aria-label=\"All applications\">\n @for (app of OtherApps; track app.ID; let i = $index) {\n <ng-container *ngTemplateOutlet=\"appCard; context: { $implicit: app, idx: i + RecentApps.length }\"></ng-container>\n }\n </div>\n @if (VisibleApps.length === 0) {\n <div class=\"launcher-empty\">\n No applications are enabled for you yet — use Configure… below to add some.\n </div>\n }\n }\n </div>\n\n <div class=\"launcher-foot\">\n <span class=\"launcher-kbd-hints\" aria-hidden=\"true\">\n <span><kbd>↑</kbd><kbd>↓</kbd> navigate</span>\n <span><kbd>↵</kbd> open</span>\n <span><kbd>esc</kbd> close</span>\n </span>\n <button class=\"launcher-configure\" type=\"button\" testId=\"app-switcher-configure\" (click)=\"EnterConfigMode()\">\n <i class=\"fa-solid fa-gear\" aria-hidden=\"true\"></i>\n <span>Configure…</span>\n </button>\n </div>\n\n <span class=\"sr-only\" role=\"status\">{{ AnnouncedAppCount }} applications available</span>\n }\n </dialog>\n }\n</div>\n\n<!-- Shared app-card template: each card is a real focusable control (mjClickable\n adds role, tabindex 0, aria-label, and Enter/Space activation), so Tab walks\n the cards and arrow keys are the fast path. -->\n<ng-template #appCard let-app let-idx=\"idx\">\n <div class=\"app-card\"\n [id]=\"OptionId(idx)\"\n [mjClickable]=\"app.Name\"\n role=\"link\"\n [testId]=\"'app-switcher-item-' + app.Name\"\n [attr.aria-current]=\"IsActiveApp(app) ? 'true' : null\"\n [attr.aria-describedby]=\"app.Description ? OptionId(idx) + '-desc' : null\"\n [class.active]=\"IsActiveApp(app)\"\n [class.loading]=\"IsLoadingApp(app)\"\n [style.--app-color]=\"app.GetColor()\"\n (click)=\"selectApp(app)\"\n (keydown)=\"OnCardKeydown(idx, $event)\">\n <div class=\"app-card-glyph\">\n @if (IsLoadingApp(app)) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i [class]=\"app.Icon || 'fa-solid fa-cube'\" aria-hidden=\"true\"></i>\n }\n </div>\n <div class=\"app-card-text\">\n <div class=\"app-card-name\">\n <span class=\"app-card-name-label\">{{ app.Name }}</span>\n </div>\n @if (app.Description) {\n <div class=\"app-card-desc\" [id]=\"OptionId(idx) + '-desc'\">{{ app.Description }}</div>\n }\n </div>\n </div>\n</ng-template>\n\n"]}
|
|
@@ -52,7 +52,7 @@ export class RecordBarComponent {
|
|
|
52
52
|
i0.ɵɵtextInterpolate(ctx.Title);
|
|
53
53
|
i0.ɵɵadvance(4);
|
|
54
54
|
i0.ɵɵtextInterpolate(ctx.Count);
|
|
55
|
-
} }, styles: ["[_nghost-%COMP%] {\n display: block;\n flex-shrink: 0;\n}\n\n.record-bar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n height: 44px; \n
|
|
55
|
+
} }, styles: ["[_nghost-%COMP%] {\n display: block;\n flex-shrink: 0;\n}\n\n.record-bar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n height: 44px; \n padding: 0 10px 0 12px;\n border: none;\n border-bottom: 1px solid var(--mj-border-default);\n \n background: var(--mj-bg-surface);\n font-family: inherit;\n cursor: pointer;\n text-align: left;\n}\n\n.record-bar[_ngcontent-%COMP%]:active {\n background: var(--mj-bg-surface-hover);\n}\n\n.record-bar[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -2px;\n}\n\n.record-bar-icon[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border-radius: 7px;\n flex-shrink: 0;\n font-size: 13px;\n \n background: color-mix(in srgb, currentColor 14%, transparent);\n}\n\n.record-bar-title[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n font-size: var(--mj-text-sm, 14px);\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.record-bar-count[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n flex-shrink: 0;\n padding: 5px 10px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 8px;\n font-size: 12.5px;\n font-weight: 700;\n color: var(--mj-text-secondary);\n}\n\n.record-bar-chevron[_ngcontent-%COMP%] {\n font-size: 9px;\n color: var(--mj-text-muted);\n}"] });
|
|
56
56
|
}
|
|
57
57
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecordBarComponent, [{
|
|
58
58
|
type: Component,
|
|
@@ -101,7 +101,7 @@ export class RecordOriginCrumbComponent {
|
|
|
101
101
|
i0.ɵɵconditionalCreate(0, RecordOriginCrumbComponent_Conditional_0_Template, 2, 1);
|
|
102
102
|
} if (rf & 2) {
|
|
103
103
|
i0.ɵɵconditional(ctx.Origin ? 0 : -1);
|
|
104
|
-
} }, styles: ["[_nghost-%COMP%] {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n height: 36px;\n \n\n\n
|
|
104
|
+
} }, styles: ["[_nghost-%COMP%] {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n height: 36px;\n \n\n\n padding: 0 12px 0 24px;\n \n\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n }\n [_nghost-%COMP%]:not(:has(*)) { display: none; }\n .crumb-lead[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-right: 6px;\n }\n \n .crumb-seg[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n padding: 5px 8px;\n border: none;\n border-radius: 4px;\n background: transparent;\n font-size: 12px;\n line-height: 1;\n font-family: inherit;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .crumb-seg[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n .crumb-seg[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -2px;\n }\n .crumb-page[_ngcontent-%COMP%] { font-weight: 500; }\n .crumb-sep[_ngcontent-%COMP%] { color: var(--mj-text-muted); font-size: 12px; }\n .origin-static[_ngcontent-%COMP%] {\n padding: 5px 8px;\n font-size: 12px;\n color: var(--mj-text-muted);\n }"] });
|
|
105
105
|
}
|
|
106
106
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecordOriginCrumbComponent, [{
|
|
107
107
|
type: Component,
|
|
@@ -82,7 +82,7 @@ export class RecordSwitcherSheetComponent {
|
|
|
82
82
|
i0.ɵɵproperty("Visible", ctx.Visible);
|
|
83
83
|
i0.ɵɵadvance(2);
|
|
84
84
|
i0.ɵɵrepeater(ctx.Entries);
|
|
85
|
-
} }, dependencies: [MJBottomSheetComponent], styles: [".switcher-list[_ngcontent-%COMP%] {\n padding: 0 10px;\n}\n\n.switcher-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n border-radius: 11px;\n}\n\n.switcher-row--active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.switcher-row-main[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 11px;\n flex: 1;\n min-width: 0;\n padding: 10px 8px;\n border: none;\n background: transparent;\n border-radius: 11px;\n font-family: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.switcher-row-main[_ngcontent-%COMP%]:active {\n background: var(--mj-bg-surface-hover);\n}\n\n.switcher-row-main[_ngcontent-%COMP%]:focus-visible, \n.switcher-row-close[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -2px;\n}\n\n.switcher-row-icon[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 9px;\n flex-shrink: 0;\n font-size: 16px;\n \n
|
|
85
|
+
} }, dependencies: [MJBottomSheetComponent], styles: [".switcher-list[_ngcontent-%COMP%] {\n padding: 0 10px;\n}\n\n.switcher-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n border-radius: 11px;\n}\n\n.switcher-row--active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.switcher-row-main[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 11px;\n flex: 1;\n min-width: 0;\n padding: 10px 8px;\n border: none;\n background: transparent;\n border-radius: 11px;\n font-family: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.switcher-row-main[_ngcontent-%COMP%]:active {\n background: var(--mj-bg-surface-hover);\n}\n\n.switcher-row-main[_ngcontent-%COMP%]:focus-visible, \n.switcher-row-close[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -2px;\n}\n\n.switcher-row-icon[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 9px;\n flex-shrink: 0;\n font-size: 16px;\n \n background: color-mix(in srgb, currentColor 14%, transparent);\n}\n\n.switcher-row-text[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n min-width: 0;\n gap: 1px;\n}\n\n.switcher-row-title[_ngcontent-%COMP%] {\n font-size: var(--mj-text-sm, 14px);\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.switcher-row-origin[_ngcontent-%COMP%] {\n font-size: 11.5px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.switcher-row-close[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 38px;\n height: 38px;\n flex-shrink: 0;\n border: none;\n background: transparent;\n border-radius: 8px;\n color: var(--mj-text-muted);\n font-size: 14px;\n cursor: pointer;\n}\n\n.switcher-row-close[_ngcontent-%COMP%]:active {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}"] });
|
|
86
86
|
}
|
|
87
87
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecordSwitcherSheetComponent, [{
|
|
88
88
|
type: Component,
|
|
@@ -131,7 +131,7 @@ export class RecordsHubPillComponent {
|
|
|
131
131
|
i0.ɵɵconditionalCreate(0, RecordsHubPillComponent_Conditional_0_Template, 6, 5, "button", 0);
|
|
132
132
|
} if (rf & 2) {
|
|
133
133
|
i0.ɵɵconditional(ctx.Count > 0 ? 0 : -1);
|
|
134
|
-
} }, styles: ["\n\n\n\n\n\n
|
|
134
|
+
} }, styles: ["\n\n\n\n\n\n[_nghost-%COMP%] {\n display: flex;\n align-items: center;\n}\n\n\n\n[_nghost-%COMP%]::before {\n content: '';\n width: 1px;\n height: 20px;\n background: var(--mj-border-default);\n margin: 0 var(--mj-space-1);\n flex: 0 0 auto;\n}\n\n\n[_nghost-%COMP%]:not(:has(.records-pill))::before {\n display: none;\n}\n\n\n\n\n\n.mobile-nav-records[_nghost-%COMP%]::before, .mobile-nav-records [_nghost-%COMP%]::before {\n display: none;\n}\n\n.records-pill[_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: none;\n border-radius: var(--mj-radius-md);\n background: transparent;\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 flex-shrink: 0;\n}\n\n.records-pill[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.records-pill[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n\n\n.records-pill.active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--mj-brand-primary);\n}\n\n.records-pill.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.records-pill[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: var(--mj-text-base);\n}\n\n\n.records-pill-count[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\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}"] });
|
|
135
135
|
}
|
|
136
136
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecordsHubPillComponent, [{
|
|
137
137
|
type: Component,
|
|
@@ -163,6 +163,31 @@ export declare class TabContainerComponent extends BaseAngularComponent implemen
|
|
|
163
163
|
private syncRecordsRegion;
|
|
164
164
|
/** Add/remove/style record tabs in the records GL to match the configuration */
|
|
165
165
|
private syncRecordsTabs;
|
|
166
|
+
/**
|
|
167
|
+
* True when the record tab's live component reports in-progress edits.
|
|
168
|
+
* The shell folds this into the records temp-tab pool predicate, so a tab
|
|
169
|
+
* being edited is simply not in the pool and the next plain open creates its
|
|
170
|
+
* own tab instead of replacing it. Unknown/unloaded tabs are not editing:
|
|
171
|
+
* nothing is rendered, so there is nothing to lose.
|
|
172
|
+
*/
|
|
173
|
+
IsRecordTabEditing(tabId: string): boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Records-region mirror of the main sync path's `needsReload` check (see
|
|
176
|
+
* {@link syncTabsWithConfiguration}). Temp-tab consumption overwrites a
|
|
177
|
+
* record tab IN PLACE — same tab id, new Entity/recordId — so the tab list
|
|
178
|
+
* alone can't tell us the pane went stale; the resource signature bound to
|
|
179
|
+
* the live component can. Without this, replacement renames the tab and
|
|
180
|
+
* leaves the PREVIOUS record rendered underneath the new title.
|
|
181
|
+
*
|
|
182
|
+
* Id reuse (rather than close + create) is deliberate: it's what keeps
|
|
183
|
+
* `layoutCoversExactTabSet` satisfied, so desktop split layouts survive a
|
|
184
|
+
* replacement.
|
|
185
|
+
*
|
|
186
|
+
* @returns true when it took ownership of the pane. The caller must then
|
|
187
|
+
* leave the origin crumb alone — `loadTabContent` re-creates it via
|
|
188
|
+
* `ensureRecordOriginCrumb` once the incoming record attaches.
|
|
189
|
+
*/
|
|
190
|
+
private reloadRecordsTabIfResourceChanged;
|
|
166
191
|
/** Focus a records-region tab without feeding back into SetActiveTab loops */
|
|
167
192
|
private focusRecordsTab;
|
|
168
193
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tab-container.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/shell/components/tabs/tab-container.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EACN,SAAS,EACT,aAAa,EAEb,UAAU,EACV,cAAc,EACd,mBAAmB,EAKnB,iBAAiB,EAGjB,YAAY,EAEb,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAMnB,MAAM,qCAAqC,CAAC;AAM7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gDAAgD,CAAC;AAGrF,OAAO,EAAyB,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAEvF,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAIrE;;;;;;;;;GASG;AACH,qBAOa,qBAAsB,SAAQ,oBAAqB,YAAW,MAAM,EAAE,SAAS,EAAE,aAAa;
|
|
1
|
+
{"version":3,"file":"tab-container.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/shell/components/tabs/tab-container.component.ts"],"names":[],"mappings":"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"}
|
|
@@ -456,11 +456,86 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
456
456
|
appColor: app?.GetColor() || DEFAULT_APP_COLOR,
|
|
457
457
|
typeIcon: this.resolveTabTypeIcon(tab)
|
|
458
458
|
});
|
|
459
|
-
//
|
|
460
|
-
|
|
459
|
+
// Preview-tab replacement REUSES this tab id with a different record,
|
|
460
|
+
// so the pane content has to follow the style update. When the reload
|
|
461
|
+
// fires it owns the pane, crumb included — don't touch the crumb here.
|
|
462
|
+
if (!this.reloadRecordsTabIfResourceChanged(tab)) {
|
|
463
|
+
// Origin can change on re-open re-capture — keep the pane crumb live
|
|
464
|
+
this.updateOriginCrumb(tab);
|
|
465
|
+
}
|
|
461
466
|
}
|
|
462
467
|
});
|
|
463
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* True when the record tab's live component reports in-progress edits.
|
|
471
|
+
* The shell folds this into the records temp-tab pool predicate, so a tab
|
|
472
|
+
* being edited is simply not in the pool and the next plain open creates its
|
|
473
|
+
* own tab instead of replacing it. Unknown/unloaded tabs are not editing:
|
|
474
|
+
* nothing is rendered, so there is nothing to lose.
|
|
475
|
+
*/
|
|
476
|
+
IsRecordTabEditing(tabId) {
|
|
477
|
+
return this.componentRefs.get(tabId)?.instance.IsEditing() === true;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Records-region mirror of the main sync path's `needsReload` check (see
|
|
481
|
+
* {@link syncTabsWithConfiguration}). Temp-tab consumption overwrites a
|
|
482
|
+
* record tab IN PLACE — same tab id, new Entity/recordId — so the tab list
|
|
483
|
+
* alone can't tell us the pane went stale; the resource signature bound to
|
|
484
|
+
* the live component can. Without this, replacement renames the tab and
|
|
485
|
+
* leaves the PREVIOUS record rendered underneath the new title.
|
|
486
|
+
*
|
|
487
|
+
* Id reuse (rather than close + create) is deliberate: it's what keeps
|
|
488
|
+
* `layoutCoversExactTabSet` satisfied, so desktop split layouts survive a
|
|
489
|
+
* replacement.
|
|
490
|
+
*
|
|
491
|
+
* @returns true when it took ownership of the pane. The caller must then
|
|
492
|
+
* leave the origin crumb alone — `loadTabContent` re-creates it via
|
|
493
|
+
* `ensureRecordOriginCrumb` once the incoming record attaches.
|
|
494
|
+
*/
|
|
495
|
+
reloadRecordsTabIfResourceChanged(tab) {
|
|
496
|
+
// Batch creation/restore and breakpoint rebuilds re-enter this sync with
|
|
497
|
+
// panes that are mid-construction; those paths load their own content.
|
|
498
|
+
if (this.recordsCreatingTabs || this.recordsRebuilding) {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
// No live component means nothing is rendered to go stale — the tab loads
|
|
502
|
+
// from current config on its next show (MarkTabNotLoaded → isFirstShow).
|
|
503
|
+
const componentRef = this.componentRefs.get(tab.id);
|
|
504
|
+
const existing = componentRef?.instance.Data;
|
|
505
|
+
if (!existing) {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
const config = tab.configuration;
|
|
509
|
+
const existingConfig = (existing.Configuration ?? {});
|
|
510
|
+
// Same precedence getResourceDataFromTab uses, so this compares against
|
|
511
|
+
// what a reload would ACTUALLY bind. Reading the two in a different order
|
|
512
|
+
// would register as a permanent difference and reload the pane on every
|
|
513
|
+
// configuration emission.
|
|
514
|
+
const nextRecordId = config['recordId'] || tab.resourceRecordId || '';
|
|
515
|
+
const changed = (existing.ResourceRecordID || '') !== nextRecordId ||
|
|
516
|
+
existingConfig['Entity'] !== config['Entity'] ||
|
|
517
|
+
existingConfig['applicationId'] !== tab.applicationId;
|
|
518
|
+
if (!changed) {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
// Detach the outgoing record into the component cache — it keys on
|
|
522
|
+
// driver + record + app, NOT on tab id, so re-opening that record later is
|
|
523
|
+
// still a cache hit and the incoming record is a miss (a fresh component,
|
|
524
|
+
// which is the whole point). Also drops the outgoing crumb.
|
|
525
|
+
this.cleanupTabComponent(tab.id);
|
|
526
|
+
this.recordsLayoutManager.MarkTabNotLoaded(tab.id);
|
|
527
|
+
this.updateTabDisplayName(tab);
|
|
528
|
+
// Only the visible pane reloads now; the rest reload when next shown.
|
|
529
|
+
if (this.workspaceManager.GetActiveTabId() === tab.id) {
|
|
530
|
+
const container = this.recordsLayoutManager.GetContainer(tab.id);
|
|
531
|
+
if (container) {
|
|
532
|
+
void this.loadTabContent(tab.id, container).then(() => {
|
|
533
|
+
this.recordsLayoutManager.MarkTabLoaded(tab.id);
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
464
539
|
/** Focus a records-region tab without feeding back into SetActiveTab loops */
|
|
465
540
|
focusRecordsTab(tabId) {
|
|
466
541
|
if (this.recordsRegionActiveTabId !== tabId) {
|
|
@@ -1788,8 +1863,17 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
1788
1863
|
if (!displayName) {
|
|
1789
1864
|
return;
|
|
1790
1865
|
}
|
|
1791
|
-
// Update the tab title in Golden Layout
|
|
1792
|
-
|
|
1866
|
+
// Update the tab title in whichever Golden Layout hosts this tab. Its
|
|
1867
|
+
// siblings (updateTabDisplayName, onTabShown) already fork on region;
|
|
1868
|
+
// this one did not, so a records tab's resource-derived title was
|
|
1869
|
+
// written to the MAIN manager, which does not own that tab id, and the
|
|
1870
|
+
// record kept its generic placeholder title. Masked while records tabs
|
|
1871
|
+
// were immortal — the next configuration emission re-applied the title
|
|
1872
|
+
// from config — but preview-tab replacement retitles a records tab on
|
|
1873
|
+
// every plain click, so it stops being cosmetic.
|
|
1874
|
+
const tab = this.workspaceManager.GetTab(tabId);
|
|
1875
|
+
const manager = tab && this.isRecordTab(tab) ? this.recordsLayoutManager : this.layoutManager;
|
|
1876
|
+
manager.UpdateTabStyle(tabId, { title: displayName });
|
|
1793
1877
|
// Update the tab title in workspace configuration for persistence
|
|
1794
1878
|
this.workspaceManager.UpdateTabTitle(tabId, displayName);
|
|
1795
1879
|
}
|