@simplysm/angular 14.0.6 → 14.0.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.
Files changed (105) 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/injectParent.d.ts +7 -0
  14. package/dist/core/utils/injectParent.d.ts.map +1 -0
  15. package/dist/core/utils/injectParent.js +26 -0
  16. package/dist/core/utils/setups/setupCanDeactivate.d.ts.map +1 -1
  17. package/dist/core/utils/setups/setupCanDeactivate.js +9 -2
  18. package/dist/core/utils/setups/setupInvalid.d.ts.map +1 -1
  19. package/dist/core/utils/setups/setupInvalid.js +4 -0
  20. package/dist/core/utils/setups/setupModelHook.d.ts +1 -1
  21. package/dist/core/utils/setups/setupModelHook.d.ts.map +1 -1
  22. package/dist/core/utils/setups/setupModelHook.js +5 -1
  23. package/dist/core/utils/setups/setupRipple.d.ts.map +1 -1
  24. package/dist/core/utils/setups/setupRipple.js +3 -2
  25. package/dist/core/utils/useExpandingManager.d.ts.map +1 -1
  26. package/dist/core/utils/useExpandingManager.js +2 -1
  27. package/dist/core/utils/useSdSystemConfigResource.d.ts.map +1 -1
  28. package/dist/core/utils/useSdSystemConfigResource.js +6 -3
  29. package/dist/features/base/sd-base-container.control.d.ts +26 -0
  30. package/dist/features/base/sd-base-container.control.d.ts.map +1 -0
  31. package/dist/features/base/sd-base-container.control.js +183 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +3 -0
  35. package/dist/ui/data/list/sd-list-item.control.js +1 -1
  36. package/dist/ui/data/sheet/sd-sheet.control.d.ts +2 -1
  37. package/dist/ui/data/sheet/sd-sheet.control.d.ts.map +1 -1
  38. package/dist/ui/data/sheet/sd-sheet.control.js +16 -13
  39. package/dist/ui/data/sheet/useSheetCellAgent.d.ts.map +1 -1
  40. package/dist/ui/data/sheet/useSheetCellAgent.js +1 -2
  41. package/dist/ui/data/sheet/useSheetLayoutEngine.d.ts.map +1 -1
  42. package/dist/ui/data/sheet/useSheetLayoutEngine.js +1 -0
  43. package/dist/ui/form/editor/sd-tiptap-editor.control.d.ts +5 -5
  44. package/dist/ui/form/editor/sd-tiptap-editor.control.d.ts.map +1 -1
  45. package/dist/ui/form/editor/sd-tiptap-editor.control.js +79 -78
  46. package/dist/ui/form/input/sd-date-range.picker.d.ts.map +1 -1
  47. package/dist/ui/form/input/sd-date-range.picker.js +4 -3
  48. package/dist/ui/form/input/sd-textfield-type-handlers.d.ts.map +1 -1
  49. package/dist/ui/form/input/sd-textfield-type-handlers.js +21 -2
  50. package/dist/ui/form/input/sd-textfield.control.d.ts.map +1 -1
  51. package/dist/ui/form/input/sd-textfield.control.js +1 -0
  52. package/dist/ui/layout/dock/sd-dock.control.d.ts +2 -0
  53. package/dist/ui/layout/dock/sd-dock.control.d.ts.map +1 -1
  54. package/dist/ui/layout/dock/sd-dock.control.js +13 -4
  55. package/dist/ui/layout/kanban/sd-kanban-lane.control.d.ts.map +1 -1
  56. package/dist/ui/layout/kanban/sd-kanban-lane.control.js +7 -1
  57. package/dist/ui/layout/sd-gap.control.d.ts +2 -2
  58. package/dist/ui/navigation/pagination/sd-pagination.control.d.ts.map +1 -1
  59. package/dist/ui/navigation/pagination/sd-pagination.control.js +7 -3
  60. package/dist/ui/navigation/sidebar/sd-sidebar-container.control.js +1 -1
  61. package/dist/ui/overlay/dropdown/sd-dropdown-popup.control.js +1 -1
  62. package/dist/ui/overlay/modal/sd-modal.control.d.ts +1 -0
  63. package/dist/ui/overlay/modal/sd-modal.control.d.ts.map +1 -1
  64. package/dist/ui/overlay/modal/sd-modal.control.js +11 -3
  65. package/dist/ui/overlay/modal/sd-modal.provider.d.ts +1 -1
  66. package/dist/ui/overlay/modal/sd-modal.provider.js +1 -1
  67. package/dist/ui/overlay/toast/sd-toast.provider.d.ts.map +1 -1
  68. package/dist/ui/overlay/toast/sd-toast.provider.js +7 -3
  69. package/dist/ui/visual/sd-progress.control.d.ts +1 -0
  70. package/dist/ui/visual/sd-progress.control.d.ts.map +1 -1
  71. package/dist/ui/visual/sd-progress.control.js +5 -4
  72. package/docs/providers.md +1 -1
  73. package/docs/setup-functions.md +1 -1
  74. package/package.json +7 -10
  75. package/src/core/directives/sd-router-link.directive.ts +5 -13
  76. package/src/core/plugins/events/sd-resize-event.plugin.ts +3 -0
  77. package/src/core/plugins/sd-global-error-handler.plugin.ts +4 -0
  78. package/src/core/providers/sd-app-structure.provider.ts +4 -1
  79. package/src/core/providers/sd-navigate-window.provider.ts +12 -13
  80. package/src/core/utils/injectParent.ts +41 -0
  81. package/src/core/utils/setups/setupCanDeactivate.ts +10 -2
  82. package/src/core/utils/setups/setupInvalid.ts +5 -0
  83. package/src/core/utils/setups/setupModelHook.ts +5 -2
  84. package/src/core/utils/setups/setupRipple.ts +3 -2
  85. package/src/core/utils/useExpandingManager.ts +2 -1
  86. package/src/core/utils/useSdSystemConfigResource.ts +6 -3
  87. package/src/features/base/sd-base-container.control.ts +113 -0
  88. package/src/index.ts +4 -0
  89. package/src/ui/data/list/sd-list-item.control.ts +1 -1
  90. package/src/ui/data/sheet/sd-sheet.control.ts +17 -13
  91. package/src/ui/data/sheet/useSheetCellAgent.ts +1 -2
  92. package/src/ui/data/sheet/useSheetLayoutEngine.ts +1 -0
  93. package/src/ui/form/editor/sd-tiptap-editor.control.ts +64 -61
  94. package/src/ui/form/input/sd-date-range.picker.ts +4 -3
  95. package/src/ui/form/input/sd-textfield-type-handlers.ts +25 -5
  96. package/src/ui/form/input/sd-textfield.control.ts +1 -0
  97. package/src/ui/layout/dock/sd-dock.control.ts +15 -2
  98. package/src/ui/layout/kanban/sd-kanban-lane.control.ts +6 -3
  99. package/src/ui/navigation/pagination/sd-pagination.control.ts +5 -3
  100. package/src/ui/navigation/sidebar/sd-sidebar-container.control.ts +1 -1
  101. package/src/ui/overlay/dropdown/sd-dropdown-popup.control.ts +1 -1
  102. package/src/ui/overlay/modal/sd-modal.control.ts +10 -1
  103. package/src/ui/overlay/modal/sd-modal.provider.ts +1 -1
  104. package/src/ui/overlay/toast/sd-toast.provider.ts +9 -3
  105. package/src/ui/visual/sd-progress.control.ts +4 -1
