@simplysm/sd-claude 14.0.93 → 14.0.95
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.
- package/claude/references/sd-simplysm14/README.md +10 -6
- package/claude/references/sd-simplysm14/apis/angular/README.md +180 -43
- package/claude/references/sd-simplysm14/apis/angular/controls.md +275 -125
- package/claude/references/sd-simplysm14/apis/angular/crud.md +54 -59
- package/claude/references/sd-simplysm14/apis/angular/directives.md +139 -48
- package/claude/references/sd-simplysm14/apis/angular/features.md +102 -88
- package/claude/references/sd-simplysm14/apis/angular/kanban.md +54 -0
- package/claude/references/sd-simplysm14/apis/angular/layout.md +60 -36
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +127 -75
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +97 -51
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +74 -58
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +81 -60
- package/claude/references/sd-simplysm14/apis/excel/README.md +5 -5
- package/claude/references/sd-simplysm14/apis/excel/cell.md +3 -3
- package/claude/references/sd-simplysm14/apis/excel/style.md +2 -2
- package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +5 -4
- package/claude/references/sd-simplysm14/apis/excel/wrapper.md +2 -2
- package/claude/references/sd-simplysm14/manuals/client-app-structure.md +4 -2
- package/claude/references/sd-simplysm14/manuals/client-component.md +24 -24
- package/claude/references/sd-simplysm14/manuals/client-crud.md +328 -0
- package/claude/references/sd-simplysm14/manuals/client-demo.md +6 -19
- package/claude/references/sd-simplysm14/manuals/client-shared-data.md +49 -0
- package/claude/references/sd-simplysm14/manuals/data-log.md +33 -1
- package/claude/references/sd-simplysm14/manuals/orm.md +37 -0
- package/claude/sd-system-prompt.md +11 -0
- package/claude/skills/sd-debug/SKILL.md +142 -27
- package/claude/skills/sd-review/SKILL.md +158 -20
- package/claude/skills/sd-spec/SKILL.md +1 -0
- package/package.json +1 -1
- package/claude/references/sd-simplysm14/apis/angular/infra.md +0 -82
- package/claude/skills/sd-debug/workflow.js +0 -390
- package/claude/skills/sd-review/workflow.js +0 -324
|
@@ -2,110 +2,162 @@
|
|
|
2
2
|
|
|
3
3
|
화면에서 프로그래밍 방식으로 모달을 띄우거나, 토스트로 알림·진행률을 표시하거나, busy 인디케이터·인쇄/PDF 출력을 호출할 때 함께 읽히는 군. provider 는 모두 `providedIn: "root"`, 동적으로 body 에 attach 하므로 컴포넌트를 템플릿에 직접 둘 일은 거의 없음.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`
|
|
18
|
-
- `
|
|
19
|
-
- `close: OutputEmitterRef<O | undefined>` — 닫기 + 페이로드 emit. emit 값이 `showAsync` 반환값.
|
|
20
|
-
- `actionTplRef?: TemplateRef` — 헤더 우측에 끼울 액션 영역 템플릿(선택).
|
|
21
|
-
|
|
22
|
-
`SdModalOptions` (선택):
|
|
23
|
-
- `key?: string` — 모달 식별 키.
|
|
24
|
-
- `hideHeader: boolean` — 헤더 숨김. true 면 제목/닫기 영역 없음.
|
|
25
|
-
- `hideCloseButton: boolean` — 닫기 버튼만 숨김.
|
|
26
|
-
- `headerStyle?: string` — 헤더 인라인 스타일.
|
|
27
|
-
- `useCloseByBackdrop: boolean` — 배경 클릭으로 닫기 허용(기본 true).
|
|
28
|
-
- `useCloseByEscapeKey: boolean` — ESC 로 닫기 허용(기본 true).
|
|
29
|
-
- `float: boolean` — 화면에 띄우는 플로팅 모드.
|
|
30
|
-
- `fill: boolean` — 화면 가득 채움.
|
|
31
|
-
- `resizable: boolean` — 가장자리 드래그 리사이즈 허용.
|
|
32
|
-
- `movable: boolean` — 헤더 드래그 이동 허용.
|
|
33
|
-
- `position: "bottom-right"|"top-right"` — 고정 위치.
|
|
34
|
-
- `minHeightPx`/`minWidthPx`/`heightPx`/`widthPx: number` — 크기(px).
|
|
35
|
-
- `noFirstControlFocusing: boolean` — true 면 첫 입력에 자동 포커스하지 않고 dialog 에 포커스.
|
|
5
|
+
## 모달
|
|
6
|
+
|
|
7
|
+
### SdModalProvider
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
@Injectable({ providedIn: "root" }) class SdModalProvider {
|
|
11
|
+
modalCount: WritableSignal<number>;
|
|
12
|
+
showAsync<T extends SdModalContentDef<any>>(modal: SdModalInfo<T>, options?: SdModalOptions):
|
|
13
|
+
Promise<Parameters<T["close"]["emit"]>[0] | undefined>;
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- `showAsync(modal, options)` — 모달 컴포넌트를 동적 생성·표시하고, 컨텐츠가 `close.emit(value)` 한 값(또는 배경/ESC/닫기 시 undefined)으로 resolve. 첫 탭 가능 요소에 자동 포커스, 닫힘 후 이전 포커스 복귀.
|
|
18
|
+
- `modal.title` — 헤더 제목. `modal.type` — `SdModalContentDef` 구현 컴포넌트. `modal.inputs` — 컴포넌트 input 바인딩(`close`/`initialized`/`actionTplRef` 제외, optional 마킹된 키는 생략 가능).
|
|
36
19
|
|
|
37
20
|
```ts
|
|
38
21
|
const result = await inject(SdModalProvider).showAsync(
|
|
39
|
-
{
|
|
40
|
-
{
|
|
22
|
+
{ title: "역할 선택", type: RoleListModal, inputs: { selectMode: "single" } },
|
|
23
|
+
{ useCloseByBackdrop: false },
|
|
41
24
|
);
|
|
42
|
-
if (result == null) return;
|
|
43
25
|
```
|
|
44
26
|
|
|
45
|
-
|
|
27
|
+
### SdModal — `<sd-modal>`
|
|
46
28
|
|
|
47
|
-
|
|
29
|
+
```ts
|
|
30
|
+
open = model(false); key = input<string>(); title = input("");
|
|
31
|
+
hideHeader; hideCloseButton; headerStyle = input<string>();
|
|
32
|
+
useCloseByBackdrop = input(true); useCloseByEscapeKey = input(true);
|
|
33
|
+
float; fill; resizable; movable;
|
|
34
|
+
position = input<"bottom-right" | "top-right" | undefined>();
|
|
35
|
+
minHeightPx; minWidthPx; heightPx; widthPx = input<number>();
|
|
36
|
+
actionTplRef = input<TemplateRef<any>>(); closeRequest = output<void>();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- 모달 셸 컴포넌트(보통 `SdModalProvider` 가 생성, 직접 템플릿 사용은 드묾). `key` 지정 시 크기·위치를 `SdSystemConfigProvider` 에 저장/복원.
|
|
40
|
+
- `useCloseByBackdrop`/`useCloseByEscapeKey` — 배경 클릭/ESC 닫기 허용(기본 true). `float`=배경 없는 부유창, `fill`=전체 채움, `resizable`/`movable`=리사이즈/드래그(헤더), `position`=고정 위치.
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
- `contentComponent: WritableSignal<T | undefined>` — 컨텐츠 컴포넌트 참조.
|
|
51
|
-
- `canDeactivateFn: () => boolean` — 닫기 시도 시 가드. `setupCanDeactivate` 가 이 값을 설정.
|
|
42
|
+
### 관련 타입·내장 모달
|
|
52
43
|
|
|
53
|
-
|
|
44
|
+
```ts
|
|
45
|
+
SdModalContentDef<O> { initialized: Signal<boolean>; close: OutputEmitterRef<O | undefined>; actionTplRef?; _optionalModalInputs? }
|
|
46
|
+
SdModalInfo<T, X> { title: string; type: Type<T>; inputs: ... }
|
|
47
|
+
SdModalOptions { key?; hideHeader?; hideCloseButton?; headerStyle?; useCloseByBackdrop?; useCloseByEscapeKey?; float?; fill?; resizable?; movable?; position?; minHeightPx?; minWidthPx?; heightPx?; widthPx?; noFirstControlFocusing? }
|
|
48
|
+
```
|
|
54
49
|
|
|
55
|
-
|
|
50
|
+
- `SdModalContentDef<O>` — 모달 컨텐츠 컴포넌트 계약(`initialized` 시그널 + `close` 출력). `O` 가 close 페이로드 타입. `_optionalModalInputs` 에 optional input 키를 문자열 리터럴로 선언하면 `showAsync` 호출 시 해당 input 생략 허용.
|
|
51
|
+
- `SdModalOptions.noFirstControlFocusing` — true 면 첫 컨트롤 자동 포커스 안 함(다이얼로그만 포커스).
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
- `SdConfirmModal` — `SdModalContentDef<boolean>`. `message: input.required<string>` 표시 후 확인 시 `true`, 취소 시 `undefined` emit.
|
|
53
|
+
### SdActivatedModalProvider
|
|
59
54
|
|
|
60
55
|
```ts
|
|
61
|
-
|
|
56
|
+
@Injectable() class SdActivatedModalProvider<T> {
|
|
57
|
+
modalComponent: WritableSignal<SdModal | undefined>;
|
|
58
|
+
contentComponent: WritableSignal<T | undefined>;
|
|
59
|
+
canDeactivateFn: () => boolean; // 기본 () => true
|
|
60
|
+
}
|
|
62
61
|
```
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
- 모달 컨텐츠 내부에서 `inject` 해 사용. `canDeactivateFn` 을 세팅하면 닫기 전 가드(미저장 변경 보호). `contentComponent` 로 자기 컴포넌트 참조(crud-list 가 close 호출에 사용).
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
### SdPromptModal / SdConfirmModal
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
```ts
|
|
68
|
+
// SdPromptModal: SdModalContentDef<string> — message 입력 + 텍스트 입력 후 확인/취소
|
|
69
|
+
message = input.required<string>();
|
|
70
|
+
// SdConfirmModal: SdModalContentDef<boolean> — message 표시 후 확인(true)/취소(undefined)
|
|
71
|
+
message = input.required<string>();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- 범용 입력/확인 모달. `showAsync({ type: SdPromptModal, inputs: { message } })` 로 호출. prompt 는 확인 시 입력 문자열, confirm 은 확인 시 `true`, 취소는 둘 다 undefined.
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
- `SdToastContentDef<O>` — `close: OutputEmitterRef<O | undefined>` 보유.
|
|
79
|
-
- `SdToastInput<T>` — `{ type: Type<T>; inputs }`(`close` 제외).
|
|
76
|
+
## 토스트
|
|
77
|
+
|
|
78
|
+
### SdToastProvider
|
|
80
79
|
|
|
81
80
|
```ts
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
@Injectable({ providedIn: "root" }) class SdToastProvider {
|
|
82
|
+
alertThemes: WritableSignal<SdToastSeverity[]>; overlap: WritableSignal<boolean>;
|
|
83
|
+
beforeShowFn?: (theme: SdToastSeverity) => void;
|
|
84
|
+
info(msg, useProgress?): WritableSignal<number> | void; // success/warning/danger 동일 시그니처
|
|
85
|
+
notify<T>(input: SdToastInput<T>): Promise<...>;
|
|
86
|
+
try<R>(fn: () => Promise<R> | R, messageFn?: (err: Error) => string): Promise<R | undefined>;
|
|
87
|
+
}
|
|
88
|
+
// SdToastSeverity = "info" | "success" | "warning" | "danger"
|
|
86
89
|
```
|
|
87
90
|
|
|
88
|
-
|
|
91
|
+
- `info`/`success`/`warning`/`danger(msg, useProgress?)` — 토스트 표시. `useProgress=true` 면 진행률 토스트의 `WritableSignal<number>`(0~100) 반환(100 도달 후 자동 해제), 아니면 3초 후(호버 시 지연) 자동 해제.
|
|
92
|
+
- `try(fn, messageFn?)` — fn 실행 중 Error 발생 시 `danger` 토스트 + 시스템로그 적재 후 undefined 반환(Error 외 예외는 rethrow). 화면 핸들러를 감싸 에러를 사용자에게 알림.
|
|
93
|
+
- `alertThemes` — 해당 severity 는 토스트 대신 `window.alert` 사용. `overlap`=새 토스트가 기존을 대체. `notify` 는 커스텀 컴포넌트 토스트.
|
|
89
94
|
|
|
90
|
-
|
|
95
|
+
```ts
|
|
96
|
+
await inject(SdToastProvider).try(async () => { await save(); this._sdToast.success("저장됨"); });
|
|
97
|
+
```
|
|
91
98
|
|
|
92
|
-
|
|
93
|
-
- `globalBusyCount: WritableSignal<number>` — 0 보다 크면 화면 전체 busy 오버레이 표시. 비동기 작업 시작 `+1`, 종료 `-1`.
|
|
99
|
+
### SdToast / SdToastContainer — `<sd-toast>` / `<sd-toast-container>`
|
|
94
100
|
|
|
95
|
-
|
|
101
|
+
```ts
|
|
102
|
+
// toast
|
|
103
|
+
open = model(false); useProgress = input(false); theme = input<SdToastTheme>("info");
|
|
104
|
+
progress = model(0); message = model<string | undefined>();
|
|
105
|
+
// container
|
|
106
|
+
overlap = input(false);
|
|
107
|
+
// SdToastTheme = "primary"|"secondary"|"info"|"success"|"warning"|"danger"|"gray"|"blue-gray"
|
|
108
|
+
```
|
|
96
109
|
|
|
97
|
-
|
|
110
|
+
- 토스트 표시 단위/컨테이너(provider 가 동적 생성, 직접 사용은 드묾). `theme` info/success 는 aria status·polite, warning/danger 는 alert·assertive 로 접근성 자동 설정.
|
|
98
111
|
|
|
99
|
-
|
|
112
|
+
### 관련 타입
|
|
100
113
|
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
```ts
|
|
115
|
+
SdToastContentDef<O> { close: OutputEmitterRef<O | undefined> }
|
|
116
|
+
SdToastInput<T> { type: Type<T>; inputs: Omit<DirectiveInputSignals<T>, "close"> }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- 커스텀 컴포넌트 토스트(`notify`)의 계약·입력 타입.
|
|
120
|
+
|
|
121
|
+
## busy
|
|
103
122
|
|
|
104
|
-
|
|
123
|
+
### SdBusyProvider
|
|
105
124
|
|
|
106
125
|
```ts
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
@Injectable({ providedIn: "root" }) class SdBusyProvider {
|
|
127
|
+
type: WritableSignal<SdBusyType>; // 기본 "bar"
|
|
128
|
+
globalBusyCount: WritableSignal<number>;
|
|
129
|
+
}
|
|
130
|
+
// SdBusyType = "spinner" | "bar" | "cube"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- `globalBusyCount` — 0 초과면 전역 busy 오버레이 표시(라우팅 네비게이션·인쇄가 자동 +1/-1). 화면에서 비동기 작업 동안 직접 증감 가능. `type` 은 전역 인디케이터 모양.
|
|
134
|
+
|
|
135
|
+
### SdBusyContainer — `<sd-busy-container>`
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
busy = input(false); message = input<string>(); type = input<SdBusyType>();
|
|
139
|
+
progressPercent = input<number>();
|
|
111
140
|
```
|
|
141
|
+
|
|
142
|
+
- 특정 영역에 busy 오버레이를 씌우는 컨테이너. `busy` true 동안 콘텐츠 위에 인디케이터 + 키보드 차단. `type` 미지정 시 `SdBusyProvider.type`, `progressPercent`=상단 진행바.
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<sd-busy-container [busy]="loading()"> <ng-content /> </sd-busy-container>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 인쇄·PDF
|
|
149
|
+
|
|
150
|
+
### SdPrintProvider
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
@Injectable({ providedIn: "root" }) class SdPrintProvider {
|
|
154
|
+
printAsync<T extends SdPrint>(template: SdPrintInput<T>, options?: { size?: string; margin?: string }): Promise<void>;
|
|
155
|
+
getPdfBufferAsync<T extends SdPrint>(template: SdPrintInput<T>, options?: { orientation?: "portrait"|"landscape"; pageSize?: string }): Promise<Uint8Array>;
|
|
156
|
+
}
|
|
157
|
+
// SdPrint { initialized: Signal<boolean>; _optionalPrintInputs? }
|
|
158
|
+
// SdPrintInput<T, X> { type: Type<T>; inputs: ... }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- `printAsync(template, options)` — 인쇄용 컴포넌트를 임시 렌더(`initialized` 대기 + 이미지 로드 대기) 후 `window.print()`. `options.size`(예: `"A4 auto"`)/`margin` 은 `@page` 규칙. 동안 globalBusy.
|
|
162
|
+
- `getPdfBufferAsync(template, options)` — 같은 방식으로 렌더 후 `.page` 요소(없으면 전체)를 페이지별 이미지로 PDF 생성, `Uint8Array` 반환. `orientation`/`pageSize`(예: `"a4"`) 지정.
|
|
163
|
+
- `template.type` 는 `SdPrint` 구현 컴포넌트(`initialized` 시그널 필수). `inputs` 로 데이터 주입.
|
|
@@ -2,89 +2,135 @@
|
|
|
2
2
|
|
|
3
3
|
라우터 링크·현재 페이지 식별·뷰 컨텍스트(page/control/modal)·이탈 가드, 그리고 앱 구조 트리에서 메뉴·권한을 파생하는 군. 화면 컴포넌트의 표준 시그널 `viewType`, 권한 가드 `injectPermsSignal`, 사이드바/탑바 메뉴가 이 군에 의존.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 라우팅 디렉티브·프로바이더
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- `injectFullPageCodeSignal(): Signal<string>` — 전체 URL 경로를 점(`.`) 연결 코드로 반환(`/home/a/b` → `"a.b"`). 라우팅 변경 시 갱신.
|
|
10
|
-
- `injectCurrentPageCodeSignal(): Signal<string> | undefined` — 현재 컴포넌트 기준 상대 페이지 코드. `ActivatedRoute` 없으면 `undefined`.
|
|
11
|
-
- `injectViewTitleSignal(): Signal<string>` — 현재 화면 제목. 모달이면 모달 제목, 아니면 앱 구조에서 코드로 찾은 제목.
|
|
12
|
-
- `injectViewTypeSignal(): Signal<SdViewType>` — 화면이 동작 중인 컨텍스트. `SdViewType = "page"|"modal"|"control"`. 라우팅 진입(셀렉터=현재 코드)이면 `"page"`, 모달이면 `"modal"`, view 의 자식 등이면 `"control"`. `sd-base-container`/`sd-crud-*` 의 `[viewType]` 입력에 그대로 전달.
|
|
7
|
+
### SdRouterLink — `[sdRouterLink]`
|
|
13
8
|
|
|
14
9
|
```ts
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
option = input<{ link: string; params?: Record<string,string>;
|
|
11
|
+
window?: { width?: number; height?: number };
|
|
12
|
+
outletName?: string; queryParams?: Record<string,string> } | undefined>(undefined, { alias: "sdRouterLink" });
|
|
17
13
|
```
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
- 클릭 시 라우터 내비게이트. `option.link`=경로, `params`=matrix 파라미터, `queryParams`=쿼리, `outletName`=명명 outlet. Ctrl/Shift 클릭 또는 윈도우 모드면 새 창으로(`window.width/height`). Alt 클릭은 무시.
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
```html
|
|
18
|
+
<sd-list-item [sdRouterLink]="{ link: '/home/order/list' }">주문</sd-list-item>
|
|
19
|
+
```
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
### SdNavigateWindowProvider
|
|
24
22
|
|
|
25
23
|
```ts
|
|
26
|
-
|
|
24
|
+
@Injectable({ providedIn: "root" }) class SdNavigateWindowProvider {
|
|
25
|
+
get isWindow: boolean;
|
|
26
|
+
open(navigate: string, params?: Record<string,string>, features?: string): void;
|
|
27
|
+
}
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
- `isWindow` — 현재가 팝업 창 모드인지(hash 의 `window=true`). `open(navigate, params, features)` — 새 창/탭으로 화면을 염(features 있으면 팝업, 없으면 새 탭). 부모 종료 시 자식 창 일괄 닫힘.
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
## 페이지 식별·뷰 컨텍스트 시그널
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
- `open(navigate: string, params?: Record<string,string>, features?: string): void` — 창 모드이거나 `features` 가 있으면 `window.open` 으로 새 창(beforeunload 시 자식 창 정리), 아니면 `_blank` 새 탭으로 라우트 열기.
|
|
34
|
+
주입 컨텍스트(컴포넌트 생성자)에서 호출하는 헬퍼.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
### injectFullPageCodeSignal / injectCurrentPageCodeSignal
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
```ts
|
|
39
|
+
injectFullPageCodeSignal(): Signal<string>;
|
|
40
|
+
injectCurrentPageCodeSignal(): Signal<string> | undefined;
|
|
41
|
+
```
|
|
39
42
|
|
|
40
|
-
- `
|
|
43
|
+
- `injectFullPageCodeSignal` — 현재 URL 의 전체 페이지 코드(`a.b.c`, `/home/` 이후 세그먼트를 `.` 결합). 메뉴 선택 판정에 사용.
|
|
44
|
+
- `injectCurrentPageCodeSignal` — 현재 라우트 컴포넌트 기준 페이지 코드(중첩 라우트의 자기 위치). `ActivatedRoute` 없으면 undefined.
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
### injectViewTitleSignal / injectViewTypeSignal
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
injectViewTitleSignal(): Signal<string>;
|
|
50
|
+
injectViewTypeSignal(): Signal<SdViewType>; // "page" | "modal" | "control"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- `injectViewTitleSignal` — 현재 뷰 제목. 모달이면 모달 title, 페이지면 app-structure 에서 코드로 제목 조회. `sd-base-container` 가 사용.
|
|
54
|
+
- `injectViewTypeSignal` — 현재 뷰 컨텍스트. `"modal"`=모달 안, `"page"`=라우팅 진입 단위(컴포넌트 selector 가 라우트 컴포넌트와 일치+풀코드 매칭), 그 외 `"control"`(임베드). crud 골격의 `viewType` 입력에 그대로 전달.
|
|
55
|
+
|
|
56
|
+
### setupCanDeactivate
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
setupCanDeactivate(fn: () => boolean): void;
|
|
44
60
|
```
|
|
45
61
|
|
|
46
|
-
|
|
62
|
+
- 화면 이탈 가드 등록. 모달 안이면 `SdActivatedModalProvider.canDeactivateFn` 에, 페이지면 라우트 `canDeactivate` 에 연결(파괴 시 해제). `fn` 이 false 면 이탈 차단(미저장 변경 보호).
|
|
47
63
|
|
|
48
|
-
|
|
64
|
+
### 메뉴 유틸
|
|
49
65
|
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
```ts
|
|
67
|
+
getMenuRouterLinkOption(menu: SdMenu): { link: string; queryParams: Record<string,string>|undefined } | undefined;
|
|
68
|
+
getIsMenuSelected(menu: SdMenu, fullPageCode: string|undefined, customFn?: (menu: SdMenu)=>boolean): boolean;
|
|
69
|
+
```
|
|
52
70
|
|
|
53
|
-
|
|
71
|
+
- `getMenuRouterLinkOption` — 리프 메뉴(자식·url 없음)면 `/home/<codeChain>` 링크 옵션 반환, 그룹/외부url 이면 undefined(클릭 비내비게이트). `sdRouterLink` 에 전달.
|
|
72
|
+
- `getIsMenuSelected` — 메뉴가 현재 페이지인지. `customFn` 있으면 그 결과, 없으면 코드 일치. 메뉴 컴포넌트가 선택 강조에 사용.
|
|
54
73
|
|
|
55
|
-
앱 메뉴·권한
|
|
74
|
+
## 앱 구조(메뉴·권한)
|
|
56
75
|
|
|
57
|
-
|
|
58
|
-
- `permRecord: WritableSignal<Record<string, boolean> | undefined>` — `"코드.액션"→보유여부` 권한 맵.
|
|
59
|
-
- `items: WritableSignal<AppStructureItem<TModule>[]>` — 앱 구조 원본.
|
|
60
|
-
- `initialize(items)` — 구조 트리 주입.
|
|
61
|
-
- `usableMenus`/`usableFlatMenus` — 모듈·권한으로 필터된 메뉴 트리/평면 메뉴(computed).
|
|
62
|
-
- `getPermsByFullCode(fullCodes, permKeys): K[]` — 해당 코드들에서 보유한 권한 키 배열. 권한 정의 자체가 없는 항목은 모든 키 통과.
|
|
63
|
-
- `getTitleByFullCode`/`findTitleByFullCode` — 코드로 제목 조회(전자는 미발견 시 throw, 후자는 `undefined`).
|
|
64
|
-
- `getItemChainByFullCode`/`getPermissionsByStructure` — 코드 체인·권한 트리 조회.
|
|
76
|
+
### SdAppStructureProvider
|
|
65
77
|
|
|
66
|
-
|
|
78
|
+
```ts
|
|
79
|
+
@Injectable({ providedIn: "root" }) class SdAppStructureProvider<TModule = unknown> {
|
|
80
|
+
usableModules: WritableSignal<TModule[] | undefined>;
|
|
81
|
+
permRecord: WritableSignal<Record<string, boolean> | undefined>;
|
|
82
|
+
items: WritableSignal<AppStructureItem<TModule>[]>;
|
|
83
|
+
initialize(items): void;
|
|
84
|
+
usableMenus = computed<SdMenu[]>(...);
|
|
85
|
+
usableFlatMenus = computed<SdFlatMenu<TModule>[]>(...);
|
|
86
|
+
getPermissionsByStructure(items, codeChain?): SdPermission<TModule>[];
|
|
87
|
+
getTitleByFullCode(fullCode): string; // 못 찾으면 throw
|
|
88
|
+
findTitleByFullCode(fullCode): string | undefined; // 결측 보존
|
|
89
|
+
getItemChainByFullCode(fullCode): AppStructureItem<TModule>[];
|
|
90
|
+
getPermsByFullCode<K>(fullCodes: string[], permKeys: K[]): K[];
|
|
91
|
+
}
|
|
92
|
+
```
|
|
67
93
|
|
|
68
|
-
|
|
94
|
+
- `initialize(items)` — 앱 구조 트리 주입. `usableModules`/`permRecord` 를 인증 후 채우면 메뉴/권한이 그에 맞게 필터됨.
|
|
95
|
+
- `usableMenus` — 모듈·권한으로 필터된 메뉴 트리(사이드바/탑바용). `usableFlatMenus` — 평탄화된 메뉴(검색 등).
|
|
96
|
+
- `getTitleByFullCode` 는 못 찾으면 throw, `findTitleByFullCode` 는 undefined(결측 보존) — 상황에 맞게 선택. `getPermsByFullCode` 는 주어진 코드들에 대해 보유한 권한 키만 반환.
|
|
69
97
|
|
|
70
|
-
|
|
98
|
+
### injectPermsSignal
|
|
71
99
|
|
|
72
100
|
```ts
|
|
73
|
-
|
|
74
|
-
// this.perms().includes("use")
|
|
101
|
+
injectPermsSignal<K extends string>(viewCodes: string[], keys: K[]): Signal<K[]>;
|
|
75
102
|
```
|
|
76
103
|
|
|
77
|
-
|
|
104
|
+
- 화면 권한 가드. `viewCodes`(보통 현재 화면 코드)에 대해 `keys`(`["use","edit"]` 등) 중 보유한 것만 반환하는 시그널. `perms().includes("use")` 로 분기.
|
|
78
105
|
|
|
79
|
-
|
|
106
|
+
```ts
|
|
107
|
+
perms = injectPermsSignal(["base.role"], ["use", "edit"]);
|
|
108
|
+
```
|
|
80
109
|
|
|
81
|
-
|
|
82
|
-
- `getFlatMenus(...)`: `SdFlatMenu[]` — 평면 메뉴(BFS, titleChain/codeChain/modulesChain 보유).
|
|
83
|
-
- `getPermissions(items, codeChain, usableModules): SdPermission[]` — 권한 트리(subPerms 를 children 으로).
|
|
84
|
-
- `getTitleByFullCode`/`findTitleByFullCode`/`getItemChainByFullCode`/`getPermsByFullCode`/`getFlatPermissions` — provider 와 동일 동작의 정적 버전.
|
|
110
|
+
### SdAppStructureUtils
|
|
85
111
|
|
|
86
|
-
|
|
112
|
+
```ts
|
|
113
|
+
abstract class SdAppStructureUtils {
|
|
114
|
+
static getTitleByFullCode(items, fullCode): string;
|
|
115
|
+
static findTitleByFullCode(items, fullCode): string | undefined;
|
|
116
|
+
static getPermsByFullCode(items, fullCodes, permKeys, permRecord): K[];
|
|
117
|
+
static getItemChainByFullCode(items, fullCode): AppStructureItem[];
|
|
118
|
+
static getMenus(items, codeChain, usableModules, permRecord): SdMenu[];
|
|
119
|
+
static getFlatMenus(items, usableModules, permRecord): SdFlatMenu[];
|
|
120
|
+
static getPermissions(items, codeChain, usableModules): SdPermission[];
|
|
121
|
+
static getFlatPermissions(items, usableModules);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- 트리에서 메뉴/권한/제목/체인을 계산하는 순수 정적 유틸(`SdAppStructureProvider` 가 위임). provider 없이 트리만으로 파생할 때 직접 호출.
|
|
126
|
+
|
|
127
|
+
### 타입
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
SdMenu { title: string; codeChain: string[]; url?: string; icon?: string; children?: SdMenu[] }
|
|
131
|
+
SdFlatMenu<TModule> { titleChain: string[]; codeChain: string[]; modulesChain: TModule[][] }
|
|
132
|
+
SdPermission<TModule> { title; codeChain; modules?; perms?: ("use"|"edit")[]; children? }
|
|
133
|
+
SdViewType = "page" | "modal" | "control"
|
|
134
|
+
```
|
|
87
135
|
|
|
88
|
-
- `SdMenu` — `
|
|
89
|
-
- `SdFlatMenu<TModule>` — `{ titleChain: string[]; codeChain: string[]; modulesChain: TModule[][] }`. 검색용 평면 메뉴.
|
|
90
|
-
- `SdPermission<TModule>` — `{ title; codeChain; modules; perms: ("use"|"edit")[] | undefined; children }`. 권한표(`sd-permission-table`) 입력 단위.
|
|
136
|
+
- `SdMenu` — 메뉴 트리 노드(레이아웃 메뉴 컴포넌트 입력). `SdFlatMenu` — 평탄화 메뉴. `SdPermission` — 권한 트리 노드(`SdPermissionTable` 입력, features.md). `SdViewType` — crud/뷰 컨텍스트.
|
|
@@ -1,91 +1,107 @@
|
|
|
1
1
|
# @simplysm/angular — 공유 마스터 데이터
|
|
2
2
|
|
|
3
|
-
고객사·품목 등 자주 참조하는 마스터 데이터를 한 번 등록해 어느 화면에서든 공유 시그널로 쓰고, 그 데이터를 선택하는 드롭다운/버튼/리스트 컨트롤을 제공하는 군. 등록·항목 추가 절차는
|
|
3
|
+
고객사·품목 등 자주 참조하는 마스터 데이터를 한 번 등록해 어느 화면에서든 공유 시그널로 쓰고, 그 데이터를 선택하는 드롭다운/버튼/리스트 컨트롤을 제공하는 군. 등록·항목 추가 절차는 [client-shared-data.md](../manuals/client-shared-data.md) 참조.
|
|
4
4
|
|
|
5
|
-
## SdSharedDataProvider
|
|
5
|
+
## SdSharedDataProvider
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```ts
|
|
8
|
+
@Injectable() abstract class SdSharedDataProvider<T extends Record<string, SharedDataBase<string|number>>> {
|
|
9
|
+
loadingCount: WritableSignal<number>;
|
|
10
|
+
abstract initialize(): void;
|
|
11
|
+
register<K>(name: K, info: SharedDataInfo<T[K]>): void;
|
|
12
|
+
getHandle<K>(name: K): SharedDataHandle<T[K]>;
|
|
13
|
+
emitAsync<K>(name: K, changeKeys?: (string|number)[]): Promise<void>;
|
|
14
|
+
wait(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
```
|
|
8
17
|
|
|
9
|
-
-
|
|
10
|
-
- `
|
|
11
|
-
- `
|
|
12
|
-
- `
|
|
13
|
-
- `
|
|
14
|
-
- `wait(): Promise<void>` — `loadingCount <= 0` 까지 대기.
|
|
18
|
+
- 추상 클래스 — 앱에서 상속(`AppSharedDataProvider`)해 `initialize()` 안에서 `register`. `T` 는 이름→항목타입 매핑.
|
|
19
|
+
- `register(name, info)` — 마스터 데이터 등록. 재등록 시 이전 리스너 정리 + generation 증가로 이전 이벤트 무시 후 재로드.
|
|
20
|
+
- `getHandle(name)` — 핸들 반환(첫 접근 시 lazy 로드 + 변경 이벤트 리스너 등록). 미등록이면 throw.
|
|
21
|
+
- `emitAsync(name, changeKeys?)` — 변경 통지 이벤트 발생. `changeKeys` 지정 시 해당 키만 부분 갱신, 미지정 시 전체 리로드. CRUD 저장/삭제 후 호출해 다른 화면을 동기화.
|
|
22
|
+
- `wait()` — 진행 중인 로드(`loadingCount`)가 끝날 때까지 대기. `sd-base-container` 의 ready 게이트가 사용.
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
### 관련 타입
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
```ts
|
|
27
|
+
SharedDataBase<TKey> { __valueKey: TKey; __searchText: string; __isHidden: boolean; __parentKey?: TKey }
|
|
28
|
+
SharedDataInfo<T> { serviceKey: string; getter: (changeKeys?) => Promise<T[]>; filter?; orderBy?: (item) => ...|undefined }
|
|
29
|
+
SharedDataHandle<T> { items: Signal<T[]>; get(key): T | undefined }
|
|
30
|
+
SdSharedDataChangeEvent // defineEvent — 변경 통지 이벤트 정의
|
|
31
|
+
```
|
|
19
32
|
|
|
20
|
-
- `SharedDataBase
|
|
21
|
-
- `SharedDataInfo
|
|
22
|
-
- `SharedDataHandle<T>` — `items: Signal<T[]>`(항목 시그널), `get(key): T | undefined`(키로 단건 조회).
|
|
33
|
+
- `SharedDataBase` — 모든 공유 항목이 가져야 할 매직 필드: `__valueKey`(키), `__searchText`(검색 텍스트), `__isHidden`(숨김 여부), `__parentKey`(트리 부모, 선택).
|
|
34
|
+
- `SharedDataInfo.getter(changeKeys)` — DB 조회 함수. changeKeys 주어지면 그 키만 재조회(incremental refresh). `orderBy` 는 정렬 키 반환. `SharedDataHandle.get(key)` 로 단건 O(1) 조회.
|
|
23
35
|
|
|
24
36
|
```ts
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
serviceKey: "MAIN",
|
|
28
|
-
getter: async (changeKeys) => this._appOrm.connectAsync((db) => { /* ... */ }),
|
|
29
|
-
orderBy: (item) => item.code,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
37
|
+
sharedProducts = useSharedSignal("품목"); // 앱 헬퍼
|
|
38
|
+
// sharedProducts.items() / sharedProducts.get(id)
|
|
32
39
|
```
|
|
33
40
|
|
|
34
|
-
##
|
|
41
|
+
## 선택 컨트롤
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
### SdSharedDataSelect — `<sd-shared-data-select>`
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
```ts
|
|
46
|
+
value = model<SelectModeValue<TItem["__valueKey"] | undefined>[TMode]>();
|
|
47
|
+
items = input.required<TItem[]>();
|
|
48
|
+
disabled; required; useUndefined; inset; inline;
|
|
49
|
+
size = input<"sm"|"lg">(); selectMode = input<TMode>("single"); // "single" | "multi"
|
|
50
|
+
filterFn = input<(item, index, ...params) => boolean>(); filterFnParams = input<any[]>();
|
|
51
|
+
modal = input<SdSelectModalInfo<TModal>>(); editModal = input<SdModalInfo<SdModalContentDef<boolean>>>();
|
|
52
|
+
selectClass; multiSelectionDisplayDirection = input<"vertical">();
|
|
53
|
+
getIsHiddenFn = input(item => item.__isHidden); getSearchTextFn = input(item => item.__searchText);
|
|
54
|
+
displayOrderByFn = input<(item) => ...|undefined>();
|
|
55
|
+
// 콘텐츠: [itemOf] 템플릿(항목 렌더), #undefinedTpl(미지정 표시)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- 공유데이터 드롭다운 선택. `value` 는 선택된 `__valueKey`(single) 또는 키 배열(multi). `items` 에 `sharedX.items()` 전달.
|
|
59
|
+
- `selectMode` — `"single"`/`"multi"`. `useUndefined`=multi 에서 "미지정" 항목 노출, `required=false`+single 이면 미지정 선택 가능. 내부 검색바로 `__searchText` 필터(부모키 트리면 자식 매칭 포함).
|
|
60
|
+
- `modal` — 관리·선택 모달(`selectMode:"single"`+현재 키 주입, 결과로 선택 갱신). `editModal` — 관리 전용(선택 변경 없음). 둘 다 `<sd-select-button>` 아이콘으로 노출.
|
|
61
|
+
- `filterFn`/`displayOrderByFn` — 표시 필터/정렬. `getIsHiddenFn`/`getSearchTextFn` — 숨김·검색텍스트 추출 커스텀(기본 매직필드). `__parentKey` 있으면 트리(`getChildrenFn` 자동).
|
|
49
62
|
|
|
50
63
|
```html
|
|
51
|
-
<sd-shared-data-select [items]="
|
|
52
|
-
<ng-template [itemOf]="
|
|
64
|
+
<sd-shared-data-select [items]="sharedProducts.items()" [(value)]="productId" [required]="true">
|
|
65
|
+
<ng-template [itemOf]="sharedProducts.items()" let-item="item">{{ item.name }}</ng-template>
|
|
53
66
|
</sd-shared-data-select>
|
|
54
67
|
```
|
|
55
68
|
|
|
56
|
-
|
|
69
|
+
### SdSharedDataSelectButton — `<sd-shared-data-select-button>`
|
|
57
70
|
|
|
58
|
-
|
|
71
|
+
```ts
|
|
72
|
+
value = model<SelectModeValue<string|number>[TMode]>();
|
|
73
|
+
items = input<TItem[]>([]); modal = input.required<SdSelectModalInfo<TModal>>();
|
|
74
|
+
selectMode = input<TMode>("single"); disabled; required; inset; size = input<"sm"|"lg">();
|
|
75
|
+
itemTplRef = contentChild.required(SdItemOfTemplate); // [itemOf] 템플릿(필수)
|
|
76
|
+
```
|
|
59
77
|
|
|
60
|
-
- `
|
|
61
|
-
- `items: input<TItem[]>` — 항목 배열.
|
|
62
|
-
- `modal: input.required<SdSelectModalInfo<TModal>>` — 선택 모달(필수).
|
|
63
|
-
- `selectMode: "single"|"multi"` — 선택 모드(기본 single).
|
|
64
|
-
- `disabled`/`required`/`inset: boolean`, `size: "sm"|"lg"` — 상태/크기.
|
|
78
|
+
- 모달로만 선택하는 버튼형(`SdModalSelectButton` 래핑). 선택된 항목을 `[itemOf]` 템플릿으로 표시(multi 면 콤마 구분). 항목이 많아 드롭다운보다 모달 검색이 나을 때.
|
|
65
79
|
|
|
66
|
-
|
|
80
|
+
### SdSharedDataSelectList — `<sd-shared-data-select-list>`
|
|
67
81
|
|
|
68
|
-
|
|
82
|
+
```ts
|
|
83
|
+
selectedItem = model<TItem>();
|
|
84
|
+
canChangeFn = input<(item: TItem | undefined) => boolean | Promise<boolean>>(() => true);
|
|
85
|
+
items = input.required<TItem[]>(); selectedIcon = input<string>(); useUndefined;
|
|
86
|
+
filterFn = input<(item, index) => boolean>(); modal = input<SdSelectModalInfo<TModal>>();
|
|
87
|
+
header = input<string>(); pageItemCount = input<number>();
|
|
88
|
+
// 콘텐츠: [itemOf](항목) #headerTpl #filterTpl #undefinedTpl
|
|
89
|
+
```
|
|
69
90
|
|
|
70
|
-
- `selectedItem
|
|
71
|
-
- `
|
|
72
|
-
- `canChangeFn: (item) => boolean | Promise<boolean>` — 선택 변경 허용 가드.
|
|
73
|
-
- `selectedIcon: string` — 선택 표시 아이콘.
|
|
74
|
-
- `useUndefined: boolean` — 미지정 항목 노출.
|
|
75
|
-
- `filterFn: (item, index) => boolean` — 표시 필터.
|
|
76
|
-
- `modal: SdSelectModalInfo<TModal>` — 관리·선택 모달(목록 화면 재사용).
|
|
77
|
-
- `header: string` — 목록 헤더 라벨.
|
|
78
|
-
- `pageItemCount: number` — 페이지당 항목 수.
|
|
91
|
+
- 좌측 선택 목록형(master-detail 의 좌측 패널). `selectedItem` 은 항목 객체(키 아님). `canChangeFn` 으로 선택 전환 가드(미저장 변경 보호), Promise 가능.
|
|
92
|
+
- `pageItemCount` 지정 시 페이징. `header`/`#headerTpl`=상단 제목, `#filterTpl`=검색 대체, `modal`=목록 관리 모달. 검색은 `__searchText` 매칭, `__isHidden` 항목 제외.
|
|
79
93
|
|
|
80
94
|
```html
|
|
81
95
|
<sd-shared-data-select-list [items]="sharedRoles.items()" [(selectedItem)]="selectedRole"
|
|
82
|
-
[
|
|
96
|
+
[canChangeFn]="checkCanLeave" [header]="'역할'">
|
|
83
97
|
<ng-template [itemOf]="sharedRoles.items()" let-item="item">{{ item.name }}</ng-template>
|
|
84
98
|
</sd-shared-data-select-list>
|
|
85
99
|
```
|
|
86
100
|
|
|
87
|
-
|
|
101
|
+
### matchesSearchText
|
|
88
102
|
|
|
89
|
-
|
|
103
|
+
```ts
|
|
104
|
+
matchesSearchText(itemText: string, searchQuery: string | undefined): boolean;
|
|
105
|
+
```
|
|
90
106
|
|
|
91
|
-
-
|
|
107
|
+
- 공백 분리 AND 부분일치(대소문자 무시). 빈 쿼리면 true. 선택 컨트롤이 내부 검색에 사용하며, 커스텀 필터에서 재사용 가능.
|