@simplysm/angular 14.0.6 → 14.0.7

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.
Files changed (57) hide show
  1. package/dist/core/directives/sd-router-link.directive.d.ts.map +1 -1
  2. package/dist/core/directives/sd-router-link.directive.js +5 -10
  3. package/dist/core/plugins/events/sd-resize-event.plugin.d.ts.map +1 -1
  4. package/dist/core/plugins/events/sd-resize-event.plugin.js +3 -0
  5. package/dist/core/plugins/sd-global-error-handler.plugin.d.ts +1 -0
  6. package/dist/core/plugins/sd-global-error-handler.plugin.d.ts.map +1 -1
  7. package/dist/core/plugins/sd-global-error-handler.plugin.js +4 -0
  8. package/dist/core/providers/sd-app-structure.provider.d.ts.map +1 -1
  9. package/dist/core/providers/sd-app-structure.provider.js +4 -1
  10. package/dist/core/providers/sd-navigate-window.provider.d.ts +2 -1
  11. package/dist/core/providers/sd-navigate-window.provider.d.ts.map +1 -1
  12. package/dist/core/providers/sd-navigate-window.provider.js +13 -10
  13. package/dist/core/utils/setups/setupCanDeactivate.d.ts.map +1 -1
  14. package/dist/core/utils/setups/setupCanDeactivate.js +9 -2
  15. package/dist/core/utils/setups/setupInvalid.d.ts.map +1 -1
  16. package/dist/core/utils/setups/setupInvalid.js +4 -0
  17. package/dist/core/utils/setups/setupModelHook.d.ts +1 -1
  18. package/dist/core/utils/setups/setupModelHook.d.ts.map +1 -1
  19. package/dist/core/utils/setups/setupModelHook.js +5 -1
  20. package/dist/core/utils/setups/setupRipple.d.ts.map +1 -1
  21. package/dist/core/utils/setups/setupRipple.js +3 -2
  22. package/dist/core/utils/useExpandingManager.d.ts.map +1 -1
  23. package/dist/core/utils/useExpandingManager.js +2 -1
  24. package/dist/core/utils/useSdSystemConfigResource.d.ts.map +1 -1
  25. package/dist/core/utils/useSdSystemConfigResource.js +6 -3
  26. package/dist/ui/form/editor/sd-tiptap-editor.control.d.ts +5 -5
  27. package/dist/ui/form/editor/sd-tiptap-editor.control.d.ts.map +1 -1
  28. package/dist/ui/form/editor/sd-tiptap-editor.control.js +79 -78
  29. package/dist/ui/form/input/sd-date-range.picker.d.ts.map +1 -1
  30. package/dist/ui/form/input/sd-date-range.picker.js +4 -3
  31. package/dist/ui/form/input/sd-textfield-type-handlers.d.ts.map +1 -1
  32. package/dist/ui/form/input/sd-textfield-type-handlers.js +21 -2
  33. package/dist/ui/form/input/sd-textfield.control.d.ts.map +1 -1
  34. package/dist/ui/form/input/sd-textfield.control.js +1 -0
  35. package/dist/ui/overlay/modal/sd-modal.control.js +1 -1
  36. package/dist/ui/overlay/modal/sd-modal.provider.d.ts +1 -1
  37. package/dist/ui/overlay/modal/sd-modal.provider.js +1 -1
  38. package/docs/providers.md +1 -1
  39. package/docs/setup-functions.md +1 -1
  40. package/package.json +7 -10
  41. package/src/core/directives/sd-router-link.directive.ts +5 -13
  42. package/src/core/plugins/events/sd-resize-event.plugin.ts +3 -0
  43. package/src/core/plugins/sd-global-error-handler.plugin.ts +4 -0
  44. package/src/core/providers/sd-app-structure.provider.ts +4 -1
  45. package/src/core/providers/sd-navigate-window.provider.ts +12 -13
  46. package/src/core/utils/setups/setupCanDeactivate.ts +10 -2
  47. package/src/core/utils/setups/setupInvalid.ts +5 -0
  48. package/src/core/utils/setups/setupModelHook.ts +5 -2
  49. package/src/core/utils/setups/setupRipple.ts +3 -2
  50. package/src/core/utils/useExpandingManager.ts +2 -1
  51. package/src/core/utils/useSdSystemConfigResource.ts +6 -3
  52. package/src/ui/form/editor/sd-tiptap-editor.control.ts +64 -61
  53. package/src/ui/form/input/sd-date-range.picker.ts +4 -3
  54. package/src/ui/form/input/sd-textfield-type-handlers.ts +25 -5
  55. package/src/ui/form/input/sd-textfield.control.ts +1 -0
  56. package/src/ui/overlay/modal/sd-modal.control.ts +1 -1
  57. package/src/ui/overlay/modal/sd-modal.provider.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { ElementRef, inject, reflectComponentType } from "@angular/core";
