@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
@@ -7,45 +7,41 @@ import { SdListItem } from "../../controls/list/sd-list-item";
7
7
  import * as i0 from "@angular/core";
8
8
  const _c0 = ["*"];
9
9
  const _forTrack0 = ($index, $item) => $item.title;
10
+ function SdSidebarUser_Conditional_2_For_6_Template(rf, ctx) { if (rf & 1) {
11
+ const _r3 = i0.ɵɵgetCurrentView();
12
+ i0.ɵɵelementStart(0, "sd-list-item", 5);
13
+ i0.ɵɵlistener("click", function SdSidebarUser_Conditional_2_For_6_Template_sd_list_item_click_0_listener() { const menu_r4 = i0.ɵɵrestoreView(_r3).$implicit; return i0.ɵɵresetView(menu_r4.onClick()); });
14
+ i0.ɵɵtext(1);
15
+ i0.ɵɵelementEnd();
16
+ } if (rf & 2) {
17
+ const menu_r4 = ctx.$implicit;
18
+ i0.ɵɵadvance();
19
+ i0.ɵɵtextInterpolate1(" ", menu_r4.title, " ");
20
+ } }
10
21
  function SdSidebarUser_Conditional_2_Template(rf, ctx) { if (rf & 1) {
11
22
  const _r1 = i0.ɵɵgetCurrentView();
12
- i0.ɵɵelementStart(0, "div", 3);
23
+ i0.ɵɵelementStart(0, "div", 1);
13
24
  i0.ɵɵlistener("click", function SdSidebarUser_Conditional_2_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onMenuOpenButtonClick()); })("keydown.enter", function SdSidebarUser_Conditional_2_Template_div_keydown_enter_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onMenuOpenButtonClick()); });
14
25
  i0.ɵɵtext(1);
15
- i0.ɵɵelement(2, "sd-collapse-icon", 4);
26
+ i0.ɵɵelement(2, "sd-collapse-icon", 2);
16
27
  i0.ɵɵelementEnd();
28
+ i0.ɵɵelementStart(3, "sd-collapse", 3)(4, "sd-list", 4);
29
+ i0.ɵɵrepeaterCreate(5, SdSidebarUser_Conditional_2_For_6_Template, 2, 1, "sd-list-item", null, _forTrack0);
30
+ i0.ɵɵelementEnd()();
17
31
  } if (rf & 2) {
18
- let tmp_2_0;
32
+ const _userMenu_r5 = ctx;
19
33
  const ctx_r1 = i0.ɵɵnextContext();
20
34
  i0.ɵɵproperty("sdRipple", true);
21
35
  i0.ɵɵadvance();
22
- i0.ɵɵtextInterpolate1(" ", (tmp_2_0 = ctx_r1.userMenu()) == null ? null : tmp_2_0.title, " ");
36
+ i0.ɵɵtextInterpolate1(" ", _userMenu_r5.title, " ");
23
37
  i0.ɵɵadvance();
24
38
  i0.ɵɵproperty("open", ctx_r1.menuOpen())("openRotate", 180);
25
- } }
26
- function SdSidebarUser_Conditional_3_For_3_Template(rf, ctx) { if (rf & 1) {
27
- const _r3 = i0.ɵɵgetCurrentView();
28
- i0.ɵɵelementStart(0, "sd-list-item", 6);
29
- i0.ɵɵlistener("click", function SdSidebarUser_Conditional_3_For_3_Template_sd_list_item_click_0_listener() { const menu_r4 = i0.ɵɵrestoreView(_r3).$implicit; return i0.ɵɵresetView(menu_r4.onClick()); });
30
- i0.ɵɵtext(1);
31
- i0.ɵɵelementEnd();
32
- } if (rf & 2) {
33
- const menu_r4 = ctx.$implicit;
34
39
  i0.ɵɵadvance();
35
- i0.ɵɵtextInterpolate1(" ", menu_r4.title, " ");
36
- } }
37
- function SdSidebarUser_Conditional_3_Template(rf, ctx) { if (rf & 1) {
38
- i0.ɵɵelementStart(0, "sd-collapse", 2)(1, "sd-list", 5);
39
- i0.ɵɵrepeaterCreate(2, SdSidebarUser_Conditional_3_For_3_Template, 2, 1, "sd-list-item", null, _forTrack0);
40
- i0.ɵɵelementEnd()();
41
- } if (rf & 2) {
42
- let tmp_3_0;
43
- const ctx_r1 = i0.ɵɵnextContext();
44
40
  i0.ɵɵproperty("open", ctx_r1.menuOpen());
45
41
  i0.ɵɵadvance();
46
42
  i0.ɵɵproperty("inset", true);
47
43
  i0.ɵɵadvance();
48
- i0.ɵɵrepeater((tmp_3_0 = ctx_r1.userMenu()) == null ? null : tmp_3_0.menus);
44
+ i0.ɵɵrepeater(_userMenu_r5.menus);
49
45
  } }
