@simplysm/sd-claude 14.0.94 → 14.0.96
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 +6 -2
- 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 +5 -3
- package/claude/references/sd-simplysm14/manuals/client-component.md +31 -26
- package/claude/references/sd-simplysm14/manuals/client-crud.md +154 -4
- package/claude/references/sd-simplysm14/manuals/client-demo.md +5 -18
- package/claude/references/sd-simplysm14/manuals/client-orm.md +3 -12
- package/claude/references/sd-simplysm14/manuals/client-service.md +18 -7
- package/claude/references/sd-simplysm14/manuals/client-shared-data.md +24 -5
- package/claude/references/sd-simplysm14/manuals/data-log.md +1 -1
- package/claude/sd-system-prompt.md +7 -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 +53 -61
- package/claude/skills/sd-spec/references/format.md +476 -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
- package/claude/skills/sd-spec/references/format-analyze.md +0 -232
- package/claude/skills/sd-spec/references/format-design.md +0 -248
- package/claude/skills/sd-spec/workflow-analyze.js +0 -615
- package/claude/skills/sd-spec/workflow-design.js +0 -667
|
@@ -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. 선택 컨트롤이 내부 검색에 사용하며, 커스텀 필터에서 재사용 가능.
|