@cdevhub/ngx-tw 0.4.0 → 0.6.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/README.md +78 -15
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs +16 -5
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs +26 -15
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-badge.mjs +17 -5
- package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +27 -6
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-button.mjs +23 -11
- package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +5 -5
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs +3 -3
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs +427 -122
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-card.mjs +21 -11
- package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs +215 -96
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +108 -64
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs +18 -6
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +55 -30
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs +218 -0
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs +236 -211
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs +200 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +34 -7
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-core.mjs +135 -15
- package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs +203 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +160 -102
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs +209 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +182 -107
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -1
- 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-testing.mjs +122 -0
- package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs +199 -220
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs +149 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs +57 -67
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +42 -24
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs +114 -21
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-icon.mjs +16 -6
- package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-input.mjs +52 -61
- package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-item.mjs +18 -3
- package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs +168 -0
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-menu.mjs +58 -21
- package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs +34 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs +95 -45
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-popover.mjs +81 -32
- package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-radio.mjs +131 -98
- package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +207 -42
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs +165 -0
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-select.mjs +386 -210
- package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs +256 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs +129 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +197 -278
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-slider.mjs +89 -54
- package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sort.mjs +122 -17
- package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-split.mjs +72 -19
- package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stat.mjs +63 -25
- package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs +35 -8
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-switch.mjs +54 -48
- package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +12 -15
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-table.mjs +74 -13
- package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs +84 -30
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs +172 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs +107 -71
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs +41 -9
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-theme.mjs +257 -45
- package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs +201 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +184 -77
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs +110 -27
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs +616 -0
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +556 -945
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +102 -15
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs +219 -0
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs +88 -69
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tree.mjs +68 -19
- package/fesm2022/cdevhub-ngx-tw-tree.mjs.map +1 -1
- package/index.json +1 -1
- package/package.json +63 -2
- package/theme/_base.css +13 -0
- package/theme/_dark.css +110 -36
- package/theme/_high-contrast-dark.css +367 -0
- package/theme/_high-contrast.css +13 -2
- package/theme/_light.css +276 -0
- package/theme/_semantic.css +47 -12
- package/theme/_typography.css +17 -0
- package/theme/index.css +86 -7
- package/types/cdevhub-ngx-tw-accordion.d.ts +12 -4
- package/types/cdevhub-ngx-tw-avatar.d.ts +10 -3
- package/types/cdevhub-ngx-tw-button.d.ts +1 -0
- package/types/cdevhub-ngx-tw-calendar-luxon.d.ts +2 -2
- package/types/cdevhub-ngx-tw-calendar-testing.d.ts +1 -1
- package/types/cdevhub-ngx-tw-calendar.d.ts +179 -73
- package/types/cdevhub-ngx-tw-card.d.ts +11 -3
- package/types/cdevhub-ngx-tw-carousel.d.ts +89 -20
- package/types/cdevhub-ngx-tw-checkbox.d.ts +40 -12
- package/types/cdevhub-ngx-tw-code-block.d.ts +11 -2
- package/types/cdevhub-ngx-tw-collapsible.d.ts +32 -5
- package/types/cdevhub-ngx-tw-combobox-testing.d.ts +157 -0
- package/types/cdevhub-ngx-tw-combobox.d.ts +94 -53
- package/types/cdevhub-ngx-tw-command-palette-testing.d.ts +136 -0
- package/types/cdevhub-ngx-tw-command-palette.d.ts +33 -5
- package/types/cdevhub-ngx-tw-core.d.ts +152 -21
- package/types/cdevhub-ngx-tw-date-picker-testing.d.ts +141 -0
- package/types/cdevhub-ngx-tw-date-picker.d.ts +37 -29
- package/types/cdevhub-ngx-tw-date-range-picker-testing.d.ts +148 -0
- package/types/cdevhub-ngx-tw-date-range-picker.d.ts +36 -29
- package/types/cdevhub-ngx-tw-dialog-testing.d.ts +98 -0
- package/types/cdevhub-ngx-tw-dialog.d.ts +89 -19
- package/types/cdevhub-ngx-tw-file-upload-testing.d.ts +120 -0
- package/types/cdevhub-ngx-tw-file-upload.d.ts +8 -10
- package/types/cdevhub-ngx-tw-flip-card.d.ts +18 -11
- package/types/cdevhub-ngx-tw-form-field.d.ts +19 -2
- package/types/cdevhub-ngx-tw-icon.d.ts +3 -3
- package/types/cdevhub-ngx-tw-input.d.ts +5 -8
- package/types/cdevhub-ngx-tw-item.d.ts +2 -0
- package/types/cdevhub-ngx-tw-menu-testing.d.ts +131 -0
- package/types/cdevhub-ngx-tw-menu.d.ts +2 -2
- package/types/cdevhub-ngx-tw-number-input.d.ts +20 -0
- package/types/cdevhub-ngx-tw-paginator.d.ts +58 -24
- package/types/cdevhub-ngx-tw-popover.d.ts +41 -10
- package/types/cdevhub-ngx-tw-progress-bar.d.ts +1 -1
- package/types/cdevhub-ngx-tw-radio.d.ts +68 -25
- package/types/cdevhub-ngx-tw-segmented-control.d.ts +79 -16
- package/types/cdevhub-ngx-tw-select-testing.d.ts +114 -0
- package/types/cdevhub-ngx-tw-select.d.ts +104 -33
- package/types/cdevhub-ngx-tw-sheet-testing.d.ts +104 -0
- package/types/cdevhub-ngx-tw-sheet.d.ts +96 -21
- package/types/cdevhub-ngx-tw-slider.d.ts +79 -25
- package/types/cdevhub-ngx-tw-sort.d.ts +45 -4
- package/types/cdevhub-ngx-tw-split.d.ts +32 -2
- package/types/cdevhub-ngx-tw-stat.d.ts +14 -4
- package/types/cdevhub-ngx-tw-stepper.d.ts +16 -2
- package/types/cdevhub-ngx-tw-switch.d.ts +35 -13
- package/types/cdevhub-ngx-tw-tab-nav.d.ts +7 -6
- package/types/cdevhub-ngx-tw-table.d.ts +53 -10
- package/types/cdevhub-ngx-tw-tabs.d.ts +41 -10
- package/types/cdevhub-ngx-tw-tags-input-testing.d.ts +135 -0
- package/types/cdevhub-ngx-tw-tags-input.d.ts +26 -11
- package/types/cdevhub-ngx-tw-textarea.d.ts +3 -2
- package/types/cdevhub-ngx-tw-theme.d.ts +245 -39
- package/types/cdevhub-ngx-tw-time-picker-testing.d.ts +129 -0
- package/types/cdevhub-ngx-tw-time-picker.d.ts +76 -19
- package/types/cdevhub-ngx-tw-timeline.d.ts +36 -12
- package/types/cdevhub-ngx-tw-toast.d.ts +61 -12
- package/types/cdevhub-ngx-tw-tooltip.d.ts +28 -6
- package/types/cdevhub-ngx-tw-transfer-testing.d.ts +152 -0
- package/types/cdevhub-ngx-tw-transfer.d.ts +21 -8
- package/types/cdevhub-ngx-tw-tree.d.ts +6 -1
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { CdkDialogContainer, Dialog, DialogConfig } from '@angular/cdk/dialog';
|
|
2
|
+
import { createBlockScrollStrategy, createNoopScrollStrategy, createRepositionScrollStrategy, createCloseScrollStrategy } from '@angular/cdk/overlay';
|
|
3
|
+
import { TwDialogRef, TW_DIALOG_DATA, TwDialogConfig } from './cdevhub-ngx-tw-dialog.mjs';
|
|
4
|
+
import * as i0 from '@angular/core';
|
|
5
|
+
import { inject, computed, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
6
|
+
import { CdkPortalOutlet } from '@angular/cdk/portal';
|
|
7
|
+
import { OverlayContainerCoordinator, mergeOverlayPanelClass, coerceOverlayDuration } from '@cdevhub/ngx-tw/core';
|
|
8
|
+
import { tv } from 'tailwind-variants';
|
|
9
|
+
|
|
10
|
+
// Dialog uses a `data-[state]` driven CSS transition rather than the project's
|
|
11
|
+
// `animate.enter`/`animate.leave` keyframes. Justification:
|
|
12
|
+
// 1. The ref owns the open/close lifecycle (state signal, observables) and
|
|
13
|
+
// needs runtime-configurable enter/exit durations per `TwDialog.open()`
|
|
14
|
+
// call — `animate.enter` only accepts a static class name.
|
|
15
|
+
// 2. The container drives the same CSS variables (opacity + scale) for both
|
|
16
|
+
// transitions, so a single `transition-[opacity,transform]` rule with a
|
|
17
|
+
// data-state attribute is simpler than two keyframe declarations.
|
|
18
|
+
// All other library overlays (popover, menu, tooltip) use animate.enter/leave;
|
|
19
|
+
// this divergence is intentional and isolated to the dialog container.
|
|
20
|
+
const dialogContainerVariants = tv({
|
|
21
|
+
slots: {
|
|
22
|
+
host: 'relative flex flex-col outline-none bg-surface-raised text-fg rounded-lg shadow-md border border-border overflow-hidden transition-[opacity,transform] ease-out motion-reduce:transition-none opacity-0 scale-95 data-[state=open]:opacity-100 data-[state=open]:scale-100 data-[state=closing]:opacity-0 data-[state=closing]:scale-95',
|
|
23
|
+
},
|
|
24
|
+
variants: {
|
|
25
|
+
size: {
|
|
26
|
+
xs: { host: 'w-full max-w-sm max-h-[85vh]' },
|
|
27
|
+
sm: { host: 'w-full max-w-md max-h-[85vh]' },
|
|
28
|
+
md: { host: 'w-full max-w-lg max-h-[85vh]' },
|
|
29
|
+
lg: { host: 'w-full max-w-2xl max-h-[85vh]' },
|
|
30
|
+
xl: { host: 'w-full max-w-4xl max-h-[85vh]' },
|
|
31
|
+
fullscreen: {
|
|
32
|
+
host: 'w-screen h-screen max-w-none max-h-none rounded-none border-0',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: { size: 'md' },
|
|
37
|
+
}, { twMerge: true });
|
|
38
|
+
/**
|
|
39
|
+
* Dialog container rendered inside the CDK overlay. Wraps the user-provided
|
|
40
|
+
* content in a Tailwind-styled surface and coordinates enter/exit transitions
|
|
41
|
+
* with {@link TwDialogRef}.
|
|
42
|
+
*
|
|
43
|
+
* The animation state machine, `aria-describedby` queue, and panel-class
|
|
44
|
+
* merge are shared with `SheetContainer` via {@link OverlayContainerCoordinator}
|
|
45
|
+
* (component-scoped — see `providers: [OverlayContainerCoordinator]`).
|
|
46
|
+
*
|
|
47
|
+
* @docs-private
|
|
48
|
+
*/
|
|
49
|
+
class DialogContainer extends CdkDialogContainer {
|
|
50
|
+
coordinator = inject(OverlayContainerCoordinator);
|
|
51
|
+
/** Lifecycle state of the dialog's enter/exit animation. */
|
|
52
|
+
state = this.coordinator.state;
|
|
53
|
+
/** Emits whenever the animation state transitions. */
|
|
54
|
+
animationStateChanged = this.coordinator.animationStateChanged;
|
|
55
|
+
/** Resolved enter-animation duration in ms. */
|
|
56
|
+
enterAnimationDuration;
|
|
57
|
+
/** Resolved exit-animation duration in ms. */
|
|
58
|
+
exitAnimationDuration;
|
|
59
|
+
sizeVariant = computed(() => {
|
|
60
|
+
const size = this._config.size ?? 'md';
|
|
61
|
+
return dialogContainerVariants({ size });
|
|
62
|
+
}, /* @ts-ignore */
|
|
63
|
+
...(ngDevMode ? [{ debugName: "sizeVariant" }] : /* istanbul ignore next */ []));
|
|
64
|
+
hostClasses = computed(() => mergeOverlayPanelClass(this.sizeVariant().host(), this._config.panelClass), /* @ts-ignore */
|
|
65
|
+
...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
|
|
66
|
+
ariaDescribedByAttr = computed(() => this._config.ariaDescribedBy ||
|
|
67
|
+
this.coordinator.describedByIds()[0] ||
|
|
68
|
+
null, /* @ts-ignore */
|
|
69
|
+
...(ngDevMode ? [{ debugName: "ariaDescribedByAttr" }] : /* istanbul ignore next */ []));
|
|
70
|
+
transitionDuration = this.coordinator.transitionDuration;
|
|
71
|
+
constructor() {
|
|
72
|
+
super();
|
|
73
|
+
this.enterAnimationDuration = coerceOverlayDuration(this._config.enterAnimationDuration, 150);
|
|
74
|
+
this.exitAnimationDuration = coerceOverlayDuration(this._config.exitAnimationDuration, 120);
|
|
75
|
+
this.coordinator.setDurations(this.enterAnimationDuration, this.exitAnimationDuration);
|
|
76
|
+
}
|
|
77
|
+
_contentAttached() {
|
|
78
|
+
super._contentAttached();
|
|
79
|
+
this.coordinator.startEnterAnimation();
|
|
80
|
+
}
|
|
81
|
+
/** Triggered by the dialog ref to play the exit transition before disposing the overlay. */
|
|
82
|
+
_startExitAnimation() {
|
|
83
|
+
this.coordinator.startExitAnimation();
|
|
84
|
+
}
|
|
85
|
+
/** Registers a description id for the container's `aria-describedby`. */
|
|
86
|
+
_addAriaDescribedBy(id) {
|
|
87
|
+
this.coordinator.addAriaDescribedBy(id);
|
|
88
|
+
}
|
|
89
|
+
/** Removes a previously registered description id. */
|
|
90
|
+
_removeAriaDescribedBy(id) {
|
|
91
|
+
this.coordinator.removeAriaDescribedBy(id);
|
|
92
|
+
}
|
|
93
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: DialogContainer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
94
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: DialogContainer, isStandalone: true, selector: "tw-dialog-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()", "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 });
|
|
95
|
+
}
|
|
96
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: DialogContainer, decorators: [{
|
|
97
|
+
type: Component,
|
|
98
|
+
args: [{
|
|
99
|
+
selector: 'tw-dialog-container',
|
|
100
|
+
template: '<ng-template cdkPortalOutlet />',
|
|
101
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
102
|
+
encapsulation: ViewEncapsulation.None,
|
|
103
|
+
imports: [CdkPortalOutlet],
|
|
104
|
+
providers: [OverlayContainerCoordinator],
|
|
105
|
+
host: {
|
|
106
|
+
tabindex: '-1',
|
|
107
|
+
'[attr.id]': '_config.id || null',
|
|
108
|
+
'[attr.role]': '_config.role',
|
|
109
|
+
'[attr.aria-modal]': '_config.ariaModal',
|
|
110
|
+
'[attr.aria-labelledby]': '_config.ariaLabel ? null : _ariaLabelledByQueue[0]',
|
|
111
|
+
'[attr.aria-label]': '_config.ariaLabel',
|
|
112
|
+
'[attr.aria-describedby]': 'ariaDescribedByAttr()',
|
|
113
|
+
'[attr.data-state]': 'state()',
|
|
114
|
+
'[class]': 'hostClasses()',
|
|
115
|
+
'[style.transition-duration.ms]': 'transitionDuration()',
|
|
116
|
+
},
|
|
117
|
+
}]
|
|
118
|
+
}], ctorParameters: () => [] });
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Rendering half of the dialog feature: the only module that pulls in
|
|
122
|
+
* `@angular/cdk/dialog`, the overlay scroll strategies, and the Tailwind
|
|
123
|
+
* `DialogContainer`. Reached exclusively through a dynamic `import()` in
|
|
124
|
+
* {@link TwDialog}, which is what keeps that graph out of a consumer's initial
|
|
125
|
+
* bundle. Nothing here may be imported as a value from `dialog.ts`.
|
|
126
|
+
*
|
|
127
|
+
* @docs-private
|
|
128
|
+
*/
|
|
129
|
+
function openRenderedDialog(injector, content, merged, twRef) {
|
|
130
|
+
// `Dialog` is `providedIn: 'root'`, so resolving it here — rather than
|
|
131
|
+
// injecting it into the service — keeps the CDK symbol in this lazy chunk.
|
|
132
|
+
const cdkDialog = injector.get(Dialog);
|
|
133
|
+
const cdkRef = cdkDialog.open(content, {
|
|
134
|
+
id: merged.id,
|
|
135
|
+
role: merged.role,
|
|
136
|
+
data: merged.data,
|
|
137
|
+
panelClass: merged.panelClass,
|
|
138
|
+
backdropClass: merged.backdropClass ?? 'tw-dialog-backdrop',
|
|
139
|
+
hasBackdrop: merged.hasBackdrop,
|
|
140
|
+
width: merged.width,
|
|
141
|
+
height: merged.height,
|
|
142
|
+
minWidth: merged.minWidth,
|
|
143
|
+
minHeight: merged.minHeight,
|
|
144
|
+
maxWidth: merged.maxWidth,
|
|
145
|
+
maxHeight: merged.maxHeight,
|
|
146
|
+
direction: merged.direction,
|
|
147
|
+
ariaDescribedBy: merged.ariaDescribedBy,
|
|
148
|
+
ariaLabelledBy: merged.ariaLabelledBy,
|
|
149
|
+
ariaLabel: merged.ariaLabel,
|
|
150
|
+
ariaModal: merged.ariaModal,
|
|
151
|
+
autoFocus: merged.autoFocus,
|
|
152
|
+
restoreFocus: merged.restoreFocus,
|
|
153
|
+
scrollStrategy: merged.scrollStrategy ?? resolveScrollStrategy(injector, merged.scrollBehavior),
|
|
154
|
+
closeOnNavigation: merged.closeOnNavigation,
|
|
155
|
+
viewContainerRef: merged.viewContainerRef,
|
|
156
|
+
injector: merged.injector,
|
|
157
|
+
// We handle close/Escape/backdrop ourselves so we can run the exit animation.
|
|
158
|
+
disableClose: true,
|
|
159
|
+
closeOnOverlayDetachments: false,
|
|
160
|
+
container: {
|
|
161
|
+
type: DialogContainer,
|
|
162
|
+
providers: () => [
|
|
163
|
+
{ provide: TwDialogConfig, useValue: merged },
|
|
164
|
+
{ provide: DialogConfig, useValue: merged },
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
providers: (cdkRef, _cdkConfig, container) => {
|
|
168
|
+
// Wire the facade to its CDK backend at the exact point the ref
|
|
169
|
+
// constructor ran before deferral, so animation subscriptions are live
|
|
170
|
+
// before the container emits.
|
|
171
|
+
twRef._attach(cdkRef, container);
|
|
172
|
+
const providers = [
|
|
173
|
+
{ provide: TwDialogRef, useValue: twRef },
|
|
174
|
+
{ provide: TW_DIALOG_DATA, useValue: merged.data ?? null },
|
|
175
|
+
];
|
|
176
|
+
if (Array.isArray(merged.providers))
|
|
177
|
+
providers.push(...merged.providers);
|
|
178
|
+
return providers;
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
twRef._setComponent(cdkRef.componentInstance, cdkRef.componentRef);
|
|
182
|
+
}
|
|
183
|
+
function resolveScrollStrategy(injector, strategy) {
|
|
184
|
+
switch (strategy) {
|
|
185
|
+
case 'close':
|
|
186
|
+
return createCloseScrollStrategy(injector);
|
|
187
|
+
case 'reposition':
|
|
188
|
+
return createRepositionScrollStrategy(injector);
|
|
189
|
+
case 'noop':
|
|
190
|
+
return createNoopScrollStrategy();
|
|
191
|
+
case 'block':
|
|
192
|
+
default:
|
|
193
|
+
return createBlockScrollStrategy(injector);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { openRenderedDialog };
|
|
198
|
+
//# sourceMappingURL=cdevhub-ngx-tw-dialog-dialog-renderer-DoIhoV3d.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdevhub-ngx-tw-dialog-dialog-renderer-DoIhoV3d.mjs","sources":["../../../projects/ngx-tw/dialog/dialog-container.ts","../../../projects/ngx-tw/dialog/dialog-renderer.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n type EventEmitter,\n inject,\n type Signal,\n ViewEncapsulation,\n computed,\n} from '@angular/core';\nimport { CdkDialogContainer } from '@angular/cdk/dialog';\nimport { CdkPortalOutlet } from '@angular/cdk/portal';\nimport {\n coerceOverlayDuration,\n mergeOverlayPanelClass,\n OverlayContainerCoordinator,\n type OverlayContainerAnimationEvent,\n type OverlayContainerState,\n} from '@cdevhub/ngx-tw/core';\nimport { tv } from 'tailwind-variants';\nimport { type TwDialogConfig } from './dialog-config';\n\n/** Lifecycle states a dialog passes through. */\nexport type DialogState = OverlayContainerState;\n\n/** Event emitted when the dialog's animation state transitions. */\nexport type DialogAnimationEvent = OverlayContainerAnimationEvent;\n\n// Dialog uses a `data-[state]` driven CSS transition rather than the project's\n// `animate.enter`/`animate.leave` keyframes. Justification:\n// 1. The ref owns the open/close lifecycle (state signal, observables) and\n// needs runtime-configurable enter/exit durations per `TwDialog.open()`\n// call — `animate.enter` only accepts a static class name.\n// 2. The container drives the same CSS variables (opacity + scale) for both\n// transitions, so a single `transition-[opacity,transform]` rule with a\n// data-state attribute is simpler than two keyframe declarations.\n// All other library overlays (popover, menu, tooltip) use animate.enter/leave;\n// this divergence is intentional and isolated to the dialog container.\nconst dialogContainerVariants = tv(\n {\n slots: {\n host: 'relative flex flex-col outline-none bg-surface-raised text-fg rounded-lg shadow-md border border-border overflow-hidden transition-[opacity,transform] ease-out motion-reduce:transition-none opacity-0 scale-95 data-[state=open]:opacity-100 data-[state=open]:scale-100 data-[state=closing]:opacity-0 data-[state=closing]:scale-95',\n },\n variants: {\n size: {\n xs: { host: 'w-full max-w-sm max-h-[85vh]' },\n sm: { host: 'w-full max-w-md max-h-[85vh]' },\n md: { host: 'w-full max-w-lg max-h-[85vh]' },\n lg: { host: 'w-full max-w-2xl max-h-[85vh]' },\n xl: { host: 'w-full max-w-4xl max-h-[85vh]' },\n fullscreen: {\n host: 'w-screen h-screen max-w-none max-h-none rounded-none border-0',\n },\n },\n },\n defaultVariants: { size: 'md' },\n },\n { twMerge: true },\n);\n\n/**\n * Dialog container rendered inside the CDK overlay. Wraps the user-provided\n * content in a Tailwind-styled surface and coordinates enter/exit transitions\n * with {@link TwDialogRef}.\n *\n * The animation state machine, `aria-describedby` queue, and panel-class\n * merge are shared with `SheetContainer` via {@link OverlayContainerCoordinator}\n * (component-scoped — see `providers: [OverlayContainerCoordinator]`).\n *\n * @docs-private\n */\n@Component({\n selector: 'tw-dialog-container',\n template: '<ng-template cdkPortalOutlet />',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [CdkPortalOutlet],\n providers: [OverlayContainerCoordinator],\n host: {\n tabindex: '-1',\n '[attr.id]': '_config.id || null',\n '[attr.role]': '_config.role',\n '[attr.aria-modal]': '_config.ariaModal',\n '[attr.aria-labelledby]': '_config.ariaLabel ? null : _ariaLabelledByQueue[0]',\n '[attr.aria-label]': '_config.ariaLabel',\n '[attr.aria-describedby]': 'ariaDescribedByAttr()',\n '[attr.data-state]': 'state()',\n '[class]': 'hostClasses()',\n '[style.transition-duration.ms]': 'transitionDuration()',\n },\n})\nexport class DialogContainer extends CdkDialogContainer<TwDialogConfig> {\n private readonly coordinator = inject(OverlayContainerCoordinator);\n\n /** Lifecycle state of the dialog's enter/exit animation. */\n readonly state: Signal<DialogState> = this.coordinator.state;\n\n /** Emits whenever the animation state transitions. */\n readonly animationStateChanged: EventEmitter<DialogAnimationEvent> =\n this.coordinator.animationStateChanged;\n\n /** Resolved enter-animation duration in ms. */\n readonly enterAnimationDuration: number;\n /** Resolved exit-animation duration in ms. */\n readonly exitAnimationDuration: number;\n\n private readonly sizeVariant = computed(() => {\n const size = this._config.size ?? 'md';\n return dialogContainerVariants({ size });\n });\n\n protected readonly hostClasses = computed(() =>\n mergeOverlayPanelClass(this.sizeVariant().host(), this._config.panelClass),\n );\n\n protected readonly ariaDescribedByAttr = computed(\n () =>\n this._config.ariaDescribedBy ||\n this.coordinator.describedByIds()[0] ||\n null,\n );\n\n protected readonly transitionDuration = this.coordinator.transitionDuration;\n\n constructor() {\n super();\n this.enterAnimationDuration = coerceOverlayDuration(this._config.enterAnimationDuration, 150);\n this.exitAnimationDuration = coerceOverlayDuration(this._config.exitAnimationDuration, 120);\n this.coordinator.setDurations(this.enterAnimationDuration, this.exitAnimationDuration);\n }\n\n protected override _contentAttached(): void {\n super._contentAttached();\n this.coordinator.startEnterAnimation();\n }\n\n /** Triggered by the dialog ref to play the exit transition before disposing the overlay. */\n _startExitAnimation(): void {\n this.coordinator.startExitAnimation();\n }\n\n /** Registers a description id for the container's `aria-describedby`. */\n _addAriaDescribedBy(id: string): void {\n this.coordinator.addAriaDescribedBy(id);\n }\n\n /** Removes a previously registered description id. */\n _removeAriaDescribedBy(id: string): void {\n this.coordinator.removeAriaDescribedBy(id);\n }\n}\n","import {\n type ComponentRef,\n type Injector,\n type StaticProvider,\n type TemplateRef,\n} from '@angular/core';\nimport { Dialog, DialogConfig as CdkDialogConfig } from '@angular/cdk/dialog';\nimport {\n createBlockScrollStrategy,\n createCloseScrollStrategy,\n createNoopScrollStrategy,\n createRepositionScrollStrategy,\n type ScrollStrategy,\n} from '@angular/cdk/overlay';\nimport type { ComponentType } from '@angular/cdk/portal';\nimport {\n TW_DIALOG_DATA,\n TwDialogConfig,\n type TwDialogScrollStrategy,\n} from './dialog-config';\nimport { DialogContainer } from './dialog-container';\nimport { TwDialogRef } from './dialog-ref';\n\n/**\n * Rendering half of the dialog feature: the only module that pulls in\n * `@angular/cdk/dialog`, the overlay scroll strategies, and the Tailwind\n * `DialogContainer`. Reached exclusively through a dynamic `import()` in\n * {@link TwDialog}, which is what keeps that graph out of a consumer's initial\n * bundle. Nothing here may be imported as a value from `dialog.ts`.\n *\n * @docs-private\n */\nexport function openRenderedDialog<R, D, C>(\n injector: Injector,\n content: ComponentType<C> | TemplateRef<C>,\n merged: TwDialogConfig<D, R>,\n twRef: TwDialogRef<R, C>,\n): void {\n // `Dialog` is `providedIn: 'root'`, so resolving it here — rather than\n // injecting it into the service — keeps the CDK symbol in this lazy chunk.\n const cdkDialog = injector.get(Dialog);\n\n const cdkRef = cdkDialog.open<R, D, C>(content, {\n id: merged.id,\n role: merged.role,\n data: merged.data,\n panelClass: merged.panelClass,\n backdropClass: merged.backdropClass ?? 'tw-dialog-backdrop',\n hasBackdrop: merged.hasBackdrop,\n width: merged.width,\n height: merged.height,\n minWidth: merged.minWidth,\n minHeight: merged.minHeight,\n maxWidth: merged.maxWidth,\n maxHeight: merged.maxHeight,\n direction: merged.direction,\n ariaDescribedBy: merged.ariaDescribedBy,\n ariaLabelledBy: merged.ariaLabelledBy,\n ariaLabel: merged.ariaLabel,\n ariaModal: merged.ariaModal,\n autoFocus: merged.autoFocus,\n restoreFocus: merged.restoreFocus,\n scrollStrategy:\n merged.scrollStrategy ?? resolveScrollStrategy(injector, merged.scrollBehavior),\n closeOnNavigation: merged.closeOnNavigation,\n viewContainerRef: merged.viewContainerRef,\n injector: merged.injector,\n // We handle close/Escape/backdrop ourselves so we can run the exit animation.\n disableClose: true,\n closeOnOverlayDetachments: false,\n container: {\n type: DialogContainer,\n providers: () => [\n { provide: TwDialogConfig, useValue: merged },\n { provide: CdkDialogConfig, useValue: merged },\n ],\n },\n providers: (cdkRef, _cdkConfig, container) => {\n // Wire the facade to its CDK backend at the exact point the ref\n // constructor ran before deferral, so animation subscriptions are live\n // before the container emits.\n twRef._attach(cdkRef, container as DialogContainer);\n const providers: StaticProvider[] = [\n { provide: TwDialogRef, useValue: twRef },\n { provide: TW_DIALOG_DATA, useValue: merged.data ?? null },\n ];\n if (Array.isArray(merged.providers)) providers.push(...merged.providers);\n return providers;\n },\n });\n\n twRef._setComponent(\n cdkRef.componentInstance as C | null,\n cdkRef.componentRef as ComponentRef<C> | null,\n );\n}\n\nfunction resolveScrollStrategy(\n injector: Injector,\n strategy: TwDialogScrollStrategy | undefined,\n): ScrollStrategy {\n switch (strategy) {\n case 'close':\n return createCloseScrollStrategy(injector);\n case 'reposition':\n return createRepositionScrollStrategy(injector);\n case 'noop':\n return createNoopScrollStrategy();\n case 'block':\n default:\n return createBlockScrollStrategy(injector);\n }\n}\n"],"names":["CdkDialogConfig"],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Harness for a dialog opened with `TwDialog.open()`.
|
|
5
|
+
*
|
|
6
|
+
* ## Loading it
|
|
7
|
+
*
|
|
8
|
+
* A dialog is **service-opened**, not template-declared: nothing in the test
|
|
9
|
+
* fixture's own DOM represents it, and the container renders into the CDK
|
|
10
|
+
* overlay container on `document.body`. This harness's host *is* that container,
|
|
11
|
+
* so it must be loaded from
|
|
12
|
+
* `TestbedHarnessEnvironment.documentRootLoader(fixture)` — the ordinary
|
|
13
|
+
* `loader(fixture)` searches inside `fixture.nativeElement` and will never find
|
|
14
|
+
* it. This is not the same situation as an overlay *panel* belonging to a
|
|
15
|
+
* template-declared component (`tw-select`, `tw-popover-overlay`, `tw-menu`),
|
|
16
|
+
* where the harness hosts on the in-fixture trigger and reaches its panel
|
|
17
|
+
* internally; there the plain fixture loader is correct.
|
|
18
|
+
*
|
|
19
|
+
* ## Waiting for it
|
|
20
|
+
*
|
|
21
|
+
* `TwDialog` loads its render layer through a dynamic `import()`, so the
|
|
22
|
+
* container does **not** exist synchronously after `open()`. Await the ref's
|
|
23
|
+
* public {@link TwDialogRef.whenComponentReady} before asking for the harness —
|
|
24
|
+
* it resolves once the chunk has landed and the overlay is attached, for
|
|
25
|
+
* template dialogs (with `null`) as well as component dialogs.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const ref = dialog.open(EditorDialog);
|
|
30
|
+
* await ref.whenComponentReady();
|
|
31
|
+
* const rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
|
|
32
|
+
* const harness = await rootLoader.getHarness(DialogHarness);
|
|
33
|
+
* expect(await harness.getTitleText()).toBe('Edit profile');
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
class DialogHarness extends ComponentHarness {
|
|
37
|
+
static hostSelector = 'tw-dialog-container';
|
|
38
|
+
title = this.locatorForOptional('[twDialogTitle], tw-dialog-title');
|
|
39
|
+
content = this.locatorForOptional('[twDialogContent], tw-dialog-content');
|
|
40
|
+
/** Any descendant of the container that currently holds DOM focus. */
|
|
41
|
+
focusedDescendant = this.locatorForOptional(':focus');
|
|
42
|
+
/**
|
|
43
|
+
* Overlay backdrops, which are siblings of the dialog's overlay pane rather
|
|
44
|
+
* than descendants of the container — hence the document-root lookup.
|
|
45
|
+
*/
|
|
46
|
+
backdrops = this.documentRootLocatorFactory().locatorForAll('.cdk-overlay-backdrop');
|
|
47
|
+
/** Predicate for `locatorFor` / `locatorForAll`. */
|
|
48
|
+
static with(options = {}) {
|
|
49
|
+
return new HarnessPredicate(DialogHarness, options)
|
|
50
|
+
.addOption('id', options.id, async (h, id) => HarnessPredicate.stringMatches(await h.getId(), id))
|
|
51
|
+
.addOption('title', options.title, async (h, title) => HarnessPredicate.stringMatches(await h.getTitleText(), title))
|
|
52
|
+
.addOption('role', options.role, async (h, role) => HarnessPredicate.stringMatches(await h.getRole(), role));
|
|
53
|
+
}
|
|
54
|
+
/** The dialog's id. Matches the `id` on the `TwDialogRef` returned by `open()`. */
|
|
55
|
+
async getId() {
|
|
56
|
+
return (await this.host()).getAttribute('id');
|
|
57
|
+
}
|
|
58
|
+
/** The dialog's ARIA role — `'dialog'`, or `'alertdialog'` when so configured. */
|
|
59
|
+
async getRole() {
|
|
60
|
+
return (await this.host()).getAttribute('role');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Text of the projected `[twDialogTitle]` element, trimmed, or `null` when
|
|
64
|
+
* the dialog's content does not use the title directive.
|
|
65
|
+
*/
|
|
66
|
+
async getTitleText() {
|
|
67
|
+
const title = await this.title();
|
|
68
|
+
return title ? (await title.text()).trim() : null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Text of the projected `[twDialogContent]` region, trimmed, or `null` when
|
|
72
|
+
* the dialog's content does not use the content directive.
|
|
73
|
+
*/
|
|
74
|
+
async getContentText() {
|
|
75
|
+
const content = await this.content();
|
|
76
|
+
return content ? (await content.text()).trim() : null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether DOM focus currently sits on the dialog container or anywhere inside
|
|
80
|
+
* it — the observable consequence of the CDK focus trap. CDK moves focus
|
|
81
|
+
* asynchronously, so let the fixture settle after opening before asserting.
|
|
82
|
+
*/
|
|
83
|
+
async containsFocus() {
|
|
84
|
+
if (await (await this.host()).isFocused())
|
|
85
|
+
return true;
|
|
86
|
+
return (await this.focusedDescendant()) !== null;
|
|
87
|
+
}
|
|
88
|
+
/** Whether the dialog rendered a backdrop. */
|
|
89
|
+
async hasBackdrop() {
|
|
90
|
+
return (await this.backdrops()).length > 0;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Clicks the backdrop — the pointer dismiss gesture. Does nothing observable
|
|
94
|
+
* when the dialog was opened with `disableClose: true`, which is exactly how
|
|
95
|
+
* a test asserts that flag.
|
|
96
|
+
*
|
|
97
|
+
* Throws when no backdrop is rendered. With several overlays stacked, the
|
|
98
|
+
* last backdrop in the overlay container is used, i.e. the topmost one.
|
|
99
|
+
*/
|
|
100
|
+
async clickBackdrop() {
|
|
101
|
+
const backdrops = await this.backdrops();
|
|
102
|
+
if (backdrops.length === 0) {
|
|
103
|
+
throw new Error('DialogHarness.clickBackdrop: no backdrop is rendered. The dialog was opened with `hasBackdrop: false`.');
|
|
104
|
+
}
|
|
105
|
+
await backdrops[backdrops.length - 1].click();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Sends Escape to the dialog — the keyboard dismiss gesture. Like
|
|
109
|
+
* {@link clickBackdrop}, it is deliberately a no-op when the dialog was
|
|
110
|
+
* opened with `disableClose: true`.
|
|
111
|
+
*/
|
|
112
|
+
async pressEscape() {
|
|
113
|
+
await (await this.host()).sendKeys(TestKey.ESCAPE);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generated bundle index. Do not edit.
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
export { DialogHarness };
|
|
122
|
+
//# sourceMappingURL=cdevhub-ngx-tw-dialog-testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdevhub-ngx-tw-dialog-testing.mjs","sources":["../../../projects/ngx-tw/dialog/testing/dialog-harness.ts","../../../projects/ngx-tw/dialog/testing/cdevhub-ngx-tw-dialog-testing.ts"],"sourcesContent":["import { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';\nimport type { BaseHarnessFilters, TestElement } from '@angular/cdk/testing';\n\n/** Filters accepted by `DialogHarness.with`. */\nexport interface DialogHarnessFilters extends BaseHarnessFilters {\n /** Match by the dialog's id — the same value as `TwDialogRef.id`. */\n id?: string | RegExp;\n /** Match by the text of the projected `[twDialogTitle]` element. */\n title?: string | RegExp;\n /** Match by ARIA role — `'dialog'` or `'alertdialog'`. */\n role?: string | RegExp;\n}\n\n/**\n * Harness for a dialog opened with `TwDialog.open()`.\n *\n * ## Loading it\n *\n * A dialog is **service-opened**, not template-declared: nothing in the test\n * fixture's own DOM represents it, and the container renders into the CDK\n * overlay container on `document.body`. This harness's host *is* that container,\n * so it must be loaded from\n * `TestbedHarnessEnvironment.documentRootLoader(fixture)` — the ordinary\n * `loader(fixture)` searches inside `fixture.nativeElement` and will never find\n * it. This is not the same situation as an overlay *panel* belonging to a\n * template-declared component (`tw-select`, `tw-popover-overlay`, `tw-menu`),\n * where the harness hosts on the in-fixture trigger and reaches its panel\n * internally; there the plain fixture loader is correct.\n *\n * ## Waiting for it\n *\n * `TwDialog` loads its render layer through a dynamic `import()`, so the\n * container does **not** exist synchronously after `open()`. Await the ref's\n * public {@link TwDialogRef.whenComponentReady} before asking for the harness —\n * it resolves once the chunk has landed and the overlay is attached, for\n * template dialogs (with `null`) as well as component dialogs.\n *\n * @example\n * ```ts\n * const ref = dialog.open(EditorDialog);\n * await ref.whenComponentReady();\n * const rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);\n * const harness = await rootLoader.getHarness(DialogHarness);\n * expect(await harness.getTitleText()).toBe('Edit profile');\n * ```\n */\nexport class DialogHarness extends ComponentHarness {\n static hostSelector = 'tw-dialog-container';\n\n private readonly title = this.locatorForOptional(\n '[twDialogTitle], tw-dialog-title',\n );\n private readonly content = this.locatorForOptional(\n '[twDialogContent], tw-dialog-content',\n );\n /** Any descendant of the container that currently holds DOM focus. */\n private readonly focusedDescendant = this.locatorForOptional(':focus');\n /**\n * Overlay backdrops, which are siblings of the dialog's overlay pane rather\n * than descendants of the container — hence the document-root lookup.\n */\n private readonly backdrops =\n this.documentRootLocatorFactory().locatorForAll('.cdk-overlay-backdrop');\n\n /** Predicate for `locatorFor` / `locatorForAll`. */\n static with(options: DialogHarnessFilters = {}): HarnessPredicate<DialogHarness> {\n return new HarnessPredicate(DialogHarness, options)\n .addOption('id', options.id, async (h, id) =>\n HarnessPredicate.stringMatches(await h.getId(), id),\n )\n .addOption('title', options.title, async (h, title) =>\n HarnessPredicate.stringMatches(await h.getTitleText(), title),\n )\n .addOption('role', options.role, async (h, role) =>\n HarnessPredicate.stringMatches(await h.getRole(), role),\n );\n }\n\n /** The dialog's id. Matches the `id` on the `TwDialogRef` returned by `open()`. */\n async getId(): Promise<string | null> {\n return (await this.host()).getAttribute('id');\n }\n\n /** The dialog's ARIA role — `'dialog'`, or `'alertdialog'` when so configured. */\n async getRole(): Promise<string | null> {\n return (await this.host()).getAttribute('role');\n }\n\n /**\n * Text of the projected `[twDialogTitle]` element, trimmed, or `null` when\n * the dialog's content does not use the title directive.\n */\n async getTitleText(): Promise<string | null> {\n const title: TestElement | null = await this.title();\n return title ? (await title.text()).trim() : null;\n }\n\n /**\n * Text of the projected `[twDialogContent]` region, trimmed, or `null` when\n * the dialog's content does not use the content directive.\n */\n async getContentText(): Promise<string | null> {\n const content: TestElement | null = await this.content();\n return content ? (await content.text()).trim() : null;\n }\n\n /**\n * Whether DOM focus currently sits on the dialog container or anywhere inside\n * it — the observable consequence of the CDK focus trap. CDK moves focus\n * asynchronously, so let the fixture settle after opening before asserting.\n */\n async containsFocus(): Promise<boolean> {\n if (await (await this.host()).isFocused()) return true;\n return (await this.focusedDescendant()) !== null;\n }\n\n /** Whether the dialog rendered a backdrop. */\n async hasBackdrop(): Promise<boolean> {\n return (await this.backdrops()).length > 0;\n }\n\n /**\n * Clicks the backdrop — the pointer dismiss gesture. Does nothing observable\n * when the dialog was opened with `disableClose: true`, which is exactly how\n * a test asserts that flag.\n *\n * Throws when no backdrop is rendered. With several overlays stacked, the\n * last backdrop in the overlay container is used, i.e. the topmost one.\n */\n async clickBackdrop(): Promise<void> {\n const backdrops = await this.backdrops();\n if (backdrops.length === 0) {\n throw new Error(\n 'DialogHarness.clickBackdrop: no backdrop is rendered. The dialog was opened with `hasBackdrop: false`.',\n );\n }\n await backdrops[backdrops.length - 1].click();\n }\n\n /**\n * Sends Escape to the dialog — the keyboard dismiss gesture. Like\n * {@link clickBackdrop}, it is deliberately a no-op when the dialog was\n * opened with `disableClose: true`.\n */\n async pressEscape(): Promise<void> {\n await (await this.host()).sendKeys(TestKey.ESCAPE);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|