50
46
  export class SdSidebarUser {
51
47
  userMenu = input(...(ngDevMode ? [undefined, { debugName: "userMenu" }] : /* istanbul ignore next */ []));
@@ -54,20 +50,16 @@ export class SdSidebarUser {
54
50
  this.menuOpen.update((v) => !v);
55
51
  }
56
52
  static ɵfac = function SdSidebarUser_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SdSidebarUser)(); };
57
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SdSidebarUser, selectors: [["sd-sidebar-user"]], inputs: { userMenu: [1, "userMenu"] }, ngContentSelectors: _c0, decls: 4, vars: 2, consts: [[1, "p-lg"], ["tabindex", "0", 1, "_menu-button", 3, "sdRipple"], [3, "open"], ["tabindex", "0", 1, "_menu-button", 3, "click", "keydown.enter", "sdRipple"], [2, "float", "right", 3, "open", "openRotate"], [3, "inset"], [3, "click"]], template: function SdSidebarUser_Template(rf, ctx) { if (rf & 1) {
53
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SdSidebarUser, selectors: [["sd-sidebar-user"]], inputs: { userMenu: [1, "userMenu"] }, ngContentSelectors: _c0, decls: 3, vars: 1, consts: [[1, "p-lg"], ["tabindex", "0", 1, "_menu-button", 3, "click", "keydown.enter", "sdRipple"], [2, "float", "right", 3, "open", "openRotate"], [3, "open"], [3, "inset"], [3, "click"]], template: function SdSidebarUser_Template(rf, ctx) { if (rf & 1) {
58
54
  i0.ɵɵprojectionDef();
59
55
  i0.ɵɵelementStart(0, "div", 0);
60
56
  i0.ɵɵprojection(1);
61
57
  i0.ɵɵelementEnd();
62
- i0.ɵɵconditionalCreate(2, SdSidebarUser_Conditional_2_Template, 3, 4, "div", 1);
63
- i0.ɵɵconditionalCreate(3, SdSidebarUser_Conditional_3_Template, 4, 2, "sd-collapse", 2);
58
+ i0.ɵɵconditionalCreate(2, SdSidebarUser_Conditional_2_Template, 7, 6);
64
59
  } if (rf & 2) {
65
60
  let tmp_0_0;
66
- let tmp_1_0;
67
61
  i0.ɵɵadvance(2);
68
- i0.ɵɵconditional(((tmp_0_0 = ctx.userMenu()) == null ? null : tmp_0_0.title) ? 2 : -1);
69
- i0.ɵɵadvance();
70
- i0.ɵɵconditional(((tmp_1_0 = ctx.userMenu()) == null ? null : tmp_1_0.title) ? 3 : -1);
62
+ i0.ɵɵconditional((tmp_0_0 = ctx.userMenu()) ? 2 : -1, tmp_0_0);
71
63
  } }, dependencies: [SdCollapseIcon,
72
64
  SdCollapse,
73
65
  SdRipple,
@@ -86,17 +78,15 @@ export class SdSidebarUser {
86
78
  <div class="p-lg">
87
79
  <ng-content />
88
80
  </div>
89
- @if (userMenu()?.title) {
81
+ @if (userMenu(); as _userMenu) {
90
82
  <div class="_menu-button" tabindex="0" (click)="onMenuOpenButtonClick()" (keydown.enter)="onMenuOpenButtonClick()" [sdRipple]="true">
91
- {{ userMenu()?.title }}
83
+ {{ _userMenu.title }}
92
84
  <sd-collapse-icon [open]="menuOpen()" style="float: right;" [openRotate]="180" />
93
85
  </div>
94
- }
95
86
 
96
- @if (userMenu()?.title) {
97
87
  <sd-collapse [open]="menuOpen()">
98
88
  <sd-list [inset]="true">
99
- @for (menu of userMenu()?.menus; track menu.title) {
89
+ @for (menu of _userMenu.menus; track menu.title) {
100
90
  <sd-list-item (click)="menu.onClick()">
101
91
  {{ menu.title }}
102
92
  </sd-list-item>
@@ -106,4 +96,4 @@ export class SdSidebarUser {
106
96
  }
107
97
  `, styles: ["sd-sidebar-user {\n display: block;\n}\nsd-sidebar-user > ._menu-button {\n display: block;\n cursor: pointer;\n user-select: none;\n padding: var(--gap-default);\n}\nsd-sidebar-user > ._menu-button:hover {\n background: var(--trans-lighter);\n}\nsd-sidebar-user > sd-collapse > ._content > sd-list {\n background: var(--trans-lightest);\n padding: var(--gap-xs) 0;\n}"] }]
108
98
  }], null, { userMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "userMenu", required: false }] }] }); })();
109
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SdSidebarUser, { className: "SdSidebarUser", filePath: "packages/angular/src/layout/sidebar/sd-sidebar-user.ts", lineNumber: 68 }); })();
99
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SdSidebarUser, { className: "SdSidebarUser", filePath: "packages/angular/src/layout/sidebar/sd-sidebar-user.ts", lineNumber: 66 }); })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/angular",
3
- "version": "14.0.85",
3
+ "version": "14.0.87",
4
4
  "description": "심플리즘 패키지 - Angular",
5
5
  "author": "심플리즘",
6
6
  "license": "Apache-2.0",
@@ -24,40 +24,40 @@
24
24
  "./dist/**/*.css"
25
25
  ],
26
26
  "dependencies": {
27
- "@angular/animations": "^21.2.14",
28
- "@angular/common": "^21.2.14",
29
- "@angular/core": "^21.2.14",
30
- "@angular/platform-browser": "^21.2.14",
31
- "@angular/router": "^21.2.14",
32
- "@angular/service-worker": "^21.2.14",
33
- "@ng-icons/core": "^33.2.2",
34
- "@ng-icons/tabler-icons": "^33.2.2",
35
- "@tiptap/core": "^3.23.6",
36
- "@tiptap/extension-color": "^3.23.6",
37
- "@tiptap/extension-document": "^3.23.6",
38
- "@tiptap/extension-highlight": "^3.23.6",
39
- "@tiptap/extension-image": "^3.23.6",
40
- "@tiptap/extension-paragraph": "^3.23.6",
41
- "@tiptap/extension-placeholder": "^3.23.6",
42
- "@tiptap/extension-text": "^3.23.6",
43
- "@tiptap/extension-text-align": "^3.23.6",
44
- "@tiptap/extension-text-style": "^3.23.6",
45
- "@tiptap/extension-underline": "^3.23.6",
46
- "@tiptap/starter-kit": "^3.23.6",
47
- "bwip-js": "^4.10.1",
27
+ "@angular/animations": "^21.2.15",
28
+ "@angular/common": "^21.2.15",
29
+ "@angular/core": "^21.2.15",
30
+ "@angular/platform-browser": "^21.2.15",
31
+ "@angular/router": "^21.2.15",
32
+ "@angular/service-worker": "^21.2.15",
33
+ "@ng-icons/core": "^33.2.3",
34
+ "@ng-icons/tabler-icons": "^33.2.3",
35
+ "@tiptap/core": "^3.24.0",
36
+ "@tiptap/extension-color": "^3.24.0",
37
+ "@tiptap/extension-document": "^3.24.0",
38
+ "@tiptap/extension-highlight": "^3.24.0",
39
+ "@tiptap/extension-image": "^3.24.0",
40
+ "@tiptap/extension-paragraph": "^3.24.0",
41
+ "@tiptap/extension-placeholder": "^3.24.0",
42
+ "@tiptap/extension-text": "^3.24.0",
43
+ "@tiptap/extension-text-align": "^3.24.0",
44
+ "@tiptap/extension-text-style": "^3.24.0",
45
+ "@tiptap/extension-underline": "^3.24.0",
46
+ "@tiptap/starter-kit": "^3.24.0",
47
+ "bwip-js": "^4.11.1",
48
48
  "echarts": "^6.1.0",
49
49
  "html-to-image": "^1.11.13",
50
50
  "jspdf": "^4.2.1",
51
51
  "rxjs": "^7.8.2",
52
52
  "tabbable": "^6.4.0",
53
- "@simplysm/core-common": "14.0.85",
54
- "@simplysm/service-common": "14.0.85",
55
- "@simplysm/service-client": "14.0.85",
56
- "@simplysm/core-browser": "14.0.85"
53
+ "@simplysm/core-browser": "14.0.87",
54
+ "@simplysm/core-common": "14.0.87",
55
+ "@simplysm/service-client": "14.0.87",
56
+ "@simplysm/service-common": "14.0.87"
57
57
  },
58
58
  "devDependencies": {
59
- "@angular/compiler": "^21.2.14",
60
- "@angular/platform-browser-dynamic": "^21.2.14"
59
+ "@angular/compiler": "^21.2.15",
60
+ "@angular/platform-browser-dynamic": "^21.2.15"
61
61
  },
62
62
  "scripts": {}
63
63
  }
@@ -184,7 +184,7 @@ export class SdModalSelectButton<
184
184
  }
185
185
 
186
186
  onEraseClick(): void {
187
- this.value.set((this.selectMode() === "multi" ? [] : undefined) as any);
187
+ this.value.set((this.selectMode() === "multi" ? [] : undefined) as SelectModeValue<K>[M]);
188
188
  }
189
189
 
190
190
  async onSearchClick(event: MouseEvent): Promise<void> {
@@ -21,7 +21,7 @@ import {
21
21
  `,
22
22
  })
23
23
  export class SdForm {
24
- formElRef = viewChild.required<any, ElementRef<HTMLFormElement>>("formEl", {
24
+ formElRef = viewChild.required<unknown, ElementRef<HTMLFormElement>>("formEl", {
25
25
  read: ElementRef,
26
26
  });
27
27
 
@@ -409,10 +409,10 @@ function createTimeHandler(withSeconds: boolean): TextfieldTypeHandler {
409
409
  }
410
410
  },
411
411
  toControlValue(value) {
412
- return (value as DateTime | Time).toFormatString(controlFormat);
412
+ return (value as Time).toFormatString(controlFormat);
413
413
  },
414
414
  toDisplayText(value) {
415
- return (value as DateTime | Time).toFormatString(displayFormat);
415
+ return (value as Time).toFormatString(displayFormat);
416
416
  },
417
417
  validate(value, opts) {
418
418
  const errors: string[] = [];
@@ -108,6 +108,7 @@ export class SdPagination {
108
108
  }
109
109
 
110
110
  goToNextGroup(): void {
111
+ if (!this.hasNext()) return;
111
112
  this.currentPage.set((this._groupIndex() + 1) * Math.max(this.visiblePageCount(), 1));
112
113
  }
113
114
 
@@ -101,7 +101,7 @@ export class SdSelectItem<T> {
101
101
  const itemValue = this.value();
102
102
  if (this._parentControl.selectMode() === "multi") {
103
103
  const arr = parentValue as T[] | undefined;
104
- return arr != null && arr.includes(itemValue as T);
104
+ return arr != null && arr.some((x) => x === itemValue);
105
105
  }
106
106
  return parentValue === itemValue;
107
107
  });
@@ -404,7 +404,7 @@ export class SdSelect<M extends "single" | "multi", T> {
404
404
  } else {
405
405
  this.value.update((v) => {
406
406
  const arr: T[] = Array.isArray(v) ? v : [];
407
- if (arr.includes(itemValue as T)) {
407
+ if (arr.some((x) => x === itemValue)) {
408
408
  return arr.filter((item) => item !== itemValue);
409
409
  } else {
410
410
  return [...arr, itemValue];
@@ -1,10 +1,8 @@
1
1
  import { computed, inject, Injectable, signal } from "@angular/core";
2
2
  import type { Signal } from "@angular/core";
3
- import type { AppStructureItem, AppStructureService } from "@simplysm/service-common";
3
+ import type { AppStructureItem } from "@simplysm/service-common";
4
4
  import type { SdFlatMenu } from "./sd-app-structure.types";
5
5
  import { SdAppStructureUtils } from "./sd-app-structure.utils";
6
- import { SdServiceClientFactoryProvider } from "../service-client/sd-service-client-factory.provider";
7
- import { SdAngularConfigProvider } from "../config/sd-angular-config.provider";
8
6
 
9
7
  export function injectPermsSignal<K extends string>(viewCodes: string[], keys: K[]): Signal<K[]> {
10
8
  const sdAppStructure = inject(SdAppStructureProvider);
@@ -15,20 +13,12 @@ export function injectPermsSignal<K extends string>(viewCodes: string[], keys: K
15
13
  // 메뉴는 모듈/권한 모두 체크함
16
14
  @Injectable({ providedIn: "root" })
17
15
  export class SdAppStructureProvider<TModule = unknown> {
18
- private readonly _sdServiceClientFactory = inject(SdServiceClientFactoryProvider);
19
- private readonly _sdAngularConfig = inject(SdAngularConfigProvider);
20
-
21
16
  readonly usableModules = signal<TModule[] | undefined>(undefined);
22
17
  readonly permRecord = signal<Record<string, boolean> | undefined>(undefined);
23
18
  readonly items = signal<AppStructureItem<TModule>[]>([]);
24
19
 
25
- async initialize(serviceKey: string): Promise<void> {
26
- const client = this._sdServiceClientFactory.get(serviceKey);
27
- const svc = client.getService<AppStructureService>("AppStructure");
28
- const itemsMap = await svc.getItems();
29
- this.items.set(
30
- (itemsMap[this._sdAngularConfig.clientName] ?? []) as AppStructureItem<TModule>[],
31
- );
20
+ initialize(items: AppStructureItem<TModule>[]): void {
21
+ this.items.set(items);
32
22
  }
33
23
 
34
24
  usableMenus = computed(() =>
@@ -46,6 +36,10 @@ export class SdAppStructureProvider<TModule = unknown> {
46
36
  return SdAppStructureUtils.getTitleByFullCode(this.items(), fullCode);
47
37
  }
48
38
 
39
+ findTitleByFullCode(fullCode: string) {
40
+ return SdAppStructureUtils.findTitleByFullCode(this.items(), fullCode);
41
+ }
42
+
49
43
  getItemChainByFullCode(fullCode: string) {
50
44
  return SdAppStructureUtils.getItemChainByFullCode(this.items(), fullCode);
51
45
  }
@@ -9,10 +9,22 @@ import type { SdMenu, SdFlatMenu, SdPermission } from "./sd-app-structure.types"
9
9
  export abstract class SdAppStructureUtils {
10
10
  //---------- Info
11
11
 
12
- static getTitleByFullCode<TModule>(items: AppStructureItem<TModule>[], fullCode: string) {
12
+ static getTitleByFullCode<TModule>(items: AppStructureItem<TModule>[], fullCode: string): string {
13
+ const title = this.findTitleByFullCode(items, fullCode);
14
+ if (title == null) {
15
+ throw new Error(`Item not found for fullCode: ${fullCode}`);
16
+ }
17
+ return title;
18
+ }
19
+
20
+ /** 항목을 못 찾으면 throw 대신 undefined 반환 (결측 보존). */
21
+ static findTitleByFullCode<TModule>(
22
+ items: AppStructureItem<TModule>[],
23
+ fullCode: string,
24
+ ): string | undefined {
13
25
  const itemChain = this.getItemChainByFullCode(items, fullCode);
14
26
  if (itemChain.length === 0) {
15
- throw new Error(`Item not found for fullCode: ${fullCode}`);
27
+ return undefined;
16
28
  }
17
29
  const parent = itemChain
18
30
  .slice(0, -1)
@@ -3,7 +3,6 @@ import {
3
3
  ChangeDetectionStrategy,
4
4
  Component,
5
5
  computed,
6
- ElementRef,
7
6
  inject,
8
7
  input,
9
8
  ViewEncapsulation,
@@ -18,6 +17,7 @@ import { SdBusyProvider, type SdBusyType } from "./sd-busy.provider";
18
17
  host: {
19
18
  "[attr.data-sd-busy]": "busy() || undefined",
20
19
  "[attr.data-sd-type]": "currType()",
20
+ "(keydown.capture)": "onKeydownCapture($event)",
21
21
  },
22
22
  template: `
23
23
  <div class="_screen">
@@ -339,17 +339,10 @@ export class SdBusyContainer {
339
339
 
340
340
  currType = computed(() => this.type() ?? this._sdBusy.type());
341
341
 
342
- constructor() {
343
- const el = inject(ElementRef).nativeElement as HTMLElement;
344
- el.addEventListener(
345
- "keydown",
346
- (event) => {
347
- if (this.busy()) {
348
- event.preventDefault();
349
- event.stopPropagation();
350
- }
351
- },
352
- true,
353
- );
342
+ onKeydownCapture(event: Event): void {
343
+ if (this.busy()) {
344
+ event.preventDefault();
345
+ event.stopPropagation();
346
+ }
354
347
  }
355
348
  }
@@ -44,6 +44,10 @@ export class SdBusyProvider {
44
44
  constructor() {
45
45
  effect(() => {
46
46
  const busy = this.globalBusyCount() > 0;
47
+
48
+ // busy 가 처음 true 가 되기 전엔 컨테이너를 생성하지 않음 (lazy)
49
+ if (!busy && this._containerRef == null) return;
50
+
47
51
  const ref = this.containerRef;
48
52
  const el = ref.location.nativeElement as HTMLElement;
49
53
 
@@ -20,7 +20,7 @@ export function injectSdSystemConfigResource<T>(options: { key: Signal<string |
20
20
  const key = options.key();
21
21
  if (key == null) return;
22
22
  queueMicrotask(() => {
23
- sdSystemConfig.setAsync(`${elTag}.${key}`, value as T).catch((err) => {
23
+ sdSystemConfig.setAsync(`${elTag}.${key}`, value).catch((err) => {
24
24
  errorHandler.handleError(err);
25
25
  });
26
26
  });
@@ -6,13 +6,15 @@ export class SdSystemConfigProvider<T> {
6
6
  private readonly _sdLocalStorage = inject<SdLocalStorageProvider<T>>(SdLocalStorageProvider);
7
7
 
8
8
  fn?: {
9
- set<K extends keyof T & string>(key: K, data: T[K]): Promise<void> | void;
10
- get(key: keyof T & string): PromiseLike<any>;
9
+ set<K extends keyof T & string>(key: K, data: T[K] | undefined): Promise<void> | void;
10
+ get(key: keyof T & string): PromiseLike<unknown>;
11
11
  };
12
12
 
13
- async setAsync<K extends keyof T & string>(key: K, data: T[K]) {
13
+ async setAsync<K extends keyof T & string>(key: K, data: T[K] | undefined) {
14
14
  if (this.fn) {
15
15
  await this.fn.set(key, data);
16
+ } else if (data == null) {
17
+ this._sdLocalStorage.remove(key);
16
18
  } else {
17
19
  this._sdLocalStorage.set(key, data);
18
20
  }
@@ -17,7 +17,7 @@ export class SdGlobalErrorHandlerPlugin implements ErrorHandler {
17
17
  private readonly _sdSystemLog = inject(SdSystemLogProvider);
18
18
  private _hasDisplayedError = false;
19
19
 
20
- handleError(event: any) {
20
+ handleError(event: unknown) {
21
21
  try {
22
22
  if (event instanceof PromiseRejectionEvent) {
23
23
  const reason = event.reason;
@@ -23,7 +23,11 @@ export class SdFileDialogProvider {
23
23
  }
24
24
  inputEl.onchange = (event: Event): void => {
25
25
  cleanup();
26
- const files = (event.target as HTMLInputElement).files!;
26
+ const files = (event.target as HTMLInputElement).files;
27
+ if (files == null) {
28
+ resolve(undefined);
29
+ return;
30
+ }
27
31
  resolve(multiple ? Array.from(files) : files[0]);
28
32
  };
29
33
 
@@ -1,12 +1,13 @@
1
1
  import { Injectable, signal } from "@angular/core";
2
2
  import type { SdModalContentDef } from "./sd-modal.provider";
3
+ import type { SdModal } from "./sd-modal";
3
4
 
4
5
  /**
5
6
  * 모달 내부에서 inject하여 사용하는 프로바이더
6
7
  */
7
8
  @Injectable()
8
9
  export class SdActivatedModalProvider<T extends SdModalContentDef<any> = SdModalContentDef<any>> {
9
- modalComponent = signal<any>(undefined);
10
+ modalComponent = signal<SdModal | undefined>(undefined);
10
11
  contentComponent = signal<T | undefined>(undefined);
11
12
  canDeactivateFn: () => boolean = () => true;
12
13
  }
@@ -195,7 +195,12 @@ export class SdModalProvider {
195
195
 
196
196
  const modalEl = modalRef.location.nativeElement as HTMLElement;
197
197
 
198
+ let destroyed = false;
198
199
  const doDestroy = () => {
200
+ // transitionend 와 setTimeout 폴백 중 먼저 도달한 쪽에서 1회만 실행
201
+ if (destroyed) return;
202
+ destroyed = true;
203
+
199
204
  // DOM에서 제거
200
205
  if (modalEl.parentNode != null) {
201
206
  modalEl.parentNode.removeChild(modalEl);
@@ -221,7 +226,10 @@ export class SdModalProvider {
221
226
  // transition duration 확인 후 대기 또는 즉시 destroy
222
227
  const duration = parseFloat(getComputedStyle(modalEl).transitionDuration || "0");
223
228
  if (duration > 0) {
229
+ // transitionend 가 정상 경로. 단 백그라운드 탭·transition 인터럽트 시 미발화하므로
230
+ // duration(초→ms) + 여유 100ms 후 발화하는 setTimeout 폴백을 함께 걸어 destroy 누락을 방지.
224
231
  modalEl.addEventListener("transitionend", doDestroy, { once: true });
232
+ setTimeout(doDestroy, duration * 1000 + 100);
225
233
  } else {
226
234
  doDestroy();
227
235
  }
@@ -1,4 +1,5 @@
1
1
  import {
2
+ afterNextRender,
2
3
  ChangeDetectionStrategy,
3
4
  Component,
4
5
  effect,
@@ -323,7 +324,9 @@ import "@simplysm/core-browser";
323
324
  export class SdModal {
324
325
  private readonly _elRef = inject(ElementRef<HTMLElement>);
325
326
  private readonly _sdActivatedModal = inject(SdActivatedModalProvider, { optional: true });
326
- private readonly _sdSystemConfig = inject(SdSystemConfigProvider, { optional: true });
327
+ private readonly _sdSystemConfig = inject<
328
+ SdSystemConfigProvider<Record<string, Record<string, string>>>
329
+ >(SdSystemConfigProvider, { optional: true });
327
330
  private readonly _errorHandler = inject(ErrorHandler);
328
331
  private readonly _focusTrap = injectFocusTrap();
329
332
 
@@ -359,7 +362,7 @@ export class SdModal {
359
362
 
360
363
  constructor() {
361
364
  // data-sd-init: 첫 렌더 후 설정하여 CSS transition 트리거 허용
362
- effect(() => {
365
+ afterNextRender(() => {
363
366
  this._elRef.nativeElement.setAttribute("data-sd-init", "");
364
367
  });
365
368
 
@@ -508,7 +511,7 @@ export class SdModal {
508
511
  if (dialogEl.style.left !== "") config["left"] = dialogEl.style.left;
509
512
  if (dialogEl.style.top !== "") config["top"] = dialogEl.style.top;
510
513
 
511
- await this._sdSystemConfig.setAsync(`sd-modal.${k}` as any, config as any);
514
+ await this._sdSystemConfig.setAsync(`sd-modal.${k}`, config);
512
515
  }
513
516
 
514
517
  private async _restoreConfig(k: string): Promise<void> {
@@ -9,35 +9,33 @@ import {
9
9
  import type { SdModalContentDef } from "./sd-modal.provider";
10
10
  import { SdButton } from "../../controls/button/sd-button";
11
11
  import { SdTextfield } from "../../controls/input/sd-textfield";
12
+ import { SdForm } from "../../controls/form/sd-form";
12
13
 
13
14
  /**
14
15
  * 범용 프롬프트 모달.
15
16
  * SdModalContentDef<string> 구현. message를 표시하고, 텍스트 입력 후 확인/취소.
16
17
  * 확인 시 입력값 emit, 취소 시 undefined emit.
18
+ * 입력값은 필수(required)이며, 빈 값으로 확인 시 sd-form 네이티브 검증으로 차단된다.
17
19
  */
18
20
  @Component({
19
21
  selector: "sd-prompt-modal",
20
22
  changeDetection: ChangeDetectionStrategy.OnPush,
21
23
  encapsulation: ViewEncapsulation.None,
22
24
  standalone: true,
23
- imports: [SdButton, SdTextfield],
25
+ imports: [SdForm, SdButton, SdTextfield],
24
26
  host: {
25
27
  style: "display: block",
26
28
  class: "p-default",
27
29
  },
28
30
  template: `
29
- <p class="mb-default" [innerHTML]="message()"></p>
30
- <sd-textfield
31
- [type]="'text'"
32
- [value]="_inputValue()"
33
- (input)="onInputChange($event)"
34
- (keydown.enter)="onConfirm()"
35
- class="mb-default"
36
- />
37
- <div class="flex-row main-align-end gap-sm">
38
- <sd-button [theme]="'primary'" (click)="onConfirm()">확인</sd-button>
39
- <sd-button (click)="onCancel()">취소</sd-button>
40
- </div>
31
+ <sd-form (formSubmit)="onConfirm()">
32
+ <p class="mb-default" [innerHTML]="message()"></p>
33
+ <sd-textfield [type]="'text'" [(value)]="_inputValue" [required]="true" class="mb-default" />
34
+ <div class="flex-row main-align-end gap-sm">
35
+ <sd-button [type]="'submit'" [theme]="'primary'">확인</sd-button>
36
+ <sd-button (click)="onCancel()">취소</sd-button>
37
+ </div>
38
+ </sd-form>
41
39
  `,
42
40
  })
43
41
  export class SdPromptModal implements SdModalContentDef<string> {
@@ -46,18 +44,10 @@ export class SdPromptModal implements SdModalContentDef<string> {
46
44
 
47
45
  message = input.required<string>();
48
46
 
49
- _inputValue = signal("");
50
-
51
- onInputChange(event: Event): void {
52
- const inputEl = event.target as HTMLInputElement;
53
- this._inputValue.set(inputEl.value);
54
- }
47
+ _inputValue = signal<string | undefined>(undefined);
55
48
 
56
49
  onConfirm(): void {
57
- const val = this._inputValue();
58
- if (val !== "") {
59
- this.close.emit(val);
60
- }
50
+ this.close.emit(this._inputValue());
61
51
  }
62
52
 
63
53
  onCancel(): void {
@@ -9,10 +9,12 @@ import {
9
9
  } from "@angular/core";
10
10
  import type { DirectiveInputSignals, WithOptional } from "../directive-input-signals";
11
11
  import { SdBusyProvider } from "../busy/sd-busy.provider";
12
- import { wait } from "@simplysm/core-common";
12
+ import { createLogger, wait } from "@simplysm/core-common";
13
13
  import { jsPDF } from "jspdf";
14
14
  import * as htmlToImage from "html-to-image";
15
15
 
16
+ const logger = createLogger("angular:print");
17
+
16
18
  export interface SdPrint {
17
19
  initialized: Signal<boolean>;
18
20
  readonly _optionalPrintInputs?: string;
@@ -175,6 +177,7 @@ export class SdPrintProvider {
175
177
 
176
178
  private async _waitForAllImagesLoadedAsync(container: HTMLElement): Promise<void> {
177
179
  const imgs = Array.from(container.querySelectorAll("img"));
180
+ const failedSrcs: string[] = [];
178
181
 
179
182
  await Promise.all(
180
183
  imgs.map((img) => {
@@ -182,16 +185,28 @@ export class SdPrintProvider {
182
185
  if (img.complete && img.naturalWidth !== 0) {
183
186
  resolve();
184
187
  } else {
185
- const onDone = () => {
186
- img.removeEventListener("load", onDone);
187
- img.removeEventListener("error", onDone);
188
+ const cleanup = () => {
189
+ img.removeEventListener("load", onLoad);
190
+ img.removeEventListener("error", onError);
191
+ };
192
+ const onLoad = () => {
193
+ cleanup();
194
+ resolve();
195
+ };
196
+ const onError = () => {
197
+ failedSrcs.push(img.src);
198
+ cleanup();
188
199
  resolve();
189
200
  };
190
- img.addEventListener("load", onDone);
191
- img.addEventListener("error", onDone);
201
+ img.addEventListener("load", onLoad);
202
+ img.addEventListener("error", onError);
192
203
  }
193
204
  });
194
205
  }),
195
206
  );
207
+
208
+ if (failedSrcs.length > 0) {
209
+ logger.error(`인쇄/PDF: 이미지 ${failedSrcs.length}건 로드 실패`, failedSrcs);
210
+ }
196
211
  }
197
212
  }