@cdevhub/ngx-tw 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs +108 -0
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-alert.mjs +240 -0
- package/fesm2022/cdevhub-ngx-tw-alert.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs +287 -0
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-badge.mjs +332 -0
- package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +452 -0
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-button.mjs +227 -0
- package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +417 -0
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs +232 -0
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs +4482 -0
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-card.mjs +157 -0
- package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs +1454 -0
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +593 -0
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs +228 -0
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +510 -0
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs +1721 -0
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +721 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-core.mjs +861 -0
- package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +1558 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +1833 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs +693 -0
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +331 -0
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +289 -0
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs +572 -0
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs +36 -0
- package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-icon.mjs +278 -0
- package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-input.mjs +355 -0
- package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-item.mjs +241 -0
- package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-menu.mjs +433 -0
- package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs +691 -0
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-popover.mjs +678 -0
- package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +335 -0
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-radio.mjs +708 -0
- package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +319 -0
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-select.mjs +1722 -0
- package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-separator.mjs +115 -0
- package/fesm2022/cdevhub-ngx-tw-separator.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +768 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-skeleton.mjs +149 -0
- package/fesm2022/cdevhub-ngx-tw-skeleton.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-slider.mjs +1147 -0
- package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sort.mjs +346 -0
- package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-spinner.mjs +186 -0
- package/fesm2022/cdevhub-ngx-tw-spinner.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-split.mjs +1082 -0
- package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-stat.mjs +579 -0
- package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs +443 -0
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-switch.mjs +391 -0
- package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +315 -0
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-table.mjs +994 -0
- package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs +508 -0
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs +147 -0
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-theme.mjs +178 -0
- package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +1344 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs +1136 -0
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +1311 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +533 -0
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw.mjs +61 -0
- package/fesm2022/cdevhub-ngx-tw.mjs.map +1 -0
- package/package.json +265 -0
- package/theme/_base.css +394 -0
- package/theme/_dark.css +510 -0
- package/theme/_high-contrast.css +290 -0
- package/theme/_semantic.css +317 -0
- package/theme/_typography.css +9 -0
- package/theme/index.css +54 -0
- package/types/cdevhub-ngx-tw-accordion.d.ts +48 -0
- package/types/cdevhub-ngx-tw-alert.d.ts +67 -0
- package/types/cdevhub-ngx-tw-avatar.d.ts +79 -0
- package/types/cdevhub-ngx-tw-badge.d.ts +94 -0
- package/types/cdevhub-ngx-tw-breadcrumbs.d.ts +227 -0
- package/types/cdevhub-ngx-tw-button.d.ts +74 -0
- package/types/cdevhub-ngx-tw-calendar-luxon.d.ts +200 -0
- package/types/cdevhub-ngx-tw-calendar-testing.d.ts +113 -0
- package/types/cdevhub-ngx-tw-calendar.d.ts +1704 -0
- package/types/cdevhub-ngx-tw-card.d.ts +48 -0
- package/types/cdevhub-ngx-tw-carousel.d.ts +406 -0
- package/types/cdevhub-ngx-tw-checkbox.d.ts +136 -0
- package/types/cdevhub-ngx-tw-code-block.d.ts +93 -0
- package/types/cdevhub-ngx-tw-collapsible.d.ts +151 -0
- package/types/cdevhub-ngx-tw-combobox.d.ts +389 -0
- package/types/cdevhub-ngx-tw-command-palette.d.ts +215 -0
- package/types/cdevhub-ngx-tw-core.d.ts +719 -0
- package/types/cdevhub-ngx-tw-date-picker.d.ts +317 -0
- package/types/cdevhub-ngx-tw-date-range-picker.d.ts +313 -0
- package/types/cdevhub-ngx-tw-dialog.d.ts +303 -0
- package/types/cdevhub-ngx-tw-empty-state.d.ts +104 -0
- package/types/cdevhub-ngx-tw-flip-card.d.ts +95 -0
- package/types/cdevhub-ngx-tw-form-field.d.ts +210 -0
- package/types/cdevhub-ngx-tw-icon-lucide.d.ts +29 -0
- package/types/cdevhub-ngx-tw-icon.d.ts +134 -0
- package/types/cdevhub-ngx-tw-input.d.ts +110 -0
- package/types/cdevhub-ngx-tw-item.d.ts +70 -0
- package/types/cdevhub-ngx-tw-menu.d.ts +99 -0
- package/types/cdevhub-ngx-tw-paginator.d.ts +305 -0
- package/types/cdevhub-ngx-tw-popover.d.ts +157 -0
- package/types/cdevhub-ngx-tw-progress-bar.d.ts +100 -0
- package/types/cdevhub-ngx-tw-radio.d.ts +174 -0
- package/types/cdevhub-ngx-tw-segmented-control.d.ts +66 -0
- package/types/cdevhub-ngx-tw-select.d.ts +384 -0
- package/types/cdevhub-ngx-tw-separator.d.ts +28 -0
- package/types/cdevhub-ngx-tw-sheet.d.ts +335 -0
- package/types/cdevhub-ngx-tw-skeleton.d.ts +52 -0
- package/types/cdevhub-ngx-tw-slider.d.ts +258 -0
- package/types/cdevhub-ngx-tw-sort.d.ts +136 -0
- package/types/cdevhub-ngx-tw-spinner.d.ts +50 -0
- package/types/cdevhub-ngx-tw-split.d.ts +265 -0
- package/types/cdevhub-ngx-tw-stat.d.ts +140 -0
- package/types/cdevhub-ngx-tw-stepper.d.ts +127 -0
- package/types/cdevhub-ngx-tw-switch.d.ts +88 -0
- package/types/cdevhub-ngx-tw-tab-nav.d.ts +107 -0
- package/types/cdevhub-ngx-tw-table.d.ts +479 -0
- package/types/cdevhub-ngx-tw-tabs.d.ts +121 -0
- package/types/cdevhub-ngx-tw-textarea.d.ts +62 -0
- package/types/cdevhub-ngx-tw-theme.d.ts +106 -0
- package/types/cdevhub-ngx-tw-time-picker.d.ts +303 -0
- package/types/cdevhub-ngx-tw-timeline.d.ts +278 -0
- package/types/cdevhub-ngx-tw-toast.d.ts +388 -0
- package/types/cdevhub-ngx-tw-tooltip.d.ts +95 -0
- package/types/cdevhub-ngx-tw.d.ts +49 -0
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, signal, Injector, Injectable, makeEnvironmentProviders, Directive, input } from '@angular/core';
|
|
3
|
+
import { DialogConfig, CdkDialogContainer, Dialog } from '@angular/cdk/dialog';
|
|
4
|
+
import { createBlockScrollStrategy, createNoopScrollStrategy, createRepositionScrollStrategy, createCloseScrollStrategy, createGlobalPositionStrategy } from '@angular/cdk/overlay';
|
|
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';
|
|
9
|
+
import { ESCAPE, hasModifierKey } from '@angular/cdk/keycodes';
|
|
10
|
+
import { _IdGenerator } from '@angular/cdk/a11y';
|
|
11
|
+
import * as i1 from '@angular/cdk/scrolling';
|
|
12
|
+
import { CdkScrollable } from '@angular/cdk/scrolling';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Configuration for opening a sheet with {@link Sheet.open}.
|
|
16
|
+
*
|
|
17
|
+
* Extends `@angular/cdk/dialog`'s `DialogConfig` with sheet-specific options
|
|
18
|
+
* (`side`, `size`, animation durations, split close flags).
|
|
19
|
+
*/
|
|
20
|
+
class SheetConfig extends DialogConfig {
|
|
21
|
+
/** Edge the sheet anchors against. Defaults to `'right'`. */
|
|
22
|
+
side = 'right';
|
|
23
|
+
/** Preset size for the sheet panel. Axis-dependent (width for left/right, height for top/bottom). Defaults to `'md'`. */
|
|
24
|
+
size = 'md';
|
|
25
|
+
/** Duration of the open slide animation in ms. Defaults to `200`. Use `0` to disable. */
|
|
26
|
+
enterAnimationDuration = 200;
|
|
27
|
+
/**
|
|
28
|
+
* Duration of the close slide animation in ms. Defaults to `160`. Use `0` to disable.
|
|
29
|
+
* Asymmetric with `enterAnimationDuration` by design — the close transition is slightly
|
|
30
|
+
* faster so the sheet feels dismissive rather than reluctant.
|
|
31
|
+
*/
|
|
32
|
+
exitAnimationDuration = 160;
|
|
33
|
+
/** Scroll strategy preset applied when `scrollStrategy` isn't set. Defaults to `'block'`. */
|
|
34
|
+
scrollBehavior = 'block';
|
|
35
|
+
/**
|
|
36
|
+
* Whether the sheet is modal — sets `aria-modal` on the container. Defaults to `true`:
|
|
37
|
+
* a sheet opened over the page is modal by default and the rest of the document is
|
|
38
|
+
* inert while it is open. Set to `false` only for non-modal surfaces.
|
|
39
|
+
*/
|
|
40
|
+
ariaModal = true;
|
|
41
|
+
/** Whether the sheet renders a backdrop. Defaults to `true`. */
|
|
42
|
+
hasBackdrop = true;
|
|
43
|
+
/**
|
|
44
|
+
* Whether pressing `Escape` closes the sheet.
|
|
45
|
+
*
|
|
46
|
+
* Defaults to `true` — Escape is the universal dismiss key for modal surfaces; the
|
|
47
|
+
* "swallow Escape" variant is the special case. `disableClose` overrides this when
|
|
48
|
+
* `true`.
|
|
49
|
+
*/
|
|
50
|
+
closeOnEscape = true;
|
|
51
|
+
/**
|
|
52
|
+
* Whether clicking the backdrop closes the sheet.
|
|
53
|
+
*
|
|
54
|
+
* Defaults to `true` — clicking outside a modal sheet is the expected dismiss
|
|
55
|
+
* gesture; enforcing an explicit dismiss button is the special case. `disableClose`
|
|
56
|
+
* overrides this when `true`.
|
|
57
|
+
*/
|
|
58
|
+
closeOnBackdropClick = true;
|
|
59
|
+
/**
|
|
60
|
+
* Whether Escape AND backdrop clicks are blocked. Defaults to `false`.
|
|
61
|
+
* When `true`, this overrides both `closeOnEscape` and `closeOnBackdropClick`.
|
|
62
|
+
*/
|
|
63
|
+
disableClose = false;
|
|
64
|
+
/** Whether navigation (e.g. router) closes the sheet. Defaults to `true`. */
|
|
65
|
+
closeOnNavigation = true;
|
|
66
|
+
/** Where to move focus when the sheet opens. Defaults to `'first-tabbable'`. */
|
|
67
|
+
autoFocus = 'first-tabbable';
|
|
68
|
+
/** Whether to restore focus to the previously focused element after close. Defaults to `true`. */
|
|
69
|
+
restoreFocus = true;
|
|
70
|
+
}
|
|
71
|
+
/** Injection token carrying the `data` value passed via {@link SheetConfig.data}. */
|
|
72
|
+
const SHEET_DATA = new InjectionToken('SHEET_DATA');
|
|
73
|
+
/** Injection token for application-wide default sheet options. Set via `provideSheet()`. */
|
|
74
|
+
const SHEET_DEFAULT_OPTIONS = new InjectionToken('SHEET_DEFAULT_OPTIONS');
|
|
75
|
+
|
|
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', ...(ngDevMode ? [{ debugName: "sideAttr" }] : /* istanbul ignore next */ []));
|
|
172
|
+
variants = computed(() => {
|
|
173
|
+
const side = this._config.side ?? 'right';
|
|
174
|
+
const size = this._config.size ?? 'md';
|
|
175
|
+
return sheetContainerVariants({ side, size });
|
|
176
|
+
}, ...(ngDevMode ? [{ debugName: "variants" }] : /* istanbul ignore next */ []));
|
|
177
|
+
hostClasses = computed(() => mergeOverlayPanelClass(this.variants().host(), this._config.panelClass), ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
|
|
178
|
+
ariaDescribedByAttr = computed(() => this._config.ariaDescribedBy ||
|
|
179
|
+
this.coordinator.describedByIds()[0] ||
|
|
180
|
+
null, ...(ngDevMode ? [{ debugName: "ariaDescribedByAttr" }] : /* istanbul ignore next */ []));
|
|
181
|
+
transitionDuration = this.coordinator.transitionDuration;
|
|
182
|
+
constructor() {
|
|
183
|
+
super();
|
|
184
|
+
this.enterAnimationDuration = coerceOverlayDuration(this._config.enterAnimationDuration, 200);
|
|
185
|
+
this.exitAnimationDuration = coerceOverlayDuration(this._config.exitAnimationDuration, 160);
|
|
186
|
+
this.coordinator.setDurations(this.enterAnimationDuration, this.exitAnimationDuration);
|
|
187
|
+
}
|
|
188
|
+
_contentAttached() {
|
|
189
|
+
super._contentAttached();
|
|
190
|
+
this.coordinator.startEnterAnimation();
|
|
191
|
+
}
|
|
192
|
+
/** Triggered by the sheet ref to play the exit transition before disposing the overlay. */
|
|
193
|
+
_startExitAnimation() {
|
|
194
|
+
this.coordinator.startExitAnimation();
|
|
195
|
+
}
|
|
196
|
+
/** Registers a description id for the container's `aria-describedby`. */
|
|
197
|
+
_addAriaDescribedBy(id) {
|
|
198
|
+
this.coordinator.addAriaDescribedBy(id);
|
|
199
|
+
}
|
|
200
|
+
/** Removes a previously registered description id. */
|
|
201
|
+
_removeAriaDescribedBy(id) {
|
|
202
|
+
this.coordinator.removeAriaDescribedBy(id);
|
|
203
|
+
}
|
|
204
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetContainer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
205
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", 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 });
|
|
206
|
+
}
|
|
207
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetContainer, decorators: [{
|
|
208
|
+
type: Component,
|
|
209
|
+
args: [{
|
|
210
|
+
selector: 'tw-sheet-container',
|
|
211
|
+
template: '<ng-template cdkPortalOutlet />',
|
|
212
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
213
|
+
encapsulation: ViewEncapsulation.None,
|
|
214
|
+
imports: [CdkPortalOutlet],
|
|
215
|
+
providers: [OverlayContainerCoordinator],
|
|
216
|
+
host: {
|
|
217
|
+
tabindex: '-1',
|
|
218
|
+
'[attr.id]': '_config.id || null',
|
|
219
|
+
'[attr.role]': '_config.role',
|
|
220
|
+
'[attr.aria-modal]': '_config.ariaModal',
|
|
221
|
+
'[attr.aria-labelledby]': '_config.ariaLabel ? null : _ariaLabelledByQueue[0]',
|
|
222
|
+
'[attr.aria-label]': '_config.ariaLabel',
|
|
223
|
+
'[attr.aria-describedby]': 'ariaDescribedByAttr()',
|
|
224
|
+
'[attr.data-state]': 'state()',
|
|
225
|
+
'[attr.data-side]': 'sideAttr()',
|
|
226
|
+
'[class]': 'hostClasses()',
|
|
227
|
+
'[style.transition-duration.ms]': 'transitionDuration()',
|
|
228
|
+
},
|
|
229
|
+
}]
|
|
230
|
+
}], ctorParameters: () => [] });
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Reference to a sheet opened via {@link Sheet.open}. Drives the sheet
|
|
234
|
+
* lifecycle (close, state, observables) and forwards useful overlay streams.
|
|
235
|
+
*/
|
|
236
|
+
class SheetRef {
|
|
237
|
+
cdkRef;
|
|
238
|
+
config;
|
|
239
|
+
containerInstance;
|
|
240
|
+
/** Unique ID of the sheet. */
|
|
241
|
+
id;
|
|
242
|
+
/** Instance of the component rendered inside the sheet, or `null` for template sheets. */
|
|
243
|
+
componentInstance = null;
|
|
244
|
+
/** `ComponentRef` of the content component, or `null` for template sheets. */
|
|
245
|
+
componentRef = null;
|
|
246
|
+
/** Current lifecycle state. Reactively readable. */
|
|
247
|
+
state;
|
|
248
|
+
/** When `true`, close-via-escape AND close-via-backdrop are both disabled. */
|
|
249
|
+
disableClose;
|
|
250
|
+
/** When `false`, pressing Escape does not close the sheet. */
|
|
251
|
+
closeOnEscape;
|
|
252
|
+
/** When `false`, clicking the backdrop does not close the sheet. */
|
|
253
|
+
closeOnBackdropClick;
|
|
254
|
+
stateSignal = signal('opening', ...(ngDevMode ? [{ debugName: "stateSignal" }] : /* istanbul ignore next */ []));
|
|
255
|
+
afterOpenedSubject = new ReplaySubject(1);
|
|
256
|
+
beforeClosedSubject = new ReplaySubject(1);
|
|
257
|
+
afterClosedSubject = new ReplaySubject(1);
|
|
258
|
+
pendingResult;
|
|
259
|
+
closeFocusOrigin;
|
|
260
|
+
constructor(cdkRef, config, containerInstance) {
|
|
261
|
+
this.cdkRef = cdkRef;
|
|
262
|
+
this.config = config;
|
|
263
|
+
this.containerInstance = containerInstance;
|
|
264
|
+
this.id = cdkRef.id;
|
|
265
|
+
this.disableClose = config.disableClose;
|
|
266
|
+
this.closeOnEscape = config.closeOnEscape;
|
|
267
|
+
this.closeOnBackdropClick = config.closeOnBackdropClick;
|
|
268
|
+
this.state = this.stateSignal.asReadonly();
|
|
269
|
+
cdkRef.addPanelClass('tw-sheet-panel');
|
|
270
|
+
const animationChanges = containerInstance.animationStateChanged;
|
|
271
|
+
animationChanges
|
|
272
|
+
.pipe(filter((event) => event.state === 'open'), take(1))
|
|
273
|
+
.subscribe(() => {
|
|
274
|
+
this.stateSignal.set('open');
|
|
275
|
+
this.afterOpenedSubject.next();
|
|
276
|
+
this.afterOpenedSubject.complete();
|
|
277
|
+
});
|
|
278
|
+
// The container owns the exit-animation fallback timer
|
|
279
|
+
// (ANIMATION_FALLBACK_PADDING in sheet-container.ts), so we trust its
|
|
280
|
+
// `closed` emission and run finishClose exactly once from here.
|
|
281
|
+
animationChanges
|
|
282
|
+
.pipe(filter((event) => event.state === 'closed'), take(1))
|
|
283
|
+
.subscribe(() => this.finishClose());
|
|
284
|
+
cdkRef.overlayRef.detachments().subscribe(() => {
|
|
285
|
+
if (this.stateSignal() !== 'closed') {
|
|
286
|
+
this.beforeClosedSubject.next(this.pendingResult);
|
|
287
|
+
this.beforeClosedSubject.complete();
|
|
288
|
+
this.finishClose();
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
merge(cdkRef.backdropClick.pipe(filter(() => !this.disableClose && this.closeOnBackdropClick !== false)), cdkRef.keydownEvents.pipe(filter((event) => event.keyCode === ESCAPE &&
|
|
292
|
+
!this.disableClose &&
|
|
293
|
+
this.closeOnEscape !== false &&
|
|
294
|
+
!hasModifierKey(event)))).subscribe((event) => {
|
|
295
|
+
event.preventDefault();
|
|
296
|
+
this.closeWithOrigin(event.type === 'keydown' ? 'keyboard' : 'mouse');
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Closes the sheet. The exit slide animation runs before the overlay is disposed.
|
|
301
|
+
* @param result Value forwarded to `afterClosed()` subscribers.
|
|
302
|
+
*/
|
|
303
|
+
close(result) {
|
|
304
|
+
this.closeWithOrigin('program', result);
|
|
305
|
+
}
|
|
306
|
+
/** Observable that emits once after the enter animation finishes. */
|
|
307
|
+
afterOpened() {
|
|
308
|
+
return this.afterOpenedSubject.asObservable();
|
|
309
|
+
}
|
|
310
|
+
/** Observable that emits once when the close animation starts. */
|
|
311
|
+
beforeClosed() {
|
|
312
|
+
return this.beforeClosedSubject.asObservable();
|
|
313
|
+
}
|
|
314
|
+
/** Observable that emits once after the sheet has fully closed and the overlay is disposed. */
|
|
315
|
+
afterClosed() {
|
|
316
|
+
return this.afterClosedSubject.asObservable();
|
|
317
|
+
}
|
|
318
|
+
/** Backdrop click stream (emits even when `closeOnBackdropClick` / `disableClose` is set). */
|
|
319
|
+
backdropClick() {
|
|
320
|
+
return this.cdkRef.backdropClick;
|
|
321
|
+
}
|
|
322
|
+
/** Keydown event stream for the overlay. */
|
|
323
|
+
keydownEvents() {
|
|
324
|
+
return this.cdkRef.keydownEvents;
|
|
325
|
+
}
|
|
326
|
+
/** Adds CSS classes to the overlay panel. */
|
|
327
|
+
addPanelClass(classes) {
|
|
328
|
+
this.cdkRef.addPanelClass(classes);
|
|
329
|
+
return this;
|
|
330
|
+
}
|
|
331
|
+
/** Removes CSS classes from the overlay panel. */
|
|
332
|
+
removePanelClass(classes) {
|
|
333
|
+
this.cdkRef.removePanelClass(classes);
|
|
334
|
+
return this;
|
|
335
|
+
}
|
|
336
|
+
closeWithOrigin(origin, result) {
|
|
337
|
+
if (this.stateSignal() === 'closing' || this.stateSignal() === 'closed')
|
|
338
|
+
return;
|
|
339
|
+
const predicate = this.config.closePredicate;
|
|
340
|
+
if (predicate &&
|
|
341
|
+
!predicate(result, this.config, this.componentInstance)) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
this.pendingResult = result;
|
|
345
|
+
this.closeFocusOrigin = origin;
|
|
346
|
+
this.stateSignal.set('closing');
|
|
347
|
+
this.beforeClosedSubject.next(result);
|
|
348
|
+
this.beforeClosedSubject.complete();
|
|
349
|
+
this.cdkRef.overlayRef.detachBackdrop();
|
|
350
|
+
this.containerInstance._startExitAnimation();
|
|
351
|
+
}
|
|
352
|
+
finishClose() {
|
|
353
|
+
if (this.stateSignal() === 'closed')
|
|
354
|
+
return;
|
|
355
|
+
this.stateSignal.set('closed');
|
|
356
|
+
this.afterClosedSubject.next(this.pendingResult);
|
|
357
|
+
this.afterClosedSubject.complete();
|
|
358
|
+
if (this.cdkRef.containerInstance) {
|
|
359
|
+
this.cdkRef.close(this.pendingResult, { focusOrigin: this.closeFocusOrigin });
|
|
360
|
+
}
|
|
361
|
+
this.componentInstance = null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Opens edge-anchored sheet (drawer) overlays. Composes `@angular/cdk/dialog`
|
|
367
|
+
* for focus trapping, portals, and overlay plumbing — adds a `GlobalPositionStrategy`
|
|
368
|
+
* pinned to the requested viewport edge, a Tailwind container with axis-aware
|
|
369
|
+
* sizing, slide enter/exit animations, and split close-behavior flags.
|
|
370
|
+
*
|
|
371
|
+
* Not `providedIn: 'root'` — register it via {@link provideSheet}.
|
|
372
|
+
*/
|
|
373
|
+
class Sheet {
|
|
374
|
+
cdkDialog = inject(Dialog);
|
|
375
|
+
injector = inject(Injector);
|
|
376
|
+
defaultOptions = inject(SHEET_DEFAULT_OPTIONS, { optional: true }) ?? {};
|
|
377
|
+
parentSheet = inject(Sheet, { optional: true, skipSelf: true });
|
|
378
|
+
openSheetsAtThisLevel = signal([], ...(ngDevMode ? [{ debugName: "openSheetsAtThisLevel" }] : /* istanbul ignore next */ []));
|
|
379
|
+
afterOpenedSubject = new Subject();
|
|
380
|
+
afterAllClosedSubject = new Subject();
|
|
381
|
+
/** Reactively-readable list of currently-open sheets across the full sheet tree. */
|
|
382
|
+
openSheets = this.parentSheet
|
|
383
|
+
? this.parentSheet.openSheets
|
|
384
|
+
: this.openSheetsAtThisLevel.asReadonly();
|
|
385
|
+
/** Emits every time a sheet is opened. */
|
|
386
|
+
afterOpened = this.parentSheet
|
|
387
|
+
? this.parentSheet.afterOpened
|
|
388
|
+
: this.afterOpenedSubject.asObservable();
|
|
389
|
+
/**
|
|
390
|
+
* Emits when every open sheet has closed. Emits immediately on subscribe if
|
|
391
|
+
* there are none open.
|
|
392
|
+
*/
|
|
393
|
+
afterAllClosed = defer(() => this.openSheets().length
|
|
394
|
+
? this.getAfterAllClosedSource()
|
|
395
|
+
: this.getAfterAllClosedSource().pipe(startWith(undefined)));
|
|
396
|
+
/**
|
|
397
|
+
* Opens a sheet using the given component or template.
|
|
398
|
+
* @param content Component class or `TemplateRef`.
|
|
399
|
+
* @param config Options merged over the application defaults.
|
|
400
|
+
* @returns Reference controlling the opened sheet.
|
|
401
|
+
*/
|
|
402
|
+
open(content, config) {
|
|
403
|
+
const merged = this.resolveConfig(config);
|
|
404
|
+
let sheetRef;
|
|
405
|
+
const cdkRef = this.cdkDialog.open(content, {
|
|
406
|
+
id: merged.id,
|
|
407
|
+
role: merged.role,
|
|
408
|
+
data: merged.data,
|
|
409
|
+
panelClass: merged.panelClass,
|
|
410
|
+
backdropClass: merged.backdropClass || 'tw-sheet-backdrop',
|
|
411
|
+
hasBackdrop: merged.hasBackdrop,
|
|
412
|
+
// Sheet sizing happens on the container element (axis-aware width/height
|
|
413
|
+
// utilities). The overlay pane itself is the full-edge bounding box.
|
|
414
|
+
direction: merged.direction,
|
|
415
|
+
ariaDescribedBy: merged.ariaDescribedBy,
|
|
416
|
+
ariaLabelledBy: merged.ariaLabelledBy,
|
|
417
|
+
ariaLabel: merged.ariaLabel,
|
|
418
|
+
ariaModal: merged.ariaModal,
|
|
419
|
+
autoFocus: merged.autoFocus,
|
|
420
|
+
restoreFocus: merged.restoreFocus,
|
|
421
|
+
scrollStrategy: merged.scrollStrategy ?? this.resolveScrollStrategy(merged.scrollBehavior),
|
|
422
|
+
positionStrategy: merged.positionStrategy ?? this.resolvePositionStrategy(merged.side ?? 'right'),
|
|
423
|
+
closeOnNavigation: merged.closeOnNavigation,
|
|
424
|
+
viewContainerRef: merged.viewContainerRef,
|
|
425
|
+
injector: merged.injector,
|
|
426
|
+
// We handle close/Escape/backdrop ourselves so we can run the exit slide animation.
|
|
427
|
+
disableClose: true,
|
|
428
|
+
closeOnOverlayDetachments: false,
|
|
429
|
+
container: {
|
|
430
|
+
type: SheetContainer,
|
|
431
|
+
providers: () => [
|
|
432
|
+
{ provide: SheetConfig, useValue: merged },
|
|
433
|
+
{ provide: DialogConfig, useValue: merged },
|
|
434
|
+
],
|
|
435
|
+
},
|
|
436
|
+
providers: (_cdkRef, _cdkConfig, container) => {
|
|
437
|
+
sheetRef = new SheetRef(_cdkRef, merged, container);
|
|
438
|
+
const providers = [
|
|
439
|
+
{ provide: SheetRef, useValue: sheetRef },
|
|
440
|
+
{ provide: SHEET_DATA, useValue: merged.data ?? null },
|
|
441
|
+
];
|
|
442
|
+
if (Array.isArray(merged.providers))
|
|
443
|
+
providers.push(...merged.providers);
|
|
444
|
+
return providers;
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
// After CDK attaches the component, copy references onto our ref.
|
|
448
|
+
sheetRef.componentRef = cdkRef.componentRef;
|
|
449
|
+
sheetRef.componentInstance = cdkRef.componentInstance;
|
|
450
|
+
const scope = this.parentSheet ?? this;
|
|
451
|
+
scope.registerOpen(sheetRef);
|
|
452
|
+
sheetRef.afterClosed().subscribe(() => {
|
|
453
|
+
scope.unregister(sheetRef);
|
|
454
|
+
});
|
|
455
|
+
return sheetRef;
|
|
456
|
+
}
|
|
457
|
+
/** Closes every open sheet managed by this service (and child services). */
|
|
458
|
+
closeAll() {
|
|
459
|
+
const sheets = [...this.openSheets()];
|
|
460
|
+
for (let i = sheets.length - 1; i >= 0; i--)
|
|
461
|
+
sheets[i].close();
|
|
462
|
+
}
|
|
463
|
+
/** Looks up an open sheet by its id. */
|
|
464
|
+
getSheetById(id) {
|
|
465
|
+
return this.openSheets().find((sheet) => sheet.id === id);
|
|
466
|
+
}
|
|
467
|
+
ngOnDestroy() {
|
|
468
|
+
const sheets = [...this.openSheetsAtThisLevel()];
|
|
469
|
+
for (let i = sheets.length - 1; i >= 0; i--)
|
|
470
|
+
sheets[i].close();
|
|
471
|
+
this.afterOpenedSubject.complete();
|
|
472
|
+
this.afterAllClosedSubject.complete();
|
|
473
|
+
}
|
|
474
|
+
registerOpen(ref) {
|
|
475
|
+
if (this.parentSheet) {
|
|
476
|
+
this.parentSheet.registerOpen(ref);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
this.openSheetsAtThisLevel.update((list) => [...list, ref]);
|
|
480
|
+
this.afterOpenedSubject.next(ref);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
unregister(ref) {
|
|
484
|
+
if (this.parentSheet) {
|
|
485
|
+
this.parentSheet.unregister(ref);
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
this.openSheetsAtThisLevel.update((list) => list.filter((d) => d !== ref));
|
|
489
|
+
if (this.openSheetsAtThisLevel().length === 0) {
|
|
490
|
+
this.afterAllClosedSubject.next();
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
getAfterAllClosedSource() {
|
|
495
|
+
return this.parentSheet
|
|
496
|
+
? this.parentSheet.getAfterAllClosedSource()
|
|
497
|
+
: this.afterAllClosedSubject;
|
|
498
|
+
}
|
|
499
|
+
resolveConfig(config) {
|
|
500
|
+
const merged = new SheetConfig();
|
|
501
|
+
Object.assign(merged, this.defaultOptions, config);
|
|
502
|
+
return merged;
|
|
503
|
+
}
|
|
504
|
+
resolveScrollStrategy(strategy) {
|
|
505
|
+
switch (strategy) {
|
|
506
|
+
case 'close':
|
|
507
|
+
return createCloseScrollStrategy(this.injector);
|
|
508
|
+
case 'reposition':
|
|
509
|
+
return createRepositionScrollStrategy(this.injector);
|
|
510
|
+
case 'noop':
|
|
511
|
+
return createNoopScrollStrategy();
|
|
512
|
+
case 'block':
|
|
513
|
+
default:
|
|
514
|
+
return createBlockScrollStrategy(this.injector);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
resolvePositionStrategy(side) {
|
|
518
|
+
const strategy = createGlobalPositionStrategy(this.injector);
|
|
519
|
+
switch (side) {
|
|
520
|
+
case 'left':
|
|
521
|
+
return strategy.top('0').left('0').bottom('0');
|
|
522
|
+
case 'top':
|
|
523
|
+
return strategy.top('0').left('0').right('0');
|
|
524
|
+
case 'bottom':
|
|
525
|
+
return strategy.bottom('0').left('0').right('0');
|
|
526
|
+
case 'right':
|
|
527
|
+
default:
|
|
528
|
+
return strategy.top('0').right('0').bottom('0');
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: Sheet, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
532
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: Sheet });
|
|
533
|
+
}
|
|
534
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: Sheet, decorators: [{
|
|
535
|
+
type: Injectable
|
|
536
|
+
}] });
|
|
537
|
+
/**
|
|
538
|
+
* Registers the {@link Sheet} service for dependency injection.
|
|
539
|
+
*
|
|
540
|
+
* @param defaultOptions Optional defaults merged into every `open()` call.
|
|
541
|
+
* @example
|
|
542
|
+
* ```ts
|
|
543
|
+
* provideSheet({ side: 'left', size: 'lg' })
|
|
544
|
+
* ```
|
|
545
|
+
*/
|
|
546
|
+
function provideSheet(defaultOptions) {
|
|
547
|
+
const providers = [Sheet];
|
|
548
|
+
if (defaultOptions) {
|
|
549
|
+
providers.push({ provide: SHEET_DEFAULT_OPTIONS, useValue: defaultOptions });
|
|
550
|
+
}
|
|
551
|
+
return makeEnvironmentProviders(providers);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Header wrapper for a sheet. Provides consistent padding and spacing for
|
|
556
|
+
* title + subtitle + leading icon, and separates the header from scrollable
|
|
557
|
+
* content below.
|
|
558
|
+
*/
|
|
559
|
+
class SheetHeaderDirective {
|
|
560
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
561
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.14", type: SheetHeaderDirective, isStandalone: true, selector: "[twSheetHeader], tw-sheet-header", host: { classAttribute: "flex items-start gap-3 px-6 pt-6 pb-4" }, ngImport: i0 });
|
|
562
|
+
}
|
|
563
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetHeaderDirective, decorators: [{
|
|
564
|
+
type: Directive,
|
|
565
|
+
args: [{
|
|
566
|
+
selector: '[twSheetHeader], tw-sheet-header',
|
|
567
|
+
host: {
|
|
568
|
+
class: 'flex items-start gap-3 px-6 pt-6 pb-4',
|
|
569
|
+
},
|
|
570
|
+
}]
|
|
571
|
+
}] });
|
|
572
|
+
// Slot tokens own light/dark contrast — no `dark:` overrides, no shade picks.
|
|
573
|
+
// See role slot conventions in `projects/ngx-tw/theme/_semantic.css`.
|
|
574
|
+
const ICON_COLOR_CLASSES = {
|
|
575
|
+
primary: 'bg-primary-soft text-primary-icon',
|
|
576
|
+
secondary: 'bg-secondary-soft text-secondary-icon',
|
|
577
|
+
accent: 'bg-accent-soft text-accent-icon',
|
|
578
|
+
neutral: 'bg-neutral-soft text-neutral-icon',
|
|
579
|
+
info: 'bg-info-soft text-info-icon',
|
|
580
|
+
success: 'bg-success-soft text-success-icon',
|
|
581
|
+
warning: 'bg-warning-soft text-warning-icon',
|
|
582
|
+
error: 'bg-error-soft text-error-icon',
|
|
583
|
+
};
|
|
584
|
+
/**
|
|
585
|
+
* Decorative leading icon for a sheet header. Use with a semantic `color` to
|
|
586
|
+
* match destructive / informational / success sheets.
|
|
587
|
+
*/
|
|
588
|
+
class SheetIconDirective {
|
|
589
|
+
/** Semantic color for the icon container. Defaults to a neutral surface. */
|
|
590
|
+
color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
591
|
+
classes = computed(() => {
|
|
592
|
+
const base = 'flex size-10 shrink-0 items-center justify-center rounded-full';
|
|
593
|
+
const color = this.color();
|
|
594
|
+
return color ? `${base} ${ICON_COLOR_CLASSES[color]}` : `${base} bg-surface-muted text-fg`;
|
|
595
|
+
}, ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
596
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetIconDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
597
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.14", type: SheetIconDirective, isStandalone: true, selector: "[twSheetIcon]", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
|
|
598
|
+
}
|
|
599
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetIconDirective, decorators: [{
|
|
600
|
+
type: Directive,
|
|
601
|
+
args: [{
|
|
602
|
+
selector: '[twSheetIcon]',
|
|
603
|
+
host: {
|
|
604
|
+
'[class]': 'classes()',
|
|
605
|
+
},
|
|
606
|
+
}]
|
|
607
|
+
}], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }] } });
|
|
608
|
+
/**
|
|
609
|
+
* Sheet title. Registers its ID with the container's `aria-labelledby` queue
|
|
610
|
+
* so screen readers announce it automatically.
|
|
611
|
+
*/
|
|
612
|
+
class SheetTitleDirective {
|
|
613
|
+
generatedId = inject(_IdGenerator).getId('tw-sheet-title-');
|
|
614
|
+
sheetRef = inject(SheetRef, { optional: true });
|
|
615
|
+
// Ancestor-DI fallback for the rare case where `SheetRef` is not in the
|
|
616
|
+
// directive's injector chain (e.g. heavily nested template portals). The
|
|
617
|
+
// container ALWAYS resolves via element-injector traversal because the
|
|
618
|
+
// directive lives inside `<tw-sheet-container>`'s DOM tree.
|
|
619
|
+
container = inject(SheetContainer, { optional: true, skipSelf: true });
|
|
620
|
+
/** Custom id for the title element. Defaults to a generated unique id. */
|
|
621
|
+
id = input(this.generatedId, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
622
|
+
ngOnInit() {
|
|
623
|
+
const containerInstance = this.sheetRef?.containerInstance ?? this.container;
|
|
624
|
+
if (containerInstance)
|
|
625
|
+
containerInstance._addAriaLabelledBy(this.id());
|
|
626
|
+
}
|
|
627
|
+
ngOnDestroy() {
|
|
628
|
+
const containerInstance = this.sheetRef?.containerInstance ?? this.container;
|
|
629
|
+
if (containerInstance)
|
|
630
|
+
containerInstance._removeAriaLabelledBy(this.id());
|
|
631
|
+
}
|
|
632
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
633
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.14", type: SheetTitleDirective, isStandalone: true, selector: "[twSheetTitle], tw-sheet-title", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "id()" }, classAttribute: "text-sm font-semibold text-fg" }, ngImport: i0 });
|
|
634
|
+
}
|
|
635
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetTitleDirective, decorators: [{
|
|
636
|
+
type: Directive,
|
|
637
|
+
args: [{
|
|
638
|
+
selector: '[twSheetTitle], tw-sheet-title',
|
|
639
|
+
host: {
|
|
640
|
+
class: 'text-sm font-semibold text-fg',
|
|
641
|
+
'[id]': 'id()',
|
|
642
|
+
},
|
|
643
|
+
}]
|
|
644
|
+
}], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
|
|
645
|
+
/** Secondary line beneath the sheet title — intended for a short description. */
|
|
646
|
+
class SheetSubtitleDirective {
|
|
647
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetSubtitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
648
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.14", type: SheetSubtitleDirective, isStandalone: true, selector: "[twSheetSubtitle], tw-sheet-subtitle", host: { classAttribute: "text-sm text-fg-muted" }, ngImport: i0 });
|
|
649
|
+
}
|
|
650
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetSubtitleDirective, decorators: [{
|
|
651
|
+
type: Directive,
|
|
652
|
+
args: [{
|
|
653
|
+
selector: '[twSheetSubtitle], tw-sheet-subtitle',
|
|
654
|
+
host: {
|
|
655
|
+
class: 'text-sm text-fg-muted',
|
|
656
|
+
},
|
|
657
|
+
}]
|
|
658
|
+
}] });
|
|
659
|
+
/**
|
|
660
|
+
* Sheet description. Registers its ID with the container's `aria-describedby`
|
|
661
|
+
* queue so screen readers announce the descriptive paragraph after the title.
|
|
662
|
+
* Mirrors {@link SheetTitleDirective} but for `aria-describedby`.
|
|
663
|
+
*/
|
|
664
|
+
class SheetDescriptionDirective {
|
|
665
|
+
generatedId = inject(_IdGenerator).getId('tw-sheet-description-');
|
|
666
|
+
sheetRef = inject(SheetRef, { optional: true });
|
|
667
|
+
// Ancestor-DI fallback — see SheetTitleDirective.
|
|
668
|
+
container = inject(SheetContainer, { optional: true, skipSelf: true });
|
|
669
|
+
/** Custom id for the description element. Defaults to a generated unique id. */
|
|
670
|
+
id = input(this.generatedId, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
671
|
+
ngOnInit() {
|
|
672
|
+
const containerInstance = this.sheetRef?.containerInstance ?? this.container;
|
|
673
|
+
if (containerInstance)
|
|
674
|
+
containerInstance._addAriaDescribedBy(this.id());
|
|
675
|
+
}
|
|
676
|
+
ngOnDestroy() {
|
|
677
|
+
const containerInstance = this.sheetRef?.containerInstance ?? this.container;
|
|
678
|
+
if (containerInstance)
|
|
679
|
+
containerInstance._removeAriaDescribedBy(this.id());
|
|
680
|
+
}
|
|
681
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetDescriptionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
682
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.14", type: SheetDescriptionDirective, isStandalone: true, selector: "[twSheetDescription], tw-sheet-description", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "id()" } }, ngImport: i0 });
|
|
683
|
+
}
|
|
684
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetDescriptionDirective, decorators: [{
|
|
685
|
+
type: Directive,
|
|
686
|
+
args: [{
|
|
687
|
+
selector: '[twSheetDescription], tw-sheet-description',
|
|
688
|
+
host: {
|
|
689
|
+
'[id]': 'id()',
|
|
690
|
+
},
|
|
691
|
+
}]
|
|
692
|
+
}], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
|
|
693
|
+
/**
|
|
694
|
+
* Scrollable content region of the sheet. Apply between the header and the
|
|
695
|
+
* actions bar. Inherits CDK's `CdkScrollable` to play nicely with scroll
|
|
696
|
+
* strategies and nested scrollables.
|
|
697
|
+
*/
|
|
698
|
+
class SheetContentDirective {
|
|
699
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
700
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.14", type: SheetContentDirective, isStandalone: true, selector: "[twSheetContent], tw-sheet-content", host: { classAttribute: "flex-1 overflow-y-auto px-6 py-4 text-sm text-fg" }, hostDirectives: [{ directive: i1.CdkScrollable }], ngImport: i0 });
|
|
701
|
+
}
|
|
702
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetContentDirective, decorators: [{
|
|
703
|
+
type: Directive,
|
|
704
|
+
args: [{
|
|
705
|
+
selector: '[twSheetContent], tw-sheet-content',
|
|
706
|
+
hostDirectives: [CdkScrollable],
|
|
707
|
+
host: {
|
|
708
|
+
class: 'flex-1 overflow-y-auto px-6 py-4 text-sm text-fg',
|
|
709
|
+
},
|
|
710
|
+
}]
|
|
711
|
+
}] });
|
|
712
|
+
/**
|
|
713
|
+
* Bottom action bar of a sheet. Use inside the sheet content or template to
|
|
714
|
+
* host Cancel/Confirm buttons. Stays pinned below scrollable content.
|
|
715
|
+
*/
|
|
716
|
+
class SheetActionsDirective {
|
|
717
|
+
/** Horizontal alignment of the action buttons. Defaults to `'end'`. */
|
|
718
|
+
align = input('end', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
719
|
+
classes = computed(() => {
|
|
720
|
+
const align = this.align();
|
|
721
|
+
const justify = align === 'start' ? 'justify-start' : align === 'center' ? 'justify-center' : 'justify-end';
|
|
722
|
+
return `flex flex-wrap items-center gap-2 px-6 py-4 border-t border-border-muted ${justify}`;
|
|
723
|
+
}, ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
724
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetActionsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
725
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.14", type: SheetActionsDirective, isStandalone: true, selector: "[twSheetActions], tw-sheet-actions", inputs: { align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
|
|
726
|
+
}
|
|
727
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetActionsDirective, decorators: [{
|
|
728
|
+
type: Directive,
|
|
729
|
+
args: [{
|
|
730
|
+
selector: '[twSheetActions], tw-sheet-actions',
|
|
731
|
+
host: {
|
|
732
|
+
'[class]': 'classes()',
|
|
733
|
+
},
|
|
734
|
+
}]
|
|
735
|
+
}], propDecorators: { align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }] } });
|
|
736
|
+
/**
|
|
737
|
+
* Closes the enclosing sheet when the host button is clicked. Provide a
|
|
738
|
+
* `[twSheetClose]` value to pass a result to `afterClosed()` subscribers.
|
|
739
|
+
*/
|
|
740
|
+
class SheetCloseDirective {
|
|
741
|
+
/** Value passed to `afterClosed()` when the button is clicked. */
|
|
742
|
+
twSheetClose = input(undefined, ...(ngDevMode ? [{ debugName: "twSheetClose" }] : /* istanbul ignore next */ []));
|
|
743
|
+
/** Native button `type`. Defaults to `'button'` to avoid accidental form submission. */
|
|
744
|
+
type = input('button', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
745
|
+
sheetRef = inject(SheetRef, { optional: true });
|
|
746
|
+
onClick() {
|
|
747
|
+
this.sheetRef?.close(this.twSheetClose());
|
|
748
|
+
}
|
|
749
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetCloseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
750
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.14", type: SheetCloseDirective, isStandalone: true, selector: "[twSheetClose]", inputs: { twSheetClose: { classPropertyName: "twSheetClose", publicName: "twSheetClose", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick()" }, properties: { "attr.type": "type()" } }, ngImport: i0 });
|
|
751
|
+
}
|
|
752
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SheetCloseDirective, decorators: [{
|
|
753
|
+
type: Directive,
|
|
754
|
+
args: [{
|
|
755
|
+
selector: '[twSheetClose]',
|
|
756
|
+
host: {
|
|
757
|
+
'(click)': 'onClick()',
|
|
758
|
+
'[attr.type]': 'type()',
|
|
759
|
+
},
|
|
760
|
+
}]
|
|
761
|
+
}], propDecorators: { twSheetClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "twSheetClose", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }] } });
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Generated bundle index. Do not edit.
|
|
765
|
+
*/
|
|
766
|
+
|
|
767
|
+
export { SHEET_DATA, SHEET_DEFAULT_OPTIONS, Sheet, SheetActionsDirective, SheetCloseDirective, SheetConfig, SheetContainer, SheetContentDirective, SheetDescriptionDirective, SheetHeaderDirective, SheetIconDirective, SheetRef, SheetSubtitleDirective, SheetTitleDirective, provideSheet };
|
|
768
|
+
//# sourceMappingURL=cdevhub-ngx-tw-sheet.mjs.map
|