@simplysm/angular 14.0.85 → 14.0.87

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 (112) hide show
  1. package/dist/controls/pagination/sd-pagination.d.ts.map +1 -1
  2. package/dist/controls/pagination/sd-pagination.js +2 -0
  3. package/dist/controls/select/sd-select-item.js +1 -1
  4. package/dist/controls/select/sd-select.js +1 -1
  5. package/dist/core/app-structure/sd-app-structure.provider.d.ts +2 -3
  6. package/dist/core/app-structure/sd-app-structure.provider.d.ts.map +1 -1
  7. package/dist/core/app-structure/sd-app-structure.provider.js +5 -9
  8. package/dist/core/app-structure/sd-app-structure.utils.d.ts +2 -0
  9. package/dist/core/app-structure/sd-app-structure.utils.d.ts.map +1 -1
  10. package/dist/core/app-structure/sd-app-structure.utils.js +9 -1
  11. package/dist/core/busy/sd-busy-container.d.ts +1 -1
  12. package/dist/core/busy/sd-busy-container.d.ts.map +1 -1
  13. package/dist/core/busy/sd-busy-container.js +11 -11
  14. package/dist/core/busy/sd-busy.provider.d.ts.map +1 -1
  15. package/dist/core/busy/sd-busy.provider.js +3 -0
  16. package/dist/core/config/sd-system-config.provider.d.ts +4 -4
  17. package/dist/core/config/sd-system-config.provider.d.ts.map +1 -1
  18. package/dist/core/config/sd-system-config.provider.js +3 -0
  19. package/dist/core/error-handler/sd-global-error-handler.plugin.d.ts +1 -1
  20. package/dist/core/error-handler/sd-global-error-handler.plugin.d.ts.map +1 -1
  21. package/dist/core/file-dialog/sd-file-dialog.provider.d.ts.map +1 -1
  22. package/dist/core/file-dialog/sd-file-dialog.provider.js +4 -0
  23. package/dist/core/modal/sd-activated-modal.provider.d.ts +2 -1
  24. package/dist/core/modal/sd-activated-modal.provider.d.ts.map +1 -1
  25. package/dist/core/modal/sd-modal.d.ts.map +1 -1
  26. package/dist/core/modal/sd-modal.js +3 -3
  27. package/dist/core/modal/sd-modal.provider.d.ts.map +1 -1
  28. package/dist/core/modal/sd-modal.provider.js +8 -0
  29. package/dist/core/modal/sd-prompt-modal.d.ts +2 -2
  30. package/dist/core/modal/sd-prompt-modal.d.ts.map +1 -1
  31. package/dist/core/modal/sd-prompt-modal.js +32 -37
  32. package/dist/core/print/sd-print.provider.d.ts.map +1 -1
  33. package/dist/core/print/sd-print.provider.js +19 -6
  34. package/dist/core/provideSdAngular.d.ts.map +1 -1
  35. package/dist/core/provideSdAngular.js +4 -1
  36. package/dist/core/routing/injectViewTitleSignal.d.ts.map +1 -1
  37. package/dist/core/routing/injectViewTitleSignal.js +1 -6
  38. package/dist/core/shared-data/sd-shared-data.provider.d.ts +2 -1
  39. package/dist/core/shared-data/sd-shared-data.provider.d.ts.map +1 -1
  40. package/dist/core/shared-data/sd-shared-data.provider.js +3 -3
  41. package/dist/core/toast/sd-toast.provider.d.ts +1 -0
  42. package/dist/core/toast/sd-toast.provider.d.ts.map +1 -1
  43. package/dist/core/toast/sd-toast.provider.js +18 -4
  44. package/dist/data/crud/sd-base-container.d.ts.map +1 -1
  45. package/dist/data/crud/sd-base-container.js +56 -54
  46. package/dist/data/kanban/sd-kanban-lane.d.ts.map +1 -1
  47. package/dist/data/shared-data/sd-shared-data-select-button.d.ts +1 -2
  48. package/dist/data/shared-data/sd-shared-data-select-button.d.ts.map +1 -1
  49. package/dist/data/shared-data/sd-shared-data-select-button.js +18 -21
  50. package/dist/data/shared-data/sd-shared-data-select.d.ts +3 -2
  51. package/dist/data/shared-data/sd-shared-data-select.d.ts.map +1 -1
  52. package/dist/data/shared-data/sd-shared-data-select.js +10 -10
  53. package/dist/data/sheet/sd-sheet-config.modal.d.ts +3 -3
  54. package/dist/data/sheet/sd-sheet-config.modal.d.ts.map +1 -1
  55. package/dist/data/sheet/sd-sheet-config.modal.js +37 -34
  56. package/dist/data/sheet/sd-sheet.d.ts.map +1 -1
  57. package/dist/data/sheet/sd-sheet.js +5 -1
  58. package/dist/data/sheet/useSheetCellStyling.d.ts.map +1 -1
  59. package/dist/data/sheet/useSheetCellStyling.js +4 -22
  60. package/dist/data/state-preset/sd-state-preset.d.ts +10 -10
  61. package/dist/data/state-preset/sd-state-preset.d.ts.map +1 -1
  62. package/dist/data/state-preset/sd-state-preset.js +4 -4
  63. package/dist/features/address/sd-address-search.modal.js +1 -1
  64. package/dist/features/editor/sd-tiptap-editor.d.ts.map +1 -1
  65. package/dist/features/editor/sd-tiptap-editor.js +3 -2
  66. package/dist/features/theme/sd-theme-selector.d.ts +6 -8
  67. package/dist/features/theme/sd-theme-selector.d.ts.map +1 -1
  68. package/dist/features/theme/sd-theme-selector.js +54 -51
  69. package/dist/features/visual/sd-barcode.d.ts.map +1 -1
  70. package/dist/features/visual/sd-barcode.js +5 -10
  71. package/dist/layout/sidebar/sd-sidebar-user.d.ts.map +1 -1
  72. package/dist/layout/sidebar/sd-sidebar-user.js +26 -36
  73. package/package.json +28 -28
  74. package/src/controls/button/sd-modal-select-button.ts +1 -1
  75. package/src/controls/form/sd-form.ts +1 -1
  76. package/src/controls/input/sd-textfield-type-handlers.ts +2 -2
  77. package/src/controls/pagination/sd-pagination.ts +1 -0
  78. package/src/controls/select/sd-select-item.ts +1 -1
  79. package/src/controls/select/sd-select.ts +1 -1
  80. package/src/core/app-structure/sd-app-structure.provider.ts +7 -13
  81. package/src/core/app-structure/sd-app-structure.utils.ts +14 -2
  82. package/src/core/busy/sd-busy-container.ts +6 -13
  83. package/src/core/busy/sd-busy.provider.ts +4 -0
  84. package/src/core/config/injectSdSystemConfigResource.ts +1 -1
  85. package/src/core/config/sd-system-config.provider.ts +5 -3
  86. package/src/core/error-handler/sd-global-error-handler.plugin.ts +1 -1
  87. package/src/core/file-dialog/sd-file-dialog.provider.ts +5 -1
  88. package/src/core/modal/sd-activated-modal.provider.ts +2 -1
  89. package/src/core/modal/sd-modal.provider.ts +8 -0
  90. package/src/core/modal/sd-modal.ts +6 -3
  91. package/src/core/modal/sd-prompt-modal.ts +13 -23
  92. package/src/core/print/sd-print.provider.ts +21 -6
  93. package/src/core/provideSdAngular.ts +5 -1
  94. package/src/core/routing/injectViewTitleSignal.ts +1 -5
  95. package/src/core/shared-data/sd-shared-data.provider.ts +4 -4
  96. package/src/core/toast/sd-toast.provider.ts +35 -12
  97. package/src/data/crud/sd-base-container.ts +120 -118
  98. package/src/data/kanban/sd-kanban-lane.ts +2 -2
  99. package/src/data/shared-data/sd-shared-data-select-button.ts +15 -20
  100. package/src/data/shared-data/sd-shared-data-select-list.ts +4 -4
  101. package/src/data/shared-data/sd-shared-data-select.ts +10 -12
  102. package/src/data/sheet/sd-sheet-column.ts +1 -1
  103. package/src/data/sheet/sd-sheet-config.modal.ts +39 -35
  104. package/src/data/sheet/sd-sheet.ts +6 -1
  105. package/src/data/sheet/useSheetCellStyling.ts +3 -20
  106. package/src/data/state-preset/sd-state-preset.ts +11 -11
  107. package/src/features/address/sd-address-search.modal.ts +1 -1
  108. package/src/features/editor/sd-tiptap-editor.ts +3 -2
  109. package/src/features/theme/sd-theme-selector.ts +77 -75
  110. package/src/features/visual/sd-barcode.ts +5 -9
  111. package/src/features/visual/sd-calendar.ts +1 -1
  112. package/src/layout/sidebar/sd-sidebar-user.ts +3 -5