@@ -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);
@@ -2,6 +2,7 @@ import {
2
2
  booleanAttribute,
3
3
  ChangeDetectionStrategy,
4
4
  Component,
5
+ DestroyRef,
5
6
  effect,
6
7
  ElementRef,
7
8
  inject,
@@ -87,6 +88,9 @@ import type { ISdResizeEvent } from "../../../core/plugins/events/sd-resize-even
87
88
  })
88
89
  export class SdDockControl {
89
90
  private readonly _elRef = inject(ElementRef<HTMLElement>);
91
+ private readonly _destroyRef = inject(DestroyRef);
92
+
93
+ private _dragCleanup: (() => void) | undefined;
90
94
 
91
95
  key = input<string>();
92
96
  position = input<"top" | "bottom" | "right" | "left">("top");
@@ -97,6 +101,10 @@ export class SdDockControl {
97
101
  private readonly _config = useSdSystemConfigResource<{ size?: string }>({ key: this.key });
98
102
 
99
103
  constructor() {
104
+ this._destroyRef.onDestroy(() => {
105
+ this._dragCleanup?.();
106
+ });
107
+
100
108
  effect(() => {
101
109
  const conf = this._config.value();
102
110
  if (this.resizable() && conf && conf.size != null) {
@@ -151,8 +159,7 @@ export class SdDockControl {
151
159
  e.stopPropagation();
152
160
  e.preventDefault();
153
161
 
154
- document.removeEventListener("mousemove", doDrag);
155
- document.removeEventListener("mouseup", stopDrag);
162
+ this._dragCleanup?.();
156
163
 
157
164
  if (this.key() != null) {
158
165
  const newConf: { size?: string } = {};
@@ -167,5 +174,11 @@ export class SdDockControl {
167
174
 
168
175
  document.addEventListener("mousemove", doDrag);
169
176
  document.addEventListener("mouseup", stopDrag);
177
+
178
+ this._dragCleanup = () => {
179
+ document.removeEventListener("mousemove", doDrag);
180
+ document.removeEventListener("mouseup", stopDrag);
181
+ this._dragCleanup = undefined;
182
+ };
170
183
  }
171
184
  }
@@ -136,9 +136,10 @@ export class SdKanbanLaneControl<L, T> implements ISdKanbanDropTarget<L, T> {
136
136
  toolTplRef = contentChild<any, TemplateRef<void>>("toolTpl", { read: TemplateRef });
137
137
  titleTplRef = contentChild<any, TemplateRef<void>>("titleTpl", { read: TemplateRef });
138
138
 
139
- isAllSelected = computed(() =>
140
- this.kanbanControls().length > 0 && this.kanbanControls().every((ctrl) => ctrl.selected()),
141
- );
139
+ isAllSelected = computed(() => {
140
+ const selectableControls = this.kanbanControls().filter((ctrl) => ctrl.selectable());
141
+ return selectableControls.length > 0 && selectableControls.every((ctrl) => ctrl.selected());
142
+ });
142
143
 
143
144
  dragKanban = computed(() => this._boardControl.dragKanban());
144
145
 
@@ -161,6 +162,7 @@ export class SdKanbanLaneControl<L, T> implements ISdKanbanDropTarget<L, T> {
161
162
  this._boardControl.selectedValues.update((v) => {
162
163
  const r = [...v];
163
164
  for (const ctrl of this.kanbanControls()) {
165
+ if (!ctrl.selectable()) continue;
164
166
  if (ctrl.value() == null) continue;
165
167
  if (!v.includes(ctrl.value()!)) {
166
168
  r.push(ctrl.value()!);
@@ -171,6 +173,7 @@ export class SdKanbanLaneControl<L, T> implements ISdKanbanDropTarget<L, T> {
171
173
  } else {
172
174
  this._boardControl.selectedValues.update((v) => {
173
175
  const kanbanValues = this.kanbanControls()
176
+ .filter((ctrl) => ctrl.selectable())
174
177
  .map((ctrl) => ctrl.value())
175
178
  .filter((v2): v2 is T => v2 != null);
176
179
  const result = v.filter((item) => !kanbanValues.includes(item));
@@ -67,7 +67,7 @@ export class SdPaginationControl {
67
67
  visiblePageCount = input(10, { transform: numberAttribute });
68
68
 
69
69
  private readonly groupIndex = computed(() => {
70
- return Math.floor(this.currentPage() / this.visiblePageCount());
70
+ return Math.floor(this.currentPage() / Math.max(this.visiblePageCount(), 1));
71
71
  });
72
72
 
73
73
  hasPrev = computed(() => {
@@ -82,7 +82,7 @@ export class SdPaginationControl {
82
82
  if (totalPageCount === 0) {
83
83
  return false;
84
84
  }
85
- const lastGroupIndex = Math.floor((totalPageCount - 1) / this.visiblePageCount());
85
+ const lastGroupIndex = Math.floor((totalPageCount - 1) / Math.max(this.visiblePageCount(), 1));
86
86
  return this.groupIndex() < lastGroupIndex;
87
87
  });
88
88
 
@@ -112,7 +112,8 @@ export class SdPaginationControl {
112
112
  }
113
113
 
114
114
  goToPrevGroup(): void {
115
- this.currentPage.set((this.groupIndex() - 1) * this.visiblePageCount());
115
+ if (!this.hasPrev()) return;
116
+ this.currentPage.set((this.groupIndex() - 1) * Math.max(this.visiblePageCount(), 1));
116
117
  }
117
118
 
118
119
  goToFirst(): void {
@@ -120,6 +121,7 @@ export class SdPaginationControl {
120
121
  }
121
122
 
122
123
  goToLast(): void {
124
+ if (this.totalPageCount() === 0) return;
123
125
  this.currentPage.set(this.totalPageCount() - 1);
124
126
  }
125
127
  }
@@ -87,6 +87,6 @@ export class SdSidebarContainerControl {
87
87
  }
88
88
 
89
89
  onBackdropClick(): void {
90
- this.toggle.update((v) => !v);
90
+ this.toggle.set(false);
91
91
  }
92
92
  }
@@ -102,7 +102,7 @@ export class SdDropdownPopupControl {
102
102
  const divEl = el.firstElementChild as HTMLElement;
103
103
 
104
104
  if (divEl.clientHeight > 300) {
105
- el.style.height = "18.75rem";
105
+ el.style.height = "300px";
106
106
  } else {
107
107
  el.style.height = "";
108
108
  }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  ChangeDetectionStrategy,
3
3
  Component,
4
+ DestroyRef,
4
5
  effect,
5
6
  ElementRef,
6
7
  inject,
@@ -62,6 +63,7 @@ export class SdModalControl {
62
63
  private readonly _elRef = inject(ElementRef<HTMLElement>);
63
64
  private readonly _activatedModal = inject(SdActivatedModalProvider, { optional: true });
64
65
  private readonly _systemConfig = inject(SdSystemConfigProvider, { optional: true });
66
+ private readonly _destroyRef = inject(DestroyRef);
65
67
 
66
68
  open = model(false);
67
69
  key = input<string | undefined>(undefined);
@@ -147,6 +149,11 @@ export class SdModalControl {
147
149
  void this._saveConfig();
148
150
  }
149
151
  };
152
+
153
+ this._destroyRef.onDestroy(() => {
154
+ document.removeEventListener("mousemove", this._onDocumentMouseMove);
155
+ document.removeEventListener("mouseup", this._onDocumentMouseUp);
156
+ });
150
157
  }
151
158
 
152
159
  onResizeMouseDown(event: MouseEvent, dir: string): void {
@@ -208,7 +215,7 @@ export class SdModalControl {
208
215
  }
209
216
 
210
217
  private _requestClose(): void {
211
- if (this._activatedModal !== null && !this._activatedModal.canDeactivefn()) {
218
+ if (this._activatedModal !== null && !this._activatedModal.canDeactiveFn()) {
212
219
  return;
213
220
  }
214
221
  void this._saveConfig();
@@ -262,6 +269,8 @@ export class SdModalControl {
262
269
  maxZ = z;
263
270
  }
264
271
  }
272
+ const currentZ = parseInt(hostEl.style.zIndex, 10);
273
+ if (currentZ >= maxZ) return;
265
274
  hostEl.style.zIndex = String(maxZ + 1);
266
275
  }
267
276
 
@@ -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
  /**
@@ -201,12 +201,13 @@ export class SdToastProvider {
201
201
  const progressSignal = toastRef.instance.progress;
202
202
 
203
203
  effect(
204
- () => {
204
+ (onCleanup) => {
205
205
  const val = progressSignal();
206
206
  if (val >= 100) {
207
- setTimeout(() => {
207
+ const timerId = setTimeout(() => {
208
208
  this._dismissToast(toastRef);
209
209
  }, 1000);
210
+ onCleanup(() => clearTimeout(timerId));
210
211
  }
211
212
  },
212
213
  { injector: toastRef.injector },
@@ -260,14 +261,19 @@ export class SdToastProvider {
260
261
  toastRef.instance.open.set(false);
261
262
 
262
263
  const el = toastRef.location.nativeElement as HTMLElement;
264
+ let fallbackId: ReturnType<typeof setTimeout>;
265
+
263
266
  const onTransitionEnd = () => {
267
+ clearTimeout(fallbackId);
264
268
  el.removeEventListener("transitionend", onTransitionEnd);
265
269
  this._destroyToast(toastRef);
266
270
  };
271
+
267
272
  el.addEventListener("transitionend", onTransitionEnd);
268
273
 
269
274
  // fallback: transition이 없는 경우 300ms 후 파괴
270
- setTimeout(() => {
275
+ fallbackId = setTimeout(() => {
276
+ el.removeEventListener("transitionend", onTransitionEnd);
271
277
  this._destroyToast(toastRef);
272
278
  }, 300);
273
279
  }
@@ -2,6 +2,7 @@ import {
2
2
  booleanAttribute,
3
3
  ChangeDetectionStrategy,
4
4
  Component,
5
+ computed,
5
6
  input,
6
7
  ViewEncapsulation,
7
8
  } from "@angular/core";
@@ -22,7 +23,7 @@ import { PercentPipe } from "@angular/common";
22
23
  <div class="_content tx-right">
23
24
  {{ value() | percent: "1.0-2" }}
24
25
  </div>
25
- <div class="_progress" [style.width]="value() * 100 + '%'"></div>
26
+ <div class="_progress" [style.width]="_barWidth()"></div>
26
27
  `,
27
28
  styles: [
28
29
  /* language=SCSS */ `
@@ -85,4 +86,6 @@ export class SdProgressControl {
85
86
  >();
86
87
 
87
88
  value = input.required<number>();
89
+
90
+ _barWidth = computed(() => Math.min(this.value() * 100, 100) + "%");
88
91
  }