@cdevhub/ngx-tw 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/cdevhub-ngx-tw-dialog-dialog-renderer-DoIhoV3d.mjs +198 -0
- package/fesm2022/cdevhub-ngx-tw-dialog-dialog-renderer-DoIhoV3d.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs +189 -219
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs +256 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +178 -275
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs +528 -0
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +414 -811
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
- package/index.json +1 -0
- package/package.json +1 -1
- package/types/cdevhub-ngx-tw-dialog.d.ts +80 -19
- package/types/cdevhub-ngx-tw-sheet.d.ts +78 -19
- package/types/cdevhub-ngx-tw-toast.d.ts +41 -6
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { ReplaySubject, filter, take, merge, Subject, defer, startWith } from 'rxjs';
|
|
6
|
-
import { CdkPortalOutlet } from '@angular/cdk/portal';
|
|
7
|
-
import { OverlayContainerCoordinator, mergeOverlayPanelClass, coerceOverlayDuration } from '@cdevhub/ngx-tw/core';
|
|
8
|
-
import { tv } from 'tailwind-variants';
|
|
2
|
+
import { InjectionToken, signal, inject, Injector, Injectable, makeEnvironmentProviders, Directive, input, computed } from '@angular/core';
|
|
3
|
+
import { ReplaySubject, Subject, filter, take, merge, defer, startWith } from 'rxjs';
|
|
4
|
+
import { DialogConfig } from '@angular/cdk/dialog';
|
|
9
5
|
import { ESCAPE, hasModifierKey } from '@angular/cdk/keycodes';
|
|
10
6
|
import { _IdGenerator } from '@angular/cdk/a11y';
|
|
11
7
|
import * as i1 from '@angular/cdk/scrolling';
|
|
@@ -73,180 +69,22 @@ const SHEET_DATA = new InjectionToken('SHEET_DATA');
|
|
|
73
69
|
/** Injection token for application-wide default sheet options. Set via `provideSheet()`. */
|
|
74
70
|
const SHEET_DEFAULT_OPTIONS = new InjectionToken('SHEET_DEFAULT_OPTIONS');
|
|
75
71
|
|
|
76
|
-
// Sheet uses a `data-[state]` + `data-[side]` driven CSS transition rather than
|
|
77
|
-
// the project's `animate.enter`/`animate.leave` keyframes. Same rationale as
|
|
78
|
-
// the dialog container (see `dialog-container.ts`):
|
|
79
|
-
// 1. The ref owns the open/close lifecycle (state signal, observables) and
|
|
80
|
-
// needs runtime-configurable enter/exit durations per `Sheet.open()` call
|
|
81
|
-
// — `animate.enter` only accepts a static class name.
|
|
82
|
-
// 2. The transform direction depends on `side`, and encoding four pairs of
|
|
83
|
-
// keyframe classes (slide-in/out × 4 sides) is noisier than four
|
|
84
|
-
// `data-[side=…]` selectors on a single transition rule.
|
|
85
|
-
// All other library overlays (popover, menu, tooltip) use animate.enter/leave;
|
|
86
|
-
// this divergence is intentional and isolated to the sheet container.
|
|
87
|
-
const sheetContainerVariants = tv({
|
|
88
|
-
slots: {
|
|
89
|
-
host: 'fixed flex flex-col outline-none bg-surface-raised text-fg shadow-md overflow-hidden transition-[transform,opacity] ease-out motion-reduce:transition-none opacity-0 data-[state=open]:opacity-100',
|
|
90
|
-
},
|
|
91
|
-
variants: {
|
|
92
|
-
side: {
|
|
93
|
-
right: {
|
|
94
|
-
host: 'h-screen right-0 top-0 border-l border-border data-[state=opening]:translate-x-full data-[state=closing]:translate-x-full data-[state=open]:translate-x-0',
|
|
95
|
-
},
|
|
96
|
-
left: {
|
|
97
|
-
host: 'h-screen left-0 top-0 border-r border-border data-[state=opening]:-translate-x-full data-[state=closing]:-translate-x-full data-[state=open]:translate-x-0',
|
|
98
|
-
},
|
|
99
|
-
top: {
|
|
100
|
-
host: 'w-screen left-0 top-0 border-b border-border data-[state=opening]:-translate-y-full data-[state=closing]:-translate-y-full data-[state=open]:translate-y-0',
|
|
101
|
-
},
|
|
102
|
-
bottom: {
|
|
103
|
-
host: 'w-screen left-0 bottom-0 border-t border-border data-[state=opening]:translate-y-full data-[state=closing]:translate-y-full data-[state=open]:translate-y-0',
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
size: {
|
|
107
|
-
xs: { host: '' },
|
|
108
|
-
sm: { host: '' },
|
|
109
|
-
md: { host: '' },
|
|
110
|
-
lg: { host: '' },
|
|
111
|
-
xl: { host: '' },
|
|
112
|
-
full: { host: '' },
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
// Sheets are axis-aware: `size` controls width on horizontal sides and
|
|
116
|
-
// height on vertical sides. Encoded as compound variants so a single
|
|
117
|
-
// {side, size} pair resolves to one set of utility classes.
|
|
118
|
-
compoundVariants: [
|
|
119
|
-
// ── Right-anchored ─────────────────────────────────────────────
|
|
120
|
-
{ side: 'right', size: 'xs', class: { host: 'w-full max-w-xs' } },
|
|
121
|
-
{ side: 'right', size: 'sm', class: { host: 'w-full max-w-sm' } },
|
|
122
|
-
{ side: 'right', size: 'md', class: { host: 'w-full max-w-md' } },
|
|
123
|
-
{ side: 'right', size: 'lg', class: { host: 'w-full max-w-xl' } },
|
|
124
|
-
{ side: 'right', size: 'xl', class: { host: 'w-full max-w-2xl' } },
|
|
125
|
-
{ side: 'right', size: 'full', class: { host: 'w-screen max-w-none' } },
|
|
126
|
-
// ── Left-anchored ──────────────────────────────────────────────
|
|
127
|
-
{ side: 'left', size: 'xs', class: { host: 'w-full max-w-xs' } },
|
|
128
|
-
{ side: 'left', size: 'sm', class: { host: 'w-full max-w-sm' } },
|
|
129
|
-
{ side: 'left', size: 'md', class: { host: 'w-full max-w-md' } },
|
|
130
|
-
{ side: 'left', size: 'lg', class: { host: 'w-full max-w-xl' } },
|
|
131
|
-
{ side: 'left', size: 'xl', class: { host: 'w-full max-w-2xl' } },
|
|
132
|
-
{ side: 'left', size: 'full', class: { host: 'w-screen max-w-none' } },
|
|
133
|
-
// ── Top-anchored ───────────────────────────────────────────────
|
|
134
|
-
{ side: 'top', size: 'xs', class: { host: 'h-[20vh]' } },
|
|
135
|
-
{ side: 'top', size: 'sm', class: { host: 'h-[33vh]' } },
|
|
136
|
-
{ side: 'top', size: 'md', class: { host: 'h-[50vh]' } },
|
|
137
|
-
{ side: 'top', size: 'lg', class: { host: 'h-[66vh]' } },
|
|
138
|
-
{ side: 'top', size: 'xl', class: { host: 'h-[80vh]' } },
|
|
139
|
-
{ side: 'top', size: 'full', class: { host: 'h-screen' } },
|
|
140
|
-
// ── Bottom-anchored ────────────────────────────────────────────
|
|
141
|
-
{ side: 'bottom', size: 'xs', class: { host: 'h-[20vh]' } },
|
|
142
|
-
{ side: 'bottom', size: 'sm', class: { host: 'h-[33vh]' } },
|
|
143
|
-
{ side: 'bottom', size: 'md', class: { host: 'h-[50vh]' } },
|
|
144
|
-
{ side: 'bottom', size: 'lg', class: { host: 'h-[66vh]' } },
|
|
145
|
-
{ side: 'bottom', size: 'xl', class: { host: 'h-[80vh]' } },
|
|
146
|
-
{ side: 'bottom', size: 'full', class: { host: 'h-screen' } },
|
|
147
|
-
],
|
|
148
|
-
defaultVariants: { side: 'right', size: 'md' },
|
|
149
|
-
}, { twMerge: true });
|
|
150
|
-
/**
|
|
151
|
-
* Sheet container rendered inside the CDK overlay. Wraps the user-provided
|
|
152
|
-
* content in a Tailwind-styled, edge-anchored surface and coordinates slide
|
|
153
|
-
* enter/exit transitions with {@link SheetRef}.
|
|
154
|
-
*
|
|
155
|
-
* The animation state machine, `aria-describedby` queue, and panel-class
|
|
156
|
-
* merge are shared with `DialogContainer` via {@link OverlayContainerCoordinator}
|
|
157
|
-
* (component-scoped — see `providers: [OverlayContainerCoordinator]`).
|
|
158
|
-
*
|
|
159
|
-
* @docs-private
|
|
160
|
-
*/
|
|
161
|
-
class SheetContainer extends CdkDialogContainer {
|
|
162
|
-
coordinator = inject(OverlayContainerCoordinator);
|
|
163
|
-
/** Lifecycle state of the sheet's enter/exit animation. */
|
|
164
|
-
state = this.coordinator.state;
|
|
165
|
-
/** Emits whenever the animation state transitions. */
|
|
166
|
-
animationStateChanged = this.coordinator.animationStateChanged;
|
|
167
|
-
/** Resolved enter-animation duration in ms. */
|
|
168
|
-
enterAnimationDuration;
|
|
169
|
-
/** Resolved exit-animation duration in ms. */
|
|
170
|
-
exitAnimationDuration;
|
|
171
|
-
sideAttr = computed(() => this._config.side ?? 'right', /* @ts-ignore */
|
|
172
|
-
...(ngDevMode ? [{ debugName: "sideAttr" }] : /* istanbul ignore next */ []));
|
|
173
|
-
variants = computed(() => {
|
|
174
|
-
const side = this._config.side ?? 'right';
|
|
175
|
-
const size = this._config.size ?? 'md';
|
|
176
|
-
return sheetContainerVariants({ side, size });
|
|
177
|
-
}, /* @ts-ignore */
|
|
178
|
-
...(ngDevMode ? [{ debugName: "variants" }] : /* istanbul ignore next */ []));
|
|
179
|
-
hostClasses = computed(() => mergeOverlayPanelClass(this.variants().host(), this._config.panelClass), /* @ts-ignore */
|
|
180
|
-
...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
|
|
181
|
-
ariaDescribedByAttr = computed(() => this._config.ariaDescribedBy ||
|
|
182
|
-
this.coordinator.describedByIds()[0] ||
|
|
183
|
-
null, /* @ts-ignore */
|
|
184
|
-
...(ngDevMode ? [{ debugName: "ariaDescribedByAttr" }] : /* istanbul ignore next */ []));
|
|
185
|
-
transitionDuration = this.coordinator.transitionDuration;
|
|
186
|
-
constructor() {
|
|
187
|
-
super();
|
|
188
|
-
this.enterAnimationDuration = coerceOverlayDuration(this._config.enterAnimationDuration, 200);
|
|
189
|
-
this.exitAnimationDuration = coerceOverlayDuration(this._config.exitAnimationDuration, 160);
|
|
190
|
-
this.coordinator.setDurations(this.enterAnimationDuration, this.exitAnimationDuration);
|
|
191
|
-
}
|
|
192
|
-
_contentAttached() {
|
|
193
|
-
super._contentAttached();
|
|
194
|
-
this.coordinator.startEnterAnimation();
|
|
195
|
-
}
|
|
196
|
-
/** Triggered by the sheet ref to play the exit transition before disposing the overlay. */
|
|
197
|
-
_startExitAnimation() {
|
|
198
|
-
this.coordinator.startExitAnimation();
|
|
199
|
-
}
|
|
200
|
-
/** Registers a description id for the container's `aria-describedby`. */
|
|
201
|
-
_addAriaDescribedBy(id) {
|
|
202
|
-
this.coordinator.addAriaDescribedBy(id);
|
|
203
|
-
}
|
|
204
|
-
/** Removes a previously registered description id. */
|
|
205
|
-
_removeAriaDescribedBy(id) {
|
|
206
|
-
this.coordinator.removeAriaDescribedBy(id);
|
|
207
|
-
}
|
|
208
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: SheetContainer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
209
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: SheetContainer, isStandalone: true, selector: "tw-sheet-container", host: { attributes: { "tabindex": "-1" }, properties: { "attr.id": "_config.id || null", "attr.role": "_config.role", "attr.aria-modal": "_config.ariaModal", "attr.aria-labelledby": "_config.ariaLabel ? null : _ariaLabelledByQueue[0]", "attr.aria-label": "_config.ariaLabel", "attr.aria-describedby": "ariaDescribedByAttr()", "attr.data-state": "state()", "attr.data-side": "sideAttr()", "class": "hostClasses()", "style.transition-duration.ms": "transitionDuration()" } }, providers: [OverlayContainerCoordinator], usesInheritance: true, ngImport: i0, template: '<ng-template cdkPortalOutlet />', isInline: true, dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
210
|
-
}
|
|
211
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: SheetContainer, decorators: [{
|
|
212
|
-
type: Component,
|
|
213
|
-
args: [{
|
|
214
|
-
selector: 'tw-sheet-container',
|
|
215
|
-
template: '<ng-template cdkPortalOutlet />',
|
|
216
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
217
|
-
encapsulation: ViewEncapsulation.None,
|
|
218
|
-
imports: [CdkPortalOutlet],
|
|
219
|
-
providers: [OverlayContainerCoordinator],
|
|
220
|
-
host: {
|
|
221
|
-
tabindex: '-1',
|
|
222
|
-
'[attr.id]': '_config.id || null',
|
|
223
|
-
'[attr.role]': '_config.role',
|
|
224
|
-
'[attr.aria-modal]': '_config.ariaModal',
|
|
225
|
-
'[attr.aria-labelledby]': '_config.ariaLabel ? null : _ariaLabelledByQueue[0]',
|
|
226
|
-
'[attr.aria-label]': '_config.ariaLabel',
|
|
227
|
-
'[attr.aria-describedby]': 'ariaDescribedByAttr()',
|
|
228
|
-
'[attr.data-state]': 'state()',
|
|
229
|
-
'[attr.data-side]': 'sideAttr()',
|
|
230
|
-
'[class]': 'hostClasses()',
|
|
231
|
-
'[style.transition-duration.ms]': 'transitionDuration()',
|
|
232
|
-
},
|
|
233
|
-
}]
|
|
234
|
-
}], ctorParameters: () => [] });
|
|
235
|
-
|
|
236
72
|
/**
|
|
237
73
|
* Reference to a sheet opened via {@link Sheet.open}. Drives the sheet
|
|
238
74
|
* lifecycle (close, state, observables) and forwards useful overlay streams.
|
|
75
|
+
*
|
|
76
|
+
* The ref is returned **synchronously** from `open()`, but the sheet's render
|
|
77
|
+
* layer (`@angular/cdk/dialog` + the Tailwind container) is loaded through a
|
|
78
|
+
* dynamic `import()`. The ref therefore starts *detached*: `id`, `state`,
|
|
79
|
+
* `close()`, the lifecycle observables, and panel mutations all work
|
|
80
|
+
* immediately (mutations are buffered and replayed on attach), but the rendered
|
|
81
|
+
* component instance does not exist yet — read it via {@link whenComponentReady}
|
|
82
|
+
* instead of a synchronous `componentInstance` field.
|
|
239
83
|
*/
|
|
240
84
|
class SheetRef {
|
|
241
|
-
cdkRef;
|
|
242
85
|
config;
|
|
243
|
-
|
|
244
|
-
/** Unique ID of the sheet. */
|
|
86
|
+
/** Unique ID of the sheet. Generated eagerly by the service, so it is valid the instant `open()` returns. */
|
|
245
87
|
id;
|
|
246
|
-
/** Instance of the component rendered inside the sheet, or `null` for template sheets. */
|
|
247
|
-
componentInstance = null;
|
|
248
|
-
/** `ComponentRef` of the content component, or `null` for template sheets. */
|
|
249
|
-
componentRef = null;
|
|
250
88
|
/** Current lifecycle state. Reactively readable. */
|
|
251
89
|
state;
|
|
252
90
|
/** When `true`, close-via-escape AND close-via-backdrop are both disabled. */
|
|
@@ -260,19 +98,56 @@ class SheetRef {
|
|
|
260
98
|
afterOpenedSubject = new ReplaySubject(1);
|
|
261
99
|
beforeClosedSubject = new ReplaySubject(1);
|
|
262
100
|
afterClosedSubject = new ReplaySubject(1);
|
|
101
|
+
// Facade-owned pass-throughs for the raw overlay streams, so a consumer that
|
|
102
|
+
// subscribes before the render chunk attaches still receives events once it
|
|
103
|
+
// does — the pre-deferral behaviour when `open()` wrapped a live `cdkRef`.
|
|
104
|
+
backdropClickSubject = new Subject();
|
|
105
|
+
keydownEventsSubject = new Subject();
|
|
106
|
+
cdkRef = null;
|
|
107
|
+
container = null;
|
|
108
|
+
componentInstanceValue = null;
|
|
109
|
+
componentRefValue = null;
|
|
110
|
+
resolveComponentReady;
|
|
111
|
+
componentReadyPromise = new Promise((resolve) => {
|
|
112
|
+
this.resolveComponentReady = resolve;
|
|
113
|
+
});
|
|
114
|
+
// Buffered mutations issued before the render chunk attached the CDK backend.
|
|
115
|
+
pendingPanelAdds = [];
|
|
116
|
+
pendingPanelRemoves = [];
|
|
263
117
|
pendingResult;
|
|
264
118
|
closeFocusOrigin;
|
|
265
|
-
constructor(
|
|
266
|
-
this.cdkRef = cdkRef;
|
|
119
|
+
constructor(id, config) {
|
|
267
120
|
this.config = config;
|
|
268
|
-
this.
|
|
269
|
-
this.id = cdkRef.id;
|
|
121
|
+
this.id = id;
|
|
270
122
|
this.disableClose = config.disableClose;
|
|
271
123
|
this.closeOnEscape = config.closeOnEscape;
|
|
272
124
|
this.closeOnBackdropClick = config.closeOnBackdropClick;
|
|
273
125
|
this.state = this.stateSignal.asReadonly();
|
|
126
|
+
}
|
|
127
|
+
/** The Tailwind container instance, or `null` until the sheet has attached. */
|
|
128
|
+
get containerInstance() {
|
|
129
|
+
return this.container;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* @internal Wire this facade to its CDK backend. Called from the sheet
|
|
133
|
+
* renderer **inside** `cdkDialog.open()`'s `providers` callback — the same
|
|
134
|
+
* point the constructor ran before deferral — so subscriptions to
|
|
135
|
+
* `animationStateChanged` are in place before the container emits `'open'`.
|
|
136
|
+
*/
|
|
137
|
+
_attach(cdkRef, container) {
|
|
138
|
+
this.cdkRef = cdkRef;
|
|
139
|
+
this.container = container;
|
|
140
|
+
// Forward the raw overlay streams into the facade pass-throughs.
|
|
141
|
+
cdkRef.backdropClick.subscribe(this.backdropClickSubject);
|
|
142
|
+
cdkRef.keydownEvents.subscribe(this.keydownEventsSubject);
|
|
274
143
|
cdkRef.addPanelClass('tw-sheet-panel');
|
|
275
|
-
const
|
|
144
|
+
for (const cls of this.pendingPanelAdds)
|
|
145
|
+
cdkRef.addPanelClass(cls);
|
|
146
|
+
for (const cls of this.pendingPanelRemoves)
|
|
147
|
+
cdkRef.removePanelClass(cls);
|
|
148
|
+
this.pendingPanelAdds.length = 0;
|
|
149
|
+
this.pendingPanelRemoves.length = 0;
|
|
150
|
+
const animationChanges = container.animationStateChanged;
|
|
276
151
|
animationChanges
|
|
277
152
|
.pipe(filter((event) => event.state === 'open'), take(1))
|
|
278
153
|
.subscribe(() => {
|
|
@@ -301,6 +176,38 @@ class SheetRef {
|
|
|
301
176
|
this.closeWithOrigin(event.type === 'keydown' ? 'keyboard' : 'mouse');
|
|
302
177
|
});
|
|
303
178
|
}
|
|
179
|
+
/** @internal Record the rendered component after `cdkDialog.open()` returns. */
|
|
180
|
+
_setComponent(instance, ref) {
|
|
181
|
+
this.componentInstanceValue = instance;
|
|
182
|
+
this.componentRefValue = ref;
|
|
183
|
+
this.resolveComponentReady(instance);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Resolves with the rendered content-component instance once the sheet's
|
|
187
|
+
* render chunk has loaded and attached. Resolves `null` for template sheets,
|
|
188
|
+
* and for a sheet closed before it ever opened.
|
|
189
|
+
*
|
|
190
|
+
* Replaces the former synchronous `componentInstance` field, which cannot be
|
|
191
|
+
* populated before the deferred render chunk lands.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* const ref = sheet.open(FilterSheet);
|
|
196
|
+
* const filters = await ref.whenComponentReady();
|
|
197
|
+
* filters?.reset();
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
whenComponentReady() {
|
|
201
|
+
return this.componentReadyPromise;
|
|
202
|
+
}
|
|
203
|
+
/** The rendered component instance, or `null` if not yet attached / a template sheet. Prefer {@link whenComponentReady}. */
|
|
204
|
+
get componentInstance() {
|
|
205
|
+
return this.componentInstanceValue;
|
|
206
|
+
}
|
|
207
|
+
/** The rendered `ComponentRef`, or `null` if not yet attached / a template sheet. */
|
|
208
|
+
get componentRef() {
|
|
209
|
+
return this.componentRefValue;
|
|
210
|
+
}
|
|
304
211
|
/**
|
|
305
212
|
* Closes the sheet. The exit slide animation runs before the overlay is disposed.
|
|
306
213
|
* @param result Value forwarded to `afterClosed()` subscribers.
|
|
@@ -320,22 +227,32 @@ class SheetRef {
|
|
|
320
227
|
afterClosed() {
|
|
321
228
|
return this.afterClosedSubject.asObservable();
|
|
322
229
|
}
|
|
323
|
-
/** Backdrop click stream (emits even when `closeOnBackdropClick` / `disableClose` is set). */
|
|
230
|
+
/** Backdrop click stream (emits even when `closeOnBackdropClick` / `disableClose` is set). Buffered — a subscription made before the sheet attaches receives events once it does. */
|
|
324
231
|
backdropClick() {
|
|
325
|
-
return this.
|
|
232
|
+
return this.backdropClickSubject.asObservable();
|
|
326
233
|
}
|
|
327
|
-
/** Keydown event stream for the overlay. */
|
|
234
|
+
/** Keydown event stream for the overlay. Buffered — a subscription made before the sheet attaches receives events once it does. */
|
|
328
235
|
keydownEvents() {
|
|
329
|
-
return this.
|
|
236
|
+
return this.keydownEventsSubject.asObservable();
|
|
330
237
|
}
|
|
331
|
-
/** Adds CSS classes to the overlay panel. */
|
|
238
|
+
/** Adds CSS classes to the overlay panel. Buffered until attach. */
|
|
332
239
|
addPanelClass(classes) {
|
|
333
|
-
this.cdkRef
|
|
240
|
+
if (this.cdkRef) {
|
|
241
|
+
this.cdkRef.addPanelClass(classes);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
this.pendingPanelAdds.push(...(Array.isArray(classes) ? classes : [classes]));
|
|
245
|
+
}
|
|
334
246
|
return this;
|
|
335
247
|
}
|
|
336
|
-
/** Removes CSS classes from the overlay panel. */
|
|
248
|
+
/** Removes CSS classes from the overlay panel. Buffered until attach. */
|
|
337
249
|
removePanelClass(classes) {
|
|
338
|
-
this.cdkRef
|
|
250
|
+
if (this.cdkRef) {
|
|
251
|
+
this.cdkRef.removePanelClass(classes);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
this.pendingPanelRemoves.push(...(Array.isArray(classes) ? classes : [classes]));
|
|
255
|
+
}
|
|
339
256
|
return this;
|
|
340
257
|
}
|
|
341
258
|
closeWithOrigin(origin, result) {
|
|
@@ -343,7 +260,7 @@ class SheetRef {
|
|
|
343
260
|
return;
|
|
344
261
|
const predicate = this.config.closePredicate;
|
|
345
262
|
if (predicate &&
|
|
346
|
-
!predicate(result, this.config, this.
|
|
263
|
+
!predicate(result, this.config, this.componentInstanceValue)) {
|
|
347
264
|
return;
|
|
348
265
|
}
|
|
349
266
|
this.pendingResult = result;
|
|
@@ -351,8 +268,15 @@ class SheetRef {
|
|
|
351
268
|
this.stateSignal.set('closing');
|
|
352
269
|
this.beforeClosedSubject.next(result);
|
|
353
270
|
this.beforeClosedSubject.complete();
|
|
271
|
+
if (!this.cdkRef) {
|
|
272
|
+
// Closed before the render chunk attached: the overlay was never created,
|
|
273
|
+
// so there is nothing to animate or dispose. Synthesize the closed state
|
|
274
|
+
// and let the service skip opening entirely (it checks state()).
|
|
275
|
+
this.finishClose();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
354
278
|
this.cdkRef.overlayRef.detachBackdrop();
|
|
355
|
-
this.
|
|
279
|
+
this.container._startExitAnimation();
|
|
356
280
|
}
|
|
357
281
|
finishClose() {
|
|
358
282
|
if (this.stateSignal() === 'closed')
|
|
@@ -360,26 +284,42 @@ class SheetRef {
|
|
|
360
284
|
this.stateSignal.set('closed');
|
|
361
285
|
this.afterClosedSubject.next(this.pendingResult);
|
|
362
286
|
this.afterClosedSubject.complete();
|
|
363
|
-
if
|
|
287
|
+
// Idempotent — a no-op if _setComponent already resolved with an instance.
|
|
288
|
+
this.resolveComponentReady(null);
|
|
289
|
+
this.backdropClickSubject.complete();
|
|
290
|
+
this.keydownEventsSubject.complete();
|
|
291
|
+
if (this.cdkRef?.containerInstance) {
|
|
364
292
|
this.cdkRef.close(this.pendingResult, { focusOrigin: this.closeFocusOrigin });
|
|
365
293
|
}
|
|
366
|
-
this.
|
|
294
|
+
this.componentInstanceValue = null;
|
|
367
295
|
}
|
|
368
296
|
}
|
|
369
297
|
|
|
298
|
+
let nextSheetId = 0;
|
|
299
|
+
function generateSheetId() {
|
|
300
|
+
return `tw-sheet-${++nextSheetId}`;
|
|
301
|
+
}
|
|
370
302
|
/**
|
|
371
303
|
* Opens edge-anchored sheet (drawer) overlays. Composes `@angular/cdk/dialog`
|
|
372
304
|
* for focus trapping, portals, and overlay plumbing — adds a `GlobalPositionStrategy`
|
|
373
305
|
* pinned to the requested viewport edge, a Tailwind container with axis-aware
|
|
374
306
|
* sizing, slide enter/exit animations, and split close-behavior flags.
|
|
375
307
|
*
|
|
308
|
+
* The rendering layer (`@angular/cdk/dialog` + the Tailwind container) is loaded
|
|
309
|
+
* through a dynamic `import()` on the first `open()` call, so merely registering
|
|
310
|
+
* this service costs nothing in the initial bundle. `open()` still returns its
|
|
311
|
+
* {@link SheetRef} synchronously — the sheet is rendered once the chunk lands.
|
|
312
|
+
* Read the rendered component via {@link SheetRef.whenComponentReady}.
|
|
313
|
+
*
|
|
376
314
|
* Not `providedIn: 'root'` — register it via {@link provideSheet}.
|
|
377
315
|
*/
|
|
378
316
|
class Sheet {
|
|
379
|
-
cdkDialog = inject(Dialog);
|
|
380
317
|
injector = inject(Injector);
|
|
381
318
|
defaultOptions = inject(SHEET_DEFAULT_OPTIONS, { optional: true }) ?? {};
|
|
382
319
|
parentSheet = inject(Sheet, { optional: true, skipSelf: true });
|
|
320
|
+
/** Cached renderer chunk import — kicked off on the first `open()`. */
|
|
321
|
+
rendererPromise = null;
|
|
322
|
+
destroyed = false;
|
|
383
323
|
openSheetsAtThisLevel = signal([], /* @ts-ignore */
|
|
384
324
|
...(ngDevMode ? [{ debugName: "openSheetsAtThisLevel" }] : /* istanbul ignore next */ []));
|
|
385
325
|
afterOpenedSubject = new Subject();
|
|
@@ -403,61 +343,25 @@ class Sheet {
|
|
|
403
343
|
* Opens a sheet using the given component or template.
|
|
404
344
|
* @param content Component class or `TemplateRef`.
|
|
405
345
|
* @param config Options merged over the application defaults.
|
|
406
|
-
* @returns Reference controlling the opened sheet.
|
|
346
|
+
* @returns Reference controlling the opened sheet, returned synchronously.
|
|
407
347
|
*/
|
|
408
348
|
open(content, config) {
|
|
409
349
|
const merged = this.resolveConfig(config);
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// utilities). The overlay pane itself is the full-edge bounding box.
|
|
420
|
-
direction: merged.direction,
|
|
421
|
-
ariaDescribedBy: merged.ariaDescribedBy,
|
|
422
|
-
ariaLabelledBy: merged.ariaLabelledBy,
|
|
423
|
-
ariaLabel: merged.ariaLabel,
|
|
424
|
-
ariaModal: merged.ariaModal,
|
|
425
|
-
autoFocus: merged.autoFocus,
|
|
426
|
-
restoreFocus: merged.restoreFocus,
|
|
427
|
-
scrollStrategy: merged.scrollStrategy ?? this.resolveScrollStrategy(merged.scrollBehavior),
|
|
428
|
-
positionStrategy: merged.positionStrategy ?? this.resolvePositionStrategy(merged.side ?? 'right'),
|
|
429
|
-
closeOnNavigation: merged.closeOnNavigation,
|
|
430
|
-
viewContainerRef: merged.viewContainerRef,
|
|
431
|
-
injector: merged.injector,
|
|
432
|
-
// We handle close/Escape/backdrop ourselves so we can run the exit slide animation.
|
|
433
|
-
disableClose: true,
|
|
434
|
-
closeOnOverlayDetachments: false,
|
|
435
|
-
container: {
|
|
436
|
-
type: SheetContainer,
|
|
437
|
-
providers: () => [
|
|
438
|
-
{ provide: SheetConfig, useValue: merged },
|
|
439
|
-
{ provide: DialogConfig, useValue: merged },
|
|
440
|
-
],
|
|
441
|
-
},
|
|
442
|
-
providers: (_cdkRef, _cdkConfig, container) => {
|
|
443
|
-
sheetRef = new SheetRef(_cdkRef, merged, container);
|
|
444
|
-
const providers = [
|
|
445
|
-
{ provide: SheetRef, useValue: sheetRef },
|
|
446
|
-
{ provide: SHEET_DATA, useValue: merged.data ?? null },
|
|
447
|
-
];
|
|
448
|
-
if (Array.isArray(merged.providers))
|
|
449
|
-
providers.push(...merged.providers);
|
|
450
|
-
return providers;
|
|
451
|
-
},
|
|
452
|
-
});
|
|
453
|
-
// After CDK attaches the component, copy references onto our ref.
|
|
454
|
-
sheetRef.componentRef = cdkRef.componentRef;
|
|
455
|
-
sheetRef.componentInstance = cdkRef.componentInstance;
|
|
350
|
+
const id = merged.id ?? generateSheetId();
|
|
351
|
+
merged.id = id;
|
|
352
|
+
// Enforce id uniqueness eagerly. CDK throws this synchronously from
|
|
353
|
+
// `open()`; since our CDK open is now deferred, replicate the check here so
|
|
354
|
+
// the error still surfaces at the call site rather than in a later tick.
|
|
355
|
+
if (this.getSheetById(id)) {
|
|
356
|
+
throw new Error(`Sheet with id "${id}" exists already. The sheet id must be unique.`);
|
|
357
|
+
}
|
|
358
|
+
const sheetRef = new SheetRef(id, merged);
|
|
456
359
|
const scope = this.parentSheet ?? this;
|
|
457
360
|
scope.registerOpen(sheetRef);
|
|
458
361
|
sheetRef.afterClosed().subscribe(() => {
|
|
459
362
|
scope.unregister(sheetRef);
|
|
460
363
|
});
|
|
364
|
+
void this.renderWhenReady(content, merged, sheetRef);
|
|
461
365
|
return sheetRef;
|
|
462
366
|
}
|
|
463
367
|
/** Closes every open sheet managed by this service (and child services). */
|
|
@@ -471,12 +375,42 @@ class Sheet {
|
|
|
471
375
|
return this.openSheets().find((sheet) => sheet.id === id);
|
|
472
376
|
}
|
|
473
377
|
ngOnDestroy() {
|
|
378
|
+
this.destroyed = true;
|
|
474
379
|
const sheets = [...this.openSheetsAtThisLevel()];
|
|
475
380
|
for (let i = sheets.length - 1; i >= 0; i--)
|
|
476
381
|
sheets[i].close();
|
|
477
382
|
this.afterOpenedSubject.complete();
|
|
478
383
|
this.afterAllClosedSubject.complete();
|
|
479
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* @internal Resolves once the renderer chunk has loaded. Exposed for tests,
|
|
387
|
+
* which must await the dynamic import before asserting on rendered DOM.
|
|
388
|
+
*/
|
|
389
|
+
async _whenRendered() {
|
|
390
|
+
await this.loadRenderer();
|
|
391
|
+
await Promise.resolve();
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Render a sheet once the renderer chunk has loaded. The ref was already
|
|
395
|
+
* returned to the caller, so it may have been closed (or the service
|
|
396
|
+
* destroyed) while the import was in flight — both skip the actual open.
|
|
397
|
+
*/
|
|
398
|
+
async renderWhenReady(content, merged, sheetRef) {
|
|
399
|
+
const openRendered = await this.loadRenderer();
|
|
400
|
+
if (!openRendered || this.destroyed)
|
|
401
|
+
return;
|
|
402
|
+
// Closed before the chunk landed — the facade already synthesized its
|
|
403
|
+
// closed state; never create the overlay.
|
|
404
|
+
if (sheetRef.state() === 'closed')
|
|
405
|
+
return;
|
|
406
|
+
openRendered(this.injector, content, merged, sheetRef);
|
|
407
|
+
}
|
|
408
|
+
loadRenderer() {
|
|
409
|
+
if (!this.rendererPromise) {
|
|
410
|
+
this.rendererPromise = import('./cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs').then(({ openRenderedSheet }) => this.destroyed ? null : openRenderedSheet);
|
|
411
|
+
}
|
|
412
|
+
return this.rendererPromise;
|
|
413
|
+
}
|
|
480
414
|
registerOpen(ref) {
|
|
481
415
|
if (this.parentSheet) {
|
|
482
416
|
this.parentSheet.registerOpen(ref);
|
|
@@ -507,33 +441,6 @@ class Sheet {
|
|
|
507
441
|
Object.assign(merged, this.defaultOptions, config);
|
|
508
442
|
return merged;
|
|
509
443
|
}
|
|
510
|
-
resolveScrollStrategy(strategy) {
|
|
511
|
-
switch (strategy) {
|
|
512
|
-
case 'close':
|
|
513
|
-
return createCloseScrollStrategy(this.injector);
|
|
514
|
-
case 'reposition':
|
|
515
|
-
return createRepositionScrollStrategy(this.injector);
|
|
516
|
-
case 'noop':
|
|
517
|
-
return createNoopScrollStrategy();
|
|
518
|
-
case 'block':
|
|
519
|
-
default:
|
|
520
|
-
return createBlockScrollStrategy(this.injector);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
resolvePositionStrategy(side) {
|
|
524
|
-
const strategy = createGlobalPositionStrategy(this.injector);
|
|
525
|
-
switch (side) {
|
|
526
|
-
case 'left':
|
|
527
|
-
return strategy.top('0').left('0').bottom('0');
|
|
528
|
-
case 'top':
|
|
529
|
-
return strategy.top('0').left('0').right('0');
|
|
530
|
-
case 'bottom':
|
|
531
|
-
return strategy.bottom('0').left('0').right('0');
|
|
532
|
-
case 'right':
|
|
533
|
-
default:
|
|
534
|
-
return strategy.top('0').right('0').bottom('0');
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
444
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: Sheet, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
538
445
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: Sheet });
|
|
539
446
|
}
|
|
@@ -620,21 +527,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
620
527
|
class SheetTitleDirective {
|
|
621
528
|
generatedId = inject(_IdGenerator).getId('tw-sheet-title-');
|
|
622
529
|
sheetRef = inject(SheetRef, { optional: true });
|
|
623
|
-
// Ancestor-DI fallback for the rare case where `SheetRef` is not in the
|
|
624
530
|
// directive's injector chain (e.g. heavily nested template portals). The
|
|
625
531
|
// container ALWAYS resolves via element-injector traversal because the
|
|
626
532
|
// directive lives inside `<tw-sheet-container>`'s DOM tree.
|
|
627
|
-
container = inject(SheetContainer, { optional: true, skipSelf: true });
|
|
628
533
|
/** Custom id for the title element. Defaults to a generated unique id. */
|
|
629
534
|
id = input(this.generatedId, /* @ts-ignore */
|
|
630
535
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
631
536
|
ngOnInit() {
|
|
632
|
-
const containerInstance = this.sheetRef?.containerInstance
|
|
537
|
+
const containerInstance = this.sheetRef?.containerInstance;
|
|
633
538
|
if (containerInstance)
|
|
634
539
|
containerInstance._addAriaLabelledBy(this.id());
|
|
635
540
|
}
|
|
636
541
|
ngOnDestroy() {
|
|
637
|
-
const containerInstance = this.sheetRef?.containerInstance
|
|
542
|
+
const containerInstance = this.sheetRef?.containerInstance;
|
|
638
543
|
if (containerInstance)
|
|
639
544
|
containerInstance._removeAriaLabelledBy(this.id());
|
|
640
545
|
}
|
|
@@ -673,18 +578,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
673
578
|
class SheetDescriptionDirective {
|
|
674
579
|
generatedId = inject(_IdGenerator).getId('tw-sheet-description-');
|
|
675
580
|
sheetRef = inject(SheetRef, { optional: true });
|
|
676
|
-
// Ancestor-DI fallback — see SheetTitleDirective.
|
|
677
|
-
container = inject(SheetContainer, { optional: true, skipSelf: true });
|
|
678
581
|
/** Custom id for the description element. Defaults to a generated unique id. */
|
|
679
582
|
id = input(this.generatedId, /* @ts-ignore */
|
|
680
583
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
681
584
|
ngOnInit() {
|
|
682
|
-
const containerInstance = this.sheetRef?.containerInstance
|
|
585
|
+
const containerInstance = this.sheetRef?.containerInstance;
|
|
683
586
|
if (containerInstance)
|
|
684
587
|
containerInstance._addAriaDescribedBy(this.id());
|
|
685
588
|
}
|
|
686
589
|
ngOnDestroy() {
|
|
687
|
-
const containerInstance = this.sheetRef?.containerInstance
|
|
590
|
+
const containerInstance = this.sheetRef?.containerInstance;
|
|
688
591
|
if (containerInstance)
|
|
689
592
|
containerInstance._removeAriaDescribedBy(this.id());
|
|
690
593
|
}
|
|
@@ -778,5 +681,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
778
681
|
* Generated bundle index. Do not edit.
|
|
779
682
|
*/
|
|
780
683
|
|
|
781
|
-
export { SHEET_DATA, SHEET_DEFAULT_OPTIONS, Sheet, SheetActionsDirective, SheetCloseDirective, SheetConfig,
|
|
684
|
+
export { SHEET_DATA, SHEET_DEFAULT_OPTIONS, Sheet, SheetActionsDirective, SheetCloseDirective, SheetConfig, SheetContentDirective, SheetDescriptionDirective, SheetHeaderDirective, SheetIconDirective, SheetRef, SheetSubtitleDirective, SheetTitleDirective, provideSheet };
|
|
782
685
|
//# sourceMappingURL=cdevhub-ngx-tw-sheet.mjs.map
|