1
+ import { DestroyRef, ElementRef, inject, reflectComponentType } from "@angular/core";
2
2
  import { ActivatedRoute, type CanDeactivateFn } from "@angular/router";
3
3
  import { SdActivatedModalProvider } from "../../../ui/overlay/modal/sd-modal.provider";
4
4
 
@@ -8,7 +8,7 @@ export function setupCanDeactivate(fn: () => boolean): void {
8
8
  const elRef = inject(ElementRef);
9
9
 
10
10
  if (activatedModal != null) {
11
- activatedModal.canDeactivefn = fn;
11
+ activatedModal.canDeactiveFn = fn;
12
12
  return;
13
13
  }
14
14
 
@@ -30,5 +30,13 @@ export function setupCanDeactivate(fn: () => boolean): void {
30
30
  activatedRoute.routeConfig.canDeactivate = [];
31
31
  }
32
32
  activatedRoute.routeConfig.canDeactivate.push(canDeactivateFn);
33
+
34
+ const destroyRef = inject(DestroyRef);
35
+ destroyRef.onDestroy(() => {
36
+ const idx = activatedRoute.routeConfig!.canDeactivate!.indexOf(canDeactivateFn);
37
+ if (idx !== -1) {
38
+ activatedRoute.routeConfig!.canDeactivate!.splice(idx, 1);
39
+ }
40
+ });
33
41
  }
34
42
  }
@@ -26,6 +26,11 @@ export function setupInvalid(getInvalidMessage: () => string): void {
26
26
  setSafeStyle(renderer, indicatorEl, { display: isInvalid ? "block" : "none" });
27
27
  });
28
28
 
29
+ destroyRef.onDestroy(() => {
30
+ indicatorEl.remove();
31
+ inputEl.remove();
32
+ });
33
+
29
34
  const formEl = inputEl.form;
30
35
  if (formEl == null) return;
31
36
 
@@ -1,9 +1,10 @@
1
- import type { Signal, WritableSignal } from "@angular/core";
1
+ import { ErrorHandler, inject, type Signal, type WritableSignal } from "@angular/core";
2
2
 
3
3
  export function setupModelHook<T, S extends WritableSignal<T>>(
4
4
  model: S,
5
5
  canFn: Signal<(item: T) => boolean | Promise<boolean>>,
6
6
  ): void {
7
+ const errorHandler = inject(ErrorHandler);
7
8
  const orgSet = model.set;
8
9
  model.set = (value) => {
9
10
  const canSet = canFn()(value);
@@ -21,6 +22,8 @@ export function setupModelHook<T, S extends WritableSignal<T>>(
21
22
  if (allowed !== false) {
22
23
  orgSet(value);
23
24
  }
24
- }).catch(() => {});
25
+ }).catch((err) => {
26
+ errorHandler.handleError(err);
27
+ });
25
28
  };
26
29
  }
@@ -44,9 +44,10 @@ export function setupRipple(enableFn?: () => boolean): void {
44
44
  });
45
45
  el.appendChild(indicatorEl);
46
46
 