@@ -28,6 +28,9 @@ import { SdThemeProvider } from "../features/theme/sd-theme-provider";
28
28
  import { SdLocalStorageProvider } from "./config/sd-local-storage.provider";
29
29
  import { SdGlobalErrorHandlerPlugin } from "./error-handler/sd-global-error-handler.plugin";
30
30
  import { SdOptionEventPlugin } from "./events/sd-option-event.plugin";
31
+ import { createLogger } from "@simplysm/core-common";
32
+
33
+ const logger = createLogger("angular:sw-update");
31
34
 
32
35
  export function provideSdAngular(opt: { clientName: string }): EnvironmentProviders {
33
36
  return makeEnvironmentProviders([
@@ -131,8 +134,9 @@ export function provideSdAngular(opt: { clientName: string }): EnvironmentProvid
131
134
  }
132
135
  }
133
136
  failCount = 0;
134
- } catch {
137
+ } catch (err) {
135
138
  failCount++;
139
+ logger.error(err);
136
140
  } finally {
137
141
  const interval = Math.min(
138
142
  BASE_INTERVAL * Math.pow(2, Math.max(0, failCount - 1)),
@@ -16,10 +16,6 @@ export function injectViewTitleSignal(): Signal<string> {
16
16
  return sdActivatedModal.modalComponent()?.title() ?? "";
17
17
  }
18
18
 
19
- try {
20
- return sdAppStructure.getTitleByFullCode(currPageCode?.() ?? fullPageCode());
21
- } catch {
22
- return "";
23
- }
19
+ return sdAppStructure.findTitleByFullCode(currPageCode?.() ?? fullPageCode()) ?? "";
24
20
  });
