@simplysm/angular 14.2.7 → 14.2.8
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/dist/core/modal/injectDragResize.d.ts +10 -0
- package/dist/core/modal/injectDragResize.d.ts.map +1 -1
- package/dist/core/modal/injectDragResize.js +61 -16
- package/dist/core/modal/sd-modal.d.ts +6 -0
- package/dist/core/modal/sd-modal.d.ts.map +1 -1
- package/dist/core/modal/sd-modal.js +68 -20
- package/dist/core/modal/sd-modal.provider.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/core/modal/injectDragResize.ts +76 -17
- package/src/core/modal/sd-modal.provider.ts +1 -4
- package/src/core/modal/sd-modal.ts +72 -14
|
@@ -5,6 +5,16 @@ interface DragResizeOptions {
|
|
|
5
5
|
minHeightPx: Signal<number | undefined>;
|
|
6
6
|
onEnd: () => void;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* `._dialog` 는 평소 CSS 로 중앙 정렬(`position: relative` + `margin: 0 auto`)되어 있어
|
|
10
|
+
* `left`/`top` 이 "정상 배치 위치 대비 상대 offset" 으로 해석된다.
|
|
11
|
+
* 드래그, 리사이즈는 절대 좌표로 계산하므로 시작 시점에 절대 좌표계로 전환한다.
|
|
12
|
+
* 전환만으로는 위치, 크기가 변하지 않으며, 이미 전환된 요소에 다시 적용해도 결과가 같다.
|
|
13
|
+
*/
|
|
14
|
+
export declare function pinDialogAbsolute(dialogEl: HTMLElement): {
|
|
15
|
+
left: number;
|
|
16
|
+
top: number;
|
|
17
|
+
};
|
|
8
18
|
export declare function injectDragResize(opt: DragResizeOptions): {
|
|
9
19
|
startDrag: (event: MouseEvent) => void;
|
|
10
20
|
startResize: (event: MouseEvent, dir: string) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"injectDragResize.d.ts","sourceRoot":"","sources":["../../../src/core/modal/injectDragResize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAsB,MAAM,eAAe,CAAC;AAEhE,UAAU,iBAAiB;IACzB,WAAW,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvC,WAAW,EAAE,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,GAAG;IACxD,SAAS,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACvC,WAAW,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD,
|
|
1
|
+
{"version":3,"file":"injectDragResize.d.ts","sourceRoot":"","sources":["../../../src/core/modal/injectDragResize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAsB,MAAM,eAAe,CAAC;AAEhE,UAAU,iBAAiB;IACzB,WAAW,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvC,WAAW,EAAE,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAuBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAiCtF;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,GAAG;IACxD,SAAS,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACvC,WAAW,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD,CA8IA"}
|
|
@@ -1,4 +1,53 @@
|
|
|
1
1
|
import { inject, DestroyRef } from "@angular/core";
|
|
2
|
+
/** `auto`, `none` 등 길이가 아닌 값은 하한 없음(0)으로 본다. */
|
|
3
|
+
function parseCssMinPx(value) {
|
|
4
|
+
const parsed = parseFloat(value);
|
|
5
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
6
|
+
}
|
|
7
|
+
function shiftPercentSize(dialogStyle, prop, padPx) {
|
|
8
|
+
if (padPx === 0)
|
|
9
|
+
return;
|
|
10
|
+
const value = dialogStyle[prop];
|
|
11
|
+
if (!value.includes("%"))
|
|
12
|
+
return;
|
|
13
|
+
dialogStyle[prop] = `calc(${value} - ${padPx}px)`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `._dialog` 는 평소 CSS 로 중앙 정렬(`position: relative` + `margin: 0 auto`)되어 있어
|
|
17
|
+
* `left`/`top` 이 "정상 배치 위치 대비 상대 offset" 으로 해석된다.
|
|
18
|
+
* 드래그, 리사이즈는 절대 좌표로 계산하므로 시작 시점에 절대 좌표계로 전환한다.
|
|
19
|
+
* 전환만으로는 위치, 크기가 변하지 않으며, 이미 전환된 요소에 다시 적용해도 결과가 같다.
|
|
20
|
+
*/
|
|
21
|
+
export function pinDialogAbsolute(dialogEl) {
|
|
22
|
+
const wasRelative = getComputedStyle(dialogEl).position === "relative";
|
|
23
|
+
const parentEl = dialogEl.offsetParent;
|
|
24
|
+
const beforeRect = dialogEl.getBoundingClientRect();
|
|
25
|
+
const roundedLeft = dialogEl.offsetLeft;
|
|
26
|
+
const roundedTop = dialogEl.offsetTop;
|
|
27
|
+
dialogEl.style.position = "absolute";
|
|
28
|
+
dialogEl.style.margin = "0";
|
|
29
|
+
dialogEl.style.right = "auto";
|
|
30
|
+
dialogEl.style.bottom = "auto";
|
|
31
|
+
dialogEl.style.left = `${roundedLeft}px`;
|
|
32
|
+
dialogEl.style.top = `${roundedTop}px`;
|
|
33
|
+
if (wasRelative && parentEl != null) {
|
|
34
|
+
// 백분율 크기의 기준 박스가 부모 content box 에서 offsetParent padding box 로 바뀐다.
|
|
35
|
+
const parentStyle = getComputedStyle(parentEl);
|
|
36
|
+
const padX = parseFloat(parentStyle.paddingLeft) + parseFloat(parentStyle.paddingRight);
|
|
37
|
+
const padY = parseFloat(parentStyle.paddingTop) + parseFloat(parentStyle.paddingBottom);
|
|
38
|
+
shiftPercentSize(dialogEl.style, "width", padX);
|
|
39
|
+
shiftPercentSize(dialogEl.style, "maxWidth", padX);
|
|
40
|
+
shiftPercentSize(dialogEl.style, "height", padY);
|
|
41
|
+
shiftPercentSize(dialogEl.style, "maxHeight", padY);
|
|
42
|
+
}
|
|
43
|
+
// offsetLeft/offsetTop 은 정수로 반올림되므로, 전환 전후 실제 위치 차이만큼 되돌린다.
|
|
44
|
+
const afterRect = dialogEl.getBoundingClientRect();
|
|
45
|
+
const pinnedLeft = roundedLeft - (afterRect.left - beforeRect.left);
|
|
46
|
+
const pinnedTop = roundedTop - (afterRect.top - beforeRect.top);
|
|
47
|
+
dialogEl.style.left = `${pinnedLeft}px`;
|
|
48
|
+
dialogEl.style.top = `${pinnedTop}px`;
|
|
49
|
+
return { left: pinnedLeft, top: pinnedTop };
|
|
50
|
+
}
|
|
2
51
|
export function injectDragResize(opt) {
|
|
3
52
|
const destroyRef = inject(DestroyRef);
|
|
4
53
|
let dragState;
|
|
@@ -22,8 +71,8 @@ export function injectDragResize(opt) {
|
|
|
22
71
|
return;
|
|
23
72
|
const dx = event.clientX - resizeState.startX;
|
|
24
73
|
const dy = event.clientY - resizeState.startY;
|
|
25
|
-
const minW =
|
|
26
|
-
const minH =
|
|
74
|
+
const minW = resizeState.minWidth;
|
|
75
|
+
const minH = resizeState.minHeight;
|
|
27
76
|
let newWidth = resizeState.startWidth;
|
|
28
77
|
let newHeight = resizeState.startHeight;
|
|
29
78
|
let newLeft = resizeState.startLeft;
|
|
@@ -71,23 +120,16 @@ export function injectDragResize(opt) {
|
|
|
71
120
|
opt.onEnd();
|
|
72
121
|
}
|
|
73
122
|
}
|
|
74
|
-
function getParentRect(dialogEl) {
|
|
75
|
-
return dialogEl.offsetParent?.getBoundingClientRect() ?? {
|
|
76
|
-
left: 0,
|
|
77
|
-
top: 0,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
123
|
function startDrag(event) {
|
|
81
124
|
const dialogEl = opt.getDialogEl();
|
|
82
125
|
if (dialogEl == null)
|
|
83
126
|
return;
|
|
84
|
-
const
|
|
85
|
-
const parentRect = getParentRect(dialogEl);
|
|
127
|
+
const pinned = pinDialogAbsolute(dialogEl);
|
|
86
128
|
dragState = {
|
|
87
129
|
startX: event.clientX,
|
|
88
130
|
startY: event.clientY,
|
|
89
|
-
startLeft:
|
|
90
|
-
startTop:
|
|
131
|
+
startLeft: pinned.left,
|
|
132
|
+
startTop: pinned.top,
|
|
91
133
|
};
|
|
92
134
|
document.addEventListener("mousemove", onDocumentMouseMove);
|
|
93
135
|
document.addEventListener("mouseup", onDocumentMouseUp);
|
|
@@ -96,16 +138,19 @@ export function injectDragResize(opt) {
|
|
|
96
138
|
const dialogEl = opt.getDialogEl();
|
|
97
139
|
if (dialogEl == null)
|
|
98
140
|
return;
|
|
99
|
-
const
|
|
100
|
-
const
|
|
141
|
+
const pinned = pinDialogAbsolute(dialogEl);
|
|
142
|
+
const dialogStyle = getComputedStyle(dialogEl);
|
|
101
143
|
resizeState = {
|
|
102
144
|
dir,
|
|
103
145
|
startX: event.clientX,
|
|
104
146
|
startY: event.clientY,
|
|
105
147
|
startWidth: dialogEl.offsetWidth,
|
|
106
148
|
startHeight: dialogEl.offsetHeight,
|
|
107
|
-
startLeft:
|
|
108
|
-
startTop:
|
|
149
|
+
startLeft: pinned.left,
|
|
150
|
+
startTop: pinned.top,
|
|
151
|
+
// CSS 하한을 넘겨 줄이면 실제 크기는 안 줄고 반대쪽 변만 밀려나므로, 실제 제약을 하한으로 삼는다.
|
|
152
|
+
minWidth: Math.max(opt.minWidthPx() ?? 0, parseCssMinPx(dialogStyle.minWidth)),
|
|
153
|
+
minHeight: Math.max(opt.minHeightPx() ?? 0, parseCssMinPx(dialogStyle.minHeight)),
|
|
109
154
|
};
|
|
110
155
|
document.addEventListener("mousemove", onDocumentMouseMove);
|
|
111
156
|
document.addEventListener("mouseup", onDocumentMouseUp);
|
|
@@ -30,8 +30,12 @@ export declare class SdModal {
|
|
|
30
30
|
actionTplRef: import("@angular/core").InputSignal<TemplateRef<any> | undefined>;
|
|
31
31
|
closeRequest: import("@angular/core").OutputEmitterRef<void>;
|
|
32
32
|
private readonly _dragResize;
|
|
33
|
+
/** 화면 넘침 클램프로 덮기 직전의 크기. 클램프 중이 아니면 undefined. */
|
|
34
|
+
private _clampedWidth;
|
|
35
|
+
private _clampedHeight;
|
|
33
36
|
constructor();
|
|
34
37
|
onResizeMouseDown(event: MouseEvent, dir: string): void;
|
|
38
|
+
onDialogMouseDown(): void;
|
|
35
39
|
onHeaderMouseDown(event: MouseEvent): void;
|
|
36
40
|
onBackdropClick(): void;
|
|
37
41
|
onCloseButtonClick(): void;
|
|
@@ -40,8 +44,10 @@ export declare class SdModal {
|
|
|
40
44
|
onHostResize(event: SdResizeEvent): void;
|
|
41
45
|
onDialogResize(event: SdResizeEvent): void;
|
|
42
46
|
onWindowResize(): void;
|
|
47
|
+
private _clampIntoHost;
|
|
43
48
|
private _requestClose;
|
|
44
49
|
private _bringToFront;
|
|
50
|
+
private _forgetClampedSize;
|
|
45
51
|
private _calcHeight;
|
|
46
52
|
private _calcWidth;
|
|
47
53
|
private _getDialogEl;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sd-modal.d.ts","sourceRoot":"","sources":["../../../src/core/modal/sd-modal.ts"],"names":[],"mappings":"AAAA,OAAO,EAWL,KAAK,WAAW,EAEjB,MAAM,eAAe,CAAC;AASvB,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,wBAAwB,CAAC;;;AAEhC,
|
|
1
|
+
{"version":3,"file":"sd-modal.d.ts","sourceRoot":"","sources":["../../../src/core/modal/sd-modal.ts"],"names":[],"mappings":"AAAA,OAAO,EAWL,KAAK,WAAW,EAEjB,MAAM,eAAe,CAAC;AASvB,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,wBAAwB,CAAC;;;AAEhC,qBAmTa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwD;IAC1F,OAAO,CAAC,QAAQ,CAAC,eAAe,CAEc;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwB;IACtD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAEhD,SAAS,CAAC,QAAQ,CAAC,OAAO,yVAAW;IAErC,IAAI,+CAAgB;IACpB,GAAG,0DAAwC;IAC3C,KAAK,8CAAa;IAClB,UAAU,+CAAgB;IAC1B,eAAe,+CAAgB;IAC/B,WAAW,0DAAwC;IACnD,kBAAkB,+CAAe;IACjC,mBAAmB,+CAAe;IAClC,KAAK,+CAAgB;IACrB,IAAI,+CAAgB;IACpB,SAAS,+CAAgB;IACzB,OAAO,+CAAgB;IACvB,QAAQ,gFAA8D;IACtE,WAAW,0DAAwC;IACnD,UAAU,0DAAwC;IAClD,QAAQ,0DAAwC;IAChD,OAAO,0DAAwC;IAC/C,YAAY,oEAAkD;IAE9D,YAAY,iDAAkB;IAE9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAKzB;IAEH,kDAAkD;IAClD,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,cAAc,CAAqB;;IAqC3C,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAOvD,iBAAiB,IAAI,IAAI;IAIzB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAO1C,eAAe,IAAI,IAAI;IAKvB,kBAAkB,IAAI,IAAI;IAI1B,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAS3C,aAAa,IAAI,IAAI;IAIrB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAKxC,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAK1C,cAAc,IAAI,IAAI;IAQtB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,WAAW;IAsBnB,OAAO,CAAC,UAAU;IAoBlB,OAAO,CAAC,YAAY;YAIN,WAAW;YAoBX,cAAc;yCAnPjB,OAAO;2CAAP,OAAO;CAyQnB"}
|
|
@@ -5,7 +5,7 @@ import { tablerX } from "@ng-icons/tabler-icons";
|
|
|
5
5
|
import { SdActivatedModalProvider } from "./sd-activated-modal.provider";
|
|
6
6
|
import { SdSystemConfigProvider } from "../config/sd-system-config.provider";
|
|
7
7
|
import { injectFocusTrap } from "./injectFocusTrap";
|
|
8
|
-
import { injectDragResize } from "./injectDragResize";
|
|
8
|
+
import { injectDragResize, pinDialogAbsolute } from "./injectDragResize";
|
|
9
9
|
import { SdAnchor } from "../../controls/button/sd-anchor";
|
|
10
10
|
import { SdResizeDirective } from "../events/sd-resize";
|
|
11
11
|
import "@simplysm/core-browser";
|
|
@@ -130,6 +130,9 @@ export class SdModal {
|
|
|
130
130
|
minHeightPx: this.minHeightPx,
|
|
131
131
|
onEnd: () => void this._saveConfig().catch((err) => this._errorHandler.handleError(err)),
|
|
132
132
|
});
|
|
133
|
+
/** 화면 넘침 클램프로 덮기 직전의 크기. 클램프 중이 아니면 undefined. */
|
|
134
|
+
_clampedWidth;
|
|
135
|
+
_clampedHeight;
|
|
133
136
|
constructor() {
|
|
134
137
|
// data-sd-init: 첫 렌더 후 설정하여 CSS transition 트리거 허용
|
|
135
138
|
afterNextRender(() => {
|
|
@@ -154,6 +157,7 @@ export class SdModal {
|
|
|
154
157
|
else {
|
|
155
158
|
dialogEl.style.height = "";
|
|
156
159
|
}
|
|
160
|
+
this._forgetClampedSize();
|
|
157
161
|
});
|
|
158
162
|
// key 기반 설정 복원
|
|
159
163
|
effect(() => {
|
|
@@ -165,8 +169,13 @@ export class SdModal {
|
|
|
165
169
|
}
|
|
166
170
|
onResizeMouseDown(event, dir) {
|
|
167
171
|
event.preventDefault();
|
|
172
|
+
// 사용자가 직접 정하는 크기가 자동 클램프 이전 크기보다 우선한다.
|
|
173
|
+
this._forgetClampedSize();
|
|
168
174
|
this._dragResize.startResize(event, dir);
|
|
169
175
|
}
|
|
176
|
+
onDialogMouseDown() {
|
|
177
|
+
this._bringToFront();
|
|
178
|
+
}
|
|
170
179
|
onHeaderMouseDown(event) {
|
|
171
180
|
if (!this.movable())
|
|
172
181
|
return;
|
|
@@ -212,12 +221,20 @@ export class SdModal {
|
|
|
212
221
|
const dialogEl = this._getDialogEl();
|
|
213
222
|
if (dialogEl == null)
|
|
214
223
|
return;
|
|
224
|
+
// 위치를 잡지 않은 모달은 CSS 중앙 정렬이 창 크기를 따라가므로 보정 대상이 아니다.
|
|
225
|
+
if (dialogEl.style.left === "" && dialogEl.style.top === "")
|
|
226
|
+
return;
|
|
227
|
+
this._clampIntoHost(dialogEl);
|
|
228
|
+
}
|
|
229
|
+
_clampIntoHost(dialogEl) {
|
|
215
230
|
const hostEl = this._elRef.nativeElement;
|
|
216
|
-
|
|
217
|
-
|
|
231
|
+
const maxLeft = Math.max(0, hostEl.offsetWidth - 100);
|
|
232
|
+
if (dialogEl.offsetLeft > maxLeft) {
|
|
233
|
+
dialogEl.style.left = `${maxLeft}px`;
|
|
218
234
|
}
|
|
219
|
-
|
|
220
|
-
|
|
235
|
+
const maxTop = Math.max(0, hostEl.offsetHeight - 100);
|
|
236
|
+
if (dialogEl.offsetTop > maxTop) {
|
|
237
|
+
dialogEl.style.top = `${maxTop}px`;
|
|
221
238
|
}
|
|
222
239
|
}
|
|
223
240
|
_requestClose() {
|
|
@@ -244,25 +261,47 @@ export class SdModal {
|
|
|
244
261
|
return;
|
|
245
262
|
hostEl.style.zIndex = String(maxZ + 1);
|
|
246
263
|
}
|
|
264
|
+
_forgetClampedSize() {
|
|
265
|
+
this._clampedWidth = undefined;
|
|
266
|
+
this._clampedHeight = undefined;
|
|
267
|
+
}
|
|
247
268
|
_calcHeight() {
|
|
248
269
|
const dialogEl = this._getDialogEl();
|
|
249
270
|
if (dialogEl == null)
|
|
250
271
|
return;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
272
|
+
// 클램프 중이면 dialog 높이가 호스트에 맞춰져 있어 실제 필요 높이를 알 수 없다. 먼저 되돌려 잰다.
|
|
273
|
+
if (this._clampedHeight != null) {
|
|
274
|
+
dialogEl.style.height = this._clampedHeight;
|
|
275
|
+
dialogEl.style.maxHeight = "";
|
|
276
|
+
}
|
|
277
|
+
const hostEl = this._elRef.nativeElement;
|
|
278
|
+
const paddingTop = parseFloat(getComputedStyle(hostEl).paddingTop) || 0;
|
|
279
|
+
if (dialogEl.offsetHeight > hostEl.offsetHeight - paddingTop) {
|
|
280
|
+
this._clampedHeight ??= dialogEl.style.height;
|
|
254
281
|
dialogEl.style.maxHeight = "100%";
|
|
255
282
|
dialogEl.style.height = "100%";
|
|
256
283
|
}
|
|
284
|
+
else {
|
|
285
|
+
this._clampedHeight = undefined;
|
|
286
|
+
}
|
|
257
287
|
}
|
|
258
288
|
_calcWidth() {
|
|
259
289
|
const dialogEl = this._getDialogEl();
|
|
260
290
|
if (dialogEl == null)
|
|
261
291
|
return;
|
|
262
|
-
if (
|
|
292
|
+
if (this._clampedWidth != null) {
|
|
293
|
+
dialogEl.style.width = this._clampedWidth;
|
|
294
|
+
dialogEl.style.maxWidth = "";
|
|
295
|
+
}
|
|
296
|
+
const hostEl = this._elRef.nativeElement;
|
|
297
|
+
if (dialogEl.offsetWidth > hostEl.offsetWidth) {
|
|
298
|
+
this._clampedWidth ??= dialogEl.style.width;
|
|
263
299
|
dialogEl.style.maxWidth = "100%";
|
|
264
300
|
dialogEl.style.width = "100%";
|
|
265
301
|
}
|
|
302
|
+
else {
|
|
303
|
+
this._clampedWidth = undefined;
|
|
304
|
+
}
|
|
266
305
|
}
|
|
267
306
|
_getDialogEl() {
|
|
268
307
|
return this._elRef.nativeElement.querySelector("._dialog");
|
|
@@ -274,11 +313,14 @@ export class SdModal {
|
|
|
274
313
|
const dialogEl = this._getDialogEl();
|
|
275
314
|
if (dialogEl == null)
|
|
276
315
|
return;
|
|
316
|
+
// 클램프 중이면 화면에 맞춘 값이 아니라 그 직전 크기를 저장해야 다시 열 때 원래 크기로 뜬다.
|
|
317
|
+
const savedWidth = this._clampedWidth ?? dialogEl.style.width;
|
|
318
|
+
const savedHeight = this._clampedHeight ?? dialogEl.style.height;
|
|
277
319
|
const config = {};
|
|
278
|
-
if (
|
|
279
|
-
config["width"] =
|
|
280
|
-
if (
|
|
281
|
-
config["height"] =
|
|
320
|
+
if (savedWidth !== "")
|
|
321
|
+
config["width"] = savedWidth;
|
|
322
|
+
if (savedHeight !== "")
|
|
323
|
+
config["height"] = savedHeight;
|
|
282
324
|
if (dialogEl.style.left !== "")
|
|
283
325
|
config["left"] = dialogEl.style.left;
|
|
284
326
|
if (dialogEl.style.top !== "")
|
|
@@ -298,23 +340,28 @@ export class SdModal {
|
|
|
298
340
|
dialogEl.style.width = config["width"];
|
|
299
341
|
if (config["height"] != null)
|
|
300
342
|
dialogEl.style.height = config["height"];
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
343
|
+
this._forgetClampedSize();
|
|
344
|
+
if (config["left"] != null || config["top"] != null) {
|
|
345
|
+
pinDialogAbsolute(dialogEl);
|
|
346
|
+
if (config["left"] != null)
|
|
347
|
+
dialogEl.style.left = config["left"];
|
|
348
|
+
if (config["top"] != null)
|
|
349
|
+
dialogEl.style.top = config["top"];
|
|
350
|
+
this._clampIntoHost(dialogEl);
|
|
351
|
+
}
|
|
305
352
|
}
|
|
306
353
|
static ɵfac = function SdModal_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SdModal)(); };
|
|
307
354
|
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SdModal, selectors: [["sd-modal"]], hostVars: 4, hostBindings: function SdModal_HostBindings(rf, ctx) { if (rf & 1) {
|
|
308
355
|
i0.ɵɵlistener("sdResize", function SdModal_sdResize_HostBindingHandler($event) { return ctx.onHostResize($event); })("resize", function SdModal_resize_HostBindingHandler() { return ctx.onWindowResize(); }, i0.ɵɵresolveWindow);
|
|
309
356
|
} if (rf & 2) {
|
|
310
357
|
i0.ɵɵattribute("data-sd-open", ctx.open() || undefined)("data-sd-float", ctx.float() || undefined)("data-sd-fill", ctx.fill() || undefined)("data-sd-position", ctx.position() || undefined);
|
|
311
|
-
} }, inputs: { open: [1, "open"], key: [1, "key"], title: [1, "title"], hideHeader: [1, "hideHeader"], hideCloseButton: [1, "hideCloseButton"], headerStyle: [1, "headerStyle"], useCloseByBackdrop: [1, "useCloseByBackdrop"], useCloseByEscapeKey: [1, "useCloseByEscapeKey"], float: [1, "float"], fill: [1, "fill"], resizable: [1, "resizable"], movable: [1, "movable"], position: [1, "position"], minHeightPx: [1, "minHeightPx"], minWidthPx: [1, "minWidthPx"], heightPx: [1, "heightPx"], widthPx: [1, "widthPx"], actionTplRef: [1, "actionTplRef"] }, outputs: { open: "openChange", closeRequest: "closeRequest" }, features: [i0.ɵɵHostDirectivesFeature([{ directive: i1.SdResizeDirective, outputs: ["sdResize", "sdResize"] }])], ngContentSelectors: _c0, decls: 6, vars: 2, consts: [["tabindex", "-1", "role", "button", 1, "_backdrop", 3, "click", "keydown.enter"], ["tabindex", "-1", 1, "_dialog", 3, "keydown", "focus", "sdResize"], [1, "_header", 3, "style"], [1, "_content"], [1, "_header", 3, "mousedown"], [1, "_title"], [1, "_close-btn", 3, "theme"], [4, "ngTemplateOutlet"], [1, "_close-btn", 3, "click", "theme"], [3, "svg"], ["data-resize-dir", "top", 1, "_resize-handle", "_resize-top", 3, "mousedown"], ["data-resize-dir", "bottom", 1, "_resize-handle", "_resize-bottom", 3, "mousedown"], ["data-resize-dir", "left", 1, "_resize-handle", "_resize-left", 3, "mousedown"], ["data-resize-dir", "right", 1, "_resize-handle", "_resize-right", 3, "mousedown"], ["data-resize-dir", "top-left", 1, "_resize-handle", "_resize-top-left", 3, "mousedown"], ["data-resize-dir", "top-right", 1, "_resize-handle", "_resize-top-right", 3, "mousedown"], ["data-resize-dir", "bottom-left", 1, "_resize-handle", "_resize-bottom-left", 3, "mousedown"], ["data-resize-dir", "bottom-right", 1, "_resize-handle", "_resize-bottom-right", 3, "mousedown"]], template: function SdModal_Template(rf, ctx) { if (rf & 1) {
|
|
358
|
+
} }, inputs: { open: [1, "open"], key: [1, "key"], title: [1, "title"], hideHeader: [1, "hideHeader"], hideCloseButton: [1, "hideCloseButton"], headerStyle: [1, "headerStyle"], useCloseByBackdrop: [1, "useCloseByBackdrop"], useCloseByEscapeKey: [1, "useCloseByEscapeKey"], float: [1, "float"], fill: [1, "fill"], resizable: [1, "resizable"], movable: [1, "movable"], position: [1, "position"], minHeightPx: [1, "minHeightPx"], minWidthPx: [1, "minWidthPx"], heightPx: [1, "heightPx"], widthPx: [1, "widthPx"], actionTplRef: [1, "actionTplRef"] }, outputs: { open: "openChange", closeRequest: "closeRequest" }, features: [i0.ɵɵHostDirectivesFeature([{ directive: i1.SdResizeDirective, outputs: ["sdResize", "sdResize"] }])], ngContentSelectors: _c0, decls: 6, vars: 2, consts: [["tabindex", "-1", "role", "button", 1, "_backdrop", 3, "click", "keydown.enter"], ["tabindex", "-1", 1, "_dialog", 3, "mousedown", "keydown", "focus", "sdResize"], [1, "_header", 3, "style"], [1, "_content"], [1, "_header", 3, "mousedown"], [1, "_title"], [1, "_close-btn", 3, "theme"], [4, "ngTemplateOutlet"], [1, "_close-btn", 3, "click", "theme"], [3, "svg"], ["data-resize-dir", "top", 1, "_resize-handle", "_resize-top", 3, "mousedown"], ["data-resize-dir", "bottom", 1, "_resize-handle", "_resize-bottom", 3, "mousedown"], ["data-resize-dir", "left", 1, "_resize-handle", "_resize-left", 3, "mousedown"], ["data-resize-dir", "right", 1, "_resize-handle", "_resize-right", 3, "mousedown"], ["data-resize-dir", "top-left", 1, "_resize-handle", "_resize-top-left", 3, "mousedown"], ["data-resize-dir", "top-right", 1, "_resize-handle", "_resize-top-right", 3, "mousedown"], ["data-resize-dir", "bottom-left", 1, "_resize-handle", "_resize-bottom-left", 3, "mousedown"], ["data-resize-dir", "bottom-right", 1, "_resize-handle", "_resize-bottom-right", 3, "mousedown"]], template: function SdModal_Template(rf, ctx) { if (rf & 1) {
|
|
312
359
|
i0.ɵɵprojectionDef();
|
|
313
360
|
i0.ɵɵelementStart(0, "div", 0);
|
|
314
361
|
i0.ɵɵlistener("click", function SdModal_Template_div_click_0_listener() { return ctx.onBackdropClick(); })("keydown.enter", function SdModal_Template_div_keydown_enter_0_listener() { return ctx.onBackdropClick(); });
|
|
315
362
|
i0.ɵɵelementEnd();
|
|
316
363
|
i0.ɵɵelementStart(1, "div", 1);
|
|
317
|
-
i0.ɵɵlistener("keydown", function SdModal_Template_div_keydown_1_listener($event) { return ctx.onDialogKeydown($event); })("focus", function SdModal_Template_div_focus_1_listener() { return ctx.onDialogFocus(); })("sdResize", function SdModal_Template_div_sdResize_1_listener($event) { return ctx.onDialogResize($event); });
|
|
364
|
+
i0.ɵɵlistener("mousedown", function SdModal_Template_div_mousedown_1_listener() { return ctx.onDialogMouseDown(); })("keydown", function SdModal_Template_div_keydown_1_listener($event) { return ctx.onDialogKeydown($event); })("focus", function SdModal_Template_div_focus_1_listener() { return ctx.onDialogFocus(); })("sdResize", function SdModal_Template_div_sdResize_1_listener($event) { return ctx.onDialogResize($event); });
|
|
318
365
|
i0.ɵɵconditionalCreate(2, SdModal_Conditional_2_Template, 5, 5, "div", 2);
|
|
319
366
|
i0.ɵɵelementStart(3, "div", 3);
|
|
320
367
|
i0.ɵɵprojection(4);
|
|
@@ -348,6 +395,7 @@ export class SdModal {
|
|
|
348
395
|
<div
|
|
349
396
|
class="_dialog"
|
|
350
397
|
tabindex="-1"
|
|
398
|
+
(mousedown)="onDialogMouseDown()"
|
|
351
399
|
(keydown)="onDialogKeydown($event)"
|
|
352
400
|
(focus)="onDialogFocus()"
|
|
353
401
|
(sdResize)="onDialogResize($event)"
|
|
@@ -411,4 +459,4 @@ export class SdModal {
|
|
|
411
459
|
</div>
|
|
412
460
|
`, styles: ["sd-modal {\n display: block;\n position: fixed;\n z-index: var(--sd-z-modal);\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n padding-top: calc(var(--sd-topbar-height) + var(--sd-gap-sm));\n opacity: 0;\n transition: opacity var(--sd-animation-duration) ease-in-out;\n pointer-events: none;\n}\nsd-modal > ._backdrop {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: var(--sd-bg-backdrop);\n}\nsd-modal > ._dialog {\n position: relative;\n display: flex;\n flex-direction: column;\n margin: 0 auto;\n width: fit-content;\n min-width: 200px;\n background-color: var(--sd-bg-elevated);\n border: 1px solid var(--sd-modal-bd);\n overflow: hidden;\n box-shadow: 0 calc(16 * var(--sd-shadow-size)) calc(16 * 4 * var(--sd-shadow-size) * var(--sd-shadow-blur-mult)) var(--sd-shadow-color), 0 var(--sd-shadow-size) calc(var(--sd-shadow-size) * var(--sd-shadow-blur-mult)) var(--sd-shadow-color);\n border-radius: var(--sd-radius-default);\n transform: translateY(-25px);\n transition: transform var(--sd-animation-duration) ease-in-out;\n}\nsd-modal > ._dialog:focus {\n outline: none;\n}\nsd-modal > ._dialog > ._header {\n display: flex;\n align-items: center;\n user-select: none;\n border-bottom: 1px solid var(--sd-bd-hairline);\n background-color: var(--sd-modal-header-bg);\n color: var(--sd-modal-header-tx);\n}\nsd-modal > ._dialog > ._header > ._title {\n flex: 1;\n padding: var(--sd-gap-sm) var(--sd-gap-default);\n}\nsd-modal > ._dialog > ._header > ._close-btn {\n padding: var(--sd-gap-sm) var(--sd-gap-default);\n color: var(--sd-modal-header-tx-muted);\n}\nsd-modal > ._dialog > ._header > ._close-btn:hover {\n color: var(--sd-modal-header-tx);\n}\nsd-modal > ._dialog > ._content {\n flex: 1;\n overflow: auto;\n}\nsd-modal > ._dialog > ._resize-handle {\n position: absolute;\n}\nsd-modal > ._dialog > ._resize-handle._resize-left {\n top: 0;\n left: 0;\n width: var(--sd-gap-sm);\n height: 100%;\n cursor: ew-resize;\n}\nsd-modal > ._dialog > ._resize-handle._resize-right {\n top: 0;\n right: 0;\n width: var(--sd-gap-sm);\n height: 100%;\n cursor: ew-resize;\n}\nsd-modal > ._dialog > ._resize-handle._resize-top {\n top: 0;\n left: 0;\n width: 100%;\n height: var(--sd-gap-sm);\n cursor: ns-resize;\n}\nsd-modal > ._dialog > ._resize-handle._resize-top-right {\n right: 0;\n top: 0;\n width: var(--sd-gap-sm);\n height: var(--sd-gap-sm);\n z-index: 1;\n cursor: nesw-resize;\n}\nsd-modal > ._dialog > ._resize-handle._resize-top-left {\n left: 0;\n top: 0;\n width: var(--sd-gap-sm);\n height: var(--sd-gap-sm);\n cursor: nwse-resize;\n z-index: 1;\n}\nsd-modal > ._dialog > ._resize-handle._resize-bottom {\n bottom: 0;\n left: 0;\n width: 100%;\n height: var(--sd-gap-sm);\n cursor: ns-resize;\n}\nsd-modal > ._dialog > ._resize-handle._resize-bottom-right {\n right: 0;\n bottom: 0;\n width: var(--sd-gap-sm);\n height: var(--sd-gap-sm);\n z-index: 1;\n cursor: nwse-resize;\n}\nsd-modal > ._dialog > ._resize-handle._resize-bottom-left {\n left: 0;\n bottom: 0;\n width: var(--sd-gap-sm);\n height: var(--sd-gap-sm);\n cursor: nesw-resize;\n z-index: 1;\n}\nsd-modal[data-sd-open][data-sd-init] {\n opacity: 1;\n pointer-events: auto;\n}\nsd-modal[data-sd-open][data-sd-init] > ._dialog {\n transform: none;\n}\nsd-modal[data-sd-float] {\n pointer-events: none;\n}\nsd-modal[data-sd-float] > ._backdrop {\n display: none;\n}\nsd-modal[data-sd-float] > ._dialog {\n pointer-events: auto;\n opacity: 0;\n box-shadow: 0 calc(4 * var(--sd-shadow-size)) calc(4 * 4 * var(--sd-shadow-size) * var(--sd-shadow-blur-mult)) var(--sd-shadow-color), 0 var(--sd-shadow-size) calc(var(--sd-shadow-size) * var(--sd-shadow-blur-mult)) var(--sd-shadow-color);\n border: 1px solid var(--sd-bd-hairline);\n}\nsd-modal[data-sd-float] > ._dialog:focus {\n box-shadow: 0 calc(16 * var(--sd-shadow-size)) calc(16 * 4 * var(--sd-shadow-size) * var(--sd-shadow-blur-mult)) var(--sd-shadow-color), 0 var(--sd-shadow-size) calc(var(--sd-shadow-size) * var(--sd-shadow-blur-mult)) var(--sd-shadow-color);\n}\nsd-modal[data-sd-float][data-sd-open][data-sd-init] {\n pointer-events: none;\n}\nsd-modal[data-sd-float][data-sd-open][data-sd-init] > ._dialog {\n pointer-events: auto;\n opacity: 1;\n}\nsd-modal[data-sd-position=bottom-right] > ._dialog {\n position: absolute;\n right: calc(var(--sd-gap-xxl) * 2);\n bottom: var(--sd-gap-xxl);\n}\nsd-modal[data-sd-position=top-right] > ._dialog {\n position: absolute;\n right: var(--sd-gap-xxl);\n top: var(--sd-gap-xxl);\n}\nsd-modal[data-sd-fill] {\n padding-top: 0;\n}\nsd-modal[data-sd-fill] > ._dialog {\n width: 100%;\n height: 100%;\n border: none;\n border-radius: 0;\n}\nsd-modal[data-sd-fill] > ._dialog > ._header {\n background-color: transparent;\n color: var(--sd-tx-faint);\n}"] }]
|
|
413
461
|
}], () => [], { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], hideHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideHeader", required: false }] }], hideCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideCloseButton", required: false }] }], headerStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerStyle", required: false }] }], useCloseByBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "useCloseByBackdrop", required: false }] }], useCloseByEscapeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "useCloseByEscapeKey", required: false }] }], float: [{ type: i0.Input, args: [{ isSignal: true, alias: "float", required: false }] }], fill: [{ type: i0.Input, args: [{ isSignal: true, alias: "fill", required: false }] }], resizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizable", required: false }] }], movable: [{ type: i0.Input, args: [{ isSignal: true, alias: "movable", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], minHeightPx: [{ type: i0.Input, args: [{ isSignal: true, alias: "minHeightPx", required: false }] }], minWidthPx: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidthPx", required: false }] }], heightPx: [{ type: i0.Input, args: [{ isSignal: true, alias: "heightPx", required: false }] }], widthPx: [{ type: i0.Input, args: [{ isSignal: true, alias: "widthPx", required: false }] }], actionTplRef: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionTplRef", required: false }] }], closeRequest: [{ type: i0.Output, args: ["closeRequest"] }] }); })();
|
|
414
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SdModal, { className: "SdModal", filePath: "packages/angular/src/core/modal/sd-modal.ts", lineNumber:
|
|
462
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SdModal, { className: "SdModal", filePath: "packages/angular/src/core/modal/sd-modal.ts", lineNumber: 333 }); })();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sd-modal.provider.d.ts","sourceRoot":"","sources":["../../../src/core/modal/sd-modal.provider.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,gBAAgB,EACrB,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,IAAI,EACV,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"sd-modal.provider.d.ts","sourceRoot":"","sources":["../../../src/core/modal/sd-modal.provider.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,gBAAgB,EACrB,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,IAAI,EACV,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAGtF,OAAO,wBAAwB,CAAC;;AAEhC;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,KAAK,EAAE,gBAAgB,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACvC,YAAY,CAAC,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC;IAChC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC;AAED,KAAK,kBAAkB,GAAG,aAAa,GAAG,OAAO,GAAG,cAAc,GAAG,sBAAsB,CAAC;AAC5F,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC,SAAS,MAAM,CAAA;CAAE,GACrF,CAAC,GACD,KAAK,CAAC;AAEV;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,GAAG,EAAE;IACrF,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,MAAM,EAAE,YAAY,CAClB,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,kBAAkB,GAAG,CAAC,CAAC,EACtD,mBAAmB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,kBAAkB,GAAG,CAAC,CAAC,CACtF,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,cAAc,GAAG,WAAW,CAAC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;GAEG;AACH,qBACa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+B;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAE9C,UAAU,iDAAa;IAEjB,SAAS,CAAC,CAAC,SAAS,iBAAiB,CAAC,GAAG,CAAC,EAC9C,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EACrB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAkKzD,OAAO,CAAC,aAAa;yCA5KV,eAAe;6CAAf,eAAe;CAuL3B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/angular",
|
|
3
|
-
"version": "14.2.
|
|
3
|
+
"version": "14.2.8",
|
|
4
4
|
"description": "심플리즘 패키지 - Angular",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "심플리즘",
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"jspdf": "^4.2.1",
|
|
59
59
|
"rxjs": "^7.8.2",
|
|
60
60
|
"tabbable": "^6.5.0",
|
|
61
|
-
"@simplysm/
|
|
62
|
-
"@simplysm/core-browser": "14.2.
|
|
63
|
-
"@simplysm/
|
|
64
|
-
"@simplysm/service-
|
|
61
|
+
"@simplysm/core-common": "14.2.8",
|
|
62
|
+
"@simplysm/core-browser": "14.2.8",
|
|
63
|
+
"@simplysm/service-common": "14.2.8",
|
|
64
|
+
"@simplysm/service-client": "14.2.8"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@angular/compiler": "^22.0.8"
|
|
@@ -7,6 +7,68 @@ interface DragResizeOptions {
|
|
|
7
7
|
onEnd: () => void;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
type SizeStyleProp = "width" | "height" | "maxWidth" | "maxHeight";
|
|
11
|
+
|
|
12
|
+
/** `auto`, `none` 등 길이가 아닌 값은 하한 없음(0)으로 본다. */
|
|
13
|
+
function parseCssMinPx(value: string): number {
|
|
14
|
+
const parsed = parseFloat(value);
|
|
15
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function shiftPercentSize(
|
|
19
|
+
dialogStyle: CSSStyleDeclaration,
|
|
20
|
+
prop: SizeStyleProp,
|
|
21
|
+
padPx: number,
|
|
22
|
+
): void {
|
|
23
|
+
if (padPx === 0) return;
|
|
24
|
+
|
|
25
|
+
const value = dialogStyle[prop];
|
|
26
|
+
if (!value.includes("%")) return;
|
|
27
|
+
|
|
28
|
+
dialogStyle[prop] = `calc(${value} - ${padPx}px)`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `._dialog` 는 평소 CSS 로 중앙 정렬(`position: relative` + `margin: 0 auto`)되어 있어
|
|
33
|
+
* `left`/`top` 이 "정상 배치 위치 대비 상대 offset" 으로 해석된다.
|
|
34
|
+
* 드래그, 리사이즈는 절대 좌표로 계산하므로 시작 시점에 절대 좌표계로 전환한다.
|
|
35
|
+
* 전환만으로는 위치, 크기가 변하지 않으며, 이미 전환된 요소에 다시 적용해도 결과가 같다.
|
|
36
|
+
*/
|
|
37
|
+
export function pinDialogAbsolute(dialogEl: HTMLElement): { left: number; top: number } {
|
|
38
|
+
const wasRelative = getComputedStyle(dialogEl).position === "relative";
|
|
39
|
+
const parentEl = dialogEl.offsetParent as HTMLElement | null;
|
|
40
|
+
const beforeRect = dialogEl.getBoundingClientRect();
|
|
41
|
+
const roundedLeft = dialogEl.offsetLeft;
|
|
42
|
+
const roundedTop = dialogEl.offsetTop;
|
|
43
|
+
|
|
44
|
+
dialogEl.style.position = "absolute";
|
|
45
|
+
dialogEl.style.margin = "0";
|
|
46
|
+
dialogEl.style.right = "auto";
|
|
47
|
+
dialogEl.style.bottom = "auto";
|
|
48
|
+
dialogEl.style.left = `${roundedLeft}px`;
|
|
49
|
+
dialogEl.style.top = `${roundedTop}px`;
|
|
50
|
+
|
|
51
|
+
if (wasRelative && parentEl != null) {
|
|
52
|
+
// 백분율 크기의 기준 박스가 부모 content box 에서 offsetParent padding box 로 바뀐다.
|
|
53
|
+
const parentStyle = getComputedStyle(parentEl);
|
|
54
|
+
const padX = parseFloat(parentStyle.paddingLeft) + parseFloat(parentStyle.paddingRight);
|
|
55
|
+
const padY = parseFloat(parentStyle.paddingTop) + parseFloat(parentStyle.paddingBottom);
|
|
56
|
+
shiftPercentSize(dialogEl.style, "width", padX);
|
|
57
|
+
shiftPercentSize(dialogEl.style, "maxWidth", padX);
|
|
58
|
+
shiftPercentSize(dialogEl.style, "height", padY);
|
|
59
|
+
shiftPercentSize(dialogEl.style, "maxHeight", padY);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// offsetLeft/offsetTop 은 정수로 반올림되므로, 전환 전후 실제 위치 차이만큼 되돌린다.
|
|
63
|
+
const afterRect = dialogEl.getBoundingClientRect();
|
|
64
|
+
const pinnedLeft = roundedLeft - (afterRect.left - beforeRect.left);
|
|
65
|
+
const pinnedTop = roundedTop - (afterRect.top - beforeRect.top);
|
|
66
|
+
dialogEl.style.left = `${pinnedLeft}px`;
|
|
67
|
+
dialogEl.style.top = `${pinnedTop}px`;
|
|
68
|
+
|
|
69
|
+
return { left: pinnedLeft, top: pinnedTop };
|
|
70
|
+
}
|
|
71
|
+
|
|
10
72
|
export function injectDragResize(opt: DragResizeOptions): {
|
|
11
73
|
startDrag: (event: MouseEvent) => void;
|
|
12
74
|
startResize: (event: MouseEvent, dir: string) => void;
|
|
@@ -26,6 +88,8 @@ export function injectDragResize(opt: DragResizeOptions): {
|
|
|
26
88
|
startHeight: number;
|
|
27
89
|
startLeft: number;
|
|
28
90
|
startTop: number;
|
|
91
|
+
minWidth: number;
|
|
92
|
+
minHeight: number;
|
|
29
93
|
}
|
|
30
94
|
| undefined;
|
|
31
95
|
|
|
@@ -50,8 +114,8 @@ export function injectDragResize(opt: DragResizeOptions): {
|
|
|
50
114
|
|
|
51
115
|
const dx = event.clientX - resizeState.startX;
|
|
52
116
|
const dy = event.clientY - resizeState.startY;
|
|
53
|
-
const minW =
|
|
54
|
-
const minH =
|
|
117
|
+
const minW = resizeState.minWidth;
|
|
118
|
+
const minH = resizeState.minHeight;
|
|
55
119
|
|
|
56
120
|
let newWidth = resizeState.startWidth;
|
|
57
121
|
let newHeight = resizeState.startHeight;
|
|
@@ -105,25 +169,17 @@ export function injectDragResize(opt: DragResizeOptions): {
|
|
|
105
169
|
}
|
|
106
170
|
}
|
|
107
171
|
|
|
108
|
-
function getParentRect(dialogEl: HTMLElement): { left: number; top: number } {
|
|
109
|
-
return (dialogEl.offsetParent as HTMLElement | null)?.getBoundingClientRect() ?? {
|
|
110
|
-
left: 0,
|
|
111
|
-
top: 0,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
172
|
function startDrag(event: MouseEvent): void {
|
|
116
173
|
const dialogEl = opt.getDialogEl();
|
|
117
174
|
if (dialogEl == null) return;
|
|
118
175
|
|
|
119
|
-
const
|
|
120
|
-
const parentRect = getParentRect(dialogEl);
|
|
176
|
+
const pinned = pinDialogAbsolute(dialogEl);
|
|
121
177
|
|
|
122
178
|
dragState = {
|
|
123
179
|
startX: event.clientX,
|
|
124
180
|
startY: event.clientY,
|
|
125
|
-
startLeft:
|
|
126
|
-
startTop:
|
|
181
|
+
startLeft: pinned.left,
|
|
182
|
+
startTop: pinned.top,
|
|
127
183
|
};
|
|
128
184
|
document.addEventListener("mousemove", onDocumentMouseMove);
|
|
129
185
|
document.addEventListener("mouseup", onDocumentMouseUp);
|
|
@@ -133,8 +189,8 @@ export function injectDragResize(opt: DragResizeOptions): {
|
|
|
133
189
|
const dialogEl = opt.getDialogEl();
|
|
134
190
|
if (dialogEl == null) return;
|
|
135
191
|
|
|
136
|
-
const
|
|
137
|
-
const
|
|
192
|
+
const pinned = pinDialogAbsolute(dialogEl);
|
|
193
|
+
const dialogStyle = getComputedStyle(dialogEl);
|
|
138
194
|
|
|
139
195
|
resizeState = {
|
|
140
196
|
dir,
|
|
@@ -142,8 +198,11 @@ export function injectDragResize(opt: DragResizeOptions): {
|
|
|
142
198
|
startY: event.clientY,
|
|
143
199
|
startWidth: dialogEl.offsetWidth,
|
|
144
200
|
startHeight: dialogEl.offsetHeight,
|
|
145
|
-
startLeft:
|
|
146
|
-
startTop:
|
|
201
|
+
startLeft: pinned.left,
|
|
202
|
+
startTop: pinned.top,
|
|
203
|
+
// CSS 하한을 넘겨 줄이면 실제 크기는 안 줄고 반대쪽 변만 밀려나므로, 실제 제약을 하한으로 삼는다.
|
|
204
|
+
minWidth: Math.max(opt.minWidthPx() ?? 0, parseCssMinPx(dialogStyle.minWidth)),
|
|
205
|
+
minHeight: Math.max(opt.minHeightPx() ?? 0, parseCssMinPx(dialogStyle.minHeight)),
|
|
147
206
|
};
|
|
148
207
|
document.addEventListener("mousemove", onDocumentMouseMove);
|
|
149
208
|
document.addEventListener("mouseup", onDocumentMouseUp);
|
|
@@ -14,10 +14,7 @@ import {
|
|
|
14
14
|
} from "@angular/core";
|
|
15
15
|
import { outputToObservable } from "@angular/core/rxjs-interop";
|
|
16
16
|
import { Subscription } from "rxjs";
|
|
17
|
-
import type {
|
|
18
|
-
DirectiveInputSignals,
|
|
19
|
-
WithOptional,
|
|
20
|
-
} from "../directive-input-signals";
|
|
17
|
+
import type { DirectiveInputSignals, WithOptional } from "../directive-input-signals";
|
|
21
18
|
import { SdModal } from "./sd-modal";
|
|
22
19
|
import { SdActivatedModalProvider } from "./sd-activated-modal.provider";
|
|
23
20
|
import "@simplysm/core-browser";
|
|
@@ -18,7 +18,7 @@ import { tablerX } from "@ng-icons/tabler-icons";
|
|
|
18
18
|
import { SdActivatedModalProvider } from "./sd-activated-modal.provider";
|
|
19
19
|
import { SdSystemConfigProvider } from "../config/sd-system-config.provider";
|
|
20
20
|
import { injectFocusTrap } from "./injectFocusTrap";
|
|
21
|
-
import { injectDragResize } from "./injectDragResize";
|
|
21
|
+
import { injectDragResize, pinDialogAbsolute } from "./injectDragResize";
|
|
22
22
|
import { SdAnchor } from "../../controls/button/sd-anchor";
|
|
23
23
|
import { SdResizeDirective, type SdResizeEvent } from "../events/sd-resize";
|
|
24
24
|
import "@simplysm/core-browser";
|
|
@@ -49,6 +49,7 @@ import "@simplysm/core-browser";
|
|
|
49
49
|
<div
|
|
50
50
|
class="_dialog"
|
|
51
51
|
tabindex="-1"
|
|
52
|
+
(mousedown)="onDialogMouseDown()"
|
|
52
53
|
(keydown)="onDialogKeydown($event)"
|
|
53
54
|
(focus)="onDialogFocus()"
|
|
54
55
|
(sdResize)="onDialogResize($event)"
|
|
@@ -368,6 +369,10 @@ export class SdModal {
|
|
|
368
369
|
onEnd: () => void this._saveConfig().catch((err) => this._errorHandler.handleError(err)),
|
|
369
370
|
});
|
|
370
371
|
|
|
372
|
+
/** 화면 넘침 클램프로 덮기 직전의 크기. 클램프 중이 아니면 undefined. */
|
|
373
|
+
private _clampedWidth: string | undefined;
|
|
374
|
+
private _clampedHeight: string | undefined;
|
|
375
|
+
|
|
371
376
|
constructor() {
|
|
372
377
|
// data-sd-init: 첫 렌더 후 설정하여 CSS transition 트리거 허용
|
|
373
378
|
afterNextRender(() => {
|
|
@@ -391,6 +396,7 @@ export class SdModal {
|
|
|
391
396
|
} else {
|
|
392
397
|
dialogEl.style.height = "";
|
|
393
398
|
}
|
|
399
|
+
this._forgetClampedSize();
|
|
394
400
|
});
|
|
395
401
|
|
|
396
402
|
// key 기반 설정 복원
|
|
@@ -404,9 +410,15 @@ export class SdModal {
|
|
|
404
410
|
|
|
405
411
|
onResizeMouseDown(event: MouseEvent, dir: string): void {
|
|
406
412
|
event.preventDefault();
|
|
413
|
+
// 사용자가 직접 정하는 크기가 자동 클램프 이전 크기보다 우선한다.
|
|
414
|
+
this._forgetClampedSize();
|
|
407
415
|
this._dragResize.startResize(event, dir);
|
|
408
416
|
}
|
|
409
417
|
|
|
418
|
+
onDialogMouseDown(): void {
|
|
419
|
+
this._bringToFront();
|
|
420
|
+
}
|
|
421
|
+
|
|
410
422
|
onHeaderMouseDown(event: MouseEvent): void {
|
|
411
423
|
if (!this.movable()) return;
|
|
412
424
|
if ((event.target as HTMLElement).closest("button, sd-anchor") != null) return;
|
|
@@ -449,12 +461,20 @@ export class SdModal {
|
|
|
449
461
|
onWindowResize(): void {
|
|
450
462
|
const dialogEl = this._getDialogEl();
|
|
451
463
|
if (dialogEl == null) return;
|
|
464
|
+
// 위치를 잡지 않은 모달은 CSS 중앙 정렬이 창 크기를 따라가므로 보정 대상이 아니다.
|
|
465
|
+
if (dialogEl.style.left === "" && dialogEl.style.top === "") return;
|
|
466
|
+
this._clampIntoHost(dialogEl);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private _clampIntoHost(dialogEl: HTMLElement): void {
|
|
452
470
|
const hostEl = this._elRef.nativeElement;
|
|
453
|
-
|
|
454
|
-
|
|
471
|
+
const maxLeft = Math.max(0, hostEl.offsetWidth - 100);
|
|
472
|
+
if (dialogEl.offsetLeft > maxLeft) {
|
|
473
|
+
dialogEl.style.left = `${maxLeft}px`;
|
|
455
474
|
}
|
|
456
|
-
|
|
457
|
-
|
|
475
|
+
const maxTop = Math.max(0, hostEl.offsetHeight - 100);
|
|
476
|
+
if (dialogEl.offsetTop > maxTop) {
|
|
477
|
+
dialogEl.style.top = `${maxTop}px`;
|
|
458
478
|
}
|
|
459
479
|
}
|
|
460
480
|
|
|
@@ -482,23 +502,50 @@ export class SdModal {
|
|
|
482
502
|
hostEl.style.zIndex = String(maxZ + 1);
|
|
483
503
|
}
|
|
484
504
|
|
|
505
|
+
private _forgetClampedSize(): void {
|
|
506
|
+
this._clampedWidth = undefined;
|
|
507
|
+
this._clampedHeight = undefined;
|
|
508
|
+
}
|
|
509
|
+
|
|
485
510
|
private _calcHeight(): void {
|
|
486
511
|
const dialogEl = this._getDialogEl();
|
|
487
512
|
if (dialogEl == null) return;
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if (
|
|
513
|
+
|
|
514
|
+
// 클램프 중이면 dialog 높이가 호스트에 맞춰져 있어 실제 필요 높이를 알 수 없다. 먼저 되돌려 잰다.
|
|
515
|
+
if (this._clampedHeight != null) {
|
|
516
|
+
dialogEl.style.height = this._clampedHeight;
|
|
517
|
+
dialogEl.style.maxHeight = "";
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const hostEl = this._elRef.nativeElement;
|
|
521
|
+
const paddingTop = parseFloat(getComputedStyle(hostEl).paddingTop) || 0;
|
|
522
|
+
|
|
523
|
+
if (dialogEl.offsetHeight > hostEl.offsetHeight - paddingTop) {
|
|
524
|
+
this._clampedHeight ??= dialogEl.style.height;
|
|
491
525
|
dialogEl.style.maxHeight = "100%";
|
|
492
526
|
dialogEl.style.height = "100%";
|
|
527
|
+
} else {
|
|
528
|
+
this._clampedHeight = undefined;
|
|
493
529
|
}
|
|
494
530
|
}
|
|
495
531
|
|
|
496
532
|
private _calcWidth(): void {
|
|
497
533
|
const dialogEl = this._getDialogEl();
|
|
498
534
|
if (dialogEl == null) return;
|
|
499
|
-
|
|
535
|
+
|
|
536
|
+
if (this._clampedWidth != null) {
|
|
537
|
+
dialogEl.style.width = this._clampedWidth;
|
|
538
|
+
dialogEl.style.maxWidth = "";
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const hostEl = this._elRef.nativeElement;
|
|
542
|
+
|
|
543
|
+
if (dialogEl.offsetWidth > hostEl.offsetWidth) {
|
|
544
|
+
this._clampedWidth ??= dialogEl.style.width;
|
|
500
545
|
dialogEl.style.maxWidth = "100%";
|
|
501
546
|
dialogEl.style.width = "100%";
|
|
547
|
+
} else {
|
|
548
|
+
this._clampedWidth = undefined;
|
|
502
549
|
}
|
|
503
550
|
}
|
|
504
551
|
|
|
@@ -513,9 +560,13 @@ export class SdModal {
|
|
|
513
560
|
const dialogEl = this._getDialogEl();
|
|
514
561
|
if (dialogEl == null) return;
|
|
515
562
|
|
|
563
|
+
// 클램프 중이면 화면에 맞춘 값이 아니라 그 직전 크기를 저장해야 다시 열 때 원래 크기로 뜬다.
|
|
564
|
+
const savedWidth = this._clampedWidth ?? dialogEl.style.width;
|
|
565
|
+
const savedHeight = this._clampedHeight ?? dialogEl.style.height;
|
|
566
|
+
|
|
516
567
|
const config: Record<string, string> = {};
|
|
517
|
-
if (
|
|
518
|
-
if (
|
|
568
|
+
if (savedWidth !== "") config["width"] = savedWidth;
|
|
569
|
+
if (savedHeight !== "") config["height"] = savedHeight;
|
|
519
570
|
if (dialogEl.style.left !== "") config["left"] = dialogEl.style.left;
|
|
520
571
|
if (dialogEl.style.top !== "") config["top"] = dialogEl.style.top;
|
|
521
572
|
|
|
@@ -526,7 +577,8 @@ export class SdModal {
|
|
|
526
577
|
if (this._sdSystemConfig == null) return;
|
|
527
578
|
|
|
528
579
|
const config = (await this._sdSystemConfig.getAsync(`sd-modal.${k}`)) as
|
|
529
|
-
Record<string, string | undefined>
|
|
580
|
+
| Record<string, string | undefined>
|
|
581
|
+
| undefined;
|
|
530
582
|
if (config == null) return;
|
|
531
583
|
|
|
532
584
|
const dialogEl = this._getDialogEl();
|
|
@@ -534,7 +586,13 @@ export class SdModal {
|
|
|
534
586
|
|
|
535
587
|
if (config["width"] != null) dialogEl.style.width = config["width"];
|
|
536
588
|
if (config["height"] != null) dialogEl.style.height = config["height"];
|
|
537
|
-
|
|
538
|
-
|
|
589
|
+
this._forgetClampedSize();
|
|
590
|
+
|
|
591
|
+
if (config["left"] != null || config["top"] != null) {
|
|
592
|
+
pinDialogAbsolute(dialogEl);
|
|
593
|
+
if (config["left"] != null) dialogEl.style.left = config["left"];
|
|
594
|
+
if (config["top"] != null) dialogEl.style.top = config["top"];
|
|
595
|
+
this._clampIntoHost(dialogEl);
|
|
596
|
+
}
|
|
539
597
|
}
|
|
540
598
|
}
|