47
- indicatorEl.ontransitionend = () => {
48
- if (indicatorEl !== undefined && getComputedStyle(indicatorEl).opacity === "0") {
47
+ indicatorEl.ontransitionend = (ev: TransitionEvent) => {
48
+ if (ev.propertyName === "opacity" && indicatorEl !== undefined) {
49
49
  indicatorEl.remove();
50
+ indicatorEl = undefined;
50
51
  }
51
52
  };
52
53
 
@@ -86,9 +86,10 @@ export function useExpandingManager<T>(binding: {
86
86
  const itemDef = map.get(item);
87
87
  if (itemDef == null) return false;
88
88
 
89
+ const expandedSet = new Set(binding.expandedItems());
89
90
  let current = itemDef.parentDef;
90
91
  while (current != null) {
91
- if (!binding.expandedItems().includes(current.item)) {
92
+ if (!expandedSet.has(current.item)) {
92
93
  return false;
93
94
  }
94
95
  current = current.parentDef;
@@ -1,9 +1,10 @@
1
- import { ElementRef, inject, type ResourceRef, resource, type Signal } from "@angular/core";
1
+ import { ElementRef, ErrorHandler, inject, type ResourceRef, resource, type Signal } from "@angular/core";
2
2
  import { SdSystemConfigProvider } from "../providers/sd-system-config.provider";
3
3
 
4
4
  export function useSdSystemConfigResource<T>(options: { key: Signal<string | undefined> }) {
5
5
  const sdSystemConfig = inject<SdSystemConfigProvider<Record<string, T>>>(SdSystemConfigProvider);
6
6
  const elRef = inject(ElementRef);
7
+ const errorHandler = inject(ErrorHandler);
7
8
 
8
9
  const elTag = elRef.nativeElement.tagName.toLowerCase();
9
10
 
@@ -24,8 +25,10 @@ export function useSdSystemConfigResource<T>(options: { key: Signal<string | und
24
25
  res.set(value);
25
26
  const key = options.key();
26
27
  if (key == null) return;
27
- queueMicrotask(async () => {
28
- await sdSystemConfig.setAsync(`${elTag}.${key}`, value as T);
28
+ queueMicrotask(() => {
29
+ sdSystemConfig.setAsync(`${elTag}.${key}`, value as T).catch((err) => {
30
+ errorHandler.handleError(err);
31
+ });
29
32
  });
30
33
  },
31
34
  update(fn: (prev: T | undefined) => T | undefined) {
@@ -9,8 +9,10 @@ import {
9
9
  inject,
10
10
  input,
11
11
  model,
12
+ signal,
12
13
  untracked,
13
14
  ViewEncapsulation,
15
+ type WritableSignal,
14
16
  } from "@angular/core";
15
17
  import { setupInvalid } from "../../../core/utils/setups/setupInvalid";
16
18
  import { Editor, type AnyExtension } from "@tiptap/core";
@@ -41,55 +43,55 @@ const DEFAULT_EXTENSIONS: AnyExtension[] = [
41
43
  @if (!disabled()) {
42
44
  <div class="_toolbar">
43
45
  <div class="_btn-group">
44
- <button type="button" data-cmd="h1" [class._active]="activeStates.h1"
46
+ <button type="button" data-cmd="h1" [class._active]="activeStates().h1"
45
47
  (click)="execCmd('h1')">H1</button>
46
- <button type="button" data-cmd="h2" [class._active]="activeStates.h2"
48
+ <button type="button" data-cmd="h2" [class._active]="activeStates().h2"
47
49
  (click)="execCmd('h2')">H2</button>
48
50
  </div>
49
51
  <div class="_btn-group">
50
- <button type="button" data-cmd="bold" [class._active]="activeStates.bold"
52
+ <button type="button" data-cmd="bold" [class._active]="activeStates().bold"
51
53
  (click)="execCmd('bold')">B</button>
52
- <button type="button" data-cmd="italic" [class._active]="activeStates.italic"
54
+ <button type="button" data-cmd="italic" [class._active]="activeStates().italic"
53
55
  (click)="execCmd('italic')">I</button>
54
- <button type="button" data-cmd="underline" [class._active]="activeStates.underline"
56
+ <button type="button" data-cmd="underline" [class._active]="activeStates().underline"
55
57
  (click)="execCmd('underline')">U</button>
56
- <button type="button" data-cmd="strike" [class._active]="activeStates.strike"
58
+ <button type="button" data-cmd="strike" [class._active]="activeStates().strike"
57
59
  (click)="execCmd('strike')">S</button>
58
60
  </div>
59
61
  <div class="_btn-group">
60
62
  <button type="button" data-cmd="textColor" class="_color-btn"
61
63
  (click)="toggleColorPicker('text')">
62
- <span class="_color-indicator" [style.background-color]="activeColor"></span>
64
+ <span class="_color-indicator" [style.background-color]="activeColor()"></span>
63
65
  A
64
66
  </button>
65
67
  <button type="button" data-cmd="bgColor" class="_color-btn"
66
68
  (click)="toggleColorPicker('bg')">
67
- <span class="_color-indicator" [style.background-color]="activeBgColor"></span>
69
+ <span class="_color-indicator" [style.background-color]="activeBgColor()"></span>
68
70
  BG
69
71
  </button>
70
72
  </div>
71
73
  <div class="_btn-group">
72
- <button type="button" data-cmd="bulletList" [class._active]="activeStates.bulletList"
74
+ <button type="button" data-cmd="bulletList" [class._active]="activeStates().bulletList"
73
75
  (click)="execCmd('bulletList')">UL</button>
74
- <button type="button" data-cmd="orderedList" [class._active]="activeStates.orderedList"
76
+ <button type="button" data-cmd="orderedList" [class._active]="activeStates().orderedList"
75
77
  (click)="execCmd('orderedList')">OL</button>
76
78
  <button type="button" data-cmd="indent" (click)="execCmd('indent')">→</button>
77
79
  <button type="button" data-cmd="outdent" (click)="execCmd('outdent')">←</button>
78
80
  </div>
79
81
  <div class="_btn-group">
80
- <button type="button" data-cmd="blockquote" [class._active]="activeStates.blockquote"
82
+ <button type="button" data-cmd="blockquote" [class._active]="activeStates().blockquote"
81
83
  (click)="execCmd('blockquote')">❝</button>
82
- <button type="button" data-cmd="codeBlock" [class._active]="activeStates.codeBlock"
84
+ <button type="button" data-cmd="codeBlock" [class._active]="activeStates().codeBlock"
83
85
  (click)="execCmd('codeBlock')">&lt;/&gt;</button>
84
86
  </div>
85
87
  <div class="_btn-group">
86
- <button type="button" data-cmd="alignLeft" [class._active]="activeStates.alignLeft"
88
+ <button type="button" data-cmd="alignLeft" [class._active]="activeStates().alignLeft"
87
89
  (click)="execCmd('alignLeft')">≡L</button>
88
- <button type="button" data-cmd="alignCenter" [class._active]="activeStates.alignCenter"
90
+ <button type="button" data-cmd="alignCenter" [class._active]="activeStates().alignCenter"
89
91
  (click)="execCmd('alignCenter')">≡C</button>
90
- <button type="button" data-cmd="alignRight" [class._active]="activeStates.alignRight"
92
+ <button type="button" data-cmd="alignRight" [class._active]="activeStates().alignRight"
91
93
  (click)="execCmd('alignRight')">≡R</button>
92
- <button type="button" data-cmd="alignJustify" [class._active]="activeStates.alignJustify"
94
+ <button type="button" data-cmd="alignJustify" [class._active]="activeStates().alignJustify"
93
95
  (click)="execCmd('alignJustify')">≡J</button>
94
96
  </div>
95
97
  <div class="_btn-group">
@@ -249,13 +251,13 @@ export class SdTiptapEditorControl {
249
251
  "#f9cb9c", "#ffe599", "#b6d7a8", "#a2c4c9", "#a4c2f4", "#9fc5e8", "#b4a7d6", "#d5a6bd",
250
252
  ];
251
253
 
252
- activeStates: Record<string, boolean> = {};
253
- activeColor = "";
254
- activeBgColor = "";
254
+ activeStates: WritableSignal<Record<string, boolean>> = signal({});
255
+ activeColor = signal("");
256
+ activeBgColor = signal("");
255
257
  colorPickerMode: "text" | "bg" | undefined;
256
258
 
257
259
  /** @internal -- TipTap Editor 인스턴스. 테스트 및 고급 사용자용 */
258
- editor: Editor | undefined;
260
+ editor: WritableSignal<Editor | undefined> = signal(undefined);
259
261
  private updatingFromEditor = false;
260
262
  private lastExtensions: AnyExtension[] | undefined;
261
263
 
@@ -288,17 +290,19 @@ export class SdTiptapEditorControl {
288
290
  }
289
291
 
290
292
  // Sync value to existing editor
291
- if (this.editor == null) return;
292
- const currentHtml = this.getEditorHtml();
293
+ const currentEditor = untracked(() => this.editor());
294
+ if (currentEditor == null) return;
295
+ const currentHtml = this.getEditorHtmlFrom(currentEditor);
293
296
  if (currentHtml === val) return;
294
- this.editor.commands.setContent(val ?? "", { emitUpdate: false });
297
+ currentEditor.commands.setContent(val ?? "", { emitUpdate: false });
295
298
  });
296
299
 
297
300
  // disabled/readonly → editor.setEditable()
298
301
  effect(() => {
299
- if (this.editor == null) return;
302
+ const ed = this.editor();
303
+ if (ed == null) return;
300
304
  const editable = !this.disabled() && !this.readonly();
301
- this.editor.setEditable(editable);
305
+ ed.setEditable(editable);
302
306
  });
303
307
 
304
308
  // setupInvalid for form validation
@@ -329,7 +333,7 @@ export class SdTiptapEditorControl {
329
333
  const container = this.elRef.nativeElement.querySelector("._editor-container");
330
334
  if (container == null) return;
331
335
 
332
- this.editor = new Editor({
336
+ this.editor.set(new Editor({
333
337
  element: container,
334
338
  extensions,
335
339
  content: initialContent ?? "",
@@ -343,21 +347,17 @@ export class SdTiptapEditorControl {
343
347
  onTransaction: () => {
344
348
  this.refreshActiveStates();
345
349
  },
346
- });
350
+ }));
347
351
  }
348
352
 
349
353
  private destroyEditor(): void {
350
- if (this.editor != null) {
351
- this.editor.destroy();
352
- this.editor = undefined;
354
+ const ed = untracked(() => this.editor());
355
+ if (ed != null) {
356
+ ed.destroy();
357
+ this.editor.set(undefined);
353
358
  }
354
359
  }
355
360
 
356
- private getEditorHtml(): string | undefined {
357
- if (this.editor == null) return undefined;
358
- return this.getEditorHtmlFrom(this.editor);
359
- }
360
-
361
361
  private getEditorHtmlFrom(editor: Editor): string | undefined {
362
362
  const html = editor.getHTML();
363
363
  if (editor.isEmpty) return undefined;
@@ -365,9 +365,10 @@ export class SdTiptapEditorControl {
365
365
  }
366
366
 
367
367
  execCmd(cmd: string): void {
368
- if (this.editor == null) return;
368
+ const ed = this.editor();
369
+ if (ed == null) return;
369
370
 
370
- const chain = this.editor.chain().focus();
371
+ const chain = ed.chain().focus();
371
372
  switch (cmd) {
372
373
  case "bold":
373
374
  chain.toggleBold().run();
@@ -428,9 +429,10 @@ export class SdTiptapEditorControl {
428
429
  }
429
430
 
430
431
  applyColor(color: string | undefined): void {
431
- if (this.editor == null) return;
432
+ const ed = this.editor();
433
+ if (ed == null) return;
432
434
 
433
- const chain = this.editor.chain().focus();
435
+ const chain = ed.chain().focus();
434
436
  if (this.colorPickerMode === "text") {
435
437
  if (color !== undefined) {
436
438
  chain.setColor(color).run();
@@ -448,27 +450,28 @@ export class SdTiptapEditorControl {
448
450
  }
449
451
 
450
452
  private refreshActiveStates(): void {
451
- if (this.editor == null) return;
452
- this.activeStates = {
453
- bold: this.editor.isActive("bold"),
454
- italic: this.editor.isActive("italic"),
455
- underline: this.editor.isActive("underline"),
456
- strike: this.editor.isActive("strike"),
457
- h1: this.editor.isActive("heading", { level: 1 }),
458
- h2: this.editor.isActive("heading", { level: 2 }),
459
- bulletList: this.editor.isActive("bulletList"),
460
- orderedList: this.editor.isActive("orderedList"),
461
- blockquote: this.editor.isActive("blockquote"),
462
- codeBlock: this.editor.isActive("codeBlock"),
463
- alignLeft: this.editor.isActive({ textAlign: "left" }),
464
- alignCenter: this.editor.isActive({ textAlign: "center" }),
465
- alignRight: this.editor.isActive({ textAlign: "right" }),
466
- alignJustify: this.editor.isActive({ textAlign: "justify" }),
467
- };
468
-
469
- const textColor = this.editor.getAttributes("textStyle")["color"];
470
- this.activeColor = typeof textColor === "string" ? textColor : "";
471
- const bgColor = this.editor.getAttributes("highlight")["color"];
472
- this.activeBgColor = typeof bgColor === "string" ? bgColor : "";
453
+ const ed = this.editor();
454
+ if (ed == null) return;
455
+ this.activeStates.set({
456
+ bold: ed.isActive("bold"),
457
+ italic: ed.isActive("italic"),
458
+ underline: ed.isActive("underline"),
459
+ strike: ed.isActive("strike"),
460
+ h1: ed.isActive("heading", { level: 1 }),
461
+ h2: ed.isActive("heading", { level: 2 }),
462
+ bulletList: ed.isActive("bulletList"),
463
+ orderedList: ed.isActive("orderedList"),
464
+ blockquote: ed.isActive("blockquote"),
465
+ codeBlock: ed.isActive("codeBlock"),
466
+ alignLeft: ed.isActive({ textAlign: "left" }),
467
+ alignCenter: ed.isActive({ textAlign: "center" }),
468
+ alignRight: ed.isActive({ textAlign: "right" }),
469
+ alignJustify: ed.isActive({ textAlign: "justify" }),
470
+ });
471
+
472
+ const textColor = ed.getAttributes("textStyle")["color"];
473
+ this.activeColor.set(typeof textColor === "string" ? textColor : "");
474
+ const bgColor = ed.getAttributes("highlight")["color"];
475
+ this.activeBgColor.set(typeof bgColor === "string" ? bgColor : "");
473
476
  }
474
477
  }
@@ -70,8 +70,9 @@ export class SdDateRangePicker {
70
70
  if (this.periodType() === "월") {
71
71
  const fromDate = this.from();
72
72
  if (fromDate) {
73
- this.from.set(fromDate.setDay(1));
74
- this.to.set(fromDate.addMonths(1).setDay(1).addDays(-1));
73
+ const firstOfMonth = fromDate.setDay(1);
74
+ this.from.set(firstOfMonth);
75
+ this.to.set(firstOfMonth.addMonths(1).addDays(-1));
75
76
  } else {
76
77
  this.to.set(undefined);
77
78
  }
@@ -83,7 +84,7 @@ export class SdDateRangePicker {
83
84
  handleFromDateChanged(): void {
84
85
  if (this.periodType() === "월") {
85
86
  const fromDate = this.from();
86
- this.to.set(fromDate?.addMonths(1).setDay(1).addDays(-1));
87
+ this.to.set(fromDate?.setDay(1).addMonths(1).addDays(-1));
87
88
  } else if (this.periodType() === "일") {
88
89
  this.to.set(this.from());
89
90
  } else if (
@@ -103,7 +103,7 @@ function createStringHandler(type: "text" | "password" | "email" | "color"): ITe
103
103
  return errors;
104
104
  }
105
105
 
106
- if (type === "text") {
106
+ if (type !== "color") {
107
107
  const str = value as string;
108
108
  if (opts.minlength !== undefined && opts.minlength > str.length) {
109
109
  errors.push(`문자의 길이가 ${opts.minlength}보다 길거나 같아야 합니다.`);
@@ -213,10 +213,10 @@ function createFormatHandler(): ITextfieldTypeHandler {
213
213
  (v, i, a) => a.indexOf(v) === i,
214
214
  );
215
215
  if (nonFormatChars != null && nonFormatChars.length > 0) {
216
- return raw.replace(
217
- new RegExp(`[${nonFormatChars.map((item) => "\\" + item).join("")}]`, "g"),
218
- "",
219
- );
216
+ const escaped = nonFormatChars.map((ch) =>
217
+ ch === "]" || ch === "\\" || ch === "^" || ch === "-" ? "\\" + ch : ch,
218
+ ).join("");
219
+ return raw.replace(new RegExp(`[${escaped}]`, "g"), "");
220
220
  }
221
221
  return raw;
222
222
  },
@@ -371,6 +371,16 @@ function createDateTimeHandler(withSeconds: boolean): ITextfieldTypeHandler {
371
371
 
372
372
  if (!(value instanceof DateTime)) {
373
373
  errors.push("날짜 및 시간을 입력하세요");
374
+ return errors;
375
+ }
376
+
377
+ const min = opts.min;
378
+ const max = opts.max;
379
+ if (min instanceof DateTime && min.tick > value.tick) {
380
+ errors.push(`${min}보다 크거나 같아야 합니다.`);
381
+ }
382
+ if (max instanceof DateTime && max.tick < value.tick) {
383
+ errors.push(`${max}보다 작거나 같아야 합니다.`);
374
384
  }
375
385
 
376
386
  return errors;
@@ -415,6 +425,16 @@ function createTimeHandler(withSeconds: boolean): ITextfieldTypeHandler {
415
425
 
416
426
  if (!(value instanceof Time)) {
417
427
  errors.push("시간을 입력하세요");
428
+ return errors;
429
+ }
430
+
431
+ const min = opts.min;
432
+ const max = opts.max;
433
+ if (min instanceof Time && min.tick > value.tick) {
434
+ errors.push(`${min}보다 크거나 같아야 합니다.`);
435
+ }
436
+ if (max instanceof Time && max.tick < value.tick) {
437
+ errors.push(`${max}보다 작거나 같아야 합니다.`);
418
438
  }
419
439
 
420
440
  return errors;
@@ -379,6 +379,7 @@ export class SdTextfieldControl<K extends keyof TSdTextfieldTypes> {
379
379
  }
380
380
 
381
381
  onInputPaste(event: ClipboardEvent): void {
382
+ event.preventDefault();
382
383
  const text = event.clipboardData?.getData("text/plain").trim();
383
384
  if (text == null || text === "") {
384
385
  this.value.set(undefined);
@@ -208,7 +208,7 @@ export class SdModalControl {
208
208
  }
209
209
 
210
210
  private _requestClose(): void {
211
- if (this._activatedModal !== null && !this._activatedModal.canDeactivefn()) {
211
+ if (this._activatedModal !== null && !this._activatedModal.canDeactiveFn()) {
212
212
  return;
213
213
  }
214
214
  void this._saveConfig();
@@ -65,7 +65,7 @@ export interface ISdModalOptions {
65
65
  export class SdActivatedModalProvider<T extends ISdModal<any> = ISdModal<any>> {
66
66
  modalComponent = signal<any>(undefined);
67
67
  contentComponent = signal<T | undefined>(undefined);
68
- canDeactivefn: () => boolean = () => true;
68
+ canDeactiveFn: () => boolean = () => true;
69
69
  }
70
70
 
71
71
  /**