25
21
  }
@@ -1,6 +1,6 @@
1
1
  import { computed, ErrorHandler, inject, Injectable, signal, type Signal, type WritableSignal } from "@angular/core";
2
2
  import { defineEvent } from "@simplysm/service-common";
3
- import { obj, wait as waitUtil } from "@simplysm/core-common";
3
+ import { DateOnly, DateTime, obj, Time, wait as waitUtil } from "@simplysm/core-common";
4
4
  import { SdServiceClientFactoryProvider } from "../service-client/sd-service-client-factory.provider";
5
5
  import "@simplysm/core-common";
6
6
 
@@ -15,7 +15,7 @@ export interface SharedDataInfo<T extends SharedDataBase<string | number>> {
15
15
  serviceKey: string;
16
16
  getter: (changeKeys?: (string | number)[]) => Promise<T[]>;
17
17
  filter?: unknown;
18
- orderBy?: (item: T) => any;
18
+ orderBy?: (item: T) => string | number | DateOnly | DateTime | Time | undefined;
19
19
  }
20
20
 
21
21
  export interface SharedDataHandle<T extends SharedDataBase<string | number>> {
@@ -110,7 +110,7 @@ export abstract class SdSharedDataProvider<T extends Record<string, SharedDataBa
110
110
  }
111
111
 
112
112
  const client = this._sdServiceClientFactory.get(entry.info.serviceKey);
