@simplysm/angular 14.0.47 → 14.0.49

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 (134) hide show
  1. package/README.md +467 -0
  2. package/dist/controls/dropdown/sd-dropdown-popup.d.ts +1 -0
  3. package/dist/controls/dropdown/sd-dropdown-popup.d.ts.map +1 -1
  4. package/dist/controls/dropdown/sd-dropdown-popup.js +6 -1
  5. package/dist/controls/dropdown/sd-dropdown.d.ts.map +1 -1
  6. package/dist/controls/dropdown/sd-dropdown.js +6 -2
  7. package/dist/controls/select/sd-select.d.ts.map +1 -1
  8. package/dist/controls/select/sd-select.js +12 -34
  9. package/dist/core/routing/injectViewTypeSignal.d.ts +1 -1
  10. package/dist/core/routing/injectViewTypeSignal.d.ts.map +1 -1
  11. package/dist/core/routing/injectViewTypeSignal.js +15 -7
  12. package/dist/core/selection/useSelectionManager.d.ts +1 -0
  13. package/dist/core/selection/useSelectionManager.d.ts.map +1 -1
  14. package/dist/core/selection/useSelectionManager.js +54 -14
  15. package/dist/data/shared-data/sd-shared-data-select-button.d.ts +11 -5
  16. package/dist/data/shared-data/sd-shared-data-select-button.d.ts.map +1 -1
  17. package/dist/data/shared-data/sd-shared-data-select-button.js +68 -29
  18. package/dist/data/sheet/sd-sheet.d.ts +4 -1
  19. package/dist/data/sheet/sd-sheet.d.ts.map +1 -1
  20. package/dist/data/sheet/sd-sheet.js +23 -4
  21. package/dist/index.d.ts +0 -14
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +0 -19
  24. package/dist/styles.css +9 -0
  25. package/docs/bootstrap.md +38 -0
  26. package/docs/directives.md +236 -0
  27. package/docs/features.md +154 -0
  28. package/docs/pipes.md +32 -0
  29. package/docs/plugins.md +37 -0
  30. package/docs/provider-types.md +283 -0
  31. package/docs/providers.md +379 -0
  32. package/docs/recipes/crud-detail.md +875 -0
  33. package/docs/recipes/crud-list.md +1037 -0
  34. package/docs/recipes/data-select-button.md +318 -0
  35. package/docs/recipes/page-modal-container.md +178 -0
  36. package/docs/styling.md +222 -0
  37. package/docs/type-utilities.md +250 -0
  38. package/docs/ui-data.md +333 -0
  39. package/docs/ui-form.md +502 -0
  40. package/docs/ui-layout.md +140 -0
  41. package/docs/ui-navigation.md +303 -0
  42. package/docs/ui-overlay.md +157 -0
  43. package/docs/ui-visual.md +127 -0
  44. package/docs/utils.md +244 -0
  45. package/package.json +21 -20
  46. package/scss/commons/_styles.scss +12 -0
  47. package/src/controls/dropdown/sd-dropdown-popup.ts +6 -2
  48. package/src/controls/dropdown/sd-dropdown.ts +3 -4
  49. package/src/controls/select/sd-select.ts +6 -30
  50. package/src/core/routing/injectViewTypeSignal.ts +17 -10
  51. package/src/core/selection/useSelectionManager.ts +62 -12
  52. package/src/data/shared-data/sd-shared-data-select-button.ts +49 -14
  53. package/src/data/sheet/sd-sheet.ts +21 -0
  54. package/src/index.ts +0 -32
  55. package/dist/core/commons.d.ts +0 -2
  56. package/dist/core/commons.d.ts.map +0 -1
  57. package/dist/core/commons.js +0 -4
  58. package/dist/core/injectParent.d.ts +0 -7
  59. package/dist/core/injectParent.d.ts.map +0 -1
  60. package/dist/core/injectParent.js +0 -30
  61. package/dist/core/selection/setupCumulateSelectedKeys.d.ts +0 -9
  62. package/dist/core/selection/setupCumulateSelectedKeys.d.ts.map +0 -1
  63. package/dist/core/selection/setupCumulateSelectedKeys.js +0 -27
  64. package/dist/core/withBusy.d.ts +0 -3
  65. package/dist/core/withBusy.d.ts.map +0 -1
  66. package/dist/core/withBusy.js +0 -9
  67. package/dist/data/data-detail/sd-data-detail.base.d.ts +0 -51
  68. package/dist/data/data-detail/sd-data-detail.base.d.ts.map +0 -1
  69. package/dist/data/data-detail/sd-data-detail.base.js +0 -127
  70. package/dist/data/data-detail/sd-data-detail.d.ts +0 -30
  71. package/dist/data/data-detail/sd-data-detail.d.ts.map +0 -1
  72. package/dist/data/data-detail/sd-data-detail.js +0 -495
  73. package/dist/data/data-select-button/sd-data-select-button.base.d.ts +0 -24
  74. package/dist/data/data-select-button/sd-data-select-button.base.d.ts.map +0 -1
  75. package/dist/data/data-select-button/sd-data-select-button.base.js +0 -68
  76. package/dist/data/data-select-button/sd-data-select-button.d.ts +0 -15
  77. package/dist/data/data-select-button/sd-data-select-button.d.ts.map +0 -1
  78. package/dist/data/data-select-button/sd-data-select-button.js +0 -152
  79. package/dist/data/data-sheet/injectDataSheetExcelManager.d.ts +0 -14
  80. package/dist/data/data-sheet/injectDataSheetExcelManager.d.ts.map +0 -1
  81. package/dist/data/data-sheet/injectDataSheetExcelManager.js +0 -31
  82. package/dist/data/data-sheet/injectDataSheetInlineEditManager.d.ts +0 -26
  83. package/dist/data/data-sheet/injectDataSheetInlineEditManager.d.ts.map +0 -1
  84. package/dist/data/data-sheet/injectDataSheetInlineEditManager.js +0 -54
  85. package/dist/data/data-sheet/injectDataSheetModalEditManager.d.ts +0 -19
  86. package/dist/data/data-sheet/injectDataSheetModalEditManager.d.ts.map +0 -1
  87. package/dist/data/data-sheet/injectDataSheetModalEditManager.js +0 -44
  88. package/dist/data/data-sheet/injectDataSheetRefreshManager.d.ts +0 -25
  89. package/dist/data/data-sheet/injectDataSheetRefreshManager.d.ts.map +0 -1
  90. package/dist/data/data-sheet/injectDataSheetRefreshManager.js +0 -50
  91. package/dist/data/data-sheet/sd-data-sheet-column.d.ts +0 -8
  92. package/dist/data/data-sheet/sd-data-sheet-column.d.ts.map +0 -1
  93. package/dist/data/data-sheet/sd-data-sheet-column.js +0 -15
  94. package/dist/data/data-sheet/sd-data-sheet.base.d.ts +0 -75
  95. package/dist/data/data-sheet/sd-data-sheet.base.d.ts.map +0 -1
  96. package/dist/data/data-sheet/sd-data-sheet.base.js +0 -188
  97. package/dist/data/data-sheet/sd-data-sheet.d.ts +0 -49
  98. package/dist/data/data-sheet/sd-data-sheet.d.ts.map +0 -1
  99. package/dist/data/data-sheet/sd-data-sheet.js +0 -968
  100. package/dist/data/data-sheet/sd-data-sheet.types.d.ts +0 -17
  101. package/dist/data/data-sheet/sd-data-sheet.types.d.ts.map +0 -1
  102. package/dist/data/data-sheet/sd-data-sheet.types.js +0 -1
  103. package/dist/data/data-sheet/setupCloserWhenSingleSelectionChange.d.ts +0 -9
  104. package/dist/data/data-sheet/setupCloserWhenSingleSelectionChange.d.ts.map +0 -1
  105. package/dist/data/data-sheet/setupCloserWhenSingleSelectionChange.js +0 -20
  106. package/dist/data/data-sheet/useDataSheetFilterManager.d.ts +0 -13
  107. package/dist/data/data-sheet/useDataSheetFilterManager.d.ts.map +0 -1
  108. package/dist/data/data-sheet/useDataSheetFilterManager.js +0 -19
  109. package/dist/data/getOrmDataEditToastErrorMessage.d.ts +0 -2
  110. package/dist/data/getOrmDataEditToastErrorMessage.d.ts.map +0 -1
  111. package/dist/data/getOrmDataEditToastErrorMessage.js +0 -8
  112. package/dist/layout/base-container/sd-base-container.d.ts +0 -27
  113. package/dist/layout/base-container/sd-base-container.d.ts.map +0 -1
  114. package/dist/layout/base-container/sd-base-container.js +0 -193
  115. package/src/core/commons.ts +0 -4
  116. package/src/core/injectParent.ts +0 -45
  117. package/src/core/selection/setupCumulateSelectedKeys.ts +0 -38
  118. package/src/core/withBusy.ts +0 -13
  119. package/src/data/data-detail/sd-data-detail.base.ts +0 -187
  120. package/src/data/data-detail/sd-data-detail.ts +0 -237
  121. package/src/data/data-select-button/sd-data-select-button.base.ts +0 -104
  122. package/src/data/data-select-button/sd-data-select-button.ts +0 -105
  123. package/src/data/data-sheet/injectDataSheetExcelManager.ts +0 -57
  124. package/src/data/data-sheet/injectDataSheetInlineEditManager.ts +0 -89
  125. package/src/data/data-sheet/injectDataSheetModalEditManager.ts +0 -76
  126. package/src/data/data-sheet/injectDataSheetRefreshManager.ts +0 -90
  127. package/src/data/data-sheet/sd-data-sheet-column.ts +0 -10
  128. package/src/data/data-sheet/sd-data-sheet.base.ts +0 -294
  129. package/src/data/data-sheet/sd-data-sheet.ts +0 -479
  130. package/src/data/data-sheet/sd-data-sheet.types.ts +0 -18
  131. package/src/data/data-sheet/setupCloserWhenSingleSelectionChange.ts +0 -29
  132. package/src/data/data-sheet/useDataSheetFilterManager.ts +0 -30
  133. package/src/data/getOrmDataEditToastErrorMessage.ts +0 -10
  134. package/src/layout/base-container/sd-base-container.ts +0 -121
