@mesgflow/ui-kit 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +200 -0
- package/fesm2022/mesgflow-ui-kit-accordion.mjs +75 -0
- package/fesm2022/mesgflow-ui-kit-accordion.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-alert.mjs +165 -0
- package/fesm2022/mesgflow-ui-kit-alert.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-avatar-group.mjs +88 -0
- package/fesm2022/mesgflow-ui-kit-avatar-group.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-avatar.mjs +123 -0
- package/fesm2022/mesgflow-ui-kit-avatar.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-badge.mjs +68 -0
- package/fesm2022/mesgflow-ui-kit-badge.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-banner.mjs +111 -0
- package/fesm2022/mesgflow-ui-kit-banner.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-box-input.mjs +78 -0
- package/fesm2022/mesgflow-ui-kit-box-input.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-brand.mjs +120 -0
- package/fesm2022/mesgflow-ui-kit-brand.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-breadcrumb.mjs +84 -0
- package/fesm2022/mesgflow-ui-kit-breadcrumb.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-button-group.mjs +37 -0
- package/fesm2022/mesgflow-ui-kit-button-group.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-button.mjs +359 -0
- package/fesm2022/mesgflow-ui-kit-button.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-card.mjs +128 -0
- package/fesm2022/mesgflow-ui-kit-card.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-chart.mjs +538 -0
- package/fesm2022/mesgflow-ui-kit-chart.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-checkbox.mjs +45 -0
- package/fesm2022/mesgflow-ui-kit-checkbox.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-chip.mjs +123 -0
- package/fesm2022/mesgflow-ui-kit-chip.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-code-block.mjs +71 -0
- package/fesm2022/mesgflow-ui-kit-code-block.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-collapsible.mjs +89 -0
- package/fesm2022/mesgflow-ui-kit-collapsible.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-color-picker.mjs +210 -0
- package/fesm2022/mesgflow-ui-kit-color-picker.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-combobox.mjs +181 -0
- package/fesm2022/mesgflow-ui-kit-combobox.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-command.mjs +491 -0
- package/fesm2022/mesgflow-ui-kit-command.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-context-menu.mjs +92 -0
- package/fesm2022/mesgflow-ui-kit-context-menu.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-copy-button.mjs +167 -0
- package/fesm2022/mesgflow-ui-kit-copy-button.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-core.mjs +624 -0
- package/fesm2022/mesgflow-ui-kit-core.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-date-picker.mjs +643 -0
- package/fesm2022/mesgflow-ui-kit-date-picker.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-description-list.mjs +76 -0
- package/fesm2022/mesgflow-ui-kit-description-list.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-dialog.mjs +700 -0
- package/fesm2022/mesgflow-ui-kit-dialog.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-divider.mjs +62 -0
- package/fesm2022/mesgflow-ui-kit-divider.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-dropzone.mjs +126 -0
- package/fesm2022/mesgflow-ui-kit-dropzone.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-empty-state.mjs +70 -0
- package/fesm2022/mesgflow-ui-kit-empty-state.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-field.mjs +42 -0
- package/fesm2022/mesgflow-ui-kit-field.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-hover-card.mjs +171 -0
- package/fesm2022/mesgflow-ui-kit-hover-card.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-i18n-tr.mjs +258 -0
- package/fesm2022/mesgflow-ui-kit-i18n-tr.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-icon.mjs +174 -0
- package/fesm2022/mesgflow-ui-kit-icon.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-input.mjs +448 -0
- package/fesm2022/mesgflow-ui-kit-input.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-kbd.mjs +24 -0
- package/fesm2022/mesgflow-ui-kit-kbd.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-link.mjs +48 -0
- package/fesm2022/mesgflow-ui-kit-link.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-menu.mjs +105 -0
- package/fesm2022/mesgflow-ui-kit-menu.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-number-input.mjs +153 -0
- package/fesm2022/mesgflow-ui-kit-number-input.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-otp-input.mjs +164 -0
- package/fesm2022/mesgflow-ui-kit-otp-input.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-page-header.mjs +83 -0
- package/fesm2022/mesgflow-ui-kit-page-header.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-pagination.mjs +99 -0
- package/fesm2022/mesgflow-ui-kit-pagination.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-password-input.mjs +120 -0
- package/fesm2022/mesgflow-ui-kit-password-input.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-popover.mjs +213 -0
- package/fesm2022/mesgflow-ui-kit-popover.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-progress.mjs +82 -0
- package/fesm2022/mesgflow-ui-kit-progress.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-radio.mjs +123 -0
- package/fesm2022/mesgflow-ui-kit-radio.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-rating.mjs +116 -0
- package/fesm2022/mesgflow-ui-kit-rating.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-scroll-area.mjs +84 -0
- package/fesm2022/mesgflow-ui-kit-scroll-area.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-search-input.mjs +154 -0
- package/fesm2022/mesgflow-ui-kit-search-input.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-segmented-control.mjs +95 -0
- package/fesm2022/mesgflow-ui-kit-segmented-control.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-select.mjs +227 -0
- package/fesm2022/mesgflow-ui-kit-select.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-sidebar.mjs +449 -0
- package/fesm2022/mesgflow-ui-kit-sidebar.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-skeleton.mjs +74 -0
- package/fesm2022/mesgflow-ui-kit-skeleton.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-slider.mjs +179 -0
- package/fesm2022/mesgflow-ui-kit-slider.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-spinner.mjs +67 -0
- package/fesm2022/mesgflow-ui-kit-spinner.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-splitter.mjs +97 -0
- package/fesm2022/mesgflow-ui-kit-splitter.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-stat-tile.mjs +98 -0
- package/fesm2022/mesgflow-ui-kit-stat-tile.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-stepper.mjs +133 -0
- package/fesm2022/mesgflow-ui-kit-stepper.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-switch.mjs +73 -0
- package/fesm2022/mesgflow-ui-kit-switch.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-table.mjs +1783 -0
- package/fesm2022/mesgflow-ui-kit-table.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-tabs.mjs +569 -0
- package/fesm2022/mesgflow-ui-kit-tabs.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-tag-input.mjs +138 -0
- package/fesm2022/mesgflow-ui-kit-tag-input.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-theme-switcher.mjs +53 -0
- package/fesm2022/mesgflow-ui-kit-theme-switcher.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-theming.mjs +1016 -0
- package/fesm2022/mesgflow-ui-kit-theming.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-time-picker.mjs +138 -0
- package/fesm2022/mesgflow-ui-kit-time-picker.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-timeline.mjs +91 -0
- package/fesm2022/mesgflow-ui-kit-timeline.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-toast.mjs +202 -0
- package/fesm2022/mesgflow-ui-kit-toast.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-toggle.mjs +243 -0
- package/fesm2022/mesgflow-ui-kit-toggle.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-toolbar.mjs +99 -0
- package/fesm2022/mesgflow-ui-kit-toolbar.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-tooltip.mjs +160 -0
- package/fesm2022/mesgflow-ui-kit-tooltip.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-tour.mjs +272 -0
- package/fesm2022/mesgflow-ui-kit-tour.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-tree-select.mjs +177 -0
- package/fesm2022/mesgflow-ui-kit-tree-select.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-tree.mjs +82 -0
- package/fesm2022/mesgflow-ui-kit-tree.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit.mjs +12851 -0
- package/fesm2022/mesgflow-ui-kit.mjs.map +1 -0
- package/package.json +374 -0
- package/preset.css +20 -0
- package/tokens/color.css +693 -0
- package/tokens/contrast.css +68 -0
- package/tokens/elevation.css +68 -0
- package/tokens/motion.css +277 -0
- package/tokens/radius.css +69 -0
- package/tokens/typography.css +127 -0
- package/tokens.css +41 -0
- package/types/mesgflow-ui-kit-accordion.d.ts +42 -0
- package/types/mesgflow-ui-kit-alert.d.ts +60 -0
- package/types/mesgflow-ui-kit-avatar-group.d.ts +28 -0
- package/types/mesgflow-ui-kit-avatar.d.ts +23 -0
- package/types/mesgflow-ui-kit-badge.d.ts +39 -0
- package/types/mesgflow-ui-kit-banner.d.ts +42 -0
- package/types/mesgflow-ui-kit-box-input.d.ts +45 -0
- package/types/mesgflow-ui-kit-brand.d.ts +48 -0
- package/types/mesgflow-ui-kit-breadcrumb.d.ts +26 -0
- package/types/mesgflow-ui-kit-button-group.d.ts +17 -0
- package/types/mesgflow-ui-kit-button.d.ts +75 -0
- package/types/mesgflow-ui-kit-card.d.ts +71 -0
- package/types/mesgflow-ui-kit-chart.d.ts +242 -0
- package/types/mesgflow-ui-kit-checkbox.d.ts +17 -0
- package/types/mesgflow-ui-kit-chip.d.ts +46 -0
- package/types/mesgflow-ui-kit-code-block.d.ts +56 -0
- package/types/mesgflow-ui-kit-collapsible.d.ts +39 -0
- package/types/mesgflow-ui-kit-color-picker.d.ts +78 -0
- package/types/mesgflow-ui-kit-combobox.d.ts +74 -0
- package/types/mesgflow-ui-kit-command.d.ts +185 -0
- package/types/mesgflow-ui-kit-context-menu.d.ts +40 -0
- package/types/mesgflow-ui-kit-copy-button.d.ts +47 -0
- package/types/mesgflow-ui-kit-core.d.ts +508 -0
- package/types/mesgflow-ui-kit-date-picker.d.ts +215 -0
- package/types/mesgflow-ui-kit-description-list.d.ts +23 -0
- package/types/mesgflow-ui-kit-dialog.d.ts +342 -0
- package/types/mesgflow-ui-kit-divider.d.ts +23 -0
- package/types/mesgflow-ui-kit-dropzone.d.ts +47 -0
- package/types/mesgflow-ui-kit-empty-state.d.ts +18 -0
- package/types/mesgflow-ui-kit-field.d.ts +26 -0
- package/types/mesgflow-ui-kit-hover-card.d.ts +52 -0
- package/types/mesgflow-ui-kit-i18n-tr.d.ts +14 -0
- package/types/mesgflow-ui-kit-icon.d.ts +76 -0
- package/types/mesgflow-ui-kit-input.d.ts +186 -0
- package/types/mesgflow-ui-kit-kbd.d.ts +9 -0
- package/types/mesgflow-ui-kit-link.d.ts +20 -0
- package/types/mesgflow-ui-kit-menu.d.ts +53 -0
- package/types/mesgflow-ui-kit-number-input.d.ts +55 -0
- package/types/mesgflow-ui-kit-otp-input.d.ts +44 -0
- package/types/mesgflow-ui-kit-page-header.d.ts +31 -0
- package/types/mesgflow-ui-kit-pagination.d.ts +50 -0
- package/types/mesgflow-ui-kit-password-input.d.ts +33 -0
- package/types/mesgflow-ui-kit-popover.d.ts +62 -0
- package/types/mesgflow-ui-kit-progress.d.ts +32 -0
- package/types/mesgflow-ui-kit-radio.d.ts +59 -0
- package/types/mesgflow-ui-kit-rating.d.ts +56 -0
- package/types/mesgflow-ui-kit-scroll-area.d.ts +27 -0
- package/types/mesgflow-ui-kit-search-input.d.ts +72 -0
- package/types/mesgflow-ui-kit-segmented-control.d.ts +54 -0
- package/types/mesgflow-ui-kit-select.d.ts +89 -0
- package/types/mesgflow-ui-kit-sidebar.d.ts +224 -0
- package/types/mesgflow-ui-kit-skeleton.d.ts +29 -0
- package/types/mesgflow-ui-kit-slider.d.ts +40 -0
- package/types/mesgflow-ui-kit-spinner.d.ts +31 -0
- package/types/mesgflow-ui-kit-splitter.d.ts +40 -0
- package/types/mesgflow-ui-kit-stat-tile.d.ts +54 -0
- package/types/mesgflow-ui-kit-stepper.d.ts +48 -0
- package/types/mesgflow-ui-kit-switch.d.ts +34 -0
- package/types/mesgflow-ui-kit-table.d.ts +805 -0
- package/types/mesgflow-ui-kit-tabs.d.ts +269 -0
- package/types/mesgflow-ui-kit-tag-input.d.ts +33 -0
- package/types/mesgflow-ui-kit-theme-switcher.d.ts +27 -0
- package/types/mesgflow-ui-kit-theming.d.ts +745 -0
- package/types/mesgflow-ui-kit-time-picker.d.ts +54 -0
- package/types/mesgflow-ui-kit-timeline.d.ts +27 -0
- package/types/mesgflow-ui-kit-toast.d.ts +94 -0
- package/types/mesgflow-ui-kit-toggle.d.ts +110 -0
- package/types/mesgflow-ui-kit-toolbar.d.ts +52 -0
- package/types/mesgflow-ui-kit-tooltip.d.ts +55 -0
- package/types/mesgflow-ui-kit-tour.d.ts +66 -0
- package/types/mesgflow-ui-kit-tree-select.d.ts +65 -0
- package/types/mesgflow-ui-kit-tree.d.ts +44 -0
- package/types/mesgflow-ui-kit.d.ts +5444 -0
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
import { DIALOG_DATA, DialogRef, Dialog as Dialog$1 } from '@angular/cdk/dialog';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { signal, inject, ElementRef, input, computed, Directive, output, viewChild, effect, DestroyRef, afterNextRender, Component, Injector, Injectable } from '@angular/core';
|
|
4
|
+
import { Button } from '@mesgflow/ui-kit/button';
|
|
5
|
+
import { glassSurface, MF_DIALOG_LABELS, cn } from '@mesgflow/ui-kit/core';
|
|
6
|
+
import { Icon } from '@mesgflow/ui-kit/icon';
|
|
7
|
+
import * as i1 from '@angular/cdk/drag-drop';
|
|
8
|
+
import { CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
|
|
9
|
+
import { cva } from 'class-variance-authority';
|
|
10
|
+
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
11
|
+
import { createGlobalPositionStrategy } from '@angular/cdk/overlay';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Dialog başına TEK instance — `DialogService.open()` her açılışta yeni bir
|
|
15
|
+
* tane üretip `options`'tan doldurur, CDK `DialogConfig.providers` ile o
|
|
16
|
+
* dialog'un (içerik component'inin) injector'ına verir. `Dialog` (dialog.ts)
|
|
17
|
+
* kendi `@Component.providers`'ında `skipSelf` ile bunu arar — bulamazsa
|
|
18
|
+
* (ör. overlay olmadan tek başına render edilen statik bir önizleme/demo)
|
|
19
|
+
* kendi yerel instance'ını üretir, hiçbir şey patlamaz.
|
|
20
|
+
*
|
|
21
|
+
* Boyut/maximize/drag/resize/sheet durumunun TAMAMI burada yaşıyor — `Dialog`
|
|
22
|
+
* kendi `@Input()`'larıyla DEĞİL, bu sınıfı okuyarak render ediyor. Böylece
|
|
23
|
+
* `DialogService.open(SomeForm, { size:'lg', maximizable:true })` gibi bir
|
|
24
|
+
* çağrı, `SomeForm`'un template'inde 1+ seviye iç içe olan `<mf-dialog>`'a
|
|
25
|
+
* DI üzerinden ulaşabiliyor (`@Input()` ile mümkün olmazdı — DialogService
|
|
26
|
+
* `Dialog`'u değil, `SomeForm`'u örnekliyor).
|
|
27
|
+
*/
|
|
28
|
+
class DialogSurface {
|
|
29
|
+
size = signal('md', /* @ts-ignore */
|
|
30
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
31
|
+
placement = signal('center', /* @ts-ignore */
|
|
32
|
+
...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
33
|
+
maximizable = signal(false, /* @ts-ignore */
|
|
34
|
+
...(ngDevMode ? [{ debugName: "maximizable" }] : /* istanbul ignore next */ []));
|
|
35
|
+
draggable = signal(false, /* @ts-ignore */
|
|
36
|
+
...(ngDevMode ? [{ debugName: "draggable" }] : /* istanbul ignore next */ []));
|
|
37
|
+
resizable = signal(false, /* @ts-ignore */
|
|
38
|
+
...(ngDevMode ? [{ debugName: "resizable" }] : /* istanbul ignore next */ []));
|
|
39
|
+
maximized = signal(false, /* @ts-ignore */
|
|
40
|
+
...(ngDevMode ? [{ debugName: "maximized" }] : /* istanbul ignore next */ []));
|
|
41
|
+
/** Kullanıcı kenardan boyutlandırdıysa dolu; maximize/sheet'te yok sayılır. */
|
|
42
|
+
customSize = signal(null, /* @ts-ignore */
|
|
43
|
+
...(ngDevMode ? [{ debugName: "customSize" }] : /* istanbul ignore next */ []));
|
|
44
|
+
/** < 640px — `DialogService` `BreakpointObserver` ile besler. */
|
|
45
|
+
sheet = signal(false, /* @ts-ignore */
|
|
46
|
+
...(ngDevMode ? [{ debugName: "sheet" }] : /* istanbul ignore next */ []));
|
|
47
|
+
/** Kapanış animasyonu oynarken `true` — bkz. `DialogService.open()` `closePredicate`. */
|
|
48
|
+
closing = signal(false, /* @ts-ignore */
|
|
49
|
+
...(ngDevMode ? [{ debugName: "closing" }] : /* istanbul ignore next */ []));
|
|
50
|
+
toggleMaximize() {
|
|
51
|
+
this.maximized.update((value) => !value);
|
|
52
|
+
this.customSize.set(null);
|
|
53
|
+
}
|
|
54
|
+
dirtyCheck = null;
|
|
55
|
+
/** Başarılı kayıt/programatik kapanışta `true` yapılır — bir sonraki kapanışta kirli-form uyarısı SORULMAZ. */
|
|
56
|
+
bypassDirtyGuard = signal(false, /* @ts-ignore */
|
|
57
|
+
...(ngDevMode ? [{ debugName: "bypassDirtyGuard" }] : /* istanbul ignore next */ []));
|
|
58
|
+
/** İçerik component'i (tüketicinin kendi form component'i) kendi "kirli mi" sorusunu buraya kaydeder — `DialogSurface` formun kendi şemasını bilmez. */
|
|
59
|
+
registerDirty(check) {
|
|
60
|
+
this.dirtyCheck = check;
|
|
61
|
+
}
|
|
62
|
+
/** `DialogService.open()`'ın `closePredicate`'i ESC/backdrop/X kapanışında bunu okur. */
|
|
63
|
+
isDirty() {
|
|
64
|
+
return !this.bypassDirtyGuard() && !!this.dirtyCheck?.();
|
|
65
|
+
}
|
|
66
|
+
/** Başarılı kayıttan hemen önce çağrılır — programatik `dialogRef.close()` kirli-form uyarısı sormasın diye. */
|
|
67
|
+
markClean() {
|
|
68
|
+
this.bypassDirtyGuard.set(true);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const MIN_WIDTH = 320;
|
|
73
|
+
const MIN_HEIGHT = 240;
|
|
74
|
+
const VIEWPORT_MARGIN = 32;
|
|
75
|
+
const ARROW_STEP = 16;
|
|
76
|
+
const ARROW_STEP_LARGE = 64;
|
|
77
|
+
/**
|
|
78
|
+
* `<button mfDialogResizeHandle [ariaLabel]="...">` — `Dialog`'un (dialog.ts)
|
|
79
|
+
* sağ-alt köşesinde, `DialogSurface.customSize`'ı besleyen serbest boyutlandırma
|
|
80
|
+
* tutamacı. Fare/dokunma İLE klavye (ok tuşları) İKİSİ de destekleniyor —
|
|
81
|
+
* projects/ui-kit/README.md "a11y checklist" bölümü "sadece fareyle çalışan bir
|
|
82
|
+
* tutamaç"ı kabul etmiyor.
|
|
83
|
+
*/
|
|
84
|
+
class DialogResizeHandle {
|
|
85
|
+
surface = inject(DialogSurface);
|
|
86
|
+
host = inject(ElementRef);
|
|
87
|
+
ariaLabel = input.required(/* @ts-ignore */
|
|
88
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
89
|
+
/** `role="slider"` `aria-valuenow` zorunlu kılıyor (axe: `aria-required-attr`) — genişliği raporlar, özel boyut yoksa panelin ölçülen genişliğine, o da yoksa `MIN_WIDTH`'e düşer. */
|
|
90
|
+
ariaValueNow = computed(() => this.surface.customSize()?.width ?? this.panelRect()?.width ?? MIN_WIDTH, /* @ts-ignore */
|
|
91
|
+
...(ngDevMode ? [{ debugName: "ariaValueNow" }] : /* istanbul ignore next */ []));
|
|
92
|
+
pointerId = null;
|
|
93
|
+
startX = 0;
|
|
94
|
+
startY = 0;
|
|
95
|
+
startWidth = 0;
|
|
96
|
+
startHeight = 0;
|
|
97
|
+
onPointerDown(event) {
|
|
98
|
+
if (event.button !== 0)
|
|
99
|
+
return;
|
|
100
|
+
const rect = this.panelRect();
|
|
101
|
+
if (!rect)
|
|
102
|
+
return;
|
|
103
|
+
this.pointerId = event.pointerId;
|
|
104
|
+
this.startX = event.clientX;
|
|
105
|
+
this.startY = event.clientY;
|
|
106
|
+
this.startWidth = this.surface.customSize()?.width ?? rect.width;
|
|
107
|
+
this.startHeight = this.surface.customSize()?.height ?? rect.height;
|
|
108
|
+
this.host.nativeElement.setPointerCapture(event.pointerId);
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
}
|
|
111
|
+
onPointerMove(event) {
|
|
112
|
+
if (this.pointerId !== event.pointerId)
|
|
113
|
+
return;
|
|
114
|
+
this.applySize(this.startWidth + (event.clientX - this.startX), this.startHeight + (event.clientY - this.startY));
|
|
115
|
+
}
|
|
116
|
+
onPointerUp(event) {
|
|
117
|
+
if (this.pointerId !== event.pointerId)
|
|
118
|
+
return;
|
|
119
|
+
this.pointerId = null;
|
|
120
|
+
}
|
|
121
|
+
onKeydown(event) {
|
|
122
|
+
const rect = this.panelRect();
|
|
123
|
+
const current = this.surface.customSize() ?? {
|
|
124
|
+
width: rect?.width ?? MIN_WIDTH,
|
|
125
|
+
height: rect?.height ?? MIN_HEIGHT,
|
|
126
|
+
};
|
|
127
|
+
const step = event.shiftKey ? ARROW_STEP_LARGE : ARROW_STEP;
|
|
128
|
+
switch (event.key) {
|
|
129
|
+
case 'ArrowRight':
|
|
130
|
+
this.applySize(current.width + step, current.height);
|
|
131
|
+
break;
|
|
132
|
+
case 'ArrowLeft':
|
|
133
|
+
this.applySize(current.width - step, current.height);
|
|
134
|
+
break;
|
|
135
|
+
case 'ArrowDown':
|
|
136
|
+
this.applySize(current.width, current.height + step);
|
|
137
|
+
break;
|
|
138
|
+
case 'ArrowUp':
|
|
139
|
+
this.applySize(current.width, current.height - step);
|
|
140
|
+
break;
|
|
141
|
+
case 'Home':
|
|
142
|
+
this.surface.customSize.set(null);
|
|
143
|
+
break;
|
|
144
|
+
default:
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
event.preventDefault();
|
|
148
|
+
}
|
|
149
|
+
panelRect() {
|
|
150
|
+
return (this.host.nativeElement.closest('.cdk-overlay-pane')?.getBoundingClientRect() ??
|
|
151
|
+
null);
|
|
152
|
+
}
|
|
153
|
+
applySize(width, height) {
|
|
154
|
+
const maxWidth = window.innerWidth - VIEWPORT_MARGIN;
|
|
155
|
+
const maxHeight = window.innerHeight - VIEWPORT_MARGIN;
|
|
156
|
+
this.surface.customSize.set({
|
|
157
|
+
width: Math.min(Math.max(width, MIN_WIDTH), maxWidth),
|
|
158
|
+
height: Math.min(Math.max(height, MIN_HEIGHT), maxHeight),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: DialogResizeHandle, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
162
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: DialogResizeHandle, isStandalone: true, selector: "[mfDialogResizeHandle]", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "slider", "tabindex": "0" }, listeners: { "pointerdown": "onPointerDown($event)", "pointermove": "onPointerMove($event)", "pointerup": "onPointerUp($event)", "pointercancel": "onPointerUp($event)", "keydown": "onKeydown($event)" }, properties: { "attr.aria-label": "ariaLabel()", "attr.aria-valuenow": "ariaValueNow()" } }, ngImport: i0 });
|
|
163
|
+
}
|
|
164
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: DialogResizeHandle, decorators: [{
|
|
165
|
+
type: Directive,
|
|
166
|
+
args: [{
|
|
167
|
+
selector: '[mfDialogResizeHandle]',
|
|
168
|
+
host: {
|
|
169
|
+
role: 'slider',
|
|
170
|
+
tabindex: '0',
|
|
171
|
+
'[attr.aria-label]': 'ariaLabel()',
|
|
172
|
+
'[attr.aria-valuenow]': 'ariaValueNow()',
|
|
173
|
+
'(pointerdown)': 'onPointerDown($event)',
|
|
174
|
+
'(pointermove)': 'onPointerMove($event)',
|
|
175
|
+
'(pointerup)': 'onPointerUp($event)',
|
|
176
|
+
'(pointercancel)': 'onPointerUp($event)',
|
|
177
|
+
'(keydown)': 'onKeydown($event)',
|
|
178
|
+
},
|
|
179
|
+
}]
|
|
180
|
+
}], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: true }] }] } });
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Boyut ölçeği CSS `min()` ile viewport'a göre kendini sınırlıyor —
|
|
184
|
+
* `max-w-[560px]` gibi ayrı bir "hiçbir zaman viewport'tan taşma" class'ıyla
|
|
185
|
+
* `cn()`/tailwind-merge üzerinden birleştirilemezdi (ikisi aynı utility
|
|
186
|
+
* grubu — `max-width` — sonuncusu kazanır, ikisi birden UYGULANMAZ). Tek
|
|
187
|
+
* arbitrary value'da `min(N, 100vw-2rem)` ile ikisi birden garanti ediliyor.
|
|
188
|
+
*
|
|
189
|
+
* `state` (normal/maximized/sheet/drawer) boyut eksenine dik, tek-seçim bir eksen —
|
|
190
|
+
* `Dialog` (dialog.ts) `DialogSurface`'ten okuyup hesaplıyor, tüketici
|
|
191
|
+
* tarafından doğrudan set edilmiyor. `drawer` boyut ölçeğini (sm/md/lg/xl)
|
|
192
|
+
* `normal` ile PAYLAŞIR ama genişliği AYNI şekilde uygulamaz — aşağıdaki
|
|
193
|
+
* `compoundVariants`'a bak.
|
|
194
|
+
*
|
|
195
|
+
* **`drawer` neden `width` de alıyor, `max-width` yetmiyor mu**: sağa yaslı
|
|
196
|
+
* `GlobalPositionStrategy` (`dialog.service.ts`) overlay wrapper'a
|
|
197
|
+
* `align-items:flex-start` veriyor — bu, cross-axis stretch'i KAPATIYOR, yani
|
|
198
|
+
* `.cdk-overlay-pane` içeriğine göre daralıyor (shrink-to-fit). Böyle bir
|
|
199
|
+
* (genişliği belirsiz/auto) kapsayıcı içinde `mf-dialog`'un tabandaki
|
|
200
|
+
* `w-full`'u (`width:100%`) CSS Sizing L3'e göre `width:auto`'ya döner —
|
|
201
|
+
* yani panel `max-w-[min(N,...)]` TAVANINA hiç ulaşmadan, içeriğin doğal
|
|
202
|
+
* genişliğinde kalır. `normal` (ortada yüzen) dialog için bu doğru davranış
|
|
203
|
+
* (kısa bir onay kutusu tavana kadar zorla GENİŞLEMEMELİ), ama drawer için
|
|
204
|
+
* YANLIŞ — üç CRUD formu içerik uzunluğuna göre üç farklı genişlikte açılırdı
|
|
205
|
+
* (gerçek, gözlemlenen bir regresyon). Çözüm: `calc(100vw-2rem)` viewport'a
|
|
206
|
+
* göre DEFINITE bir uzunluk — parent'ın genişliği belirsiz olsa bile bu
|
|
207
|
+
* paradoksu atlıyor. `width` + `max-width` aynı ifadeyi taşıdığı için
|
|
208
|
+
* `max-width` drawer'da fiilen artık gereksiz ama zararsız kalıyor.
|
|
209
|
+
*
|
|
210
|
+
* **`maximized` neden kendi `max-w-none`'ını taşıyor**: `size` (sm/md/lg/xl)
|
|
211
|
+
* her zaman uygulanır, `state` ondan BAĞIMSIZ bir eksendir — `maximized` kendi
|
|
212
|
+
* `max-w-none`'ını taşımazsa, `size`'ın `max-w-[min(N,...)]`'i (aynı `max-width`
|
|
213
|
+
* grubu, ama `size` `state`'ten ÖNCE değerlendirildiği için `state` sonuncusu
|
|
214
|
+
* kazanmadıkça) tek başına kalır ve `w-screen`'i geçersiz kılar — gerçek,
|
|
215
|
+
* gözlemlenen bir regresyon: `maximizable:true, size:'lg'` bir dialog
|
|
216
|
+
* büyütüldüğünde yükseklik `h-[100dvh]`'e ulaşırken genişlik hâlâ `lg`'nin
|
|
217
|
+
* 720px tavanına sıkışıp kalıyordu ("kenarlar kısa, sadece boyu uzuyor").
|
|
218
|
+
*/
|
|
219
|
+
/**
|
|
220
|
+
* Panel yüzeyi `glassSurface` (bkz. `@mesgflow/ui-kit/core` — mikro
|
|
221
|
+
* glassmorphism, 2026-09 Deep Emerald geçişi): arkadaki içerik `backdrop-blur`
|
|
222
|
+
* destekleyen tarayıcılarda hafifçe görünür kalır, backdrop
|
|
223
|
+
* (`.mf-dialog-backdrop`, motion.css) ayrıca karartıyor. `maximized` durumu
|
|
224
|
+
* TAM VIEWPORT'u kapladığı için (arkasında blur'lanacak sayfa içeriği yok,
|
|
225
|
+
* sadece backdrop) camı KAPATIP düz `bg-surface`'e döner — hem maliyetsiz hem
|
|
226
|
+
* anlamsız bir blur'dan kaçınır.
|
|
227
|
+
*/
|
|
228
|
+
const dialogVariants = cva(`relative flex w-full flex-col overflow-hidden border shadow-3 ${glassSurface}`, {
|
|
229
|
+
variants: {
|
|
230
|
+
size: {
|
|
231
|
+
sm: 'max-w-[min(440px,calc(100vw-2rem))]',
|
|
232
|
+
md: 'max-w-[min(560px,calc(100vw-2rem))]',
|
|
233
|
+
lg: 'max-w-[min(720px,calc(100vw-2rem))]',
|
|
234
|
+
xl: 'max-w-[min(960px,calc(100vw-2rem))]',
|
|
235
|
+
full: 'max-w-none',
|
|
236
|
+
},
|
|
237
|
+
state: {
|
|
238
|
+
normal: 'rounded-xl max-h-[85vh]',
|
|
239
|
+
maximized: 'h-[100dvh] max-h-none w-screen max-w-none rounded-none supports-[backdrop-filter]:bg-surface supports-[backdrop-filter]:backdrop-blur-none',
|
|
240
|
+
sheet: 'w-screen rounded-t-xl rounded-b-none max-h-[92dvh]',
|
|
241
|
+
drawer: 'h-[100dvh] max-h-none rounded-l-xl rounded-r-none',
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
compoundVariants: [
|
|
245
|
+
{ state: 'drawer', size: 'sm', class: 'w-[min(440px,calc(100vw-2rem))]' },
|
|
246
|
+
{ state: 'drawer', size: 'md', class: 'w-[min(560px,calc(100vw-2rem))]' },
|
|
247
|
+
{ state: 'drawer', size: 'lg', class: 'w-[min(720px,calc(100vw-2rem))]' },
|
|
248
|
+
{ state: 'drawer', size: 'xl', class: 'w-[min(960px,calc(100vw-2rem))]' },
|
|
249
|
+
],
|
|
250
|
+
defaultVariants: { size: 'md', state: 'normal' },
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const DRAG_MIN_VISIBLE = 48;
|
|
254
|
+
/**
|
|
255
|
+
* `<mf-dialog [title]="..." [subtitle]="..." [icon]="'group'" (close)="onCancel()">`
|
|
256
|
+
* — `DialogService.open()`'ın açtığı her içerik component'i (tüketicinin kendi
|
|
257
|
+
* form component'i, ör. `ConfirmDialog`) kendi kök elemanı olarak bunu kullanır. CDK Dialog'un
|
|
258
|
+
* kendisi (bkz. dialog.service.ts) hiçbir görsel çerçeve DAYATMIYOR (backdrop hariç) —
|
|
259
|
+
* panel görünümü (yuvarlak köşe/gölge/header/footer/kapatma düğmesi) TAMAMEN burada,
|
|
260
|
+
* tek yerde yaşıyor.
|
|
261
|
+
*
|
|
262
|
+
* Boyut/placement/maximize/drag/resize/sheet durumu kendi `@Input()`'ları DEĞİL,
|
|
263
|
+
* `DialogSurface` (DI ile enjekte edilir) — bkz. o dosyanın doc yorumu. `title`/
|
|
264
|
+
* `subtitle`/`icon`/`fillBody` ise İÇERİĞE ait olduğu için normal input —
|
|
265
|
+
* `[dialog-header]` slot'u tamamen özel bir başlık gerektiren durumlar için (bkz.
|
|
266
|
+
* ConfirmDialog) kaçış kapısı olarak kalıyor.
|
|
267
|
+
*
|
|
268
|
+
* `stateVariant()` sırası (sheet → maximized → drawer → normal) bilinçli: mobil
|
|
269
|
+
* sheet her zaman kazanır, ardından drag/resize/mobil-sheet'in yalnızca `normal`'da
|
|
270
|
+
* açık olması (bkz. `dragEnabled`/`resizeHandleVisible`) drawer'da bu ikisinin
|
|
271
|
+
* otomatik kapalı kalmasını BEDAVA sağlıyor — ayrı bir kontrol gerekmiyor.
|
|
272
|
+
*/
|
|
273
|
+
class Dialog {
|
|
274
|
+
title = input('', /* @ts-ignore */
|
|
275
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
276
|
+
subtitle = input('', /* @ts-ignore */
|
|
277
|
+
...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
|
|
278
|
+
icon = input('', /* @ts-ignore */
|
|
279
|
+
...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
280
|
+
/** Sadece `DialogSurface` DI ile enjekte edilmediğinde (ör. statik bir önizleme/demo) etkili — gerçek dialog'larda `DialogService.open({ size })` kullanılır. */
|
|
281
|
+
size = input(undefined, /* @ts-ignore */
|
|
282
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
283
|
+
/**
|
|
284
|
+
* `true` ise gövde kendi kaydırmasını KAPATIR (`overflow:hidden`) ve kalan
|
|
285
|
+
* yüksekliği içeriğe `flex-1` ile devreder — içeriğin KENDİ tek scroll alanını
|
|
286
|
+
* kurması beklenir (ör. uzun bir liste içeren bir form). "Tek kaydırma bölgesi"
|
|
287
|
+
* kuralının uygulama noktası: dialog gövdesi + içerik aynı anda kaymaz.
|
|
288
|
+
*/
|
|
289
|
+
fillBody = input(false, /* @ts-ignore */
|
|
290
|
+
...(ngDevMode ? [{ debugName: "fillBody" }] : /* istanbul ignore next */ []));
|
|
291
|
+
// `close` adı native DOM `close` olayıyla çakışıyor (@angular-eslint/no-output-native);
|
|
292
|
+
// mevcut public API — yeniden adlandırma kırıcı, ileri bir sürümde ele alınacak.
|
|
293
|
+
// eslint-disable-next-line @angular-eslint/no-output-native
|
|
294
|
+
close = output();
|
|
295
|
+
surface = inject(DialogSurface);
|
|
296
|
+
labels = inject(MF_DIALOG_LABELS);
|
|
297
|
+
elementRef = inject(ElementRef);
|
|
298
|
+
cdkDrag = inject(CdkDrag, { self: true });
|
|
299
|
+
scrollBody = viewChild('scrollBody', /* @ts-ignore */
|
|
300
|
+
...(ngDevMode ? [{ debugName: "scrollBody" }] : /* istanbul ignore next */ []));
|
|
301
|
+
scrolledFromTop = signal(false, /* @ts-ignore */
|
|
302
|
+
...(ngDevMode ? [{ debugName: "scrolledFromTop" }] : /* istanbul ignore next */ []));
|
|
303
|
+
hasMoreBelow = signal(false, /* @ts-ignore */
|
|
304
|
+
...(ngDevMode ? [{ debugName: "hasMoreBelow" }] : /* istanbul ignore next */ []));
|
|
305
|
+
stateVariant = computed(() => {
|
|
306
|
+
if (this.surface.sheet())
|
|
307
|
+
return 'sheet';
|
|
308
|
+
if (this.surface.maximized() || this.size() === 'full')
|
|
309
|
+
return 'maximized';
|
|
310
|
+
if (this.surface.placement() === 'right')
|
|
311
|
+
return 'drawer';
|
|
312
|
+
return 'normal';
|
|
313
|
+
}, /* @ts-ignore */
|
|
314
|
+
...(ngDevMode ? [{ debugName: "stateVariant" }] : /* istanbul ignore next */ []));
|
|
315
|
+
dragEnabled = computed(() => this.surface.draggable() && this.stateVariant() === 'normal', /* @ts-ignore */
|
|
316
|
+
...(ngDevMode ? [{ debugName: "dragEnabled" }] : /* istanbul ignore next */ []));
|
|
317
|
+
resizeHandleVisible = computed(() => this.surface.resizable() && this.stateVariant() === 'normal', /* @ts-ignore */
|
|
318
|
+
...(ngDevMode ? [{ debugName: "resizeHandleVisible" }] : /* istanbul ignore next */ []));
|
|
319
|
+
/** Drawer zaten tam yükseklik — büyütme düğmesinin anlamı yok, `surface.maximizable()` `true` olsa bile gizlenir. */
|
|
320
|
+
maximizeVisible = computed(() => this.surface.maximizable() && this.stateVariant() !== 'drawer', /* @ts-ignore */
|
|
321
|
+
...(ngDevMode ? [{ debugName: "maximizeVisible" }] : /* istanbul ignore next */ []));
|
|
322
|
+
hostClasses = computed(() => {
|
|
323
|
+
const closing = this.surface.closing();
|
|
324
|
+
const state = this.stateVariant();
|
|
325
|
+
const animateBase = state === 'sheet' ? 'sheet' : state === 'drawer' ? 'drawer' : 'dialog';
|
|
326
|
+
return cn(dialogVariants({ size: this.size() ?? this.surface.size(), state }), state === 'normal' && this.surface.customSize() ? 'max-w-none max-h-none' : '', `animate-${animateBase}-${closing ? 'out' : 'in'}`);
|
|
327
|
+
}, /* @ts-ignore */
|
|
328
|
+
...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
|
|
329
|
+
customWidth = computed(() => this.stateVariant() === 'normal' ? this.surface.customSize()?.width : undefined, /* @ts-ignore */
|
|
330
|
+
...(ngDevMode ? [{ debugName: "customWidth" }] : /* istanbul ignore next */ []));
|
|
331
|
+
customHeight = computed(() => this.stateVariant() === 'normal' ? this.surface.customSize()?.height : undefined, /* @ts-ignore */
|
|
332
|
+
...(ngDevMode ? [{ debugName: "customHeight" }] : /* istanbul ignore next */ []));
|
|
333
|
+
/** Kenara/tam ekrana yaslı yüzeyler (drawer, mobil sheet, maximized): bölgeler KALICI
|
|
334
|
+
* çizgiyle ayrılır — header/content/footer iskeleti her zaman aynı görünür. Ortada
|
|
335
|
+
* yüzen klasik dialog (`normal`, ör. ConfirmDialog) bugünkü kaydırma-gölgesi davranışını
|
|
336
|
+
* korur — kısa bir onay kutusu gereksiz çizgi kazanmaz. */
|
|
337
|
+
framed = computed(() => this.stateVariant() !== 'normal', /* @ts-ignore */
|
|
338
|
+
...(ngDevMode ? [{ debugName: "framed" }] : /* istanbul ignore next */ []));
|
|
339
|
+
headerBorder = computed(() => this.framed() || this.scrolledFromTop(), /* @ts-ignore */
|
|
340
|
+
...(ngDevMode ? [{ debugName: "headerBorder" }] : /* istanbul ignore next */ []));
|
|
341
|
+
footerBorder = computed(() => this.framed() || this.hasMoreBelow(), /* @ts-ignore */
|
|
342
|
+
...(ngDevMode ? [{ debugName: "footerBorder" }] : /* istanbul ignore next */ []));
|
|
343
|
+
constructor() {
|
|
344
|
+
this.cdkDrag.rootElementSelector = '.cdk-overlay-pane';
|
|
345
|
+
this.cdkDrag.constrainPosition = (point, _dragRef, dimensions) => ({
|
|
346
|
+
x: Math.min(Math.max(point.x, DRAG_MIN_VISIBLE - dimensions.width), window.innerWidth - DRAG_MIN_VISIBLE),
|
|
347
|
+
y: Math.min(Math.max(point.y, 0), window.innerHeight - DRAG_MIN_VISIBLE),
|
|
348
|
+
});
|
|
349
|
+
effect(() => {
|
|
350
|
+
this.cdkDrag.disabled = !this.dragEnabled();
|
|
351
|
+
});
|
|
352
|
+
effect(() => {
|
|
353
|
+
if (this.surface.maximized() || this.surface.sheet()) {
|
|
354
|
+
this.cdkDrag.reset();
|
|
355
|
+
this.surface.customSize.set(null);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
const destroyRef = inject(DestroyRef);
|
|
359
|
+
afterNextRender(() => {
|
|
360
|
+
this.updateScrollShadow();
|
|
361
|
+
const el = this.scrollBody()?.nativeElement;
|
|
362
|
+
if (!el || typeof ResizeObserver === 'undefined')
|
|
363
|
+
return;
|
|
364
|
+
const observer = new ResizeObserver(() => this.updateScrollShadow());
|
|
365
|
+
observer.observe(el);
|
|
366
|
+
destroyRef.onDestroy(() => observer.disconnect());
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
updateScrollShadow() {
|
|
370
|
+
const el = this.scrollBody()?.nativeElement;
|
|
371
|
+
if (!el)
|
|
372
|
+
return;
|
|
373
|
+
this.scrolledFromTop.set(el.scrollTop > 1);
|
|
374
|
+
this.hasMoreBelow.set(el.scrollHeight - el.clientHeight - el.scrollTop > 1);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Ctrl/Cmd+Enter — tek yerde çözülür, her form bedava kazanır. Kaydet düğmesi
|
|
378
|
+
* footer slot'unda `<form>`'un DIŞINDA yaşıyor (`form="role-form"` attribute
|
|
379
|
+
* ilişkilendirmesiyle çalışıyor) — bu yüzden düğmeye tıklamayı taklit etmek
|
|
380
|
+
* yerine doğrudan `<form>` üzerinde `requestSubmit()` çağrılıyor; bu da native
|
|
381
|
+
* kısıt doğrulamasını çalıştırıp mevcut `(submit)` bağlamasını tetikler.
|
|
382
|
+
*/
|
|
383
|
+
requestSubmit() {
|
|
384
|
+
this.elementRef.nativeElement.querySelector('form')?.requestSubmit();
|
|
385
|
+
}
|
|
386
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: Dialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
387
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: Dialog, isStandalone: true, selector: "mf-dialog", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, fillBody: { classPropertyName: "fillBody", publicName: "fillBody", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close" }, host: { listeners: { "keydown.control.enter": "requestSubmit()", "keydown.meta.enter": "requestSubmit()" }, properties: { "class": "hostClasses()", "style.width.px": "customWidth()", "style.height.px": "customHeight()" } }, providers: [
|
|
388
|
+
{
|
|
389
|
+
provide: DialogSurface,
|
|
390
|
+
useFactory: () => inject(DialogSurface, { optional: true, skipSelf: true }) ?? new DialogSurface(),
|
|
391
|
+
},
|
|
392
|
+
], viewQueries: [{ propertyName: "scrollBody", first: true, predicate: ["scrollBody"], descendants: true, isSignal: true }], exportAs: ["mfDialog"], hostDirectives: [{ directive: i1.CdkDrag }], ngImport: i0, template: "<div\r\n class=\"flex shrink-0 items-start justify-between gap-3 px-6 pt-5 pb-4\"\r\n [class.border-b]=\"headerBorder()\"\r\n [class.border-border]=\"headerBorder()\"\r\n>\r\n <ng-content select=\"[dialog-header]\">\r\n @if (icon()) {\r\n <span\r\n class=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-surface-alt text-fg-muted\"\r\n >\r\n <span mfIcon [size]=\"20\">{{ icon() }}</span>\r\n </span>\r\n }\r\n <div\r\n cdkDragHandle\r\n class=\"min-w-0 flex-1 pt-0.5\"\r\n [class.cursor-grab]=\"dragEnabled()\"\r\n [class.active:cursor-grabbing]=\"dragEnabled()\"\r\n >\r\n @if (title()) {\r\n <h2 class=\"truncate font-display text-title text-fg\">{{ title() }}</h2>\r\n }\r\n @if (subtitle()) {\r\n <p class=\"mt-0.5 truncate text-sm text-fg-muted\">{{ subtitle() }}</p>\r\n }\r\n </div>\r\n </ng-content>\r\n\r\n <div class=\"flex shrink-0 items-center gap-1\">\r\n @if (maximizeVisible()) {\r\n <button\r\n mfButton\r\n appearance=\"text\"\r\n tone=\"neutral\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n [attr.aria-label]=\"surface.maximized() ? labels().fullscreenExit : labels().fullscreenEnter\"\r\n (click)=\"surface.toggleMaximize()\"\r\n >\r\n <span mfIcon [size]=\"18\">{{ surface.maximized() ? 'fullscreen_exit' : 'fullscreen' }}</span>\r\n </button>\r\n }\r\n <button\r\n mfButton\r\n appearance=\"text\"\r\n tone=\"neutral\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n [attr.aria-label]=\"labels().close\"\r\n (click)=\"close.emit()\"\r\n >\r\n <span mfIcon [size]=\"20\">close</span>\r\n </button>\r\n </div>\r\n</div>\r\n\r\n<div\r\n #scrollBody\r\n class=\"flex-1 min-h-0 px-6\"\r\n [class.py-4]=\"framed()\"\r\n [class.overflow-y-auto]=\"!fillBody()\"\r\n [class.flex]=\"fillBody()\"\r\n [class.flex-col]=\"fillBody()\"\r\n [class.overflow-hidden]=\"fillBody()\"\r\n (scroll)=\"updateScrollShadow()\"\r\n>\r\n <ng-content />\r\n</div>\r\n\r\n<div\r\n class=\"shrink-0 px-6 py-4 [&:not(:has(*))]:hidden\"\r\n [class.border-t]=\"footerBorder()\"\r\n [class.border-border]=\"footerBorder()\"\r\n>\r\n <ng-content select=\"[dialog-footer]\" />\r\n</div>\r\n\r\n@if (resizeHandleVisible()) {\r\n <button\r\n mfDialogResizeHandle\r\n type=\"button\"\r\n [ariaLabel]=\"labels().resize\"\r\n class=\"absolute right-1 bottom-1 flex h-4 w-4 cursor-se-resize items-center justify-center rounded-xs text-fg-subtle hover:text-fg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring\"\r\n >\r\n <span mfIcon [size]=\"14\">south_east</span>\r\n </button>\r\n}\r\n", dependencies: [{ kind: "component", type: Button, selector: "button[mfButton], a[mfButton]", inputs: ["tone", "appearance", "size", "shape", "type", "loading", "iconStart", "iconEnd"], exportAs: ["mfButton"] }, { kind: "directive", type: Icon, selector: "span[mfIcon]", inputs: ["size", "filled"] }, { kind: "directive", type: DialogResizeHandle, selector: "[mfDialogResizeHandle]", inputs: ["ariaLabel"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] });
|
|
393
|
+
}
|
|
394
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: Dialog, decorators: [{
|
|
395
|
+
type: Component,
|
|
396
|
+
args: [{ selector: 'mf-dialog', exportAs: 'mfDialog', imports: [Button, Icon, DialogResizeHandle, CdkDragHandle], hostDirectives: [CdkDrag], providers: [
|
|
397
|
+
{
|
|
398
|
+
provide: DialogSurface,
|
|
399
|
+
useFactory: () => inject(DialogSurface, { optional: true, skipSelf: true }) ?? new DialogSurface(),
|
|
400
|
+
},
|
|
401
|
+
], host: {
|
|
402
|
+
'[class]': 'hostClasses()',
|
|
403
|
+
'[style.width.px]': 'customWidth()',
|
|
404
|
+
'[style.height.px]': 'customHeight()',
|
|
405
|
+
'(keydown.control.enter)': 'requestSubmit()',
|
|
406
|
+
'(keydown.meta.enter)': 'requestSubmit()',
|
|
407
|
+
}, template: "<div\r\n class=\"flex shrink-0 items-start justify-between gap-3 px-6 pt-5 pb-4\"\r\n [class.border-b]=\"headerBorder()\"\r\n [class.border-border]=\"headerBorder()\"\r\n>\r\n <ng-content select=\"[dialog-header]\">\r\n @if (icon()) {\r\n <span\r\n class=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-surface-alt text-fg-muted\"\r\n >\r\n <span mfIcon [size]=\"20\">{{ icon() }}</span>\r\n </span>\r\n }\r\n <div\r\n cdkDragHandle\r\n class=\"min-w-0 flex-1 pt-0.5\"\r\n [class.cursor-grab]=\"dragEnabled()\"\r\n [class.active:cursor-grabbing]=\"dragEnabled()\"\r\n >\r\n @if (title()) {\r\n <h2 class=\"truncate font-display text-title text-fg\">{{ title() }}</h2>\r\n }\r\n @if (subtitle()) {\r\n <p class=\"mt-0.5 truncate text-sm text-fg-muted\">{{ subtitle() }}</p>\r\n }\r\n </div>\r\n </ng-content>\r\n\r\n <div class=\"flex shrink-0 items-center gap-1\">\r\n @if (maximizeVisible()) {\r\n <button\r\n mfButton\r\n appearance=\"text\"\r\n tone=\"neutral\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n [attr.aria-label]=\"surface.maximized() ? labels().fullscreenExit : labels().fullscreenEnter\"\r\n (click)=\"surface.toggleMaximize()\"\r\n >\r\n <span mfIcon [size]=\"18\">{{ surface.maximized() ? 'fullscreen_exit' : 'fullscreen' }}</span>\r\n </button>\r\n }\r\n <button\r\n mfButton\r\n appearance=\"text\"\r\n tone=\"neutral\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n [attr.aria-label]=\"labels().close\"\r\n (click)=\"close.emit()\"\r\n >\r\n <span mfIcon [size]=\"20\">close</span>\r\n </button>\r\n </div>\r\n</div>\r\n\r\n<div\r\n #scrollBody\r\n class=\"flex-1 min-h-0 px-6\"\r\n [class.py-4]=\"framed()\"\r\n [class.overflow-y-auto]=\"!fillBody()\"\r\n [class.flex]=\"fillBody()\"\r\n [class.flex-col]=\"fillBody()\"\r\n [class.overflow-hidden]=\"fillBody()\"\r\n (scroll)=\"updateScrollShadow()\"\r\n>\r\n <ng-content />\r\n</div>\r\n\r\n<div\r\n class=\"shrink-0 px-6 py-4 [&:not(:has(*))]:hidden\"\r\n [class.border-t]=\"footerBorder()\"\r\n [class.border-border]=\"footerBorder()\"\r\n>\r\n <ng-content select=\"[dialog-footer]\" />\r\n</div>\r\n\r\n@if (resizeHandleVisible()) {\r\n <button\r\n mfDialogResizeHandle\r\n type=\"button\"\r\n [ariaLabel]=\"labels().resize\"\r\n class=\"absolute right-1 bottom-1 flex h-4 w-4 cursor-se-resize items-center justify-center rounded-xs text-fg-subtle hover:text-fg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring\"\r\n >\r\n <span mfIcon [size]=\"14\">south_east</span>\r\n </button>\r\n}\r\n" }]
|
|
408
|
+
}], ctorParameters: () => [], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], fillBody: [{ type: i0.Input, args: [{ isSignal: true, alias: "fillBody", required: false }] }], close: [{ type: i0.Output, args: ["close"] }], scrollBody: [{ type: i0.ViewChild, args: ['scrollBody', { isSignal: true }] }] } });
|
|
409
|
+
|
|
410
|
+
const TONE_ICON = {
|
|
411
|
+
danger: 'warning',
|
|
412
|
+
warning: 'warning',
|
|
413
|
+
info: 'info',
|
|
414
|
+
success: 'check_circle',
|
|
415
|
+
neutral: 'help',
|
|
416
|
+
};
|
|
417
|
+
/** Rozet renkleri Badge'in `badge.variants.ts`'teki tonal işaret rol token'ını (`bg-X-tint text-X-tint-fg`) birebir izliyor. */
|
|
418
|
+
const TONE_BADGE_CLASS = {
|
|
419
|
+
danger: 'bg-danger-tint text-danger-tint-fg',
|
|
420
|
+
warning: 'bg-warning-tint text-warning-tint-fg',
|
|
421
|
+
info: 'bg-info-tint text-info-tint-fg',
|
|
422
|
+
success: 'bg-success-tint text-success-tint-fg',
|
|
423
|
+
neutral: 'bg-surface-alt text-fg-muted',
|
|
424
|
+
};
|
|
425
|
+
/**
|
|
426
|
+
* Onay diyaloğunun içerik component'i — `DialogService.confirm()`/`confirmDelete()`/
|
|
427
|
+
* `alert()` (bkz. dialog.service.ts) bunu açıp `DialogRef<boolean>`'ı `Promise`'e
|
|
428
|
+
* çeviriyor. `Dialog`'un (dialog.ts) `[dialog-header]` kaçış kapısını kullanıyor —
|
|
429
|
+
* ton'a göre renkli ikon rozeti, generic `Dialog`'un kendi `[icon]` input'unun
|
|
430
|
+
* desteklemediği bir şey (o sadece nötr bir rozet çizer), bu yüzden kendi başlığını
|
|
431
|
+
* kendi çiziyor.
|
|
432
|
+
*/
|
|
433
|
+
class ConfirmDialog {
|
|
434
|
+
data = inject(DIALOG_DATA);
|
|
435
|
+
dialogRef = inject((DialogRef));
|
|
436
|
+
labels = inject(MF_DIALOG_LABELS);
|
|
437
|
+
tone = computed(() => this.data.tone ?? 'neutral', /* @ts-ignore */
|
|
438
|
+
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
439
|
+
toneIcon = computed(() => TONE_ICON[this.tone()], /* @ts-ignore */
|
|
440
|
+
...(ngDevMode ? [{ debugName: "toneIcon" }] : /* istanbul ignore next */ []));
|
|
441
|
+
toneBadgeClass = computed(() => TONE_BADGE_CLASS[this.tone()], /* @ts-ignore */
|
|
442
|
+
...(ngDevMode ? [{ debugName: "toneBadgeClass" }] : /* istanbul ignore next */ []));
|
|
443
|
+
/** Her ton `filled` — warning da diğer tonlarla aynı `bg-X-fill` rol deseninde dolu blok kullanıyor (bkz. button.variants.ts), eski `outlined` istisnası kaldırıldı. */
|
|
444
|
+
confirmTone = computed(() => {
|
|
445
|
+
const tone = this.tone();
|
|
446
|
+
if (tone === 'danger')
|
|
447
|
+
return 'danger';
|
|
448
|
+
if (tone === 'warning')
|
|
449
|
+
return 'warning';
|
|
450
|
+
return 'primary';
|
|
451
|
+
}, /* @ts-ignore */
|
|
452
|
+
...(ngDevMode ? [{ debugName: "confirmTone" }] : /* istanbul ignore next */ []));
|
|
453
|
+
submitting = signal(false, /* @ts-ignore */
|
|
454
|
+
...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
|
|
455
|
+
actionError = signal(null, /* @ts-ignore */
|
|
456
|
+
...(ngDevMode ? [{ debugName: "actionError" }] : /* istanbul ignore next */ []));
|
|
457
|
+
onCancel() {
|
|
458
|
+
this.dialogRef.close(false);
|
|
459
|
+
}
|
|
460
|
+
async onConfirm() {
|
|
461
|
+
if (!this.data.action) {
|
|
462
|
+
this.dialogRef.close(true);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
this.submitting.set(true);
|
|
466
|
+
this.actionError.set(null);
|
|
467
|
+
this.dialogRef.disableClose = true;
|
|
468
|
+
try {
|
|
469
|
+
await this.data.action();
|
|
470
|
+
this.dialogRef.close(true);
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
this.actionError.set(this.labels().errorGeneric);
|
|
474
|
+
}
|
|
475
|
+
finally {
|
|
476
|
+
this.submitting.set(false);
|
|
477
|
+
this.dialogRef.disableClose = false;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ConfirmDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
481
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ConfirmDialog, isStandalone: true, selector: "mf-confirm-dialog", host: { classAttribute: "contents" }, ngImport: i0, template: "<mf-dialog size=\"sm\" (close)=\"onCancel()\">\r\n <div dialog-header class=\"flex items-start gap-3\">\r\n <span\r\n class=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-md\"\r\n [class]=\"toneBadgeClass()\"\r\n >\r\n <span mfIcon [size]=\"22\">{{ toneIcon() }}</span>\r\n </span>\r\n <div class=\"min-w-0 flex-1 pt-1.5\">\r\n <h2 class=\"truncate font-display text-title text-fg\">{{ data.title }}</h2>\r\n </div>\r\n </div>\r\n\r\n <p class=\"text-sm text-fg-muted\">{{ data.content }}</p>\r\n\r\n @if (actionError()) {\r\n <p class=\"mt-3 text-sm text-danger\">{{ actionError() }}</p>\r\n }\r\n\r\n <div dialog-footer class=\"flex justify-end gap-2\">\r\n @if (!data.alert) {\r\n <button\r\n mfButton\r\n appearance=\"outlined\"\r\n tone=\"neutral\"\r\n type=\"button\"\r\n [disabled]=\"submitting()\"\r\n (click)=\"onCancel()\"\r\n >\r\n {{ data.cancelLabel ?? labels().confirmCancel }}\r\n </button>\r\n }\r\n <button\r\n mfButton\r\n [tone]=\"confirmTone()\"\r\n appearance=\"filled\"\r\n type=\"button\"\r\n [disabled]=\"submitting()\"\r\n (click)=\"onConfirm()\"\r\n >\r\n @if (submitting()) {\r\n <span mfIcon [size]=\"16\" class=\"animate-spin\">progress_activity</span>\r\n }\r\n {{ data.confirmLabel ?? (data.alert ? labels().confirmOk : labels().confirmConfirm) }}\r\n </button>\r\n </div>\r\n</mf-dialog>\r\n", dependencies: [{ kind: "component", type: Dialog, selector: "mf-dialog", inputs: ["title", "subtitle", "icon", "size", "fillBody"], outputs: ["close"], exportAs: ["mfDialog"] }, { kind: "component", type: Button, selector: "button[mfButton], a[mfButton]", inputs: ["tone", "appearance", "size", "shape", "type", "loading", "iconStart", "iconEnd"], exportAs: ["mfButton"] }, { kind: "directive", type: Icon, selector: "span[mfIcon]", inputs: ["size", "filled"] }] });
|
|
482
|
+
}
|
|
483
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ConfirmDialog, decorators: [{
|
|
484
|
+
type: Component,
|
|
485
|
+
args: [{ selector: 'mf-confirm-dialog', imports: [Dialog, Button, Icon], host: { class: 'contents' }, template: "<mf-dialog size=\"sm\" (close)=\"onCancel()\">\r\n <div dialog-header class=\"flex items-start gap-3\">\r\n <span\r\n class=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-md\"\r\n [class]=\"toneBadgeClass()\"\r\n >\r\n <span mfIcon [size]=\"22\">{{ toneIcon() }}</span>\r\n </span>\r\n <div class=\"min-w-0 flex-1 pt-1.5\">\r\n <h2 class=\"truncate font-display text-title text-fg\">{{ data.title }}</h2>\r\n </div>\r\n </div>\r\n\r\n <p class=\"text-sm text-fg-muted\">{{ data.content }}</p>\r\n\r\n @if (actionError()) {\r\n <p class=\"mt-3 text-sm text-danger\">{{ actionError() }}</p>\r\n }\r\n\r\n <div dialog-footer class=\"flex justify-end gap-2\">\r\n @if (!data.alert) {\r\n <button\r\n mfButton\r\n appearance=\"outlined\"\r\n tone=\"neutral\"\r\n type=\"button\"\r\n [disabled]=\"submitting()\"\r\n (click)=\"onCancel()\"\r\n >\r\n {{ data.cancelLabel ?? labels().confirmCancel }}\r\n </button>\r\n }\r\n <button\r\n mfButton\r\n [tone]=\"confirmTone()\"\r\n appearance=\"filled\"\r\n type=\"button\"\r\n [disabled]=\"submitting()\"\r\n (click)=\"onConfirm()\"\r\n >\r\n @if (submitting()) {\r\n <span mfIcon [size]=\"16\" class=\"animate-spin\">progress_activity</span>\r\n }\r\n {{ data.confirmLabel ?? (data.alert ? labels().confirmOk : labels().confirmConfirm) }}\r\n </button>\r\n </div>\r\n</mf-dialog>\r\n" }]
|
|
486
|
+
}] });
|
|
487
|
+
|
|
488
|
+
/** `--duration-fast` (styles.css) ile birebir — `animate-dialog-out`/`animate-sheet-out` keyframe'lerinin süresi. */
|
|
489
|
+
const EXIT_MS = 120;
|
|
490
|
+
const SHEET_QUERY = '(max-width: 639.98px)';
|
|
491
|
+
function normalizeClass(value) {
|
|
492
|
+
if (!value)
|
|
493
|
+
return [];
|
|
494
|
+
return Array.isArray(value) ? value : [value];
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Dialog açmanın tek, merkezi noktası — `@angular/cdk/dialog`'un `Dialog`
|
|
498
|
+
* servisi ÜZERİNE ince bir katman (focus-trap/ESC/backdrop-click/geri-scroll-
|
|
499
|
+
* engelleme davranışının TAMAMI CDK'dan geliyor, burada TEKRAR YAZILMADI — bkz.
|
|
500
|
+
* projects/ui-kit/README.md "a11y checklist" bölümündeki "elle yazmak son çare" ilkesi).
|
|
501
|
+
*
|
|
502
|
+
* İçerik component'i (tüketicinin kendi form component'i, ör. `ConfirmDialog`) `@angular/cdk/dialog`'un
|
|
503
|
+
* KENDİ `DIALOG_DATA`/`DialogRef` token'larını doğrudan `inject()` eder — ekstra bir
|
|
504
|
+
* sarmalayıcı token'a gerek yok. Boyut/placement/maximize/drag/resize/sheet durumu
|
|
505
|
+
* `DialogSurface` (bkz. o dosya) üzerinden, `providers` ile o dialog'un injector'ına
|
|
506
|
+
* verilir — `Dialog` (dialog.ts) bunu DI'dan okuyarak render eder.
|
|
507
|
+
*
|
|
508
|
+
* **Tek kural**: kayıt oluştur/düzenle → `openDrawer()` (sağdan, `placement:'right'`);
|
|
509
|
+
* onayla/bilgilendir/sil → `confirm()`/`confirmDelete()`/`alert()` (merkezi, `size:'sm'`).
|
|
510
|
+
* Drawer ayrı bir primitif DEĞİL — `placement` sadece konum stratejisini ve
|
|
511
|
+
* `Dialog.stateVariant()`'ı değiştiren bir eksen, geri kalan her şey (CDK Dialog,
|
|
512
|
+
* closePredicate, kirli-form koruması, mobil sheet, kaydırma gölgesi) ortak.
|
|
513
|
+
*
|
|
514
|
+
* Kapanış animasyonu: CDK'nın `DialogRef.close()`'u `overlayRef.dispose()`'u SENKRON
|
|
515
|
+
* çağırıyor, bir "kapanıyor" olayı/kancası yok. `closePredicate` bunun için bilinçli
|
|
516
|
+
* bir kaçamak — `false` döndürmek kapanmayı iptal ediyor: ilk çağrıda `surface.closing`'i
|
|
517
|
+
* `true` yapıp gerçek kapanmayı `EXIT_MS` geciktiriyoruz (panel bu sürede `Dialog`'un
|
|
518
|
+
* kendi reaktif `animate-dialog-out`/`animate-sheet-out`/`animate-drawer-out` class'ıyla
|
|
519
|
+
* görsel olarak kapanır), ikinci çağrıda (zaten `closing()===true`) gerçek `dispose()`'a
|
|
520
|
+
* izin veriyoruz. ESC/backdrop/programatik `dialogRef.close(...)` HEPSİ bu predicate'ten
|
|
521
|
+
* geçtiği için davranış tekdüze. `prefers-reduced-motion` gecikmeyi tamamen atlıyor —
|
|
522
|
+
* AMA kirli-form kontrolünden SONRA: koruma reduced-motion kullanıcılarında da çalışmalı.
|
|
523
|
+
*
|
|
524
|
+
* **Kirli form koruması**: `surface.isDirty()` `true` dönerse kapanış iptal edilip
|
|
525
|
+
* `confirmDiscard()` (kendi içindeki `ConfirmDialog`, ayrı/temiz bir `DialogSurface`
|
|
526
|
+
* ile) açılır; kullanıcı onaylarsa `surface.markClean()` + gerçek `close()`. İçerik
|
|
527
|
+
* component'i `constructor()`'da `surface.registerDirty(() => ...)` ile kendi
|
|
528
|
+
* "değişti mi" sorusunu kaydeder.
|
|
529
|
+
*/
|
|
530
|
+
class DialogService {
|
|
531
|
+
cdkDialog = inject(Dialog$1);
|
|
532
|
+
injector = inject(Injector);
|
|
533
|
+
breakpointObserver = inject(BreakpointObserver);
|
|
534
|
+
labels = inject(MF_DIALOG_LABELS);
|
|
535
|
+
/** `ref` başına `DialogSurface` — tüketicinin kendi URL senkronizasyon mantığı, kirli-form durumunu PROAKTİF sormak için kullanabilir (kendi `closePredicate`'inin async akışıyla yarışmamak için). */
|
|
536
|
+
// `DialogRef<D, C>`'nin `config.providers` callback'i kendi generic'lerine yapısal
|
|
537
|
+
// olarak döngüsel referans veriyor (bkz. @angular/cdk/dialog) — `unknown` kullanmak
|
|
538
|
+
// `ref`'i somut `DialogRef<R, C>` tipiyle çağıran DIŞARIDAKİ tüketicileri (public API)
|
|
539
|
+
// derleme zamanında kırar. Registry nesne KİMLİĞİNE göre çalışıyor, tür parametreleri
|
|
540
|
+
// ilgisiz; `any` burada BİLEREK.
|
|
541
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
542
|
+
surfaces = new WeakMap();
|
|
543
|
+
open(component, options) {
|
|
544
|
+
const surface = new DialogSurface();
|
|
545
|
+
const placement = options?.placement ?? 'center';
|
|
546
|
+
surface.size.set(options?.size ?? 'md');
|
|
547
|
+
surface.placement.set(placement);
|
|
548
|
+
surface.maximizable.set(options?.maximizable ?? false);
|
|
549
|
+
surface.draggable.set(options?.draggable ?? false);
|
|
550
|
+
surface.resizable.set(options?.resizable ?? false);
|
|
551
|
+
const position = createGlobalPositionStrategy(this.injector);
|
|
552
|
+
if (placement === 'right')
|
|
553
|
+
position.right('0').top('0');
|
|
554
|
+
else
|
|
555
|
+
position.centerHorizontally().centerVertically();
|
|
556
|
+
const reducedMotion = typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
557
|
+
const ref = this.cdkDialog.open(component, {
|
|
558
|
+
data: options?.data,
|
|
559
|
+
panelClass: normalizeClass(options?.panelClass),
|
|
560
|
+
backdropClass: 'mf-dialog-backdrop',
|
|
561
|
+
positionStrategy: position,
|
|
562
|
+
autoFocus: 'first-tabbable',
|
|
563
|
+
restoreFocus: true,
|
|
564
|
+
disableClose: options?.disableClose ?? false,
|
|
565
|
+
// CDK'nın kendi tarayıcı geri/ileri tuşu dinleyicisi (varsayılan `true`) —
|
|
566
|
+
// tüketici derin-bağlantı/URL senkronizasyonunu KENDİ route effect'i
|
|
567
|
+
// üzerinden yönetiyorsa (kirli-form kontrolü + confirmDiscard + URL
|
|
568
|
+
// senkronu dahil), CDK'nın PARALEL ve koordinasyonsuz kendi `close()`
|
|
569
|
+
// çağrısı (kendi closePredicate'inden geçerek KENDİ confirmDiscard'ını
|
|
570
|
+
// açar) tüketicininkiyle AYNI ANDA iki ayrı "Kaydedilmemiş değişiklikler"
|
|
571
|
+
// onayı açılmasına yol açabilir — gerçek bir kullanıcı senaryosunda
|
|
572
|
+
// tekrarlanan, doğrulanmış bir çakışma.
|
|
573
|
+
closeOnNavigation: false,
|
|
574
|
+
ariaLabel: options?.ariaLabel,
|
|
575
|
+
providers: [{ provide: DialogSurface, useValue: surface }],
|
|
576
|
+
closePredicate: (result) => {
|
|
577
|
+
// Çıkış animasyonu turu — ikinci çağrı, gerçek dispose'a izin ver.
|
|
578
|
+
if (surface.closing())
|
|
579
|
+
return true;
|
|
580
|
+
// Kirli form koruması `reducedMotion`'DAN ÖNCE kontrol edilir — aksi halde
|
|
581
|
+
// "animasyonu azalt" açık kullanıcılarda bu koruma hiç çalışmazdı.
|
|
582
|
+
if (surface.isDirty()) {
|
|
583
|
+
void this.confirmDiscard().then((discard) => {
|
|
584
|
+
if (!discard)
|
|
585
|
+
return;
|
|
586
|
+
surface.markClean();
|
|
587
|
+
ref.close(result);
|
|
588
|
+
});
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
if (reducedMotion)
|
|
592
|
+
return true;
|
|
593
|
+
surface.closing.set(true);
|
|
594
|
+
ref.overlayRef.backdropElement?.classList.add('is-closing');
|
|
595
|
+
setTimeout(() => ref.close(result), EXIT_MS);
|
|
596
|
+
return false;
|
|
597
|
+
},
|
|
598
|
+
});
|
|
599
|
+
this.surfaces.set(ref, surface);
|
|
600
|
+
const breakpointSub = this.breakpointObserver.observe(SHEET_QUERY).subscribe(({ matches }) => {
|
|
601
|
+
surface.sheet.set(matches);
|
|
602
|
+
if (matches) {
|
|
603
|
+
position.centerHorizontally().bottom('0');
|
|
604
|
+
}
|
|
605
|
+
else if (placement === 'right') {
|
|
606
|
+
position.right('0').top('0');
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
position.centerHorizontally().centerVertically();
|
|
610
|
+
}
|
|
611
|
+
position.apply();
|
|
612
|
+
});
|
|
613
|
+
ref.closed.subscribe(() => breakpointSub.unsubscribe());
|
|
614
|
+
return ref;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Kayıt oluştur/düzenle formlarının TEK açılış noktası — sağdan drawer.
|
|
618
|
+
* `maximizable` bilerek zorla kapatılıyor: drawer zaten tam yükseklik, büyütme
|
|
619
|
+
* düğmesinin anlamı yok (bkz. `Dialog.maximizeVisible`).
|
|
620
|
+
*
|
|
621
|
+
* Varsayılan genişlik `md` (560px) — CRUD formları `size` GEÇMEZ, hepsi aynı
|
|
622
|
+
* genişlikte açılmalı. İstisna: iki sütunlu bir form (sol dikey ray + sağ
|
|
623
|
+
* panel gibi) `md` içine sığmazsa `xl` (960px) geçilebilir.
|
|
624
|
+
*/
|
|
625
|
+
openDrawer(component, options) {
|
|
626
|
+
return this.open(component, { ...options, placement: 'right', maximizable: false });
|
|
627
|
+
}
|
|
628
|
+
/** `Promise<boolean>` döner — `if (await dialog.confirm(...))` çağıran taraf için `.subscribe()` zincirinden daha sade. */
|
|
629
|
+
confirm(data) {
|
|
630
|
+
const ref = this.open(ConfirmDialog, {
|
|
631
|
+
data,
|
|
632
|
+
size: 'sm',
|
|
633
|
+
// `data.title` her zaman görünür bir başlık taşıyor — CDK'nın dialog konteynerine
|
|
634
|
+
// erişilebilir ad olarak veriliyor (axe: `aria-dialog-name`); çağıran taraf
|
|
635
|
+
// `ConfirmDialogData`'da ayrı bir `ariaLabel` alanı sağlayamıyor zaten.
|
|
636
|
+
ariaLabel: data.title,
|
|
637
|
+
});
|
|
638
|
+
return new Promise((resolve) => ref.closed.subscribe((result) => resolve(!!result)));
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Kirli-form kapanış korumasının kendi onay diyaloğu — `confirmDiscard`'ın açtığı
|
|
642
|
+
* `ConfirmDialog`'un `DialogSurface`'i her zaman temiz (yeni instance), bu yüzden
|
|
643
|
+
* özyineleme sorunsuz. `closePredicate` içinden (ESC/backdrop/X) VE tüketicinin
|
|
644
|
+
* kendi route senkronizasyonundan (URL değişip panel PROAKTİF kapatılmadan
|
|
645
|
+
* önce) iki ayrı yoldan çağrılabildiği için `public`.
|
|
646
|
+
*
|
|
647
|
+
* `tone: 'warning'` — onay butonu artık DOLU warning (bkz.
|
|
648
|
+
* `button.variants.ts`/`confirm-dialog.ts`, eski `outlined` istisnası
|
|
649
|
+
* kaldırıldı).
|
|
650
|
+
*/
|
|
651
|
+
confirmDiscard() {
|
|
652
|
+
const t = this.labels().discard;
|
|
653
|
+
return this.confirm({
|
|
654
|
+
title: t.title,
|
|
655
|
+
content: t.content,
|
|
656
|
+
tone: 'warning',
|
|
657
|
+
confirmLabel: t.confirm,
|
|
658
|
+
cancelLabel: t.cancel,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/** Tüketici bir ref'i kapatmadan ÖNCE kirli mi diye sormak istiyorsa — kendi `closePredicate`'inin async akışıyla yarışmamak için `DialogSurface`'e doğrudan erişim. */
|
|
662
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
663
|
+
getSurface(ref) {
|
|
664
|
+
return this.surfaces.get(ref);
|
|
665
|
+
}
|
|
666
|
+
/** `confirm()`'in silme kısayolu — `tone:'danger'` + varsayılan "Sil" etiketi. */
|
|
667
|
+
confirmDelete(data) {
|
|
668
|
+
const confirmLabel = data.confirmLabel ?? this.labels().confirmDelete;
|
|
669
|
+
return this.confirm({ ...data, tone: 'danger', confirmLabel });
|
|
670
|
+
}
|
|
671
|
+
/** Tek butonlu bilgilendirme modu — sonuç önemsiz olduğu için `Promise<void>` döner. */
|
|
672
|
+
alert(data) {
|
|
673
|
+
const ref = this.open(ConfirmDialog, {
|
|
674
|
+
data: { ...data, alert: true },
|
|
675
|
+
size: 'sm',
|
|
676
|
+
ariaLabel: data.title,
|
|
677
|
+
});
|
|
678
|
+
return new Promise((resolve) => ref.closed.subscribe(() => resolve()));
|
|
679
|
+
}
|
|
680
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: DialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
681
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: DialogService, providedIn: 'root' });
|
|
682
|
+
}
|
|
683
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: DialogService, decorators: [{
|
|
684
|
+
type: Injectable,
|
|
685
|
+
args: [{ providedIn: 'root' }]
|
|
686
|
+
}] });
|
|
687
|
+
|
|
688
|
+
// `MF_DIALOG_LABELS`/`MfDialogLabels` artık `@mesgflow/ui-kit/core`'da yaşıyor
|
|
689
|
+
// (bkz. `core/i18n/tokens.ts` — MF_LABELS'ın bir seçicisi) — buradan AYRICA
|
|
690
|
+
// re-export etmek `ng build ui-kit`'de TS2308 (ambiguous export) veriyor, çünkü
|
|
691
|
+
// birincil barrel zaten hem `./core`'u hem `./dialog`'u `export *` ediyor.
|
|
692
|
+
// Tüketici `import { MF_DIALOG_LABELS } from '@mesgflow/ui-kit'` (birincil barrel)
|
|
693
|
+
// veya `'@mesgflow/ui-kit/core'` kullanır.
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Generated bundle index. Do not edit.
|
|
697
|
+
*/
|
|
698
|
+
|
|
699
|
+
export { ConfirmDialog, Dialog, DialogResizeHandle, DialogService, DialogSurface, dialogVariants };
|
|
700
|
+
//# sourceMappingURL=mesgflow-ui-kit-dialog.mjs.map
|