@shadng/sng-ui 1.0.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 +75 -0
- package/cli/sng-ui.js +331 -0
- package/ng-package.json +29 -0
- package/package.json +64 -0
- package/registry.json +72 -0
- package/src/lib/accordion/cn.ts +6 -0
- package/src/lib/accordion/index.ts +18 -0
- package/src/lib/accordion/sng-accordion-content.ts +131 -0
- package/src/lib/accordion/sng-accordion-item.ts +299 -0
- package/src/lib/accordion/sng-accordion-trigger.ts +137 -0
- package/src/lib/accordion/sng-accordion.ts +118 -0
- package/src/lib/accordion/sng-accordion.types.ts +82 -0
- package/src/lib/alert/cn.ts +6 -0
- package/src/lib/alert/index.ts +3 -0
- package/src/lib/alert/sng-alert-description.ts +49 -0
- package/src/lib/alert/sng-alert-title.ts +46 -0
- package/src/lib/alert/sng-alert.ts +48 -0
- package/src/lib/avatar/cn.ts +6 -0
- package/src/lib/avatar/index.ts +3 -0
- package/src/lib/avatar/sng-avatar-fallback.ts +50 -0
- package/src/lib/avatar/sng-avatar-image.ts +73 -0
- package/src/lib/avatar/sng-avatar.ts +60 -0
- package/src/lib/badge/cn.ts +6 -0
- package/src/lib/badge/index.ts +1 -0
- package/src/lib/badge/sng-badge.ts +36 -0
- package/src/lib/breadcrumb/cn.ts +6 -0
- package/src/lib/breadcrumb/index.ts +7 -0
- package/src/lib/breadcrumb/sng-breadcrumb-ellipsis.ts +61 -0
- package/src/lib/breadcrumb/sng-breadcrumb-item.ts +47 -0
- package/src/lib/breadcrumb/sng-breadcrumb-link.ts +43 -0
- package/src/lib/breadcrumb/sng-breadcrumb-list.ts +42 -0
- package/src/lib/breadcrumb/sng-breadcrumb-page.ts +44 -0
- package/src/lib/breadcrumb/sng-breadcrumb-separator.ts +60 -0
- package/src/lib/breadcrumb/sng-breadcrumb.ts +52 -0
- package/src/lib/button/cn.ts +6 -0
- package/src/lib/button/index.ts +2 -0
- package/src/lib/button/sng-button.ts +264 -0
- package/src/lib/calendar/cn.ts +6 -0
- package/src/lib/calendar/index.ts +2 -0
- package/src/lib/calendar/sng-calendar.ts +753 -0
- package/src/lib/card/cn.ts +6 -0
- package/src/lib/card/index.ts +6 -0
- package/src/lib/card/sng-card-content.ts +36 -0
- package/src/lib/card/sng-card-description.ts +38 -0
- package/src/lib/card/sng-card-footer.ts +34 -0
- package/src/lib/card/sng-card-header.ts +34 -0
- package/src/lib/card/sng-card-title.ts +48 -0
- package/src/lib/card/sng-card.ts +43 -0
- package/src/lib/carousel/cn.ts +6 -0
- package/src/lib/carousel/index.ts +18 -0
- package/src/lib/carousel/sng-carousel.ts +526 -0
- package/src/lib/checkbox/cn.ts +6 -0
- package/src/lib/checkbox/index.ts +1 -0
- package/src/lib/checkbox/sng-checkbox.ts +154 -0
- package/src/lib/code-block/cn.ts +6 -0
- package/src/lib/code-block/index.ts +1 -0
- package/src/lib/code-block/sng-code-block.ts +296 -0
- package/src/lib/dialog/cn.ts +6 -0
- package/src/lib/dialog/index.ts +37 -0
- package/src/lib/dialog/sng-dialog-close.ts +76 -0
- package/src/lib/dialog/sng-dialog-content.ts +132 -0
- package/src/lib/dialog/sng-dialog-description.ts +36 -0
- package/src/lib/dialog/sng-dialog-footer.ts +39 -0
- package/src/lib/dialog/sng-dialog-header.ts +39 -0
- package/src/lib/dialog/sng-dialog-title.ts +52 -0
- package/src/lib/dialog/sng-dialog.service.ts +222 -0
- package/src/lib/dialog/sng-dialog.ts +224 -0
- package/src/lib/drawer/cn.ts +6 -0
- package/src/lib/drawer/index.ts +36 -0
- package/src/lib/drawer/sng-drawer-close.ts +28 -0
- package/src/lib/drawer/sng-drawer-content.ts +135 -0
- package/src/lib/drawer/sng-drawer-description.ts +29 -0
- package/src/lib/drawer/sng-drawer-footer.ts +34 -0
- package/src/lib/drawer/sng-drawer-handle.ts +30 -0
- package/src/lib/drawer/sng-drawer-header.ts +30 -0
- package/src/lib/drawer/sng-drawer-title.ts +27 -0
- package/src/lib/drawer/sng-drawer-trigger.ts +21 -0
- package/src/lib/drawer/sng-drawer-wrapper.ts +27 -0
- package/src/lib/drawer/sng-drawer.ts +166 -0
- package/src/lib/file-input/cn.ts +6 -0
- package/src/lib/file-input/index.ts +1 -0
- package/src/lib/file-input/sng-file-input.ts +288 -0
- package/src/lib/hover-card/cn.ts +6 -0
- package/src/lib/hover-card/index.ts +3 -0
- package/src/lib/hover-card/sng-hover-card-content.ts +100 -0
- package/src/lib/hover-card/sng-hover-card-trigger.ts +43 -0
- package/src/lib/hover-card/sng-hover-card.ts +246 -0
- package/src/lib/input/cn.ts +6 -0
- package/src/lib/input/index.ts +1 -0
- package/src/lib/input/sng-input.ts +160 -0
- package/src/lib/layout/cn.ts +6 -0
- package/src/lib/layout/index.ts +98 -0
- package/src/lib/layout/sng-layout-footer.ts +37 -0
- package/src/lib/layout/sng-layout-header.ts +38 -0
- package/src/lib/layout/sng-layout-sidebar-content.ts +149 -0
- package/src/lib/layout/sng-layout-sidebar-footer.ts +54 -0
- package/src/lib/layout/sng-layout-sidebar-group-action.ts +67 -0
- package/src/lib/layout/sng-layout-sidebar-group-content.ts +41 -0
- package/src/lib/layout/sng-layout-sidebar-group-label.ts +53 -0
- package/src/lib/layout/sng-layout-sidebar-group.ts +41 -0
- package/src/lib/layout/sng-layout-sidebar-header.ts +54 -0
- package/src/lib/layout/sng-layout-sidebar-input.ts +112 -0
- package/src/lib/layout/sng-layout-sidebar-inset.ts +45 -0
- package/src/lib/layout/sng-layout-sidebar-menu-action.ts +84 -0
- package/src/lib/layout/sng-layout-sidebar-menu-badge.ts +47 -0
- package/src/lib/layout/sng-layout-sidebar-menu-button.ts +160 -0
- package/src/lib/layout/sng-layout-sidebar-menu-item.ts +40 -0
- package/src/lib/layout/sng-layout-sidebar-menu-skeleton.ts +71 -0
- package/src/lib/layout/sng-layout-sidebar-menu-sub-button.ts +142 -0
- package/src/lib/layout/sng-layout-sidebar-menu-sub-item.ts +38 -0
- package/src/lib/layout/sng-layout-sidebar-menu-sub.ts +48 -0
- package/src/lib/layout/sng-layout-sidebar-menu.ts +41 -0
- package/src/lib/layout/sng-layout-sidebar-provider.ts +189 -0
- package/src/lib/layout/sng-layout-sidebar-rail.ts +60 -0
- package/src/lib/layout/sng-layout-sidebar-separator.ts +38 -0
- package/src/lib/layout/sng-layout-sidebar-trigger.ts +97 -0
- package/src/lib/layout/sng-layout-sidebar.ts +254 -0
- package/src/lib/menu/cn.ts +6 -0
- package/src/lib/menu/index.ts +21 -0
- package/src/lib/menu/sng-context-trigger.ts +128 -0
- package/src/lib/menu/sng-menu-checkbox-item.ts +91 -0
- package/src/lib/menu/sng-menu-item.ts +80 -0
- package/src/lib/menu/sng-menu-label.ts +47 -0
- package/src/lib/menu/sng-menu-radio-group.ts +38 -0
- package/src/lib/menu/sng-menu-radio-item.ts +94 -0
- package/src/lib/menu/sng-menu-separator.ts +27 -0
- package/src/lib/menu/sng-menu-shortcut.ts +25 -0
- package/src/lib/menu/sng-menu-sub-content.ts +267 -0
- package/src/lib/menu/sng-menu-sub-trigger.ts +68 -0
- package/src/lib/menu/sng-menu-sub.ts +124 -0
- package/src/lib/menu/sng-menu-tokens.ts +52 -0
- package/src/lib/menu/sng-menu-trigger.ts +266 -0
- package/src/lib/menu/sng-menu.ts +100 -0
- package/src/lib/nav-menu/cn.ts +6 -0
- package/src/lib/nav-menu/index.ts +6 -0
- package/src/lib/nav-menu/sng-nav-menu-content.ts +72 -0
- package/src/lib/nav-menu/sng-nav-menu-item.ts +109 -0
- package/src/lib/nav-menu/sng-nav-menu-link.ts +54 -0
- package/src/lib/nav-menu/sng-nav-menu-list.ts +43 -0
- package/src/lib/nav-menu/sng-nav-menu-trigger.ts +98 -0
- package/src/lib/nav-menu/sng-nav-menu.ts +99 -0
- package/src/lib/otp-input/cn.ts +6 -0
- package/src/lib/otp-input/index.ts +14 -0
- package/src/lib/otp-input/sng-otp-input-group.ts +38 -0
- package/src/lib/otp-input/sng-otp-input-separator.ts +43 -0
- package/src/lib/otp-input/sng-otp-input-slot.ts +128 -0
- package/src/lib/otp-input/sng-otp-input-tokens.ts +20 -0
- package/src/lib/otp-input/sng-otp-input.ts +301 -0
- package/src/lib/popover/cn.ts +6 -0
- package/src/lib/popover/index.ts +3 -0
- package/src/lib/popover/sng-popover-content.ts +66 -0
- package/src/lib/popover/sng-popover-trigger.ts +44 -0
- package/src/lib/popover/sng-popover.ts +218 -0
- package/src/lib/preview-box/cn.ts +6 -0
- package/src/lib/preview-box/index.ts +5 -0
- package/src/lib/preview-box/sng-code-block.ts +80 -0
- package/src/lib/preview-box/sng-html-block.ts +79 -0
- package/src/lib/preview-box/sng-preview-block.ts +47 -0
- package/src/lib/preview-box/sng-preview-box.ts +369 -0
- package/src/lib/preview-box/sng-style-block.ts +80 -0
- package/src/lib/progress/cn.ts +6 -0
- package/src/lib/progress/index.ts +1 -0
- package/src/lib/progress/sng-progress.ts +65 -0
- package/src/lib/radio/cn.ts +6 -0
- package/src/lib/radio/index.ts +5 -0
- package/src/lib/radio/sng-radio-item.ts +100 -0
- package/src/lib/radio/sng-radio.ts +54 -0
- package/src/lib/resizable/cn.ts +6 -0
- package/src/lib/resizable/index.ts +3 -0
- package/src/lib/resizable/sng-resizable-group.ts +188 -0
- package/src/lib/resizable/sng-resizable-handle.ts +236 -0
- package/src/lib/resizable/sng-resizable-panel.ts +71 -0
- package/src/lib/search-input/cn.ts +6 -0
- package/src/lib/search-input/index.ts +16 -0
- package/src/lib/search-input/sng-search-input-context.ts +24 -0
- package/src/lib/search-input/sng-search-input-empty.ts +42 -0
- package/src/lib/search-input/sng-search-input-group.ts +69 -0
- package/src/lib/search-input/sng-search-input-item.ts +164 -0
- package/src/lib/search-input/sng-search-input-list.ts +34 -0
- package/src/lib/search-input/sng-search-input-separator.ts +32 -0
- package/src/lib/search-input/sng-search-input-shortcut.ts +29 -0
- package/src/lib/search-input/sng-search-input.ts +368 -0
- package/src/lib/select/cn.ts +6 -0
- package/src/lib/select/index.ts +7 -0
- package/src/lib/select/sng-select-content.ts +27 -0
- package/src/lib/select/sng-select-empty.ts +48 -0
- package/src/lib/select/sng-select-group.ts +29 -0
- package/src/lib/select/sng-select-item.ts +140 -0
- package/src/lib/select/sng-select-label.ts +29 -0
- package/src/lib/select/sng-select-separator.ts +29 -0
- package/src/lib/select/sng-select.ts +326 -0
- package/src/lib/separator/cn.ts +6 -0
- package/src/lib/separator/index.ts +1 -0
- package/src/lib/separator/sng-separator.ts +40 -0
- package/src/lib/skeleton/cn.ts +6 -0
- package/src/lib/skeleton/index.ts +1 -0
- package/src/lib/skeleton/sng-skeleton.ts +49 -0
- package/src/lib/slider/cn.ts +6 -0
- package/src/lib/slider/index.ts +2 -0
- package/src/lib/slider/sng-slider.ts +137 -0
- package/src/lib/sng-table/cn.ts +6 -0
- package/src/lib/sng-table/flex-render.ts +222 -0
- package/src/lib/sng-table/index.ts +85 -0
- package/src/lib/sng-table/sng-table-body.ts +59 -0
- package/src/lib/sng-table/sng-table-caption.ts +49 -0
- package/src/lib/sng-table/sng-table-cell.ts +62 -0
- package/src/lib/sng-table/sng-table-footer.ts +60 -0
- package/src/lib/sng-table/sng-table-head.ts +66 -0
- package/src/lib/sng-table/sng-table-header.ts +48 -0
- package/src/lib/sng-table/sng-table-pagination.ts +265 -0
- package/src/lib/sng-table/sng-table-row.ts +65 -0
- package/src/lib/sng-table/sng-table.ts +67 -0
- package/src/lib/sng-table-core/core/create-cell.ts +117 -0
- package/src/lib/sng-table-core/core/create-column.ts +266 -0
- package/src/lib/sng-table-core/core/create-header.ts +271 -0
- package/src/lib/sng-table-core/core/create-row.ts +293 -0
- package/src/lib/sng-table-core/core/create-table.ts +534 -0
- package/src/lib/sng-table-core/core/types.ts +1197 -0
- package/src/lib/sng-table-core/core/utils.ts +307 -0
- package/src/lib/sng-table-core/features/column-filtering.ts +376 -0
- package/src/lib/sng-table-core/features/column-ordering.ts +159 -0
- package/src/lib/sng-table-core/features/column-pinning.ts +219 -0
- package/src/lib/sng-table-core/features/column-sizing.ts +268 -0
- package/src/lib/sng-table-core/features/column-visibility.ts +128 -0
- package/src/lib/sng-table-core/features/faceting.ts +279 -0
- package/src/lib/sng-table-core/features/fuzzy-filtering.ts +188 -0
- package/src/lib/sng-table-core/features/global-filtering.ts +128 -0
- package/src/lib/sng-table-core/features/pagination.ts +179 -0
- package/src/lib/sng-table-core/features/row-expanding.ts +181 -0
- package/src/lib/sng-table-core/features/row-grouping.ts +235 -0
- package/src/lib/sng-table-core/features/row-pinning.ts +196 -0
- package/src/lib/sng-table-core/features/row-selection.ts +298 -0
- package/src/lib/sng-table-core/features/sorting.ts +425 -0
- package/src/lib/sng-table-core/features/virtualization.ts +298 -0
- package/src/lib/sng-table-core/index.ts +235 -0
- package/src/lib/sng-table-core/row-models/core-row-model.ts +256 -0
- package/src/lib/sng-table-core/row-models/expanded-row-model.ts +175 -0
- package/src/lib/sng-table-core/row-models/filtered-row-model.ts +307 -0
- package/src/lib/sng-table-core/row-models/grouped-row-model.ts +290 -0
- package/src/lib/sng-table-core/row-models/paginated-row-model.ts +135 -0
- package/src/lib/sng-table-core/row-models/sorted-row-model.ts +197 -0
- package/src/lib/styles/sng-themes.css +164 -0
- package/src/lib/switch/cn.ts +6 -0
- package/src/lib/switch/index.ts +1 -0
- package/src/lib/switch/sng-switch.ts +137 -0
- package/src/lib/tabs/cn.ts +6 -0
- package/src/lib/tabs/index.ts +4 -0
- package/src/lib/tabs/sng-tabs-content.ts +66 -0
- package/src/lib/tabs/sng-tabs-list.ts +55 -0
- package/src/lib/tabs/sng-tabs-trigger.ts +86 -0
- package/src/lib/tabs/sng-tabs.ts +83 -0
- package/src/lib/toast/cn.ts +6 -0
- package/src/lib/toast/index.ts +3 -0
- package/src/lib/toast/sng-toast.service.ts +258 -0
- package/src/lib/toast/sng-toast.ts +101 -0
- package/src/lib/toast/sng-toaster.ts +67 -0
- package/src/lib/toggle/cn.ts +6 -0
- package/src/lib/toggle/index.ts +6 -0
- package/src/lib/toggle/sng-toggle-group-item.ts +89 -0
- package/src/lib/toggle/sng-toggle-group.ts +85 -0
- package/src/lib/toggle/sng-toggle.ts +78 -0
- package/src/lib/toggle-group/index.ts +6 -0
- package/src/lib/tooltip/cn.ts +6 -0
- package/src/lib/tooltip/index.ts +5 -0
- package/src/lib/tooltip/sng-tooltip-content.ts +64 -0
- package/src/lib/tooltip/sng-tooltip.ts +216 -0
- package/src/public-api.ts +207 -0
- package/tsconfig.json +24 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.lib.prod.json +11 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
input,
|
|
4
|
+
computed,
|
|
5
|
+
ChangeDetectionStrategy,
|
|
6
|
+
ViewEncapsulation,
|
|
7
|
+
} from '@angular/core';
|
|
8
|
+
import { cn } from './cn';
|
|
9
|
+
|
|
10
|
+
/** Heading level for dialog title. */
|
|
11
|
+
export type SngDialogTitleLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Title component for dialog content.
|
|
15
|
+
* Renders a heading element to provide consistent dialog title styling.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```html
|
|
19
|
+
* <sng-dialog-title>Edit Profile</sng-dialog-title>
|
|
20
|
+
* <sng-dialog-title level="h3">Settings</sng-dialog-title>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
@Component({
|
|
24
|
+
selector: 'sng-dialog-title',
|
|
25
|
+
standalone: true,
|
|
26
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
27
|
+
encapsulation: ViewEncapsulation.None,
|
|
28
|
+
host: {
|
|
29
|
+
'class': 'contents',
|
|
30
|
+
},
|
|
31
|
+
template: `
|
|
32
|
+
@switch (level()) {
|
|
33
|
+
@case ('h1') { <h1 [class]="titleClasses()"><ng-content /></h1> }
|
|
34
|
+
@case ('h3') { <h3 [class]="titleClasses()"><ng-content /></h3> }
|
|
35
|
+
@case ('h4') { <h4 [class]="titleClasses()"><ng-content /></h4> }
|
|
36
|
+
@case ('h5') { <h5 [class]="titleClasses()"><ng-content /></h5> }
|
|
37
|
+
@case ('h6') { <h6 [class]="titleClasses()"><ng-content /></h6> }
|
|
38
|
+
@default { <h2 [class]="titleClasses()"><ng-content /></h2> }
|
|
39
|
+
}
|
|
40
|
+
`,
|
|
41
|
+
})
|
|
42
|
+
export class SngDialogTitle {
|
|
43
|
+
/** Heading level. */
|
|
44
|
+
level = input<SngDialogTitleLevel>('h2');
|
|
45
|
+
|
|
46
|
+
/** Custom CSS classes. */
|
|
47
|
+
class = input<string>('');
|
|
48
|
+
|
|
49
|
+
titleClasses = computed(() =>
|
|
50
|
+
cn('text-lg leading-none font-semibold', this.class())
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Injectable,
|
|
3
|
+
Injector,
|
|
4
|
+
InjectionToken,
|
|
5
|
+
Type,
|
|
6
|
+
inject,
|
|
7
|
+
signal,
|
|
8
|
+
} from '@angular/core';
|
|
9
|
+
import { Overlay, OverlayRef, OverlayConfig } from '@angular/cdk/overlay';
|
|
10
|
+
import { ComponentPortal } from '@angular/cdk/portal';
|
|
11
|
+
import { Subject } from 'rxjs';
|
|
12
|
+
import { SNG_DIALOG_CLOSE, SNG_DIALOG_INSTANCE } from './sng-dialog';
|
|
13
|
+
|
|
14
|
+
/** Injection token for passing data to dialog components */
|
|
15
|
+
export const SNG_DIALOG_DATA = new InjectionToken<unknown>('SNG_DIALOG_DATA');
|
|
16
|
+
|
|
17
|
+
/** Configuration options for opening a dialog */
|
|
18
|
+
export interface SngDialogConfig<D = unknown> {
|
|
19
|
+
/** Data to pass to the dialog component (inject with SNG_DIALOG_DATA) */
|
|
20
|
+
data?: D;
|
|
21
|
+
/** Width of the dialog (e.g., '400px', '80vw') */
|
|
22
|
+
width?: string;
|
|
23
|
+
/** Max width of the dialog */
|
|
24
|
+
maxWidth?: string;
|
|
25
|
+
/** Height of the dialog */
|
|
26
|
+
height?: string;
|
|
27
|
+
/** Max height of the dialog */
|
|
28
|
+
maxHeight?: string;
|
|
29
|
+
/** Whether clicking the backdrop closes the dialog (default: true) */
|
|
30
|
+
disableClose?: boolean;
|
|
31
|
+
/** Custom panel class for styling */
|
|
32
|
+
panelClass?: string | string[];
|
|
33
|
+
/** Whether the dialog has a backdrop (default: true) */
|
|
34
|
+
hasBackdrop?: boolean;
|
|
35
|
+
/** Custom backdrop class */
|
|
36
|
+
backdropClass?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Reference to an opened dialog */
|
|
40
|
+
export class SngDialogRef<T = unknown, R = unknown> {
|
|
41
|
+
private readonly _afterClosed = new Subject<R | undefined>();
|
|
42
|
+
private _result: R | undefined;
|
|
43
|
+
private _closing = false;
|
|
44
|
+
|
|
45
|
+
/** @internal Signal for SngDialogContent to track open state */
|
|
46
|
+
_isOpenSignal: ReturnType<typeof signal<boolean>> | null = null;
|
|
47
|
+
|
|
48
|
+
/** Observable that emits when the dialog is closed */
|
|
49
|
+
readonly afterClosed$ = this._afterClosed.asObservable();
|
|
50
|
+
|
|
51
|
+
constructor(
|
|
52
|
+
private overlayRef: OverlayRef,
|
|
53
|
+
/** The component instance inside the dialog */
|
|
54
|
+
public readonly componentInstance: T
|
|
55
|
+
) { }
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Closes the dialog with a CSS exit animation, then disposes the overlay.
|
|
59
|
+
*
|
|
60
|
+
* **Animation lifecycle only** — triggers the exit animation by setting
|
|
61
|
+
* `data-state="closed"` on overlay elements and waits for all CSS animations
|
|
62
|
+
* to finish (via Web Animations API) before removing the overlay.
|
|
63
|
+
*
|
|
64
|
+
* Do not add business logic here. Instead, use `afterClosed$` to react
|
|
65
|
+
* after the dialog is fully closed:
|
|
66
|
+
* ```typescript
|
|
67
|
+
* dialogRef.afterClosed$.subscribe(result => {
|
|
68
|
+
* if (result) this.saveData(result);
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @param result Optional value to pass to `afterClosed$` subscribers.
|
|
73
|
+
*/
|
|
74
|
+
close(result?: R): void {
|
|
75
|
+
if (this._closing) return;
|
|
76
|
+
this._closing = true;
|
|
77
|
+
this._result = result;
|
|
78
|
+
|
|
79
|
+
// Signal SngDialogContent that we're closing
|
|
80
|
+
this._isOpenSignal?.set(false);
|
|
81
|
+
|
|
82
|
+
// Set data-state="closed" directly on DOM to trigger CSS exit animations
|
|
83
|
+
const panel = this.overlayRef.overlayElement;
|
|
84
|
+
panel.querySelectorAll('[data-state]').forEach(el =>
|
|
85
|
+
el.setAttribute('data-state', 'closed')
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const doDispose = () => {
|
|
89
|
+
this.overlayRef.dispose();
|
|
90
|
+
this._afterClosed.next(result);
|
|
91
|
+
this._afterClosed.complete();
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// getAnimations() flushes styles, so newly triggered animations are returned.
|
|
95
|
+
const animations = panel.getAnimations({ subtree: true });
|
|
96
|
+
if (animations.length > 0) {
|
|
97
|
+
Promise.allSettled(animations.map(animation => animation.finished)).finally(doDispose);
|
|
98
|
+
} else {
|
|
99
|
+
doDispose();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Get the result (available after close) */
|
|
104
|
+
get result(): R | undefined {
|
|
105
|
+
return this._result;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Injectable({ providedIn: 'root' })
|
|
110
|
+
export class SngDialogService {
|
|
111
|
+
private overlay = inject(Overlay);
|
|
112
|
+
private injector = inject(Injector);
|
|
113
|
+
|
|
114
|
+
/** Currently open dialogs (any type to allow heterogeneous dialog types) */
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
116
|
+
private openDialogs: SngDialogRef<any, any>[] = [];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Open a dialog with the given component
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const dialogRef = this.dialogService.open(MyDialogComponent, {
|
|
124
|
+
* data: { name: 'World' },
|
|
125
|
+
* width: '400px'
|
|
126
|
+
* });
|
|
127
|
+
*
|
|
128
|
+
* dialogRef.afterClosed$.subscribe(result => {
|
|
129
|
+
* console.log('Dialog closed with:', result);
|
|
130
|
+
* });
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
open<T, D = unknown, R = unknown>(
|
|
134
|
+
component: Type<T>,
|
|
135
|
+
config: SngDialogConfig<D> = {}
|
|
136
|
+
): SngDialogRef<T, R> {
|
|
137
|
+
const overlayRef = this.createOverlay(config);
|
|
138
|
+
const dialogRef = new SngDialogRef<T, R>(overlayRef, null as unknown as T);
|
|
139
|
+
|
|
140
|
+
// Create adapter for SNG_DIALOG_INSTANCE (used by SngDialogContent)
|
|
141
|
+
// Start as false so the closed→open transition triggers sng-dialog-enter animation
|
|
142
|
+
const isOpenSignal = signal(false);
|
|
143
|
+
dialogRef._isOpenSignal = isOpenSignal;
|
|
144
|
+
const dialogInstanceAdapter = {
|
|
145
|
+
isOpen: isOpenSignal,
|
|
146
|
+
close: () => dialogRef.close(),
|
|
147
|
+
alert: () => false,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Create injector with dialog-specific providers
|
|
151
|
+
const dialogInjector = Injector.create({
|
|
152
|
+
providers: [
|
|
153
|
+
{ provide: SNG_DIALOG_DATA, useValue: config.data },
|
|
154
|
+
{ provide: SNG_DIALOG_CLOSE, useValue: () => dialogRef.close() },
|
|
155
|
+
{ provide: SngDialogRef, useValue: dialogRef },
|
|
156
|
+
{ provide: SNG_DIALOG_INSTANCE, useValue: dialogInstanceAdapter },
|
|
157
|
+
],
|
|
158
|
+
parent: this.injector,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Create and attach the component
|
|
162
|
+
const portal = new ComponentPortal(component, null, dialogInjector);
|
|
163
|
+
const componentRef = overlayRef.attach(portal);
|
|
164
|
+
|
|
165
|
+
// Set open after attach so SngDialogContent transitions from closed→open
|
|
166
|
+
isOpenSignal.set(true);
|
|
167
|
+
|
|
168
|
+
// Update dialogRef with actual component instance
|
|
169
|
+
(dialogRef as SngDialogRef<T, R> & { componentInstance: T }).componentInstance = componentRef.instance;
|
|
170
|
+
|
|
171
|
+
// Handle backdrop click
|
|
172
|
+
if (!config.disableClose) {
|
|
173
|
+
overlayRef.backdropClick().subscribe(() => dialogRef.close());
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Track open dialogs
|
|
177
|
+
this.openDialogs.push(dialogRef);
|
|
178
|
+
dialogRef.afterClosed$.subscribe(() => {
|
|
179
|
+
const index = this.openDialogs.indexOf(dialogRef);
|
|
180
|
+
if (index > -1) {
|
|
181
|
+
this.openDialogs.splice(index, 1);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return dialogRef;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Close all open dialogs */
|
|
189
|
+
closeAll(): void {
|
|
190
|
+
// Make a copy because close() modifies the openDialogs array
|
|
191
|
+
[...this.openDialogs].forEach((dialogRef) => dialogRef.close());
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private createOverlay(config: SngDialogConfig): OverlayRef {
|
|
195
|
+
const overlayConfig = new OverlayConfig({
|
|
196
|
+
hasBackdrop: false, // SngDialogContent handles its own backdrop overlay
|
|
197
|
+
panelClass: this.getPanelClasses(config),
|
|
198
|
+
width: config.width,
|
|
199
|
+
maxWidth: config.maxWidth || '90vw',
|
|
200
|
+
height: config.height,
|
|
201
|
+
maxHeight: config.maxHeight || '90vh',
|
|
202
|
+
positionStrategy: this.overlay
|
|
203
|
+
.position()
|
|
204
|
+
.global(),
|
|
205
|
+
scrollStrategy: this.overlay.scrollStrategies.block(),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return this.overlay.create(overlayConfig);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private getPanelClasses(config: SngDialogConfig): string[] {
|
|
212
|
+
const classes = ['sng-dialog-panel'];
|
|
213
|
+
if (config.panelClass) {
|
|
214
|
+
if (Array.isArray(config.panelClass)) {
|
|
215
|
+
classes.push(...config.panelClass);
|
|
216
|
+
} else {
|
|
217
|
+
classes.push(config.panelClass);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return classes;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
signal,
|
|
4
|
+
input,
|
|
5
|
+
output,
|
|
6
|
+
ChangeDetectionStrategy,
|
|
7
|
+
TemplateRef,
|
|
8
|
+
ViewContainerRef,
|
|
9
|
+
inject,
|
|
10
|
+
Injector,
|
|
11
|
+
InjectionToken,
|
|
12
|
+
OnDestroy,
|
|
13
|
+
booleanAttribute,
|
|
14
|
+
Signal,
|
|
15
|
+
ElementRef,
|
|
16
|
+
} from '@angular/core';
|
|
17
|
+
import { Overlay, OverlayRef, OverlayConfig } from '@angular/cdk/overlay';
|
|
18
|
+
import { TemplatePortal } from '@angular/cdk/portal';
|
|
19
|
+
import { Subscription } from 'rxjs';
|
|
20
|
+
import { DOCUMENT } from '@angular/common';
|
|
21
|
+
|
|
22
|
+
export const SNG_DIALOG_CLOSE = new InjectionToken<() => void>('SNG_DIALOG_CLOSE');
|
|
23
|
+
export const SNG_DIALOG_INSTANCE = new InjectionToken<SngDialog>('SNG_DIALOG_INSTANCE');
|
|
24
|
+
export const SNG_DIALOG_STATE = new InjectionToken<Signal<boolean>>('SNG_DIALOG_STATE');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Modal dialog container using CDK Overlay.
|
|
28
|
+
* Manages dialog lifecycle including open/close animations.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```html
|
|
32
|
+
* <sng-dialog #dialog>
|
|
33
|
+
* <sng-button (click)="dialog.open(dialogContent)">Open Dialog</sng-button>
|
|
34
|
+
* <ng-template #dialogContent>
|
|
35
|
+
* <sng-dialog-content>
|
|
36
|
+
* <sng-dialog-header>
|
|
37
|
+
* <sng-dialog-title>Dialog Title</sng-dialog-title>
|
|
38
|
+
* <sng-dialog-description>Dialog description text.</sng-dialog-description>
|
|
39
|
+
* </sng-dialog-header>
|
|
40
|
+
* <sng-dialog-footer>
|
|
41
|
+
* <sng-button class="border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground" (click)="dialog.close()">Cancel</sng-button>
|
|
42
|
+
* <sng-button (click)="dialog.close()">Confirm</sng-button>
|
|
43
|
+
* </sng-dialog-footer>
|
|
44
|
+
* </sng-dialog-content>
|
|
45
|
+
* </ng-template>
|
|
46
|
+
* </sng-dialog>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
@Component({
|
|
50
|
+
selector: 'sng-dialog',
|
|
51
|
+
standalone: true,
|
|
52
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
53
|
+
host: {
|
|
54
|
+
'class': 'contents',
|
|
55
|
+
},
|
|
56
|
+
template: `<ng-content />`,
|
|
57
|
+
})
|
|
58
|
+
export class SngDialog implements OnDestroy {
|
|
59
|
+
private overlay = inject(Overlay);
|
|
60
|
+
private viewContainerRef = inject(ViewContainerRef);
|
|
61
|
+
private hostElementRef = inject(ElementRef<HTMLElement>);
|
|
62
|
+
private document = inject(DOCUMENT);
|
|
63
|
+
private overlayRef: OverlayRef | null = null;
|
|
64
|
+
private subscriptions = new Subscription();
|
|
65
|
+
private _closing = false;
|
|
66
|
+
/** Element that had focus before dialog opened (for focus restoration in alert mode) */
|
|
67
|
+
private triggerElement: HTMLElement | null = null;
|
|
68
|
+
/** Active signal driving the current dialog instance (for mimicking service behavior) */
|
|
69
|
+
private _activeOpenSignal: ReturnType<typeof signal<boolean>> | null = null;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* When true, behaves like an alert dialog:
|
|
73
|
+
* - Uses role="alertdialog" instead of role="dialog"
|
|
74
|
+
* - Does NOT close on backdrop click (requires explicit action)
|
|
75
|
+
* - Restores focus to trigger element on close
|
|
76
|
+
*/
|
|
77
|
+
alert = input(false, { transform: booleanAttribute });
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Emits when open state changes.
|
|
81
|
+
*/
|
|
82
|
+
openChange = output<boolean>();
|
|
83
|
+
|
|
84
|
+
/** Whether the dialog is currently open. */
|
|
85
|
+
isOpen = signal(false);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Opens the dialog with the given template content.
|
|
89
|
+
* Creates a CDK Overlay, attaches the template, and triggers the CSS enter animation.
|
|
90
|
+
*
|
|
91
|
+
* This method is part of the **animation lifecycle** — it handles overlay creation
|
|
92
|
+
* and the open transition. Do not place business logic here.
|
|
93
|
+
* Use `openChange` output or template event handlers for user actions.
|
|
94
|
+
*
|
|
95
|
+
* @param template The template to render inside the overlay.
|
|
96
|
+
* @param triggerElement Optional element to restore focus to on close (alert mode).
|
|
97
|
+
*/
|
|
98
|
+
open(template: TemplateRef<unknown>, triggerElement?: HTMLElement) {
|
|
99
|
+
if (this.overlayRef || this._closing) return;
|
|
100
|
+
|
|
101
|
+
// Store trigger for focus restoration in alert mode
|
|
102
|
+
if (this.alert()) {
|
|
103
|
+
this.triggerElement = this.resolveTriggerElement(triggerElement);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Create a fresh signal for this specific dialog instance to ensure
|
|
107
|
+
// pristine state for the animation (mimicking SngDialogService behavior)
|
|
108
|
+
const localIsOpen = signal(false);
|
|
109
|
+
this._activeOpenSignal = localIsOpen;
|
|
110
|
+
|
|
111
|
+
const dialogAdapter = {
|
|
112
|
+
isOpen: localIsOpen,
|
|
113
|
+
close: () => this.close(),
|
|
114
|
+
alert: this.alert,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const config = new OverlayConfig({
|
|
118
|
+
hasBackdrop: false, // We handle overlay in sng-dialog-content
|
|
119
|
+
panelClass: this.alert() ? 'sng-alert-dialog-panel' : 'sng-dialog-panel',
|
|
120
|
+
positionStrategy: this.overlay.position()
|
|
121
|
+
.global(),
|
|
122
|
+
scrollStrategy: this.overlay.scrollStrategies.block(),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
this.overlayRef = this.overlay.create(config);
|
|
126
|
+
const injector = Injector.create({
|
|
127
|
+
providers: [
|
|
128
|
+
{ provide: SNG_DIALOG_CLOSE, useValue: () => this.close() },
|
|
129
|
+
{ provide: SNG_DIALOG_INSTANCE, useValue: dialogAdapter },
|
|
130
|
+
// Use local signal for state token as well
|
|
131
|
+
{ provide: SNG_DIALOG_STATE, useValue: localIsOpen },
|
|
132
|
+
],
|
|
133
|
+
parent: this.viewContainerRef.injector,
|
|
134
|
+
});
|
|
135
|
+
const portal = new TemplatePortal(template, this.viewContainerRef, null, injector);
|
|
136
|
+
this.overlayRef.attach(portal);
|
|
137
|
+
|
|
138
|
+
// Sync local state with public state
|
|
139
|
+
localIsOpen.set(true);
|
|
140
|
+
this.isOpen.set(true);
|
|
141
|
+
this.openChange.emit(true);
|
|
142
|
+
|
|
143
|
+
// Overlay click is handled by sng-dialog-content's overlay div
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Closes the dialog with a CSS exit animation, then disposes the overlay.
|
|
148
|
+
*
|
|
149
|
+
* **Animation lifecycle only** — this method triggers the exit animation by
|
|
150
|
+
* setting `data-state="closed"` on overlay elements and waits for all CSS
|
|
151
|
+
* animations to finish (via Web Animations API) before removing the overlay.
|
|
152
|
+
*
|
|
153
|
+
* Do not add business logic (API calls, navigation, data saving) here.
|
|
154
|
+
* Instead, use the `openChange` output or template event handlers:
|
|
155
|
+
* ```html
|
|
156
|
+
* <sng-button (click)="onSave(); dialog.close()">Save</sng-button>
|
|
157
|
+
* ```
|
|
158
|
+
* Or listen to `openChange`:
|
|
159
|
+
* ```html
|
|
160
|
+
* <sng-dialog (openChange)="onDialogStateChange($event)">
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
close() {
|
|
164
|
+
if (!this.overlayRef || this._closing) return;
|
|
165
|
+
this._closing = true;
|
|
166
|
+
|
|
167
|
+
this.isOpen.set(false);
|
|
168
|
+
this._activeOpenSignal?.set(false);
|
|
169
|
+
this.openChange.emit(false);
|
|
170
|
+
|
|
171
|
+
// Set data-state="closed" directly on DOM to trigger CSS exit animations
|
|
172
|
+
// immediately (Angular's signal binding will also set this on next CD — same value).
|
|
173
|
+
const panel = this.overlayRef.overlayElement;
|
|
174
|
+
panel.querySelectorAll('[data-state]').forEach(el =>
|
|
175
|
+
el.setAttribute('data-state', 'closed')
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// getAnimations() flushes styles, so newly triggered animations are returned.
|
|
179
|
+
const animations = panel.getAnimations({ subtree: true });
|
|
180
|
+
if (animations.length > 0) {
|
|
181
|
+
Promise.allSettled(animations.map(animation => animation.finished)).finally(() => this.dispose());
|
|
182
|
+
} else {
|
|
183
|
+
this.dispose();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** @internal Immediately removes the overlay from DOM. Called after exit animation completes. */
|
|
188
|
+
private dispose() {
|
|
189
|
+
this._closing = false;
|
|
190
|
+
this.subscriptions.unsubscribe();
|
|
191
|
+
this.subscriptions = new Subscription();
|
|
192
|
+
if (this.overlayRef) {
|
|
193
|
+
this.overlayRef.dispose();
|
|
194
|
+
this.overlayRef = null;
|
|
195
|
+
}
|
|
196
|
+
this.isOpen.set(false);
|
|
197
|
+
|
|
198
|
+
// Restore focus to trigger element in alert mode
|
|
199
|
+
if (this.alert() && this.triggerElement) {
|
|
200
|
+
this.triggerElement.focus();
|
|
201
|
+
this.triggerElement = null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Resolves a reliable trigger element for alert-mode focus restoration. */
|
|
206
|
+
private resolveTriggerElement(explicitTrigger?: HTMLElement): HTMLElement | null {
|
|
207
|
+
if (explicitTrigger) return explicitTrigger;
|
|
208
|
+
|
|
209
|
+
const activeElement = this.document.activeElement;
|
|
210
|
+
if (activeElement instanceof HTMLElement && activeElement !== this.document.body) {
|
|
211
|
+
return activeElement;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const host = this.hostElementRef.nativeElement;
|
|
215
|
+
const fallbackTrigger = host.querySelector(
|
|
216
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
217
|
+
) as HTMLElement | null;
|
|
218
|
+
return fallbackTrigger ?? null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
ngOnDestroy() {
|
|
222
|
+
this.dispose();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export * from './sng-drawer';
|
|
2
|
+
export * from './sng-drawer-trigger';
|
|
3
|
+
export * from './sng-drawer-content';
|
|
4
|
+
export * from './sng-drawer-header';
|
|
5
|
+
export * from './sng-drawer-footer';
|
|
6
|
+
export * from './sng-drawer-title';
|
|
7
|
+
export * from './sng-drawer-description';
|
|
8
|
+
export * from './sng-drawer-close';
|
|
9
|
+
export * from './sng-drawer-handle';
|
|
10
|
+
export * from './sng-drawer-wrapper';
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Sheet Backwards Compatibility Aliases (deprecated)
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Note: These are TypeScript-level aliases only. Template selectors have changed:
|
|
16
|
+
// - Old: <sng-sheet>, <sng-sheet-content>, etc.
|
|
17
|
+
// - New: <sng-drawer [modal]="true">, <sng-drawer-content>, etc.
|
|
18
|
+
|
|
19
|
+
/** @deprecated Use SngDrawer with [modal]="true" instead */
|
|
20
|
+
export { SngDrawer as SngSheet } from './sng-drawer';
|
|
21
|
+
/** @deprecated Use SngDrawerTrigger instead */
|
|
22
|
+
export { SngDrawerTrigger as SngSheetTrigger } from './sng-drawer-trigger';
|
|
23
|
+
/** @deprecated Use SngDrawerContent instead */
|
|
24
|
+
export { SngDrawerContent as SngSheetContent } from './sng-drawer-content';
|
|
25
|
+
/** @deprecated Use SngDrawerHeader instead */
|
|
26
|
+
export { SngDrawerHeader as SngSheetHeader } from './sng-drawer-header';
|
|
27
|
+
/** @deprecated Use SngDrawerTitle instead */
|
|
28
|
+
export { SngDrawerTitle as SngSheetTitle } from './sng-drawer-title';
|
|
29
|
+
/** @deprecated Use SngDrawerDescription instead */
|
|
30
|
+
export { SngDrawerDescription as SngSheetDescription } from './sng-drawer-description';
|
|
31
|
+
/** @deprecated Use SngDrawerFooter instead */
|
|
32
|
+
export { SngDrawerFooter as SngSheetFooter } from './sng-drawer-footer';
|
|
33
|
+
/** @deprecated Use SngDrawerClose instead */
|
|
34
|
+
export { SngDrawerClose as SngSheetClose } from './sng-drawer-close';
|
|
35
|
+
/** @deprecated Use SngDrawerHandle instead */
|
|
36
|
+
export { SngDrawerHandle as SngSheetHandle } from './sng-drawer-handle';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Directive, inject } from '@angular/core';
|
|
2
|
+
import { SNG_DRAWER_CLOSE } from './sng-drawer';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Directive that closes the drawer when clicked.
|
|
6
|
+
* Use as an element wrapper around any clickable content.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```html
|
|
10
|
+
* <sng-drawer-close>
|
|
11
|
+
* <sng-button class="bg-primary text-primary-foreground shadow-xs hover:bg-primary/90">Save</sng-button>
|
|
12
|
+
* </sng-drawer-close>
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
@Directive({
|
|
16
|
+
selector: 'sng-drawer-close',
|
|
17
|
+
standalone: true,
|
|
18
|
+
host: {
|
|
19
|
+
'(click)': 'onClick()',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
export class SngDrawerClose {
|
|
23
|
+
private closeFn = inject(SNG_DRAWER_CLOSE, { optional: true });
|
|
24
|
+
|
|
25
|
+
onClick() {
|
|
26
|
+
this.closeFn?.();
|
|
27
|
+
}
|
|
28
|
+
}
|