package/README.md ADDED
@@ -0,0 +1,467 @@
1
+ # @simplysm/angular
2
+
3
+ Angular 21 기반 UI 컴포넌트 라이브러리. Zoneless, signal-based, standalone 컴포넌트로 구성된다.
4
+
5
+ > **NOTE:** 이 문서는 `@simplysm/angular` 라이브러리의 사용법만 다룬다. Angular 프레임워크 자체의 사용법(컴포넌트 작성, DI, 라우팅, signal 등)은 `angular-cli` MCP를 활용한다.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @simplysm/angular
11
+ ```
12
+
13
+ ## API Overview
14
+
15
+ ### Bootstrap
16
+
17
+ | API | Type | Description |
18
+ |-----|------|-------------|
19
+ | `provideSdAngular` | function | 모든 기반 설정을 제공하는 환경 프로바이더 팩토리 |
20
+ | `SdAngularConfigProvider` | class | `clientName` 설정을 보유하는 프로바이더 |
21
+
22
+ -> See [docs/bootstrap.md](./docs/bootstrap.md) for details.
23
+
24
+ ### Providers
25
+
26
+ | API | Type | Description |
27
+ |-----|------|-------------|
28
+ | `SdThemeProvider` | class | 다크모드/폰트 크기 프로바이더 (`dark`, `fontSize` signal) |
29
+ | `SdThemeSelector` | component | 테마 설정 드롭다운 (다크모드 토글, 폰트 크기 조절) |
30
+ | `SdSystemLogProvider` | class | 시스템 로그 기록 프로바이더 |
31
+ | `SdAppStructureProvider` | class | 앱 구조(메뉴/권한) 관리 프로바이더 |
32
+ | `injectPermsSignal` | function | 현재 뷰의 권한 목록을 signal로 반환 |
33
+ | `SdAppStructureUtils` | class | 앱 구조 유틸리티 (메뉴/권한 조회 정적 메서드) |
34
+ | `SdFileDialogProvider` | class | 네이티브 파일 선택 대화상자 프로바이더 |
35
+ | `SdLocalStorageProvider` | class | `clientName` 스코프 localStorage 래퍼 |
36
+ | `SdSystemConfigProvider` | class | 비동기 설정 저장/조회 프로바이더 |
37
+ | `SdServiceClientFactoryProvider` | class | ServiceClient 인스턴스 팩토리/관리 |
38
+ | `SdSharedDataProvider` | class | 이벤트 기반 공유 데이터 캐시 추상 프로바이더 |
39
+ | `SdSharedDataChangeEvent` | const | 공유 데이터 변경 이벤트 정의 |
40
+ | `SdNavigateWindowProvider` | class | 새 윈도우 네비게이션 + 자동 닫기 |
41
+ | `SdActivatedModalProvider` | class | 모달 내부에서 inject하여 모달/컨텐츠 참조 |
42
+ | `SdToastProvider` | class | 토스트 알림 (info/success/warning/danger) |
43
+ | `SdBusyProvider` | class | 글로벌 busy 상태 관리 (spinner/bar/cube) |
44
+ | `SdPrintProvider` | class | 인쇄 및 PDF 생성 프로바이더 |
45
+ | `SdModalProvider` | class | 프로그래밍 방식 모달 생성 |
46
+
47
+ -> See [docs/providers.md](./docs/providers.md) for details.
48
+
49
+ ### Provider Types
50
+
51
+ | API | Type | Description |
52
+ |-----|------|-------------|
53
+ | `SdMenu` | interface | 메뉴 트리 노드 |
54
+ | `SdFlatMenu` | interface | 플랫 메뉴 항목 |
55
+ | `SdPermission` | interface | 권한 트리 노드 |
56
+ | `SharedDataBase` | interface | 공유 데이터 기본 인터페이스 |
57
+ | `SharedDataInfo` | interface | 공유 데이터 등록 정보 |
58
+ | `SharedDataHandle` | interface | 공유 데이터 핸들 (items signal + get) |
59
+ | `SdModalContentDef` | interface | 모달 컴포넌트 구현 인터페이스 |
60
+ | `SdModalInfo` | interface | 모달 생성 시 전달하는 정보 |
61
+ | `SdModalOptions` | interface | 모달 옵션 (크기, 위치, 동작) |
62
+ | `SdToastContentDef` | interface | 토스트 컴포넌트 구현 인터페이스 |
63
+ | `SdToastInput` | interface | 커스텀 토스트 생성 입력 |
64
+ | `SdToastSeverity` | type | 토스트 심각도 (`"info" \| "success" \| "warning" \| "danger"`) |
65
+ | `SdToastTheme` | type | 토스트 테마 (severity + `"primary" \| "secondary" \| "gray" \| "blue-gray"`) |
66
+ | `SdBusyType` | type | busy 표시 유형 (`"spinner" \| "bar" \| "cube"`) |
67
+ | `SdPrint` | interface | 인쇄 컴포넌트 구현 인터페이스 |
68
+ | `SdPrintInput` | interface | 인쇄 생성 입력 |
69
+ | `SelectModalOutputResult` | interface | 모달 선택 결과 (`selectedItemKeys`, `selectedItems`) |
70
+
71
+ -> See [docs/provider-types.md](./docs/provider-types.md) for details.
72
+
73
+ ### Directives
74
+
75
+ | API | Type | Description |
76
+ |-----|------|-------------|
77
+ | `SdEvents` | directive | `.capture`, `.passive`, `.once` 수식어 및 커스텀 이벤트 바인딩 |
78
+ | `SdRipple` | directive | `[sdRipple]` 리플 효과 |
79
+ | `SdShowEffect` | directive | `[sdShowEffect]` 뷰포트 진입 시 reveal 애니메이션 |
80
+ | `SdInvalid` | directive | `[sdInvalid]` 유효성 검증 표시기 |
81
+ | `SdTypedTemplate` | directive | `ng-template[typed]` 템플릿 컨텍스트 타입 가드 |
82
+ | `SdItemOfTemplate` | directive | `ng-template[itemOf]` 항목 반복 템플릿 타입 가드 |
83
+ | `SdItemOfTemplateContext` | interface | itemOf 템플릿 컨텍스트 (`$implicit`, `item`, `index`, `depth`) |
84
+ | `SdRouterLink` | directive | `[sdRouterLink]` 라우터 네비게이션 (Ctrl+클릭 새 창) |
85
+ | `SdCommandDirective` | directive | `[sdSaveCommand]`, `[sdRefreshCommand]`, `[sdInsertCommand]` 키보드 단축키 output 이벤트 디렉티브 |
86
+ | `SdResizeDirective` | directive | `[sdResize]` ResizeObserver 기반 resize output 이벤트 디렉티브 |
87
+ | `SdResizeEvent` | interface | resize 이벤트 데이터 (`heightChanged`, `widthChanged`, `target`, `contentRect`) |
88
+ | `SdIntersectionDirective` | directive | `[sdIntersection]` IntersectionObserver 기반 intersection output 이벤트 디렉티브 |
89
+ | `SdIntersectionEvent` | interface | intersection 이벤트 데이터 (`entry`) |
90
+
91
+ -> See [docs/directives.md](./docs/directives.md) for details.
92
+
93
+ ### Plugins
94
+
95
+ | API | Type | Description |
96
+ |-----|------|-------------|
97
+ | `SdOptionEventPlugin` | class | `.capture`, `.passive`, `.once` 이벤트 옵션 플러그인 (`provideSdAngular`에서 자동 등록) |
98
+ | `SdGlobalErrorHandlerPlugin` | class | 글로벌 에러 핸들러 (PromiseRejection, ErrorEvent 등) |
99
+
100
+ -> See [docs/plugins.md](./docs/plugins.md) for details.
101
+
102
+ ### Pipes
103
+
104
+ | API | Type | Description |
105
+ |-----|------|-------------|
106
+ | `FormatPipe` | pipe | DateTime/DateOnly/string 포매팅 파이프 |
107
+
108
+ -> See [docs/pipes.md](./docs/pipes.md) for details.
109
+
110
+ ### Utils & Setups
111
+
112
+ | API | Type | Description |
113
+ |-----|------|-------------|
114
+ | `mark` | function | WritableSignal 변경 알림 트리거 (shallow copy) |
115
+ | `setSafeStyle` | function | Renderer2로 여러 CSS 스타일 일괄 적용 |
116
+ | `injectSdSystemConfigResource` | function | 시스템 설정 resource 래퍼 |
117
+ | `injectCurrentPageCodeSignal` | function | 현재 페이지 코드 signal |
118
+ | `injectFullPageCodeSignal` | function | 전체 페이지 코드 signal (NavigationEnd 기반) |
119
+ | `injectViewTitleSignal` | function | 현재 뷰 타이틀 signal |
120
+ | `injectViewTypeSignal` | function | 현재 뷰 타입 signal (`page \| modal \| control`) |
121
+ | `useSelectionManager` | function | 선택 관리 composable (single/multi) |
122
+ | `useSortingManager` | function | 정렬 관리 composable |
123
+ | `useExpandingManager` | function | 트리 확장/축소 관리 composable |
124
+ | `setupBgTheme` | function | body 배경 테마 색상 설정 |
125
+ | `setupRipple` | function | 리플 효과 설정 |
126
+ | `setupRevealOnShow` | function | 뷰포트 진입 시 reveal 애니메이션 설정 |
127
+ | `setupInvalid` | function | 유효성 검증 표시기 설정 |
128
+ | `setupModelHook` | function | model signal의 set을 가드 함수로 래핑 |
129
+ | `setupCanDeactivate` | function | 모달/라우트 canDeactivate 설정 |
130
+
131
+ -> See [docs/utils.md](./docs/utils.md) for details.
132
+
133
+ ### Type Utilities
134
+
135
+ | API | Type | Description |
136
+ |-----|------|-------------|
137
+ | `DirectiveInputSignals` | type | InputSignal 프로퍼티에서 값 타입 추출 |
138
+ | `UndefToOptional` | type | undefined 포함 프로퍼티를 optional로 변환 |
139
+ | `WithOptional` | type | 특정 키를 optional로 변환 |
140
+ | `SdViewType` | type | 뷰 타입 (`"page" \| "modal" \| "control"`) |
141
+ | `SortingDef` | interface | 정렬 정의 (`key`, `desc`) |
142
+ | `ExpandItemDef` | interface | 트리 확장 항목 정의 |
143
+ | `SdSelectModal` | interface | 모달 선택 컴포넌트 인터페이스 |
144
+ | `SdSelectModalInfo` | type | 모달 선택 정보 타입 |
145
+ | `SdTextfieldTypes` | type | 텍스트필드 타입별 값 타입 매핑 |
146
+ | `sdTextfieldTypes` | const | 텍스트필드 타입 문자열 배열 |
147
+ | `SelectModeValue` | type | select mode별 value 타입 매핑 |
148
+
149
+ -> See [docs/type-utilities.md](./docs/type-utilities.md) for details.
150
+
151
+ ### Features
152
+
153
+ | API | Type | Description |
154
+ |-----|------|-------------|
155
+ | `SdAddressSearchModal` | component | Daum Postcode 주소 검색 모달 |
156
+ | `Address` | interface | 주소 검색 결과 |
157
+ | `SdPermissionTable` | component | 권한 매트릭스 테이블 (items, value) |
158
+ | 데이터 시트 CRUD 화면 조립 | recipe | → [recipes/crud-list.md](./docs/recipes/crud-list.md) (`<sd-sheet>`+`<sd-form>`+필터+페이지네이션+편집 직접 조립) |
159
+ | 상세폼 CRUD 화면 조립 | recipe | → [recipes/crud-detail.md](./docs/recipes/crud-detail.md) (`<sd-form>`+load/save/delete+snapshot 변경감지+setupCanDeactivate+Ctrl+S 직접 조립) |
160
+ | 모달 선택 버튼 화면 조립 | recipe | → [recipes/data-select-button.md](./docs/recipes/data-select-button.md) (`<sd-modal-select-button>` 직접 / `<sd-shared-data-select-button>` / 사용자 정의 wrapper) |
161
+ | `SdSharedDataSelect` | component | 공유 데이터 드롭다운 선택 |
162
+ | `SdSharedDataSelectButton` | component | 공유 데이터 모달 선택 버튼 (→ [recipes/data-select-button.md](./docs/recipes/data-select-button.md)) |
163
+ | `SdSharedDataSelectList` | component | 공유 데이터 목록형 선택 (selectedItem model) |
164
+ | `matchesSearchText` | function | 공백 구분 AND 조건 텍스트 검색 매칭 |
165
+
166
+ -> See [docs/features.md](./docs/features.md) for details.
167
+
168
+ ### UI - Layout
169
+
170
+ | API | Type | Description |
171
+ |-----|------|-------------|
172
+ | `SdDockContainer` | component | 도킹 레이아웃 컨테이너 |
173
+ | `SdDock` | component | 도킹 영역 (top/bottom/left/right) |
174
+ | `SdGap` | component | 간격 (gap) 컴포넌트 |
175
+ | `SdKanbanBoard` | component | 칸반 보드 (드래그앤드롭, selectedValues) |
176
+ | `SdKanbanBoardDropInfo` | interface | 칸반 보드 드롭 이벤트 정보 |
177
+ | `SdKanbanDragRef` | interface | 칸반 드래그 참조 인터페이스 |
178
+ | `SdKanbanDropTarget` | interface | 칸반 드롭 타겟 인터페이스 |
179
+ | `SdKanban` | component | 칸반 아이템 |
180
+ | `SdKanbanLane` | component | 칸반 레인 |
181
+
182
+ -> See [docs/ui-layout.md](./docs/ui-layout.md) for details.
183
+
184
+ ### UI - Form
185
+
186
+ | API | Type | Description |
187
+ |-----|------|-------------|
188
+ | `SdButton` | component | 버튼 |
189
+ | `SdAnchor` | component | 앵커 (인라인 버튼) |
190
+ | `SdAdditionalButton` | component | 추가 동작 버튼 (드롭다운 포함) |
191
+ | `SdModalSelectButton` | component | 모달 선택 버튼 |
192
+ | `SdTextfield` | component | 텍스트 입력 (13가지 타입: number, text, password, color, email, format, date, month, year, datetime, datetime-sec, time, time-sec) |
193
+ | `SdTextarea` | component | 멀티라인 텍스트 입력 |
194
+ | `SdNumpad` | component | 숫자 패드 |
195
+ | `SdRange` | component | 범위 슬라이더 |
196
+ | `SdDateRangePicker` | component | 날짜 범위 선택기 |
197
+ | `SdStatePreset` | component | 상태 프리셋 저장/불러오기 |
198
+ | `SdStatePresetDef` | interface | 상태 프리셋 데이터 (name, state) |
199
+ | `SdCheckbox` | component | 체크박스 |
200
+ | `SdSwitch` | component | 스위치 토글 |
201
+ | `SdCheckboxGroup` | component | 체크박스 그룹 |
202
+ | `SdCheckboxGroupItem` | component | 체크박스 그룹 항목 |
203
+ | `SdTiptapEditor` | component | TipTap 리치 텍스트 에디터 |
204
+ | `SdSelect` | component | 드롭다운 선택 (single/multi) |
205
+ | `SdSelectItem` | component | 드롭다운 선택 항목 |
206
+ | `SdSelectButton` | component | 버튼 스타일 선택 |
207
+ | `SdForm` | component | 폼 래퍼 (submit 이벤트, busy 관리) |
208
+
209
+ -> See [docs/ui-form.md](./docs/ui-form.md) for details.
210
+
211
+ ### UI - Navigation
212
+
213
+ | API | Type | Description |
214
+ |-----|------|-------------|
215
+ | `SdCollapse` | component | 접기/펼치기 패널 |
216
+ | `SdCollapseIcon` | component | 접기/펼치기 아이콘 |
217
+ | `SdTab` | component | 탭 컨테이너 |
218
+ | `SdTabItem` | component | 탭 항목 |
219
+ | `SdPagination` | component | 페이지네이션 |
220
+ | `SdSidebarContainer` | component | 사이드바 컨테이너 |
221
+ | `SdSidebar` | component | 사이드바 |
222
+ | `SdSidebarMenu` | component | 사이드바 메뉴 |
223
+ | `SdSidebarUser` | component | 사이드바 사용자 영역 |
224
+ | `SdSidebarUserMenu` | interface | 사이드바 사용자 메뉴 항목 |
225
+ | `SdTopbarContainer` | component | 탑바 컨테이너 |
226
+ | `SdTopbar` | component | 탑바 |
227
+ | `SdTopbarMenu` | component | 탑바 메뉴 |
228
+ | `SdTopbarUser` | component | 탑바 사용자 영역 |
229
+ | `SdTopbarUserMenu` | interface | 탑바 사용자 메뉴 항목 |
230
+ | `getMenuRouterLinkOption` | function | 메뉴에서 라우터 링크 옵션 추출 |
231
+ | `getIsMenuSelected` | function | 메뉴 선택 여부 확인 |
232
+
233
+ -> See [docs/ui-navigation.md](./docs/ui-navigation.md) for details.
234
+
235
+ ### UI - Data
236
+
237
+ | API | Type | Description |
238
+ |-----|------|-------------|
239
+ | `SdList` | component | 리스트 |
240
+ | `SdListItem` | component | 리스트 항목 |
241
+ | `SdSheet` | component | 스프레드시트 (정렬, 고정, 리사이즈). `key`로 설정 저장 |
242
+ | `SdSheetColumn` | directive | 시트 컬럼 정의 (헤더, 너비, 고정, 정렬 등) |
243
+ | `SdSheetColumnCellTemplate` | directive | 시트 컬럼 셀 내용 정의 (`ng-template[cell]`), `SdSheetCellContext` 타입 가드 제공 |
244
+ | `SdSheetCellContext` | interface | 시트 셀 템플릿 컨텍스트 (`$implicit`, `item`, `index`, `depth`, `edit`) |
245
+ | `SdSheetConfigModal` | component | 시트 설정 모달 |
246
+ | `SdSheetColumnDef` | interface | 시트 컬럼 정의 데이터 |
247
+ | `SdSheetConfig` | interface | 시트 설정 데이터 |
248
+ | `SdSheetHeaderDef` | interface | 시트 헤더 정의 |
249
+ | `SdSheetItemKeydownEventParam` | interface | 시트 항목 keydown 이벤트 파라미터 |
250
+ | `SdSheetCellKeydownEventParam` | interface | 시트 셀 keydown 이벤트 파라미터 |
251
+
252
+ -> See [docs/ui-data.md](./docs/ui-data.md) for details.
253
+
254
+ ### UI - Visual
255
+
256
+ | API | Type | Description |
257
+ |-----|------|-------------|
258
+ | `SdLabel` | component | 라벨 (테마, 크기) |
259
+ | `SdNote` | component | 노트/알림 메시지 |
260
+ | `SdProgress` | component | 진행률 바 |
261
+ | `SdCalendar` | component | 캘린더 |
262
+ | `SdBarcode` | component | 바코드 생성 (bwip-js) |
263
+ | `SdEcharts` | component | ECharts 차트 래퍼 |
264
+ | `BarcodeType` | type | 바코드 타입 |
265
+
266
+ -> See [docs/ui-visual.md](./docs/ui-visual.md) for details.
267
+
268
+ ### UI - Overlay
269
+
270
+ | API | Type | Description |
271
+ |-----|------|-------------|
272
+ | `SdDropdown` | component | 드롭다운 트리거 |
273
+ | `SdDropdownPopup` | component | 드롭다운 팝업 |
274
+ | `SdModal` | component | 모달 래퍼 컴포넌트 |
275
+ | `SdPromptModal` | component | 프롬프트 입력 모달 |
276
+ | `SdConfirmModal` | component | 확인/취소 모달 |
277
+ | `SdToast` | component | 토스트 개별 항목 |
278
+ | `SdToastContainer` | component | 토스트 컨테이너 |
279
+ | `SdBusyContainer` | component | busy 표시 컨테이너 |
280
+
281
+ -> See [docs/ui-overlay.md](./docs/ui-overlay.md) for details.
282
+
283
+ ### Styling
284
+
285
+ | API | Type | Description |
286
+ |-----|------|-------------|
287
+ | `.flex-row`, `.flex-column` 등 | CSS class | Flexbox 레이아웃 유틸리티 |
288
+ | `.grid`, `.grid-{1..12}` | CSS class | Grid 레이아웃 유틸리티 |
289
+ | `.card` | CSS class | 카드 스타일 |
290
+ | `.form-box`, `.form-table` | CSS class | 폼 레이아웃 |
291
+ | `.table` | CSS class | 테이블 스타일 |
292
+ | `.p-*`, `.m-*`, `.gap-*` 등 | CSS class | 간격 유틸리티 |
293
+ | `--theme-*-*` | CSS custom property | OKLCH 색상 팔레트 (17+5색 x 7단계) |
294
+ | `.sd-theme-dark` | theme class | 다크 모드 테마 |
295
+
296
+ -> See [docs/styling.md](./docs/styling.md) for details.
297
+
298
+ ## 컴포넌트 비동기 초기화 규칙
299
+
300
+ 컴포넌트에서 비동기 초기화가 필요한 경우 `async ngOnInit()`을 사용한다.
301
+
302
+ ```typescript
303
+ export class SomePage implements OnInit {
304
+ busyCount = signal(0);
305
+
306
+ async ngOnInit() {
307
+ this.busyCount.update((v) => v + 1);
308
+ await this._sdToast.try(async () => {
309
+ // 비동기 초기화 로직
310
+ });
311
+ this.busyCount.update((v) => v - 1);
312
+ }
313
+ }
314
+ ```
315
+
316
+ - constructor 내 `void (async () => { ... })()` IIFE 패턴 **금지**
317
+ - constructor 내 `void this._init()` 같은 수동 호출 패턴 **금지** — ngOnInit이 이미 같은 역할
318
+ - `resource()` / `httpResource()`는 데이터 로딩 → signal 매핑 용도. 사이드이펙트(라우팅, toast 등) 포함 초기화에는 사용하지 않는다
319
+
320
+ ## 소비 프로젝트 네이밍 규칙
321
+
322
+ `@simplysm/angular`를 소비하는 앱 프로젝트에서의 파일명·클래스명 규칙이다.
323
+ 파일명은 **kebab-case + dot-suffix**, 클래스명은 **PascalCase**를 따른다.
324
+
325
+ | 접미어 | 조건 | 파일명 예시 | 클래스명 예시 |
326
+ |--------|------|-------------|---------------|
327
+ | `.sheet.ts` / `*Sheet` | `<sd-sheet>` 기반 CRUD 리스트 화면 ([recipes/crud-list.md](./docs/recipes/crud-list.md)) | `outbound-instruction.sheet.ts` | `OutboundInstructionSheet` |
328
+ | `.detail.ts` / `*Detail` | `<sd-form>` 기반 상세 폼 화면 ([recipes/crud-detail.md](./docs/recipes/crud-detail.md)) | `outbound-instruction.detail.ts` | `OutboundInstructionDetail` |
329
+ | `.view.ts` / `*View` | sheet/detail 아닌 병합 컴포넌트 + route 연결 | `dashboard.view.ts` | `DashboardView` |
330
+ | `.modal.ts` / `*Modal` | 모달 전용 컴포넌트 | `item-select.modal.ts` | `ItemSelectModal` |
331
+ | `.provider.ts` / `*Provider` | `@Injectable` 클래스 (**`*Service` 금지**) | `app-service.provider.ts` | `AppServiceProvider` |
332
+ | 접미어 없음 | route 미연결 일반 컨트롤 컴포넌트 | `instruction-item.ts` | `InstructionItem` |
333
+
334
+ - `pipe`, `directive` 등 기타 Angular 구성요소는 `@simplysm/angular` 패키지 자체의 네이밍 패턴(`.pipe.ts`, `.directive.ts`)을 따른다
335
+
336
+ ## 소비 프로젝트 디렉토리 구조
337
+
338
+ ```
339
+ src/
340
+ ├── app/ # 라우팅 페이지 (사이드바 메뉴 트리 구조와 대응)
341
+ │ ├── login/
342
+ │ └── home/
343
+ │ ├── {메뉴-그룹}/ # 사이드바 메뉴 그룹
344
+ │ │ └── {도메인}/ # 개별 도메인 (트리 깊이 제한 없음)
345
+ │ │ ├── {도메인}.view.ts # route 연결 병합 컴포넌트
346
+ │ │ ├── {도메인}.sheet.ts # <sd-sheet> 기반 CRUD 리스트 (recipes/crud-list.md)
347
+ │ │ ├── {도메인}.detail.ts # <sd-form> 기반 상세 폼 (recipes/crud-detail.md)
348
+ │ │ ├── {이름}.modal.ts # 도메인 전용 모달
349
+ │ │ └── {이름}.ts # 일반 컨트롤 (route 미연결)
350
+ │ └── main/
351
+ ├── controls/ # 앱 공유 컨트롤 컴포넌트
352
+ ├── directives/ # 앱 공유 디렉티브
353
+ ├── modals/ # 앱 전역 공통 모달
354
+ ├── providers/ # 앱 전역 프로바이더
355
+ ├── types/ # 타입 정의
356
+ └── utils/ # 유틸리티
357
+ ```
358
+
359
+ - `app/` 하위 트리는 사이드바 메뉴 구조와 거의 대응된다
360
+ - **배치 기준은 "어느 도메인에 소속되는가"**이다. provider, modal, directive, print-template, util 등 모든 종류의 파일이 소속 도메인 폴더 안에 배치된다 (다른 도메인에서 import하여 사용하는 것은 자유)
361
+ - 특정 도메인에 소속되지 않는 공통 파일만 `src/` 직하의 `controls/`, `modals/`, `providers/` 등에 배치한다
362
+
363
+ ## inject 네이밍 컨벤션
364
+
365
+ `Sd*Provider`를 `inject()`할 때 변수명은 다음 규칙을 따른다:
366
+
367
+ - **Sd 접두어 유지**: 클래스명에서 `Sd`를 camelCase로 변환하여 유지한다
368
+ - **Provider 접미어 제거**: 변수명에서 `Provider`를 제거한다
369
+
370
+ | inject 대상 | 클래스 필드 | 로컬 변수 |
371
+ |-------------|-----------|----------|
372
+ | `SdToastProvider` | `private _sdToast = inject(SdToastProvider)` | `const sdToast = inject(SdToastProvider)` |
373
+ | `SdModalProvider` | `private _sdModal = inject(SdModalProvider)` | `const sdModal = inject(SdModalProvider)` |
374
+ | `SdServiceClientFactoryProvider` | `private _sdServiceClientFactory = inject(SdServiceClientFactoryProvider)` | `const sdServiceClientFactory = inject(...)` |
375
+
376
+ ## Usage Examples
377
+
378
+ ### 앱 부트스트랩
379
+
380
+ ```typescript
381
+ import { provideSdAngular } from "@simplysm/angular";
382
+
383
+ bootstrapApplication(AppComponent, {
384
+ providers: [
385
+ provideSdAngular({ clientName: "my-app" }),
386
+ provideRouter(routes),
387
+ ],
388
+ });
389
+ ```
390
+
391
+ ### 모달 표시
392
+
393
+ ```typescript
394
+ import { SdModalProvider, type SdModalInfo } from "@simplysm/angular";
395
+
396
+ const sdModal = inject(SdModalProvider);
397
+
398
+ const result = await sdModal.showAsync(
399
+ { title: "사용자 선택", type: UserSelectModal, inputs: { filter: "active" } },
400
+ { useCloseByBackdrop: true },
401
+ );
402
+ ```
403
+
404
+ ### 서비스 + 이벤트 프록시 (AppServiceProvider 패턴)
405
+
406
+ 소비 프로젝트에서 서비스와 이벤트를 한 곳에서 관리하는 패턴:
407
+
408
+ ```typescript
409
+ import { inject, Injectable } from "@angular/core";
410
+ import { SdServiceClientFactoryProvider } from "@simplysm/angular";
411
+ import { createOrmClientConnector, type OrmClientConnector, type ServiceProxy } from "@simplysm/service-client";
412
+ import type { SystemLogServiceType } from "@my-server-package";
413
+ import type { OrderUpdatedEvent } from "@my-server-package"; // import type만 가능
414
+
415
+ @Injectable({ providedIn: "root" })
416
+ export class AppServiceProvider {
417
+ private readonly _sdServiceClientFactory = inject(SdServiceClientFactoryProvider);
418
+
419
+ get client() {
420
+ return this._sdServiceClientFactory.get("MAIN");
421
+ }
422
+
423
+ // 서비스 프록시 — getService() 패턴
424
+ get systemLog() {
425
+ return this.client.getService<SystemLogServiceType>("SystemLog");
426
+ }
427
+
428
+ // 이벤트 프록시 — getEvent() 패턴 (getService()와 동일)
429
+ get orderUpdated() {
430
+ return this.client.getEvent<typeof OrderUpdatedEvent>("OrderUpdated");
431
+ }
432
+ }
433
+ ```
434
+
435
+ 사용처에서:
436
+
437
+ ```typescript
438
+ const appSvc = inject(AppServiceProvider);
439
+
440
+ // 서비스 호출
441
+ await appSvc.systemLog.writeLog("hello");
442
+
443
+ // 이벤트 구독 — 이벤트 이름과 제네릭 타입을 반복 지정할 필요 없음
444
+ const key = await appSvc.orderUpdated.addListener({ orderId: 123 }, async (data) => {
445
+ // data.status는 string으로 타입 추론
446
+ });
447
+
448
+ // 이벤트 발행
449
+ await appSvc.orderUpdated.emit((info) => info.orderId === 123, { status: "shipped" });
450
+
451
+ // 구독 해제
452
+ await appSvc.orderUpdated.removeListener(key);
453
+ ```
454
+
455
+ ### 토스트 알림
456
+
457
+ ```typescript
458
+ import { SdToastProvider } from "@simplysm/angular";
459
+
460
+ const sdToast = inject(SdToastProvider);
461
+
462
+ sdToast.success("저장되었습니다.");
463
+ const result = await sdToast.try(async () => {
464
+ return await someAsyncWork();
465
+ });
466
+ ```
467
+
@@ -2,6 +2,7 @@ import * as i0 from "@angular/core";
2
2
  export declare class SdDropdownPopup {
3
3
  private readonly _parentControl;
4
4
  private readonly _elRef;
5
+ private _capped;
5
6
  onKeydown(event: KeyboardEvent): void;
6
7
  onResize(): void;
7
8
  static ɵfac: i0.ɵɵFactoryDeclaration<SdDropdownPopup, never>;
@@ -1 +1 @@
1
- {"version":3,"file":"sd-dropdown-popup.d.ts","sourceRoot":"","sources":["../../../src/controls/dropdown/sd-dropdown-popup.ts"],"names":[],"mappings":";AAWA,qBA6Ea,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAE7B;IACF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAE1D,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAIrC,QAAQ,IAAI,IAAI;yCAVL,eAAe;2CAAf,eAAe;CAmB3B"}
1
+ {"version":3,"file":"sd-dropdown-popup.d.ts","sourceRoot":"","sources":["../../../src/controls/dropdown/sd-dropdown-popup.ts"],"names":[],"mappings":";AAWA,qBA6Ea,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAE7B;IACF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,OAAO,CAAC,OAAO,CAAS;IAExB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAIrC,QAAQ,IAAI,IAAI;yCAXL,eAAe;2CAAf,eAAe;CAuB3B"}
@@ -6,13 +6,18 @@ const _c0 = ["*"];
6
6
  export class SdDropdownPopup {
7
7
  _parentControl = inject(forwardRef(() => SdDropdown));
8
8
  _elRef = inject((ElementRef));
9
+ _capped = false;
9
10
  onKeydown(event) {
10
11
  this._parentControl.onPopupKeydown(event);
11
12
  }
12
13
  onResize() {
13
14
  const el = this._elRef.nativeElement;
14
15
  const divEl = el.firstElementChild;
15
- if (divEl.clientHeight > 300) {
16
+ const shouldCap = divEl.scrollHeight > 300;
17
+ if (shouldCap === this._capped)
18
+ return;
19
+ this._capped = shouldCap;
20
+ if (shouldCap) {
16
21
  el.style.height = "300px";
17
22
  }
18
23
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"sd-dropdown.d.ts","sourceRoot":"","sources":["../../../src/controls/dropdown/sd-dropdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,UAAU,EAMX,MAAM,eAAe,CAAC;AAEvB,OAAO,wBAAwB,CAAC;;AAEhC,qBAgBa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAE1D,IAAI,+CAAgB;IACpB,QAAQ,qEAAiD;IAEzD,UAAU,0DAGR;IAEF,OAAO,CAAC,QAAQ,CAAC,CAAc;IAC/B,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,WAAW,CAAC,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiB;;IAiEtC,WAAW,IAAI,IAAI;IAQnB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IA8CzC,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAY1C,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,wBAAwB;IAShC,OAAO,CAAC,sBAAsB;IAqC9B,OAAO,CAAC,YAAY;yCA3OT,UAAU;2CAAV,UAAU;CAwQtB"}
1
+ {"version":3,"file":"sd-dropdown.d.ts","sourceRoot":"","sources":["../../../src/controls/dropdown/sd-dropdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,UAAU,EAMX,MAAM,eAAe,CAAC;AAEvB,OAAO,wBAAwB,CAAC;;AAEhC,qBAgBa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAE1D,IAAI,+CAAgB;IACpB,QAAQ,qEAAiD;IAEzD,UAAU,0DAEP;IAEH,OAAO,CAAC,QAAQ,CAAC,CAAc;IAC/B,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,WAAW,CAAC,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiB;;IAiEtC,WAAW,IAAI,IAAI;IAQnB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IA8CzC,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAY1C,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,wBAAwB;IAShC,OAAO,CAAC,sBAAsB;IAqC9B,OAAO,CAAC,YAAY;yCA1OT,UAAU;2CAAV,UAAU;CAuQtB"}
@@ -8,7 +8,9 @@ export class SdDropdown {
8
8
  _elRef = inject((ElementRef));
9
9
  open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
10
10
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
11
- popupElRef = contentChild.required(SdDropdownPopup, { read: ElementRef });
11
+ popupElRef = contentChild.required(SdDropdownPopup, {
12
+ read: ElementRef,
13
+ });
12
14
  _popupEl;
13
15
  _mouseoverEl;
14
16
  _backdropEl;
@@ -268,5 +270,7 @@ export class SdDropdown {
268
270
  <ng-content select="sd-dropdown-popup" />
269
271
  `,