113
- const event = client.getEvent<typeof SdSharedDataChangeEvent>("SdSharedDataChange");
113
+ const event = client.getEvent(SdSharedDataChangeEvent);
114
114
  await event.emit(
115
115
  (item) => item.name === (name as string) && obj.equal(item.filter, entry.info.filter),
116
116
  changeKeys,
@@ -138,7 +138,7 @@ export abstract class SdSharedDataProvider<T extends Record<string, SharedDataBa
138
138
  // 이벤트 리스너 등록
139
139
  if (entry.listenerKey == null) {
140
140
  const client = this._sdServiceClientFactory.get(entry.info.serviceKey);
141
- const event = client.getEvent<typeof SdSharedDataChangeEvent>("SdSharedDataChange");
141
+ const event = client.getEvent(SdSharedDataChangeEvent);
142
142
  entry.listenerKey = await event.addListener(
143
143
  { name, filter: entry.info.filter },
144
144
  async (changeKeys) => {
@@ -45,6 +45,7 @@ export class SdToastProvider {
45
45
  private _containerRef: ComponentRef<SdToastContainer> | undefined;
46
46
  private readonly _toastRefs: ComponentRef<SdToast>[] = [];
47
47
  private readonly _contentRefs = new Map<ComponentRef<SdToast>, ComponentRef<any>>();
48
+ private readonly _autoDismissCleanups = new Map<ComponentRef<SdToast>, () => void>();
48
49
 
49
50
  constructor() {
50
51
  effect(() => {
@@ -234,19 +235,30 @@ export class SdToastProvider {
234
235
  onDismiss?: () => void,
235
236
  ): void {
236
237
  const el = toastRef.location.nativeElement as HTMLElement;
238
+ const ac = new AbortController();
239
+ let timerId: ReturnType<typeof setTimeout> | undefined;
237
240
  let isHovering = false;
238
241
  let dismissPending = false;
239
242
  let dismissed = false;
240
243
 
244
+ // 타이머·리스너 정리. 외부 파괴 경로(_dismissToast/_destroyToast)에서도 호출되어
245
+ // 죽은 toastRef 에 대해 타이머가 깨어나는 race 와 리스너 누수를 막는다.
246
+ const cleanup = () => {
247
+ dismissed = true;
248
+ if (timerId != null) clearTimeout(timerId);
249
+ ac.abort();
250
+ this._autoDismissCleanups.delete(toastRef);
251
+ };
252
+ this._autoDismissCleanups.set(toastRef, cleanup);
253
+
241
254
  const dismiss = () => {
242
255
  if (dismissed) return;
243
- dismissed = true;
244
256
  onDismiss?.();
245
257
  this._dismissToast(toastRef);
246
258
  };
247
259
 
248
260
  const dismissAfterDelay = (ms: number) => {
249
- setTimeout(() => {
261
+ timerId = setTimeout(() => {
250
262
  if (dismissed) return;
251
263
  if (isHovering) {
252
264
  dismissPending = true;
@@ -256,21 +268,30 @@ export class SdToastProvider {
256
268
  }, ms);
257
269
  };
258
270
 
259
- el.addEventListener("mouseenter", () => {
260
- isHovering = true;
261
- });
262
- el.addEventListener("mouseleave", () => {
263
- isHovering = false;
264
- if (dismissPending) {
265
- dismissPending = false;
266
- dismissAfterDelay(1000);
267
- }
268
- });
271
+ el.addEventListener(
272
+ "mouseenter",
273
+ () => {
274
+ isHovering = true;
275
+ },
276
+ { signal: ac.signal },
277
+ );
278
+ el.addEventListener(
279
+ "mouseleave",
280
+ () => {
281
+ isHovering = false;
282
+ if (dismissPending) {
283
+ dismissPending = false;
284
+ dismissAfterDelay(1000);
285
+ }
286
+ },
287
+ { signal: ac.signal },
288
+ );
269
289
 
270
290
  dismissAfterDelay(delayMs);
271
291
  }
272
292
 
273
293
  private _dismissToast(toastRef: ComponentRef<SdToast>): void {
294
+ this._autoDismissCleanups.get(toastRef)?.();
274
295
  toastRef.instance.open.set(false);
275
296
 
276
297
  const el = toastRef.location.nativeElement as HTMLElement;
@@ -292,6 +313,8 @@ export class SdToastProvider {
292
313
  }
293
314
 
294
315
  private _destroyToast(toastRef: ComponentRef<SdToast>): void {
316
+ this._autoDismissCleanups.get(toastRef)?.();
317
+
295
318
  const idx = this._toastRefs.indexOf(toastRef);
296
319
  if (idx === -1) return; // 이미 파괴됨
297
320
 
@@ -1,118 +1,120 @@
1
- import {
2
- ChangeDetectionStrategy,
3
- Component,
4
- contentChild,
5
- effect,
6
- inject,
7
- input,
8
- model,
9
- type TemplateRef,
10
- untracked,
11
- ViewEncapsulation,
12
- } from "@angular/core";
13
- import { injectViewTitleSignal } from "../../core/routing/injectViewTitleSignal";
14
- import { SdBusyContainer } from "../../core/busy/sd-busy-container";
15
- import { SdSharedDataProvider } from "../../core/shared-data/sd-shared-data.provider";
16
- import { SdToastProvider } from "../../core/toast/sd-toast.provider";
17
- import { SdTopbar } from "../../layout/topbar/sd-topbar";
18
- import { SdTopbarContainer } from "../../layout/topbar/sd-topbar-container";
19
- import type { SdViewType } from "../../core/routing/injectViewTypeSignal";
20
- import { NgIcon } from "@ng-icons/core";
21
- import { tablerAlertTriangle } from "@ng-icons/tabler-icons";
22
- import { NgTemplateOutlet } from "@angular/common";
23
-
24
- @Component({
25
- selector: "sd-base-container",
26
- changeDetection: ChangeDetectionStrategy.OnPush,
27
- encapsulation: ViewEncapsulation.None,
28
- standalone: true,
29
- imports: [SdBusyContainer, NgIcon, SdTopbarContainer, NgTemplateOutlet, SdTopbar],
30
- template: `
31
- <sd-busy-container [busy]="initialized() && busyCount() > 0">
32
- @if (initialized()) {
33
- @if (restricted()) {
34
- <div class="fill tx-theme-gray-light p-xxl tx-center">
35
- <br />
36
- <ng-icon [svg]="tablerAlertTriangle" [size]="'5rem'" />
37
- <br />
38
- <br />
39
- '{{ viewTitle() }}'에 대한 사용권한이 없습니다. 시스템 관리자에게 문의하세요.
40
- </div>
41
- } @else if (viewType() === "page") {
42
- <sd-topbar-container>
43
- <sd-topbar>
44
- <h4>{{ viewTitle() }}</h4>
45
-
46
- <ng-template [ngTemplateOutlet]="topbarTplRef()" />
47
- </sd-topbar>
48
-
49
- <ng-template [ngTemplateOutlet]="content" />
50
- </sd-topbar-container>
51
- } @else {
52
- <ng-template [ngTemplateOutlet]="content" />
53
- }
54
- }
55
- </sd-busy-container>
56
-
57
- <ng-template #content>
58
- <div class="flex-column fill">
59
- @if (commandTplRef()) {
60
- <div class="p-default flex-row gap-default bdb bdb-theme-gray-lightest">
61
- <ng-template [ngTemplateOutlet]="commandTplRef()" />
62
- </div>
63
- }
64
-
65
- <div class="flex-fill">
66
- @if (contentTplRef()) {
67
- <ng-template [ngTemplateOutlet]="contentTplRef()" />
68
- }
69
- </div>
70
-
71
- @if (bottomCommandTplRef()) {
72
- <div class="p-sm-default flex-row main-align-end gap-sm bdt bdt-theme-gray-lightest">
73
- <ng-template [ngTemplateOutlet]="bottomCommandTplRef()" />
74
- </div>
75
- }
76
- </div>
77
- </ng-template>
78
- `,
79
- })
80
- export class SdBaseContainer {
81
- private readonly _sdSharedData = inject(SdSharedDataProvider);
82
- private readonly _sdToast = inject(SdToastProvider);
83
-
84
- viewTitle = injectViewTitleSignal();
85
-
86
- ready = model(false);
87
- initialized = input(false);
88
- busyCount = model(0);
89
-
90
- restricted = input(false);
91
-
92
- viewType = input.required<SdViewType>();
93
-
94
- topbarTplRef = contentChild<TemplateRef<void>>("topbarTpl");
95
- commandTplRef = contentChild<TemplateRef<void>>("commandTpl");
96
- contentTplRef = contentChild<TemplateRef<void>>("contentTpl");
97
- bottomCommandTplRef = contentChild<TemplateRef<void>>("bottomCommandTpl");
98
-
99
- constructor() {
100
- effect(() => {
101
- if (this.restricted()) {
102
- this.ready.set(true);
103
- return;
104
- }
105
-
106
- void untracked(async () => {
107
- this.busyCount.update((v) => v + 1);
108
- await this._sdToast.try(async () => {
109
- await this._sdSharedData.wait();
110
- });
111
- this.busyCount.update((v) => v - 1);
112
- this.ready.set(true);
113
- });
114
- });
115
- }
116
-
117
- protected readonly tablerAlertTriangle = tablerAlertTriangle;
118
- }
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ contentChild,
5
+ effect,
6
+ inject,
7
+ input,
8
+ model,
9
+ type TemplateRef,
10
+ untracked,
11
+ ViewEncapsulation,
12
+ } from "@angular/core";
13
+ import { injectViewTitleSignal } from "../../core/routing/injectViewTitleSignal";
14
+ import { SdBusyContainer } from "../../core/busy/sd-busy-container";
15
+ import { SdSharedDataProvider } from "../../core/shared-data/sd-shared-data.provider";
16
+ import { SdToastProvider } from "../../core/toast/sd-toast.provider";
17
+ import { SdTopbar } from "../../layout/topbar/sd-topbar";
18
+ import { SdTopbarContainer } from "../../layout/topbar/sd-topbar-container";
19
+ import type { SdViewType } from "../../core/routing/injectViewTypeSignal";
20
+ import { NgIcon } from "@ng-icons/core";
21
+ import { tablerAlertTriangle } from "@ng-icons/tabler-icons";
22
+ import { NgTemplateOutlet } from "@angular/common";
23
+
24
+ @Component({
25
+ selector: "sd-base-container",
26
+ changeDetection: ChangeDetectionStrategy.OnPush,
27
+ encapsulation: ViewEncapsulation.None,
28
+ standalone: true,
29
+ imports: [SdBusyContainer, NgIcon, SdTopbarContainer, NgTemplateOutlet, SdTopbar],
30
+ template: `
31
+ <sd-busy-container [busy]="initialized() && busyCount() > 0">
32
+ @if (initialized()) {
33
+ @if (restricted()) {
34
+ <div class="fill tx-theme-gray-light p-xxl tx-center">
35
+ <br />
36
+ <ng-icon [svg]="tablerAlertTriangle" [size]="'5rem'" />
37
+ <br />
38
+ <br />
39
+ '{{ viewTitle() }}'에 대한 사용권한이 없습니다. 시스템 관리자에게 문의하세요.
40
+ </div>
41
+ } @else if (viewType() === "page") {
42
+ <sd-topbar-container>
43
+ <sd-topbar>
44
+ <h4>{{ viewTitle() }}</h4>
45
+
46
+ <ng-template [ngTemplateOutlet]="topbarTplRef()" />
47
+ </sd-topbar>
48
+
49
+ <ng-template [ngTemplateOutlet]="content" />
50
+ </sd-topbar-container>
51
+ } @else {
52
+ <ng-template [ngTemplateOutlet]="content" />
53
+ }
54
+ }
55
+ </sd-busy-container>
56
+
57
+ <ng-template #content>
58
+ <div class="flex-column fill">
59
+ @if (commandTplRef()) {
60
+ <div class="p-default flex-row gap-default bdb bdb-theme-gray-lightest">
61
+ <ng-template [ngTemplateOutlet]="commandTplRef()" />
62
+ </div>
63
+ }
64
+
65
+ <div class="flex-fill">
66
+ @if (contentTplRef()) {
67
+ <ng-template [ngTemplateOutlet]="contentTplRef()" />
68
+ }
69
+ </div>
70
+
71
+ @if (bottomCommandTplRef()) {
72
+ <div class="p-sm-default flex-row main-align-end gap-sm bdt bdt-theme-gray-lightest">
73
+ <ng-template [ngTemplateOutlet]="bottomCommandTplRef()" />
74
+ </div>
75
+ }
76
+ </div>
77
+ </ng-template>
78
+ `,
79
+ })
80
+ export class SdBaseContainer {
81
+ private readonly _sdSharedData = inject(SdSharedDataProvider, { optional: true });
82
+ private readonly _sdToast = inject(SdToastProvider);
83
+
84
+ viewTitle = injectViewTitleSignal();
85
+
86
+ ready = model(false);
87
+ initialized = input(false);
88
+ busyCount = model(0);
89
+
90
+ restricted = input(false);
91
+
92
+ viewType = input.required<SdViewType>();
93
+
94
+ topbarTplRef = contentChild<TemplateRef<void>>("topbarTpl");
95
+ commandTplRef = contentChild<TemplateRef<void>>("commandTpl");
96
+ contentTplRef = contentChild<TemplateRef<void>>("contentTpl");
97
+ bottomCommandTplRef = contentChild<TemplateRef<void>>("bottomCommandTpl");
98
+
99
+ constructor() {
100
+ effect(() => {
101
+ if (this.restricted()) {
102
+ this.ready.set(true);
103
+ return;
104
+ }
105
+
106
+ void untracked(async () => {
107
+ if (this._sdSharedData) {
108
+ this.busyCount.update((v) => v + 1);
109
+ await this._sdToast.try(async () => {
110
+ await this._sdSharedData!.wait();
111
+ });
112
+ this.busyCount.update((v) => v - 1);
113
+ }
114
+ this.ready.set(true);
115
+ });
116
+ });
117
+ }
118
+
119
+ protected readonly tablerAlertTriangle = tablerAlertTriangle;
120
+ }
@@ -133,8 +133,8 @@ export class SdKanbanLane<L, T> implements SdKanbanDropTarget<L, T> {
133
133
 
134
134
  kanbanControls = contentChildren<SdKanban<L, T>>(SdKanban, { descendants: true });
135
135
 
136
- toolTplRef = contentChild<any, TemplateRef<void>>("toolTpl", { read: TemplateRef });
137
- titleTplRef = contentChild<any, TemplateRef<void>>("titleTpl", { read: TemplateRef });
136
+ toolTplRef = contentChild<unknown, TemplateRef<void>>("toolTpl", { read: TemplateRef });
137
+ titleTplRef = contentChild<unknown, TemplateRef<void>>("titleTpl", { read: TemplateRef });
138
138
 
139
139
  isAllSelected = computed(() => {
140
140
  const selectableControls = this.kanbanControls().filter((ctrl) => ctrl.selectable());
@@ -3,11 +3,10 @@ import {
3
3
  booleanAttribute,
4
4
  ChangeDetectionStrategy,
5
5
  Component,
6
+ computed,
6
7
  contentChild,
7
- effect,
8
8
  input,
9
9
  model,
10
- signal,
11
10
  TemplateRef,
12
11
  ViewEncapsulation,
13
12
  } from "@angular/core";
@@ -70,27 +69,23 @@ export class SdSharedDataSelectButton<
70
69
  inset = input(false, { transform: booleanAttribute });
71
70
  size = input<"sm" | "lg">();
72
71
 
73
- itemTplRef = contentChild.required<any, TemplateRef<SdItemOfTemplateContext<TItem>>>(
72
+ itemTplRef = contentChild.required<SdItemOfTemplate<TItem>, TemplateRef<SdItemOfTemplateContext<TItem>>>(
74
73
  SdItemOfTemplate,
75
74
  { read: TemplateRef },
76
75
  );
77
76
 
78
- protected readonly _selectedItems = signal<TItem[]>([]);
77
+ _selectedItems = computed<TItem[]>(() => {
78
+ const v = this.value();
79
+ const items = this.items();
80
+ const mode = this.selectMode();
79
81
 
80
- constructor() {
81
- effect(() => {
82
- const v = this.value();
83
- const items = this.items();
84
- const mode = this.selectMode();
85
-
86
- if (mode === "multi" && Array.isArray(v) && v.filterExists().length > 0) {
87
- const keys = v.filterExists() as (string | number)[];
88
- this._selectedItems.set(items.filter((it) => keys.includes(it.__valueKey)));
89
- } else if (mode === "single" && !Array.isArray(v) && v != null) {
90
- this._selectedItems.set(items.filter((it) => it.__valueKey === v));
91
- } else {
92
- this._selectedItems.set([]);
93
- }
94
- });
95
- }
82
+ if (mode === "multi" && Array.isArray(v) && v.filterExists().length > 0) {
83
+ const keys = v.filterExists() as (string | number)[];
84
+ return items.filter((it) => keys.includes(it.__valueKey));
85
+ } else if (mode === "single" && !Array.isArray(v) && v != null) {
86
+ return items.filter((it) => it.__valueKey === v);
87
+ } else {
88
+ return [];
89
+ }
90
+ });
96
91
  }
@@ -146,17 +146,17 @@ export class SdSharedDataSelectList<
146
146
 
147
147
  header = input<string>();
148
148
 
149
- headerTplRef = contentChild<any, TemplateRef<void>>("headerTpl", {
149
+ headerTplRef = contentChild<unknown, TemplateRef<void>>("headerTpl", {
150
150
  read: TemplateRef,
151
151
  });
152
- filterTplRef = contentChild<any, TemplateRef<void>>("filterTpl", {
152
+ filterTplRef = contentChild<unknown, TemplateRef<void>>("filterTpl", {
153
153
  read: TemplateRef,
154
154
  });
155
- itemTplRef = contentChild<any, TemplateRef<SdItemOfTemplateContext<TItem>>>(
155
+ itemTplRef = contentChild<SdItemOfTemplate<TItem>, TemplateRef<SdItemOfTemplateContext<TItem>>>(
156
156
  SdItemOfTemplate,
157
157
  { read: TemplateRef },
158
158
  );
159
- undefinedTplRef = contentChild<any, TemplateRef<void>>("undefinedTpl", {
159
+ undefinedTplRef = contentChild<unknown, TemplateRef<void>>("undefinedTpl", {
160
160
  read: TemplateRef,
161
161
  });
162
162
 
@@ -1,4 +1,5 @@
1
1
  import { NgTemplateOutlet } from "@angular/common";
2
+ import type { DateOnly, DateTime, Time } from "@simplysm/core-common";
2
3
  import {
3
4
  booleanAttribute,
4
5
  ChangeDetectionStrategy,
@@ -170,13 +171,13 @@ export class SdSharedDataSelect<
170
171
  getSearchTextFn = input<(item: TItem, index: number) => string>(
171
172
  (item) => item.__searchText,
172
173
  );
173
- displayOrderKeyProp = input<string>();
174
+ displayOrderByFn = input<(item: TItem) => string | number | DateOnly | DateTime | Time | undefined>();
174
175
 
175
- itemTplRef = contentChild<any, TemplateRef<SdItemOfTemplateContext<TItem>>>(
176
+ itemTplRef = contentChild<SdItemOfTemplate<TItem>, TemplateRef<SdItemOfTemplateContext<TItem>>>(
176
177
  SdItemOfTemplate,
177
178
  { read: TemplateRef },
178
179
  );
179
- undefinedTplRef = contentChild<any, TemplateRef<void>>("undefinedTpl", {
180
+ undefinedTplRef = contentChild<unknown, TemplateRef<void>>("undefinedTpl", {
180
181
  read: TemplateRef,
181
182
  });
182
183
 
@@ -219,9 +220,9 @@ export class SdSharedDataSelect<
219
220
  return true;
220
221
  });
221
222
 
222
- const orderProp = this.displayOrderKeyProp();
223
- if (orderProp != null) {
224
- result = result.orderBy((item) => (item as any)[orderProp]);
223
+ const orderBy = this.displayOrderByFn();
224
+ if (orderBy != null) {
225
+ result = result.orderBy(orderBy);
225
226
  }
226
227
 
227
228
  return result;
@@ -316,15 +317,12 @@ export class SdSharedDataSelect<
316
317
  const parentMap = this.itemByParentKeyMap();
317
318
  if (parentMap == null) return undefined;
318
319
 
319
- const orderProp = this.displayOrderKeyProp();
320
- if (orderProp == null) return parentMap;
320
+ const orderBy = this.displayOrderByFn();
321
+ if (orderBy == null) return parentMap;
321
322
 
322
323
  const sorted = new Map<TItem["__valueKey"] | undefined, TItem[]>();
323
324
  for (const [key, children] of parentMap) {
324
- sorted.set(
325
- key,
326
- children.orderBy((item) => (item as any)[orderProp]),
327
- );
325
+ sorted.set(key, children.orderBy(orderBy));
328
326
  }
329
327
  return sorted;
330
328
  });
@@ -32,7 +32,7 @@ export class SdSheetColumn<T = unknown> {
32
32
  disableResizing = input(false, { transform: booleanAttribute });
33
33
  ordering = input(0);
34
34
 
35
- cellTplRef = contentChild.required<any, TemplateRef<SdSheetCellContext<T>>>(
35
+ cellTplRef = contentChild.required<SdSheetColumnCellTemplate<T>, TemplateRef<SdSheetCellContext<T>>>(
36
36
  SdSheetColumnCellTemplate,
37
37
  { read: TemplateRef },
38
38
  );
@@ -1,14 +1,16 @@
1
1
  import {
2
2
  ChangeDetectionStrategy,
3
3
  Component,
4
- effect,
5
4
  forwardRef,
5
+ inject,
6
6
  input,
7
+ linkedSignal,
7
8
  output,
8
9
  signal,
9
10
  ViewEncapsulation,
10
11
  } from "@angular/core";
11
- import type { SdModalContentDef } from "../../core/modal/sd-modal.provider";
12
+ import { SdModalProvider, type SdModalContentDef } from "../../core/modal/sd-modal.provider";
13
+ import { SdConfirmModal } from "../../core/modal/sd-confirm-modal";
12
14
  import type { SdSheetConfig } from "./types";
13
15
  import type { SdSheetColumn } from "./sd-sheet-column";
14
16
  import { SdSheet } from "./sd-sheet";
@@ -178,14 +180,36 @@ interface ConfigItem {
178
180
  `,
179
181
  })
180
182
  export class SdSheetConfigModal implements SdModalContentDef<SdSheetConfig | undefined> {
181
- initialized = signal(false);
183
+ private readonly _sdModal = inject(SdModalProvider);
184
+
185
+ initialized = signal(true);
182
186
  close = output<SdSheetConfig | undefined>();
183
187
 
184
188
  sheetKey = input.required<string>();
185
189
  controls = input.required<readonly SdSheetColumn[]>();
186
190
  config = input.required<SdSheetConfig | undefined>();
187
191
 
188
- items = signal<ConfigItem[]>([]);
192
+ items = linkedSignal<ConfigItem[]>(() => {
193
+ const cfg = this.config();
194
+ return this.controls()
195
+ .map((ctrl): ConfigItem => {
196
+ const key = ctrl.key();
197
+ const cfgCol = cfg?.columnRecord[key];
198
+ return {
199
+ key,
200
+ header: Array.isArray(ctrl.header())
201
+ ? (ctrl.header() as string[]).join(" > ")
202
+ : (ctrl.header() as string),
203
+ disableResizing: ctrl.disableResizing(),
204
+ fixed: cfgCol?.fixed ?? ctrl.fixed(),
205
+ hidden: cfgCol?.hidden ?? ctrl.hidden(),
206
+ width: cfgCol?.width ?? ctrl.width(),
207
+ ordering: cfgCol?.ordering ?? ctrl.ordering(),
208
+ };
209
+ })
210
+ .orderBy((i) => i.ordering ?? 0)
211
+ .orderBy((i) => (i.fixed ? 0 : 1));
212
+ });
189
213
 
190
214
  trackByFn = (item: ConfigItem): string => item.key;
191
215
 
@@ -194,33 +218,6 @@ export class SdSheetConfigModal implements SdModalContentDef<SdSheetConfig | und
194
218
  protected readonly tablerX = tablerX;
195
219
  protected readonly mark = mark;
196
220
 
197
- constructor() {
198
- effect(() => {
199
- const cfg = this.config();
200
- const items = this.controls()
201
- .map((ctrl): ConfigItem => {
202
- const key = ctrl.key();
203
- const cfgCol = cfg?.columnRecord[key];
204
- return {
205
- key,
206
- header: Array.isArray(ctrl.header())
207
- ? (ctrl.header() as string[]).join(" > ")
208
- : (ctrl.header() as string),
209
- disableResizing: ctrl.disableResizing(),
210
- fixed: cfgCol?.fixed ?? ctrl.fixed(),
211
- hidden: cfgCol?.hidden ?? ctrl.hidden(),
212
- width: cfgCol?.width ?? ctrl.width(),
213
- ordering: cfgCol?.ordering ?? ctrl.ordering(),
214
- };
215
- })
216
- .orderBy((i) => i.ordering ?? 0)
217
- .orderBy((i) => (i.fixed ? 0 : 1));
218
-
219
- this.items.set(items);
220
- this.initialized.set(true);
221
- });
222
- }
223
-
224
221
  onMoveUp(item: ConfigItem): void {
225
222
  this.items.update((v) => {
226
223
  const r = [...v];
@@ -264,9 +261,16 @@ export class SdSheetConfigModal implements SdModalContentDef<SdSheetConfig | und
264
261
  this.close.emit(undefined);
265
262
  }
266
263
 
267
- onResetClick(): void {
268
- if (confirm("설정값이 모두 초기화 됩니다.")) {
269
- this.close.emit({ columnRecord: {} });
270
- }
264
+ async onResetClick(): Promise<void> {
265
+ const confirmed = await this._sdModal.showAsync(
266
+ {
267
+ title: "설정 초기화",
268
+ type: SdConfirmModal,
269
+ inputs: { message: "설정값이 모두 초기화 됩니다." },
270
+ },
271
+ { useCloseByBackdrop: false },
272
+ );
273
+ if (confirmed !== true) return;
274
+ this.close.emit({ columnRecord: {} });
271
275
  }
272
276
  }