@frame-kit/ui-ng 0.0.2 → 0.0.4
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/fesm2022/frame-kit-ui-ng-layouts-app-shell.mjs +6 -32
- package/fesm2022/frame-kit-ui-ng-layouts-app-shell.mjs.map +1 -1
- package/fesm2022/frame-kit-ui-ng-ui-dialog.mjs +72 -91
- package/fesm2022/frame-kit-ui-ng-ui-dialog.mjs.map +1 -1
- package/fesm2022/frame-kit-ui-ng-ui-drawer.mjs +56 -82
- package/fesm2022/frame-kit-ui-ng-ui-drawer.mjs.map +1 -1
- package/fesm2022/frame-kit-ui-ng-ui-nav-group.mjs +10 -3
- package/fesm2022/frame-kit-ui-ng-ui-nav-group.mjs.map +1 -1
- package/fesm2022/frame-kit-ui-ng.mjs +0 -1
- package/fesm2022/frame-kit-ui-ng.mjs.map +1 -1
- package/package.json +1 -5
- package/types/frame-kit-ui-ng-layouts-app-shell.d.ts +5 -8
- package/types/frame-kit-ui-ng-ui-dialog.d.ts +25 -24
- package/types/frame-kit-ui-ng-ui-drawer.d.ts +20 -14
- package/types/frame-kit-ui-ng-ui-nav-group.d.ts +8 -1
- package/types/frame-kit-ui-ng.d.ts +0 -1
- package/ui/dialog/README.md +0 -4
- package/ui/drawer/README.md +0 -4
- package/ui/nav-group/README.md +25 -0
- package/fesm2022/frame-kit-ui-ng-services-overlay-orchestrator.mjs +0 -133
- package/fesm2022/frame-kit-ui-ng-services-overlay-orchestrator.mjs.map +0 -1
- package/services/overlay-orchestrator/README.md +0 -184
- package/types/frame-kit-ui-ng-services-overlay-orchestrator.d.ts +0 -96
package/ui/nav-group/README.md
CHANGED
|
@@ -15,6 +15,8 @@ An expandable sidenav group — renders a clickable trigger row (icon + label, o
|
|
|
15
15
|
| `iconSize` | `IconSize` | `"sm"` | Size passed through to the trigger's `fk-sidenav-link`. |
|
|
16
16
|
| `routerLink` | `string \| string[] \| null` | `null` | Destination for the trigger's anchor. Active in both expanded and collapsed states. |
|
|
17
17
|
| `collapsed` | `boolean` | `false` | Set this to `true` when the outer sidenav is in its collapsed rail state — clicks then both navigate AND emit `requestOpen`. |
|
|
18
|
+
| `lockedOpen` | `boolean` | `false` | When `true`, clicking an expanded group's trigger no longer folds it — children stay visible (navigation via `routerLink` still fires). No effect in collapsed rail mode. |
|
|
19
|
+
| `showChevron`| `boolean` | `false` | Renders a trailing disclosure chevron that points right while collapsed and rotates to point down while expanded. Decorative; hidden in rail mode. |
|
|
18
20
|
| `className` | `string` | `''` | Additional CSS classes merged onto the host element. |
|
|
19
21
|
|
|
20
22
|
### Models
|
|
@@ -49,6 +51,8 @@ An expandable sidenav group — renders a clickable trigger row (icon + label, o
|
|
|
49
51
|
- `requestOpen` output bridges to a collapsible outer shell (`fk-app-shell` and friends) — fires alongside navigation when clicked in rail mode
|
|
50
52
|
- Content projection for arbitrary child rows — `fk-sidenav-link`, section titles, custom markers
|
|
51
53
|
- Token-driven spacing (`--fk-nav-group-row-gap`, `--fk-nav-group-children-gap`, `--fk-nav-group-children-indent`, `--fk-nav-group-children-offset`)
|
|
54
|
+
- Section rule — a vertical line brackets the expanded children, flush-left under the icon/label, tokenized via `--fk-nav-group-children-border-{width,color,offset}` (removable)
|
|
55
|
+
- Optional disclosure chevron (`showChevron`) — points right collapsed, rotates down when expanded; colour via `--fk-nav-group-chevron-color`
|
|
52
56
|
|
|
53
57
|
---
|
|
54
58
|
|
|
@@ -125,8 +129,23 @@ export class ExampleComponent {}
|
|
|
125
129
|
--fk-nav-group-children-gap;
|
|
126
130
|
--fk-nav-group-children-indent;
|
|
127
131
|
--fk-nav-group-children-offset;
|
|
132
|
+
--fk-nav-group-children-border-width; // section rule width (default --fk-border-width / 1px)
|
|
133
|
+
--fk-nav-group-children-border-color; // section rule colour (default --fk-color-border)
|
|
134
|
+
--fk-nav-group-children-border-offset; // section rule x-position (see below)
|
|
135
|
+
--fk-nav-group-chevron-color; // disclosure chevron colour (default --fk-color-muted)
|
|
128
136
|
```
|
|
129
137
|
|
|
138
|
+
The expanded children column carries a quiet vertical **section rule** by
|
|
139
|
+
default — a line that brackets the nested items, aligned flush-left with the
|
|
140
|
+
trigger row's content-left edge (the icon when present, the label text
|
|
141
|
+
otherwise — both start at the link's inline padding, so a single offset
|
|
142
|
+
covers both, independent of icon size). The colour defaults to the semantic
|
|
143
|
+
`--fk-color-border`, so the rule adapts to dark mode through the fallback
|
|
144
|
+
chain with no dedicated dark token.
|
|
145
|
+
|
|
146
|
+
Override `--fk-nav-group-children-border-offset` to pin the line elsewhere, or
|
|
147
|
+
remove the rule by zeroing the width / making the colour transparent.
|
|
148
|
+
|
|
130
149
|
Override in your app stylesheet:
|
|
131
150
|
|
|
132
151
|
```scss
|
|
@@ -134,6 +153,11 @@ Override in your app stylesheet:
|
|
|
134
153
|
--fk-nav-group-children-indent: 1rem;
|
|
135
154
|
--fk-nav-group-children-gap: var(--fk-rhythm-2);
|
|
136
155
|
}
|
|
156
|
+
|
|
157
|
+
// Remove the section rule for a particular scope:
|
|
158
|
+
.flat-nav {
|
|
159
|
+
--fk-nav-group-children-border-width: 0;
|
|
160
|
+
}
|
|
137
161
|
```
|
|
138
162
|
|
|
139
163
|
---
|
|
@@ -142,4 +166,5 @@ Override in your app stylesheet:
|
|
|
142
166
|
|
|
143
167
|
- The built-in `toggle()` decides what happens to `expanded` on click: in rail mode it leaves `expanded` alone and only emits `requestOpen` (children must not render inside the rail); in expanded mode it flips `expanded` unless `lockedOpen` is `true`. Navigation via `routerLink` happens independently on the inner sidenav-link's anchor in both states. If you want custom behavior, listen to `(requestOpen)` and drive `[(expanded)]` yourself.
|
|
144
168
|
- The trigger row composes `fk-sidenav-link` — you get the same active-route styling and keyboard semantics for free.
|
|
169
|
+
- The section rule is an absolutely-positioned `::before` on the children column (not a `border-left`), so its x-position can anchor to the icon or text. It spans the full height of the children and sits behind them; child rows render normally on top. It only shows while the group is expanded (the children column isn't rendered otherwise).
|
|
145
170
|
- No opinionated max-width — the trigger fills the row. Cap the width at the consumer level if you need it (e.g., docs shells clamp their top-level items to 18rem).
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, Injectable } from '@angular/core';
|
|
3
|
-
|
|
4
|
-
let nextOverlayId = 0;
|
|
5
|
-
/**
|
|
6
|
-
* Centralized overlay orchestrator.
|
|
7
|
-
*
|
|
8
|
-
* Tracks all open overlays (dialogs, drawers, popovers, navigation, tooltips)
|
|
9
|
-
* in a priority-based stack. When a new overlay opens:
|
|
10
|
-
*
|
|
11
|
-
* - All overlays with LOWER priority are closed automatically.
|
|
12
|
-
* - Overlays with EQUAL or HIGHER priority are left open.
|
|
13
|
-
*
|
|
14
|
-
* This prevents UI conflicts like drawers staying open behind dialogs,
|
|
15
|
-
* or kebab menus persisting when a modal pops up.
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```ts
|
|
19
|
-
* const id = orchestrator.register({
|
|
20
|
-
* priority: OVERLAY_PRIORITY.DRAWER,
|
|
21
|
-
* type: 'drawer',
|
|
22
|
-
* close: () => drawerRef.close(),
|
|
23
|
-
* });
|
|
24
|
-
*
|
|
25
|
-
* // Later, when the overlay closes:
|
|
26
|
-
* orchestrator.unregister(id);
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
class OverlayOrchestrator {
|
|
30
|
-
entries = signal([], ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
|
|
31
|
-
/** Whether any overlay is currently open. */
|
|
32
|
-
hasActiveOverlay = computed(() => this.entries().length > 0, ...(ngDevMode ? [{ debugName: "hasActiveOverlay" }] : /* istanbul ignore next */ []));
|
|
33
|
-
/** The topmost (highest priority) overlay. */
|
|
34
|
-
topOverlay = computed(() => {
|
|
35
|
-
const all = this.entries();
|
|
36
|
-
if (all.length === 0) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
return all.reduce((top, entry) => entry.priority >= top.priority ? entry : top);
|
|
40
|
-
}, ...(ngDevMode ? [{ debugName: "topOverlay" }] : /* istanbul ignore next */ []));
|
|
41
|
-
/** Current number of active overlays. */
|
|
42
|
-
count = computed(() => this.entries().length, ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
|
|
43
|
-
/**
|
|
44
|
-
* Register an overlay and close all overlays with lower priority.
|
|
45
|
-
*
|
|
46
|
-
* @returns The unique overlay ID — pass this to `unregister()` when closing.
|
|
47
|
-
*/
|
|
48
|
-
register(entry) {
|
|
49
|
-
const id = `overlay-${nextOverlayId++}`;
|
|
50
|
-
// Close all overlays with strictly lower priority
|
|
51
|
-
const current = this.entries();
|
|
52
|
-
const toClose = current.filter((e) => e.priority < entry.priority);
|
|
53
|
-
for (const overlay of toClose) {
|
|
54
|
-
overlay.close();
|
|
55
|
-
}
|
|
56
|
-
// Remove closed overlays and add the new one
|
|
57
|
-
const remaining = current.filter((e) => e.priority >= entry.priority);
|
|
58
|
-
this.entries.set([...remaining, { ...entry, id }]);
|
|
59
|
-
return id;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Unregister an overlay by ID. Call this when the overlay closes.
|
|
63
|
-
*/
|
|
64
|
-
unregister(id) {
|
|
65
|
-
this.entries.update((entries) => entries.filter((e) => e.id !== id));
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Check if an overlay with the given priority or higher is currently open.
|
|
69
|
-
* Useful for guards that want to prevent opening lower-priority overlays.
|
|
70
|
-
*/
|
|
71
|
-
hasOverlayAtOrAbove(priority) {
|
|
72
|
-
return this.entries().some((e) => e.priority >= priority);
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Check if any overlay of a specific type is open.
|
|
76
|
-
*/
|
|
77
|
-
hasOverlayOfType(type) {
|
|
78
|
-
return this.entries().some((e) => e.type === type);
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Close all overlays. Closes from lowest to highest priority.
|
|
82
|
-
*/
|
|
83
|
-
closeAll() {
|
|
84
|
-
const sorted = [...this.entries()].sort((a, b) => a.priority - b.priority);
|
|
85
|
-
for (const entry of sorted) {
|
|
86
|
-
entry.close();
|
|
87
|
-
}
|
|
88
|
-
this.entries.set([]);
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Close all overlays at or below the given priority.
|
|
92
|
-
*/
|
|
93
|
-
closeAtOrBelow(priority) {
|
|
94
|
-
const current = this.entries();
|
|
95
|
-
const toClose = current.filter((e) => e.priority <= priority);
|
|
96
|
-
for (const overlay of toClose) {
|
|
97
|
-
overlay.close();
|
|
98
|
-
}
|
|
99
|
-
this.entries.update((entries) => entries.filter((e) => e.priority > priority));
|
|
100
|
-
}
|
|
101
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OverlayOrchestrator, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
102
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OverlayOrchestrator, providedIn: 'root' });
|
|
103
|
-
}
|
|
104
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OverlayOrchestrator, decorators: [{
|
|
105
|
-
type: Injectable,
|
|
106
|
-
args: [{ providedIn: 'root' }]
|
|
107
|
-
}] });
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Default priority levels for common overlay types.
|
|
111
|
-
* Higher number = higher priority = stays open when lower-priority overlays open.
|
|
112
|
-
*
|
|
113
|
-
* Consumers can use any number — these are conventions, not constraints.
|
|
114
|
-
*/
|
|
115
|
-
const OVERLAY_PRIORITY = {
|
|
116
|
-
/** Tooltips, inline popovers */
|
|
117
|
-
TOOLTIP: 0,
|
|
118
|
-
/** Dropdown menus, kebab menus, popovers */
|
|
119
|
-
POPOVER: 10,
|
|
120
|
-
/** Side panels, detail drawers */
|
|
121
|
-
DRAWER: 20,
|
|
122
|
-
/** Modal dialogs, confirmations */
|
|
123
|
-
DIALOG: 30,
|
|
124
|
-
/** App-level navigation (sidebar, mobile nav) */
|
|
125
|
-
NAVIGATION: 40,
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Generated bundle index. Do not edit.
|
|
130
|
-
*/
|
|
131
|
-
|
|
132
|
-
export { OVERLAY_PRIORITY, OverlayOrchestrator };
|
|
133
|
-
//# sourceMappingURL=frame-kit-ui-ng-services-overlay-orchestrator.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"frame-kit-ui-ng-services-overlay-orchestrator.mjs","sources":["../../../../packages/ui-ng/services/overlay-orchestrator/overlay-orchestrator.service.ts","../../../../packages/ui-ng/services/overlay-orchestrator/overlay-orchestrator.types.ts","../../../../packages/ui-ng/services/overlay-orchestrator/frame-kit-ui-ng-services-overlay-orchestrator.ts"],"sourcesContent":["import { computed, Injectable, signal } from '@angular/core';\n\nimport type { OverlayEntry } from './overlay-orchestrator.types';\n\nlet nextOverlayId = 0;\n\n/**\n * Centralized overlay orchestrator.\n *\n * Tracks all open overlays (dialogs, drawers, popovers, navigation, tooltips)\n * in a priority-based stack. When a new overlay opens:\n *\n * - All overlays with LOWER priority are closed automatically.\n * - Overlays with EQUAL or HIGHER priority are left open.\n *\n * This prevents UI conflicts like drawers staying open behind dialogs,\n * or kebab menus persisting when a modal pops up.\n *\n * @example\n * ```ts\n * const id = orchestrator.register({\n * priority: OVERLAY_PRIORITY.DRAWER,\n * type: 'drawer',\n * close: () => drawerRef.close(),\n * });\n *\n * // Later, when the overlay closes:\n * orchestrator.unregister(id);\n * ```\n */\n@Injectable({ providedIn: 'root' })\nexport class OverlayOrchestrator {\n private readonly entries = signal<OverlayEntry[]>([]);\n\n /** Whether any overlay is currently open. */\n readonly hasActiveOverlay = computed(() => this.entries().length > 0);\n\n /** The topmost (highest priority) overlay. */\n readonly topOverlay = computed<OverlayEntry | null>(() => {\n const all = this.entries();\n\n if (all.length === 0) {\n return null;\n }\n\n return all.reduce((top, entry) =>\n entry.priority >= top.priority ? entry : top,\n );\n });\n\n /** Current number of active overlays. */\n readonly count = computed(() => this.entries().length);\n\n /**\n * Register an overlay and close all overlays with lower priority.\n *\n * @returns The unique overlay ID — pass this to `unregister()` when closing.\n */\n register(entry: Omit<OverlayEntry, 'id'>): string {\n const id = `overlay-${nextOverlayId++}`;\n\n // Close all overlays with strictly lower priority\n const current = this.entries();\n const toClose = current.filter((e) => e.priority < entry.priority);\n\n for (const overlay of toClose) {\n overlay.close();\n }\n\n // Remove closed overlays and add the new one\n const remaining = current.filter((e) => e.priority >= entry.priority);\n\n this.entries.set([...remaining, { ...entry, id }]);\n\n return id;\n }\n\n /**\n * Unregister an overlay by ID. Call this when the overlay closes.\n */\n unregister(id: string): void {\n this.entries.update((entries) => entries.filter((e) => e.id !== id));\n }\n\n /**\n * Check if an overlay with the given priority or higher is currently open.\n * Useful for guards that want to prevent opening lower-priority overlays.\n */\n hasOverlayAtOrAbove(priority: number): boolean {\n return this.entries().some((e) => e.priority >= priority);\n }\n\n /**\n * Check if any overlay of a specific type is open.\n */\n hasOverlayOfType(type: string): boolean {\n return this.entries().some((e) => e.type === type);\n }\n\n /**\n * Close all overlays. Closes from lowest to highest priority.\n */\n closeAll(): void {\n const sorted = [...this.entries()].sort((a, b) => a.priority - b.priority);\n\n for (const entry of sorted) {\n entry.close();\n }\n\n this.entries.set([]);\n }\n\n /**\n * Close all overlays at or below the given priority.\n */\n closeAtOrBelow(priority: number): void {\n const current = this.entries();\n const toClose = current.filter((e) => e.priority <= priority);\n\n for (const overlay of toClose) {\n overlay.close();\n }\n\n this.entries.update((entries) =>\n entries.filter((e) => e.priority > priority),\n );\n }\n}\n","/**\n * Default priority levels for common overlay types.\n * Higher number = higher priority = stays open when lower-priority overlays open.\n *\n * Consumers can use any number — these are conventions, not constraints.\n */\nexport const OVERLAY_PRIORITY = {\n /** Tooltips, inline popovers */\n TOOLTIP: 0,\n\n /** Dropdown menus, kebab menus, popovers */\n POPOVER: 10,\n\n /** Side panels, detail drawers */\n DRAWER: 20,\n\n /** Modal dialogs, confirmations */\n DIALOG: 30,\n\n /** App-level navigation (sidebar, mobile nav) */\n NAVIGATION: 40,\n} as const;\n\nexport interface OverlayEntry {\n /** Unique identifier for this overlay instance */\n id: string;\n\n /** Priority level — higher stays open, lower gets closed */\n priority: number;\n\n /** Overlay type label (for debugging/logging) */\n type: string;\n\n /** Called by the orchestrator to close this overlay */\n close: () => void;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
# OverlayOrchestrator
|
|
2
|
-
|
|
3
|
-
A centralized service for managing overlay interactions (dialogs, drawers, popovers, navigation panels, tooltips) using a priority-based stack. Prevents UI conflicts like drawers staying open behind dialogs, or menus persisting when modals appear.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Initialization
|
|
8
|
-
|
|
9
|
-
The `OverlayOrchestrator` is provided at the root level (`providedIn: 'root'`). No setup is needed — inject it wherever you need overlay coordination.
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
import { OverlayOrchestrator, OVERLAY_PRIORITY } from '@frame-kit/ui-ng';
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
### Built-in Integration
|
|
16
|
-
|
|
17
|
-
`DialogService` and `DrawerService` automatically register with the orchestrator. Opening a dialog auto-closes any open drawer or popover. Opening a drawer auto-closes any open popover. No consumer code is needed for these — it works out of the box.
|
|
18
|
-
|
|
19
|
-
### Registering Custom Overlays
|
|
20
|
-
|
|
21
|
-
For overlays not managed by `DialogService` or `DrawerService` (e.g., custom popovers, app navigation panels, kebab menus), register them manually:
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
@Component({ ... })
|
|
25
|
-
export class MyComponent {
|
|
26
|
-
private readonly orchestrator = inject(OverlayOrchestrator);
|
|
27
|
-
private overlayId: string | null = null;
|
|
28
|
-
|
|
29
|
-
open(): void {
|
|
30
|
-
// Register with a priority — closes all lower-priority overlays
|
|
31
|
-
this.overlayId = this.orchestrator.register({
|
|
32
|
-
priority: OVERLAY_PRIORITY.POPOVER,
|
|
33
|
-
type: "my-popover",
|
|
34
|
-
close: () => this.close(),
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
close(): void {
|
|
39
|
-
if (this.overlayId) {
|
|
40
|
-
this.orchestrator.unregister(this.overlayId);
|
|
41
|
-
this.overlayId = null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ... your close logic
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Registering App Navigation
|
|
50
|
-
|
|
51
|
-
The app shell sidebar should register at the highest priority so it always opens on top:
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
@Component({ ... })
|
|
55
|
-
export class AppShellComponent {
|
|
56
|
-
private readonly orchestrator = inject(OverlayOrchestrator);
|
|
57
|
-
private navOverlayId: string | null = null;
|
|
58
|
-
|
|
59
|
-
openNav(): void {
|
|
60
|
-
this.navOverlayId = this.orchestrator.register({
|
|
61
|
-
priority: OVERLAY_PRIORITY.NAVIGATION,
|
|
62
|
-
type: "navigation",
|
|
63
|
-
close: () => this.closeNav(),
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
closeNav(): void {
|
|
68
|
-
if (this.navOverlayId) {
|
|
69
|
-
this.orchestrator.unregister(this.navOverlayId);
|
|
70
|
-
this.navOverlayId = null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ... your close logic
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Guarding Before Opening
|
|
79
|
-
|
|
80
|
-
Check if a higher-priority overlay is already open before opening yours:
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
if (!this.orchestrator.hasOverlayAtOrAbove(OVERLAY_PRIORITY.DIALOG)) {
|
|
84
|
-
this.openDrawer();
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## API
|
|
91
|
-
|
|
92
|
-
### OverlayOrchestrator (Injectable)
|
|
93
|
-
|
|
94
|
-
#### Methods
|
|
95
|
-
|
|
96
|
-
| Method | Signature | Description |
|
|
97
|
-
| --------------------- | --------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
98
|
-
| `register` | `(entry: Omit<OverlayEntry, 'id'>) => string` | Register an overlay. Auto-closes lower-priority overlays. Returns a unique ID. |
|
|
99
|
-
| `unregister` | `(id: string) => void` | Remove an overlay by ID. Call when the overlay closes. |
|
|
100
|
-
| `closeAll` | `() => void` | Close all overlays, lowest priority first. |
|
|
101
|
-
| `closeAtOrBelow` | `(priority: number) => void` | Close all overlays at or below the given priority. |
|
|
102
|
-
| `hasOverlayAtOrAbove` | `(priority: number) => boolean` | Check if any overlay at or above a priority is open. |
|
|
103
|
-
| `hasOverlayOfType` | `(type: string) => boolean` | Check if any overlay of a given type is open. |
|
|
104
|
-
|
|
105
|
-
#### Signals (Reactive)
|
|
106
|
-
|
|
107
|
-
| Signal | Type | Description |
|
|
108
|
-
| ------------------ | ------------------------------ | ---------------------------------- |
|
|
109
|
-
| `hasActiveOverlay` | `Signal<boolean>` | Whether any overlay is open. |
|
|
110
|
-
| `topOverlay` | `Signal<OverlayEntry \| null>` | The highest-priority open overlay. |
|
|
111
|
-
| `count` | `Signal<number>` | Number of active overlays. |
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
## Priority Levels
|
|
116
|
-
|
|
117
|
-
Default priority constants are provided via `OVERLAY_PRIORITY`. You can use any number — these are conventions, not constraints.
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
import { OVERLAY_PRIORITY } from '@frame-kit/ui-ng';
|
|
121
|
-
|
|
122
|
-
OVERLAY_PRIORITY.TOOLTIP; // 0 — tooltips, inline hints
|
|
123
|
-
OVERLAY_PRIORITY.POPOVER; // 10 — dropdown menus, kebab menus
|
|
124
|
-
OVERLAY_PRIORITY.DRAWER; // 20 — side panels, detail drawers
|
|
125
|
-
OVERLAY_PRIORITY.DIALOG; // 30 — modal dialogs, confirmations
|
|
126
|
-
OVERLAY_PRIORITY.NAVIGATION; // 40 — app-level navigation sidebar
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Custom Priorities
|
|
130
|
-
|
|
131
|
-
Use any number to fine-tune behavior:
|
|
132
|
-
|
|
133
|
-
```ts
|
|
134
|
-
// A toast notification that sits between popover and drawer
|
|
135
|
-
orchestrator.register({
|
|
136
|
-
priority: 15,
|
|
137
|
-
type: 'toast',
|
|
138
|
-
close: () => this.dismissToast(),
|
|
139
|
-
});
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## OverlayEntry Interface
|
|
145
|
-
|
|
146
|
-
```ts
|
|
147
|
-
interface OverlayEntry {
|
|
148
|
-
id: string; // Auto-generated unique ID
|
|
149
|
-
priority: number; // Higher = stays open longer
|
|
150
|
-
type: string; // Label for debugging/querying
|
|
151
|
-
close: () => void; // Called by orchestrator to close
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## Behavior Rules
|
|
158
|
-
|
|
159
|
-
1. **Opening a new overlay closes all overlays with LOWER priority.**
|
|
160
|
-
- Opening a dialog (30) closes drawers (20), popovers (10), tooltips (0).
|
|
161
|
-
- Opening a drawer (20) closes popovers (10) and tooltips (0).
|
|
162
|
-
- Opening the nav (40) does not close dialogs (30) — they have lower priority.
|
|
163
|
-
|
|
164
|
-
2. **Overlays with EQUAL or HIGHER priority are left open.**
|
|
165
|
-
- Opening a second dialog does not close the first dialog.
|
|
166
|
-
- Opening the nav while a dialog is open leaves the dialog open.
|
|
167
|
-
|
|
168
|
-
3. **Unregistration is the consumer's responsibility.**
|
|
169
|
-
- The orchestrator calls `close()` on entries to request closure.
|
|
170
|
-
- The consumer must call `unregister(id)` when the overlay actually closes.
|
|
171
|
-
- `DialogService` and `DrawerService` handle this automatically.
|
|
172
|
-
|
|
173
|
-
4. **Priority is set per instance, not per type.**
|
|
174
|
-
- The same component can register at different priorities depending on context.
|
|
175
|
-
- A critical confirmation dialog could register at 35 to stay above normal dialogs.
|
|
176
|
-
|
|
177
|
-
---
|
|
178
|
-
|
|
179
|
-
## Import
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
import { OverlayOrchestrator, OVERLAY_PRIORITY } from '@frame-kit/ui-ng';
|
|
183
|
-
import type { OverlayEntry } from '@frame-kit/ui-ng';
|
|
184
|
-
```
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import * as _angular_core from '@angular/core';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Default priority levels for common overlay types.
|
|
5
|
-
* Higher number = higher priority = stays open when lower-priority overlays open.
|
|
6
|
-
*
|
|
7
|
-
* Consumers can use any number — these are conventions, not constraints.
|
|
8
|
-
*/
|
|
9
|
-
declare const OVERLAY_PRIORITY: {
|
|
10
|
-
/** Tooltips, inline popovers */
|
|
11
|
-
readonly TOOLTIP: 0;
|
|
12
|
-
/** Dropdown menus, kebab menus, popovers */
|
|
13
|
-
readonly POPOVER: 10;
|
|
14
|
-
/** Side panels, detail drawers */
|
|
15
|
-
readonly DRAWER: 20;
|
|
16
|
-
/** Modal dialogs, confirmations */
|
|
17
|
-
readonly DIALOG: 30;
|
|
18
|
-
/** App-level navigation (sidebar, mobile nav) */
|
|
19
|
-
readonly NAVIGATION: 40;
|
|
20
|
-
};
|
|
21
|
-
interface OverlayEntry {
|
|
22
|
-
/** Unique identifier for this overlay instance */
|
|
23
|
-
id: string;
|
|
24
|
-
/** Priority level — higher stays open, lower gets closed */
|
|
25
|
-
priority: number;
|
|
26
|
-
/** Overlay type label (for debugging/logging) */
|
|
27
|
-
type: string;
|
|
28
|
-
/** Called by the orchestrator to close this overlay */
|
|
29
|
-
close: () => void;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Centralized overlay orchestrator.
|
|
34
|
-
*
|
|
35
|
-
* Tracks all open overlays (dialogs, drawers, popovers, navigation, tooltips)
|
|
36
|
-
* in a priority-based stack. When a new overlay opens:
|
|
37
|
-
*
|
|
38
|
-
* - All overlays with LOWER priority are closed automatically.
|
|
39
|
-
* - Overlays with EQUAL or HIGHER priority are left open.
|
|
40
|
-
*
|
|
41
|
-
* This prevents UI conflicts like drawers staying open behind dialogs,
|
|
42
|
-
* or kebab menus persisting when a modal pops up.
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```ts
|
|
46
|
-
* const id = orchestrator.register({
|
|
47
|
-
* priority: OVERLAY_PRIORITY.DRAWER,
|
|
48
|
-
* type: 'drawer',
|
|
49
|
-
* close: () => drawerRef.close(),
|
|
50
|
-
* });
|
|
51
|
-
*
|
|
52
|
-
* // Later, when the overlay closes:
|
|
53
|
-
* orchestrator.unregister(id);
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
declare class OverlayOrchestrator {
|
|
57
|
-
private readonly entries;
|
|
58
|
-
/** Whether any overlay is currently open. */
|
|
59
|
-
readonly hasActiveOverlay: _angular_core.Signal<boolean>;
|
|
60
|
-
/** The topmost (highest priority) overlay. */
|
|
61
|
-
readonly topOverlay: _angular_core.Signal<OverlayEntry | null>;
|
|
62
|
-
/** Current number of active overlays. */
|
|
63
|
-
readonly count: _angular_core.Signal<number>;
|
|
64
|
-
/**
|
|
65
|
-
* Register an overlay and close all overlays with lower priority.
|
|
66
|
-
*
|
|
67
|
-
* @returns The unique overlay ID — pass this to `unregister()` when closing.
|
|
68
|
-
*/
|
|
69
|
-
register(entry: Omit<OverlayEntry, 'id'>): string;
|
|
70
|
-
/**
|
|
71
|
-
* Unregister an overlay by ID. Call this when the overlay closes.
|
|
72
|
-
*/
|
|
73
|
-
unregister(id: string): void;
|
|
74
|
-
/**
|
|
75
|
-
* Check if an overlay with the given priority or higher is currently open.
|
|
76
|
-
* Useful for guards that want to prevent opening lower-priority overlays.
|
|
77
|
-
*/
|
|
78
|
-
hasOverlayAtOrAbove(priority: number): boolean;
|
|
79
|
-
/**
|
|
80
|
-
* Check if any overlay of a specific type is open.
|
|
81
|
-
*/
|
|
82
|
-
hasOverlayOfType(type: string): boolean;
|
|
83
|
-
/**
|
|
84
|
-
* Close all overlays. Closes from lowest to highest priority.
|
|
85
|
-
*/
|
|
86
|
-
closeAll(): void;
|
|
87
|
-
/**
|
|
88
|
-
* Close all overlays at or below the given priority.
|
|
89
|
-
*/
|
|
90
|
-
closeAtOrBelow(priority: number): void;
|
|
91
|
-
static ɵfac: _angular_core.ɵɵFactoryDeclaration<OverlayOrchestrator, never>;
|
|
92
|
-
static ɵprov: _angular_core.ɵɵInjectableDeclaration<OverlayOrchestrator>;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export { OVERLAY_PRIORITY, OverlayOrchestrator };
|
|
96
|
-
export type { OverlayEntry };
|