270
272
  }]
271
- }], () => [], { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], popupElRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdDropdownPopup), { ...{ read: ElementRef }, isSignal: true }] }] }); })();
273
+ }], () => [], { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], popupElRef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdDropdownPopup), { ...{
274
+ read: ElementRef,
275
+ }, isSignal: true }] }] }); })();
272
276
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SdDropdown, { className: "SdDropdown", filePath: "packages/angular/src/controls/dropdown/sd-dropdown.ts", lineNumber: 34 }); })();
@@ -1 +1 @@
1
- {"version":3,"file":"sd-select.d.ts","sourceRoot":"","sources":["../../../src/controls/select/sd-select.ts"],"names":[],"mappings":"AAAA,OAAO,EAaL,WAAW,EAIZ,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAI1D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;;AAUhD,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI;IAC/B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AAEF,qBAuNa,QAAQ,CAAC,CAAC,SAAS,QAAQ,GAAG,OAAO,EAAE,CAAC;IACnD,UAAU,yCAAwB;IAClC,KAAK,2EAAoC;IACzC,WAAW,0DAAmB;IAC9B,QAAQ,qEAAiD;IACzD,MAAM,qEAAiD;IACvD,KAAK,qEAAiD;IACtD,IAAI,+DAAwB;IAC5B,QAAQ,qEAAiD;IAEzD,aAAa,qEAAiD;IAC9D,8BAA8B,8DAAuB;IAErD,KAAK,uDAAgB;IACrB,SAAS,6CAAgB,CAAC,SAAS,MAAM,KAAK,OAAO,EAAkB;IACvE,aAAa,8CAAgB,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,eAAI;IAEtD,YAAY,0DAAmB;IAC/B,YAAY,0DAAmB;IAE/B,YAAY,+CAAgB;IAE5B,SAAS,CAAC,QAAQ,CAAC,eAAe,iUAAmB;IAErD,aAAa,+DAAiC;IAC9C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkC;IACnE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAG7B;IAEF,UAAU,gEAAgD;IAC1D,UAAU,gEAAgD;IAC1D,UAAU,+DAAyD;IAEnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwB;IAEnD,wBAAwB,+DAA2C;IAEnE,UAAU;cAIY,CAAC;eAAS,MAAM;eAAS,MAAM;SAelD;;IAgIH,UAAU,CAAC,SAAS,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI;IAO1C,UAAU,CAAC,SAAS,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI;IAI1C,OAAO,CAAC,YAAY;IAepB,WAAW,IAAI,IAAI;IAQnB,aAAa,IAAI,IAAI;IAIrB,aAAa,IAAI,IAAI;IAIrB,YAAY,IAAI,IAAI;yCApOT,QAAQ;2CAAR,QAAQ;CAwOpB"}
1
+ {"version":3,"file":"sd-select.d.ts","sourceRoot":"","sources":["../../../src/controls/select/sd-select.ts"],"names":[],"mappings":"AAAA,OAAO,EAaL,WAAW,EAIZ,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAI1D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;;AAUhD,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI;IAC/B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AAEF,qBA4Na,QAAQ,CAAC,CAAC,SAAS,QAAQ,GAAG,OAAO,EAAE,CAAC;IACnD,UAAU,yCAAwB;IAClC,KAAK,2EAAoC;IACzC,WAAW,0DAAmB;IAC9B,QAAQ,qEAAiD;IACzD,MAAM,qEAAiD;IACvD,KAAK,qEAAiD;IACtD,IAAI,+DAAwB;IAC5B,QAAQ,qEAAiD;IAEzD,aAAa,qEAAiD;IAC9D,8BAA8B,8DAAuB;IAErD,KAAK,uDAAgB;IACrB,SAAS,6CAAgB,CAAC,SAAS,MAAM,KAAK,OAAO,EAAkB;IACvE,aAAa,8CAAgB,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,eAAI;IAEtD,YAAY,0DAAmB;IAC/B,YAAY,0DAAmB;IAE/B,YAAY,+CAAgB;IAE5B,SAAS,CAAC,QAAQ,CAAC,eAAe,iUAAmB;IAErD,aAAa,+DAAiC;IAC9C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkC;IACnE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAG7B;IAEF,UAAU,gEAAgD;IAC1D,UAAU,gEAAgD;IAC1D,UAAU,+DAAyD;IAEnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwB;IAEnD,wBAAwB,+DAA2C;IAEnE,UAAU;cAIY,CAAC;eAAS,MAAM;eAAS,MAAM;SAelD;;IAmGH,UAAU,CAAC,SAAS,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI;IAO1C,UAAU,CAAC,SAAS,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI;IAI1C,OAAO,CAAC,YAAY;IAepB,WAAW,IAAI,IAAI;IAQnB,aAAa,IAAI,IAAI;IAIrB,aAAa,IAAI,IAAI;IAIrB,YAAY,IAAI,IAAI;yCAvMT,QAAQ;2CAAR,QAAQ;CA2MpB"}
@@ -156,30 +156,9 @@ export class SdSelect {
156
156
  return "선택된 항목이 없습니다.";
157
157
  return "";
158
158
  });
159
- // D3: contentClass/contentStyle via effect() on popup element
160
- // + D4: keyboard navigation via tabbable()
161
159
  effect((onCleanup) => {
162
160
  if (this.dropdownOpen()) {
163
161
  const popupEl = this._dropdownControl().popupElRef().nativeElement;
164
- const cls = this.contentClass();
165
- const addedClasses = [];
166
- if (cls != null) {
167
- for (const c of cls.split(" ").filter(Boolean)) {
168
- popupEl.classList.add(c);
169
- addedClasses.push(c);
170
- }
171
- }
172
- const style = this.contentStyle();
173
- const addedStyleProps = [];
174
- if (style != null) {
175
- const tempEl = document.createElement("div");
176
- tempEl.style.cssText = style;
177
- for (let i = 0; i < tempEl.style.length; i++) {
178
- const prop = tempEl.style[i];
179
- addedStyleProps.push(prop);
180
- popupEl.style.setProperty(prop, tempEl.style.getPropertyValue(prop));
181
- }
182
- }
183
162
  const onKeydown = (event) => {
184
163
  if (event.ctrlKey || event.altKey)
185
164
  return;
@@ -197,9 +176,7 @@ export class SdSelect {
197
176
  }
198
177
  }
199
178
  else {
200
- // ArrowUp
201
179
  if (currIndex <= 0) {
202
- // Return focus to dropdown trigger
203
180
  this._dropdownElRef().nativeElement.focus();
204
181
  }
205
182
  else {
@@ -211,12 +188,6 @@ export class SdSelect {
211
188
  popupEl.addEventListener("keydown", onKeydown);
212
189
  onCleanup(() => {
213
190
  popupEl.removeEventListener("keydown", onKeydown);
214
- for (const c of addedClasses) {
215
- popupEl.classList.remove(c);
216
- }
217
- for (const prop of addedStyleProps) {
218
- popupEl.style.removeProperty(prop);
219
- }
220
191
  });
221
192
  }
222
193
  });
@@ -319,7 +290,7 @@ export class SdSelect {
319
290
  i0.ɵɵqueryAdvance(2);
320
291
  } }, hostVars: 4, hostBindings: function SdSelect_HostBindings(rf, ctx) { if (rf & 2) {
321
292
  i0.ɵɵattribute("data-sd-size", ctx.size())("data-sd-inline", ctx.inline())("data-sd-inset", ctx.inset())("data-sd-disabled", ctx.disabled());
322
- } }, inputs: { selectMode: [1, "selectMode"], value: [1, "value"], placeholder: [1, "placeholder"], disabled: [1, "disabled"], inline: [1, "inline"], inset: [1, "inset"], size: [1, "size"], required: [1, "required"], hideSelectAll: [1, "hideSelectAll"], multiSelectionDisplayDirection: [1, "multiSelectionDisplayDirection"], items: [1, "items"], trackByFn: [1, "trackByFn"], getChildrenFn: [1, "getChildrenFn"], contentClass: [1, "contentClass"], contentStyle: [1, "contentStyle"], dropdownOpen: [1, "dropdownOpen"] }, outputs: { value: "valueChange", dropdownOpen: "dropdownOpenChange" }, ngContentSelectors: _c3, decls: 15, vars: 10, consts: [["contentEl", ""], [3, "openChange", "disabled", "open"], [1, "_sd-select-control", 3, "sdRipple"], [1, "_sd-select-control-content"], [3, "innerHTML"], [1, "sd-text-color-gray-default"], [1, "_sd-select-control-icon"], [3, "ngTemplateOutlet"], [1, "_sd-select-all-bar"], [3, "svg"], ["data-sd-select-all", "", 3, "click"], [3, "width"], ["data-sd-deselect-all", "", 3, "click"], [3, "ngTemplateOutlet", "ngTemplateOutletContext"]], template: function SdSelect_Template(rf, ctx) { if (rf & 1) {
293
+ } }, inputs: { selectMode: [1, "selectMode"], value: [1, "value"], placeholder: [1, "placeholder"], disabled: [1, "disabled"], inline: [1, "inline"], inset: [1, "inset"], size: [1, "size"], required: [1, "required"], hideSelectAll: [1, "hideSelectAll"], multiSelectionDisplayDirection: [1, "multiSelectionDisplayDirection"], items: [1, "items"], trackByFn: [1, "trackByFn"], getChildrenFn: [1, "getChildrenFn"], contentClass: [1, "contentClass"], contentStyle: [1, "contentStyle"], dropdownOpen: [1, "dropdownOpen"] }, outputs: { value: "valueChange", dropdownOpen: "dropdownOpenChange" }, ngContentSelectors: _c3, decls: 15, vars: 14, consts: [["contentEl", ""], [3, "openChange", "disabled", "open"], [1, "_sd-select-control", 3, "sdRipple"], [1, "_sd-select-control-content"], [3, "innerHTML"], [1, "sd-text-color-gray-default"], [1, "_sd-select-control-icon"], [3, "ngTemplateOutlet"], [1, "_sd-select-all-bar"], [3, "svg"], ["data-sd-select-all", "", 3, "click"], [3, "width"], ["data-sd-deselect-all", "", 3, "click"], [3, "ngTemplateOutlet", "ngTemplateOutletContext"]], template: function SdSelect_Template(rf, ctx) { if (rf & 1) {
323
294
  const _r1 = i0.ɵɵgetCurrentView();
324
295
  i0.ɵɵprojectionDef(_c2);
325
296
  i0.ɵɵelementStart(0, "sd-dropdown", 1);
@@ -337,10 +308,12 @@ export class SdSelect {
337
308
  i0.ɵɵconditionalCreate(13, SdSelect_Conditional_13_Template, 2, 0)(14, SdSelect_Conditional_14_Template, 1, 0);
338
309
  i0.ɵɵelementEnd()();
339
310
  } if (rf & 2) {
340
- let tmp_10_0;
311
+ let tmp_12_0;
341
312
  i0.ɵɵproperty("disabled", ctx.disabled());
342
313
  i0.ɵɵtwoWayProperty("open", ctx.dropdownOpen);
343
314
  i0.ɵɵadvance();
315
+ i0.ɵɵstyleMap(ctx.contentStyle());
316
+ i0.ɵɵclassMap(ctx.contentClass());
344
317
  i0.ɵɵproperty("sdRipple", !ctx.disabled());
345
318
  i0.ɵɵadvance(3);
346
319
  i0.ɵɵconditional(ctx._selectedItemContentHTML() != null ? 4 : ctx.placeholder() ? 5 : 6);
@@ -355,7 +328,7 @@ export class SdSelect {
355
328
  i0.ɵɵadvance();
356
329
  i0.ɵɵconditional(ctx._beforeTpl() ? 12 : -1);
357
330
  i0.ɵɵadvance();
358
- i0.ɵɵconditional((tmp_10_0 = ctx._itemOfTpl()) ? 13 : 14, tmp_10_0);
331
+ i0.ɵɵconditional((tmp_12_0 = ctx._itemOfTpl()) ? 13 : 14, tmp_12_0);
359
332
  } }, dependencies: [SdDropdown,
360
333
  SdDropdownPopup,
361
334
  SdAnchor,
@@ -376,7 +349,12 @@ export class SdSelect {
376
349
  NgIcon,
377
350
  ], template: `
378
351
  <sd-dropdown [disabled]="disabled()" [(open)]="dropdownOpen">
379
- <div class="_sd-select-control" [sdRipple]="!disabled()">
352
+ <div
353
+ class="_sd-select-control"
354
+ [class]="contentClass()"
355
+ [style]="contentStyle()"
356
+ [sdRipple]="!disabled()"
357
+ >
380
358
  <div class="_sd-select-control-content" #contentEl>
381
359
  @if (_selectedItemContentHTML() != null) {
382
360
  <div [innerHTML]="_selectedItemContentHTML()"></div>
@@ -428,4 +406,4 @@ export class SdSelect {
428
406
  "[attr.data-sd-disabled]": "disabled()",
429
407
  }, styles: ["sd-select {\n display: block;\n position: relative;\n width: 100%;\n min-width: 10em;\n}\nsd-select > sd-dropdown {\n display: flex;\n overflow: hidden;\n border: 1px solid var(--trans-lighter);\n border-radius: var(--border-radius-default);\n background: var(--theme-secondary-lightest);\n}\nsd-select > sd-dropdown:focus, sd-select > sd-dropdown:focus-within {\n border-color: var(--theme-primary-default);\n}\nsd-select > sd-dropdown > ._sd-select-control {\n display: flex;\n position: relative;\n gap: var(--gap-default);\n flex-grow: 1;\n padding: var(--gap-sm) var(--gap-default);\n cursor: pointer;\n}\nsd-select > sd-dropdown > ._sd-select-control > ._sd-select-control-content {\n flex: 1;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\nsd-select > sd-dropdown > ._sd-select-control > ._sd-select-control-icon {\n opacity: 0.3;\n}\nsd-select > sd-dropdown > ._sd-select-control:hover > ._sd-select-control-icon, sd-select > sd-dropdown > ._sd-select-control:focus > ._sd-select-control-icon, sd-select > sd-dropdown > ._sd-select-control:active > ._sd-select-control-icon {\n opacity: 1;\n}\nsd-select > sd-dropdown > sd-select-button {\n padding: var(--gap-sm);\n border-left: 1px solid var(--theme-gray-lightest);\n}\nsd-select > sd-dropdown > sd-select-button:last-of-type {\n border-top-right-radius: var(--border-radius-default);\n border-bottom-right-radius: var(--border-radius-default);\n}\nsd-select[data-sd-disabled=true] > sd-dropdown {\n background: var(--theme-gray-lightest);\n}\nsd-select[data-sd-disabled=true] > sd-dropdown > ._sd-select-control {\n color: var(--text-trans-light);\n cursor: default;\n}\nsd-select[data-sd-disabled=true] > sd-dropdown > ._sd-select-control > ._sd-select-control-icon {\n display: none;\n}\nsd-select[data-sd-inline=true] {\n display: inline-block;\n width: auto;\n vertical-align: top;\n}\nsd-select[data-sd-size=sm] > sd-dropdown > ._sd-select-control {\n padding: var(--gap-xs) var(--gap-sm);\n gap: var(--gap-sm);\n}\nsd-select[data-sd-size=sm] > sd-dropdown > sd-select-button {\n padding: var(--gap-xs);\n}\nsd-select[data-sd-size=lg] > sd-dropdown > ._sd-select-control {\n padding: var(--gap-default) var(--gap-lg);\n gap: var(--gap-lg);\n}\nsd-select[data-sd-size=lg] > sd-dropdown > sd-select-button {\n padding: var(--gap-default);\n}\nsd-select[data-sd-inset=true] {\n min-width: auto;\n border-radius: 0;\n}\nsd-select[data-sd-inset=true] > sd-dropdown {\n border: none;\n border-radius: 0;\n}\nsd-select[data-sd-inset=true] > sd-dropdown > sd-select-button {\n border-radius: 0;\n}\nsd-select[data-sd-inset=true] > sd-dropdown:focus, sd-select[data-sd-inset=true] > sd-dropdown:focus-within {\n outline: 1px solid var(--theme-primary-default);\n outline-offset: -1px;\n}\nsd-select[data-sd-inset=true] > sd-dropdown:focus > sd-select-button, sd-select[data-sd-inset=true] > sd-dropdown:focus-within > sd-select-button {\n outline: 1px solid var(--theme-primary-default);\n outline-offset: -1px;\n}\nsd-select[data-sd-inset=true][data-sd-disabled=true] > sd-dropdown {\n background: var(--control-color);\n}\nsd-select[data-sd-inset=true][data-sd-disabled=true] > sd-dropdown > ._sd-select-control {\n color: var(--text-trans-default);\n}\n\n._sd-select-all-bar {\n display: flex;\n padding: var(--gap-sm) var(--gap-default);\n border-bottom: 1px solid var(--border-color-light);\n}"] }]
430
408
  }], () => [], { selectMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectMode", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], inline: [{ type: i0.Input, args: [{ isSignal: true, alias: "inline", required: false }] }], inset: [{ type: i0.Input, args: [{ isSignal: true, alias: "inset", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], hideSelectAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideSelectAll", required: false }] }], multiSelectionDisplayDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiSelectionDisplayDirection", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], trackByFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackByFn", required: false }] }], getChildrenFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "getChildrenFn", required: false }] }], contentClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentClass", required: false }] }], contentStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentStyle", required: false }] }], dropdownOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropdownOpen", required: false }] }, { type: i0.Output, args: ["dropdownOpenChange"] }], _itemControls: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SdSelectItem), { isSignal: true }] }], _dropdownControl: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdDropdown), { isSignal: true }] }], _dropdownElRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdDropdown), { ...{ read: ElementRef }, isSignal: true }] }], _headerTpl: [{ type: i0.ContentChild, args: ["headerTpl", { isSignal: true }] }], _beforeTpl: [{ type: i0.ContentChild, args: ["beforeTpl", { isSignal: true }] }], _itemOfTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdItemOfTemplate), { ...{ read: TemplateRef }, isSignal: true }] }] }); })();
431
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SdSelect, { className: "SdSelect", filePath: "packages/angular/src/controls/select/sd-select.ts", lineNumber: 254 }); })();
409
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SdSelect, { className: "SdSelect", filePath: "packages/angular/src/controls/select/sd-select.ts", lineNumber: 259 }); })();