@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.
Files changed (38) hide show
  1. package/claude/references/sd-simplysm14/README.md +6 -2
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +180 -43
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +275 -125
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +54 -59
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +139 -48
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +102 -88
  7. package/claude/references/sd-simplysm14/apis/angular/kanban.md +54 -0
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +60 -36
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +127 -75
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +97 -51
  11. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +74 -58
  12. package/claude/references/sd-simplysm14/apis/angular/sheet.md +81 -60
  13. package/claude/references/sd-simplysm14/apis/excel/README.md +5 -5
  14. package/claude/references/sd-simplysm14/apis/excel/cell.md +3 -3
  15. package/claude/references/sd-simplysm14/apis/excel/style.md +2 -2
  16. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +5 -4
  17. package/claude/references/sd-simplysm14/apis/excel/wrapper.md +2 -2
  18. package/claude/references/sd-simplysm14/manuals/client-app-structure.md +5 -3
  19. package/claude/references/sd-simplysm14/manuals/client-component.md +31 -26
  20. package/claude/references/sd-simplysm14/manuals/client-crud.md +154 -4
  21. package/claude/references/sd-simplysm14/manuals/client-demo.md +5 -18
  22. package/claude/references/sd-simplysm14/manuals/client-orm.md +3 -12
  23. package/claude/references/sd-simplysm14/manuals/client-service.md +18 -7
  24. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +24 -5
  25. package/claude/references/sd-simplysm14/manuals/data-log.md +1 -1
  26. package/claude/sd-system-prompt.md +7 -0
  27. package/claude/skills/sd-debug/SKILL.md +142 -27
  28. package/claude/skills/sd-review/SKILL.md +158 -20
  29. package/claude/skills/sd-spec/SKILL.md +53 -61
  30. package/claude/skills/sd-spec/references/format.md +476 -0
  31. package/package.json +1 -1
  32. package/claude/references/sd-simplysm14/apis/angular/infra.md +0 -82
  33. package/claude/skills/sd-debug/workflow.js +0 -390
  34. package/claude/skills/sd-review/workflow.js +0 -324
  35. package/claude/skills/sd-spec/references/format-analyze.md +0 -232
  36. package/claude/skills/sd-spec/references/format-design.md +0 -248
  37. package/claude/skills/sd-spec/workflow-analyze.js +0 -615
  38. 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
- ## SdModalProvider
6
-
7
- `showAsync` 로 컴포넌트를 모달로 띄움. 컨텐츠 컴포넌트는 `SdModalContentDef<O>` 를 구현해야 함.
8
-
9
- - `modalCount: WritableSignal<number>` — 현재 열린 모달 수.
10
- - `showAsync<T>(modal: SdModalInfo<T>, options?: SdModalOptions): Promise<O | undefined>` — 모달을 띄우고 close 페이로드를 반환. 사용자가 X/취소로 닫으면 `undefined`.
11
-
12
- `SdModalInfo<T>`:
13
- - `title: string` — 모달 헤더 제목.
14
- - `type: Type<T>` — `SdModalContentDef` 구현 컴포넌트 클래스.
15
- - `inputs` — 컴포넌트 input 값들. `initialized`/`close`/`actionTplRef` 제외, optional 입력은 생략 가능.
16
-
17
- `SdModalContentDef<O>` (컨텐츠 컴포넌트가 구현):
18
- - `initialized: Signal<boolean>`로드 완료 시그널.
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
- { type: GoodsDetailModal, title: "품목 등록", inputs: { goodsId: 12 } },
40
- { resizable: true, widthPx: 720 },
22
+ { title: "역할 선택", type: RoleListModal, inputs: { selectMode: "single" } },
23
+ { useCloseByBackdrop: false },
41
24
  );
42
- if (result == null) return;
43
25
  ```
44
26
 
45
- ## SdActivatedModalProvider<T>
27
+ ### SdModal — `<sd-modal>`
46
28
 
47
- 모달 컨텐츠 컴포넌트 안에서 inject 해 자기 모달 셸을 제어. `SdModalProvider` 가 컨텐츠 인젝터에 주입.
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
- - `modalComponent: WritableSignal<SdModal | undefined>` — 모달 셸 컴포넌트 참조(제목 등 조회).
50
- - `contentComponent: WritableSignal<T | undefined>` — 컨텐츠 컴포넌트 참조.
51
- - `canDeactivateFn: () => boolean` — 닫기 시도 시 가드. `setupCanDeactivate` 가 이 값을 설정.
42
+ ### 관련 타입·내장 모달
52
43
 
53
- ## SdPromptModal / SdConfirmModal
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
- - `SdPromptModal` — `SdModalContentDef<string>`. `message: input.required<string>` 를 표시하고 텍스트 입력(필수) 후 확인 시 입력값, 취소 시 `undefined` emit.
58
- - `SdConfirmModal` — `SdModalContentDef<boolean>`. `message: input.required<string>` 표시 후 확인 시 `true`, 취소 시 `undefined` emit.
53
+ ### SdActivatedModalProvider
59
54
 
60
55
  ```ts
61
- const ok = await sdModal.showAsync({ type: SdConfirmModal, title: "삭제", inputs: { message: "삭제할까요?" } });
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
- ## SdToastProvider
63
+ - 모달 컨텐츠 내부에서 `inject` 해 사용. `canDeactivateFn` 을 세팅하면 닫기 전 가드(미저장 변경 보호). `contentComponent` 로 자기 컴포넌트 참조(crud-list 가 close 호출에 사용).
65
64
 
66
- 토스트 알림·진행률·커스텀 토스트를 띄우고, 비동기 try 래퍼를 제공.
65
+ ### SdPromptModal / SdConfirmModal
67
66
 
68
- - `info`/`success`/`warning`/`danger(message, useProgress?)` — 해당 severity 토스트. `useProgress: true` 면 진행률 토스트로 `WritableSignal<number>`(0~100) 반환(100 도달 1초 후 자동 닫힘), `false`/생략이면 일정 시간 후 자동 닫힘.
69
- - `try<R>(fn, messageFn?): Promise<R | undefined>` `fn` 실행 `Error` throw `danger` 토스트로 표시하고 시스템 로그에 기록한 뒤 `undefined` 반환(에러 외 throw 는 재전파). 화면 비동기 작업 표준 래퍼.
70
- - `notify<T>(input: SdToastInput<T>): Promise<O | undefined>` — 커스텀 컴포넌트(`SdToastContentDef<O>`)를 토스트로 띄움. close emit 값 또는 5초 후 `undefined` 반환.
71
- - `alertThemes: WritableSignal<SdToastSeverity[]>`여기에 severity 는 토스트 대신 `window.alert` 로 표시.
72
- - `overlap: WritableSignal<boolean>` — true 면 새 토스트가 기존 토스트를 모두 치움.
73
- - `beforeShowFn?: (theme) => void` — 토스트 표시 직전 콜백.
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
- - `SdToastSeverity = "info"|"success"|"warning"|"danger"`.
77
- - `SdToastTheme = "primary"|"secondary"|SdToastSeverity|"gray"|"blue-gray"`.
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
- this.busyCount.update((v) => v + 1);
83
- await this._sdToast.try(async () => { await this._refresh(); });
84
- this.busyCount.update((v) => v - 1);
85
- this._sdToast.success("저장되었습니다.");
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
- ## SdBusyProvider
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
- 전역 busy 인디케이터 상태. `provideSdAngular` 가 라우팅 동안 증감, 인쇄 provider 도 사용.
95
+ ```ts
96
+ await inject(SdToastProvider).try(async () => { await save(); this._sdToast.success("저장됨"); });
97
+ ```
91
98
 
92
- - `type: WritableSignal<SdBusyType>`인디케이터 모양(`"spinner"|"bar"|"cube"`, 기본 `"bar"`).
93
- - `globalBusyCount: WritableSignal<number>` — 0 보다 크면 화면 전체 busy 오버레이 표시. 비동기 작업 시작 `+1`, 종료 `-1`.
99
+ ### SdToast / SdToastContainer `<sd-toast>` / `<sd-toast-container>`
94
100
 
95
- `SdBusyContainer` 컴포넌트(영역 단위 busy): `busy: boolean`, `message?: string`, `type?: SdBusyType`, `progressPercent?: number`(진행 바). 화면 영역을 감싸 그 영역만 busy 표시.
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
- ## SdPrintProvider
110
+ - 토스트 표시 단위/컨테이너(provider 가 동적 생성, 직접 사용은 드묾). `theme` info/success 는 aria status·polite, warning/danger 는 alert·assertive 로 접근성 자동 설정.
98
111
 
99
- 화면 컴포넌트(`SdPrint` 구현)를 인쇄하거나 PDF 버퍼로 변환. 처리 동안 `globalBusyCount` 증감.
112
+ ### 관련 타입
100
113
 
101
- - `printAsync<T>(template: SdPrintInput<T>, options?: { size?: string; margin?: string }): Promise<void>` — 컴포넌트를 렌더해 `@page` 스타일을 걸고 `window.print` 호출. `initialized()` 와 이미지 로드를 대기.
102
- - `getPdfBufferAsync<T>(template, options?: { orientation?: "portrait"|"landscape"; pageSize?: string }): Promise<Uint8Array>` `.page` 요소(없으면 전체)를 캔버스로 떠 jsPDF 로 PDF 바이트 생성.
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
- `SdPrint` (템플릿 컴포넌트가 구현): `initialized: Signal<boolean>`(렌더 완료 시그널). `SdPrintInput<T>` — `{ type: Type<T>; inputs }`.
123
+ ### SdBusyProvider
105
124
 
106
125
  ```ts
107
- await inject(SdPrintProvider).printAsync(
108
- { type: InvoicePrintTemplate, inputs: { invoiceId: 5 } },
109
- { size: "A4", margin: "10mm" },
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
- injection 컨텍스트에서 호출하는 헬퍼들. 현재 라우트 상태를 시그널로 노출.
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
- viewType = injectViewTypeSignal();
16
- viewTitle = injectViewTitleSignal();
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
- ## setupCanDeactivate
15
+ - 클릭 시 라우터 내비게이트. `option.link`=경로, `params`=matrix 파라미터, `queryParams`=쿼리, `outletName`=명명 outlet. Ctrl/Shift 클릭 또는 윈도우 모드면 새 창으로(`window.width/height`). Alt 클릭은 무시.
20
16
 
21
- 라우터/모달 이탈 시점에 호출되는 가드를 등록. injection 컨텍스트에서 호출.
17
+ ```html
18
+ <sd-list-item [sdRouterLink]="{ link: '/home/order/list' }">주문</sd-list-item>
19
+ ```
22
20
 
23
- - `setupCanDeactivate(fn: () => boolean): void` — `fn()` 이 `false` 면 이탈 차단. 모달이면 `SdActivatedModalProvider.canDeactivateFn` 설정, 페이지면 route 의 `canDeactivate` 에 push(파괴 시 자동 제거). detail 화면의 변경사항 가드에 사용.
21
+ ### SdNavigateWindowProvider
24
22
 
25
23
  ```ts
26
- setupCanDeactivate(() => this._orgData == null || obj.equal(this.data(), this._orgData) || confirm("변경사항이 있습니다. 진행할까요?"));
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
- ## SdNavigateWindowProvider
30
+ - `isWindow` — 현재가 팝업 창 모드인지(hash 의 `window=true`). `open(navigate, params, features)` — 새 창/탭으로 화면을 염(features 있으면 팝업, 없으면 새 탭). 부모 종료 시 자식 창 일괄 닫힘.
30
31
 
31
- 창/탭으로 라우트를 여는 프로바이더. 현재 컨텍스트가 창 모드인지 판별.
32
+ ## 페이지 식별·뷰 컨텍스트 시그널
32
33
 
33
- - `isWindow: boolean` 현재가 `window=true` 쿼리로 열린 창인지.
34
- - `open(navigate: string, params?: Record<string,string>, features?: string): void` — 창 모드이거나 `features` 가 있으면 `window.open` 으로 새 창(beforeunload 시 자식 창 정리), 아니면 `_blank` 새 탭으로 라우트 열기.
34
+ 주입 컨텍스트(컴포넌트 생성자)에서 호출하는 헬퍼.
35
35
 
36
- ## SdRouterLink
36
+ ### injectFullPageCodeSignal / injectCurrentPageCodeSignal
37
37
 
38
- `[sdRouterLink]` 디렉티브. 클릭 시 옵션에 따라 라우팅/새 창/아웃렛 이동.
38
+ ```ts
39
+ injectFullPageCodeSignal(): Signal<string>;
40
+ injectCurrentPageCodeSignal(): Signal<string> | undefined;
41
+ ```
39
42
 
40
- - `sdRouterLink` (option) `{ link, params?, window?: { width?, height? }, outletName?, queryParams? }`. `link` 이동 경로, `params` matrix 파라미터, `window` 가 창 크기, `outletName` 이 named outlet, `queryParams` 가 쿼리. Ctrl/Shift+클릭이면 새 창, 창 모드면 팝업 창, 아니면 `Router.navigate`. Alt+클릭은 무시.
43
+ - `injectFullPageCodeSignal` — 현재 URL 전체 페이지 코드(`a.b.c`, `/home/` 이후 세그먼트를 `.` 결합). 메뉴 선택 판정에 사용.
44
+ - `injectCurrentPageCodeSignal` — 현재 라우트 컴포넌트 기준 페이지 코드(중첩 라우트의 자기 위치). `ActivatedRoute` 없으면 undefined.
41
45
 
42
- ```html
43
- <a [sdRouterLink]="{ link: '/home/goods/detail', params: { id: '5' } }">상세</a>
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
- ## 메뉴 유틸 (menu-utils)
62
+ - 화면 이탈 가드 등록. 모달 안이면 `SdActivatedModalProvider.canDeactivateFn` 에, 페이지면 라우트 `canDeactivate` 에 연결(파괴 시 해제). `fn` 이 false 면 이탈 차단(미저장 변경 보호).
47
63
 
48
- `SdMenu` 로부터 라우터 링크 옵션·선택 여부를 계산. 사이드바/탑바 메뉴가 사용.
64
+ ### 메뉴 유틸
49
65
 
50
- - `getMenuRouterLinkOption(menu: SdMenu): { link, queryParams } | undefined` — leaf 메뉴면 `"/home/"+코드체인` 링크와 쿼리파라미터 반환, 그룹/외부 url 메뉴면 `undefined`.
51
- - `getIsMenuSelected(menu, fullPageCode?, customFn?): boolean` `customFn` 있으면 그것으로, 없으면 현재 fullPageCode 와 메뉴 코드체인 일치 여부.
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
- ## SdAppStructureProvider<TModule>
71
+ - `getMenuRouterLinkOption` — 리프 메뉴(자식·url 없음)면 `/home/<codeChain>` 링크 옵션 반환, 그룹/외부url 이면 undefined(클릭 비내비게이트). `sdRouterLink` 에 전달.
72
+ - `getIsMenuSelected` — 메뉴가 현재 페이지인지. `customFn` 있으면 그 결과, 없으면 코드 일치. 메뉴 컴포넌트가 선택 강조에 사용.
54
73
 
55
- 앱 메뉴·권한 트리(`AppStructureItem[]`)에서 사용 가능 메뉴·권한을 파생하는 전역 프로바이더. `providedIn: "root"`.
74
+ ## 구조(메뉴·권한)
56
75
 
57
- - `usableModules: WritableSignal<TModule[] | undefined>` — 사용자가 보유한 모듈. 메뉴/권한 필터에 사용.
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
- ## injectPermsSignal
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
- 화면 컴포넌트가 권한을 시그널로 받는 헬퍼. injection 컨텍스트에서 호출.
94
+ - `initialize(items)` 구조 트리 주입. `usableModules`/`permRecord` 를 인증 후 채우면 메뉴/권한이 그에 맞게 필터됨.
95
+ - `usableMenus` — 모듈·권한으로 필터된 메뉴 트리(사이드바/탑바용). `usableFlatMenus` — 평탄화된 메뉴(검색 등).
96
+ - `getTitleByFullCode` 는 못 찾으면 throw, `findTitleByFullCode` 는 undefined(결측 보존) — 상황에 맞게 선택. `getPermsByFullCode` 는 주어진 코드들에 대해 보유한 권한 키만 반환.
69
97
 
70
- - `injectPermsSignal<K>(viewCodes: string[], keys: K[]): Signal<K[]>` — `viewCodes` 권한 path 들에서 사용자가 보유한 `keys` 만 담은 배열을 반환. 템플릿/코드에서 `perms().includes("edit")` 식으로 인라인 가드.
98
+ ### injectPermsSignal
71
99
 
72
100
  ```ts
73
- perms = injectPermsSignal(["inventory.outbound"], ["use", "edit"]);
74
- // this.perms().includes("use")
101
+ injectPermsSignal<K extends string>(viewCodes: string[], keys: K[]): Signal<K[]>;
75
102
  ```
76
103
 
77
- ## SdAppStructureUtils
104
+ - 화면 권한 가드. `viewCodes`(보통 현재 화면 코드)에 대해 `keys`(`["use","edit"]` 등) 중 보유한 것만 반환하는 시그널. `perms().includes("use")` 로 분기.
78
105
 
79
- `AppStructureItem[]` 에서 메뉴/권한/제목을 계산하는 정적 메서드 모음(추상 클래스). 대개 `SdAppStructureProvider` 가 내부에서 호출하지만 직접 쓸 수도 있음.
106
+ ```ts
107
+ perms = injectPermsSignal(["base.role"], ["use", "edit"]);
108
+ ```
80
109
 
81
- - `getMenus(items, codeChain, usableModules, permRecord): SdMenu[]` — 모듈·`use` 권한으로 필터된 메뉴 트리.
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
- ## 타입 (sd-app-structure.types)
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` — `{ title; codeChain: string[]; url?; icon?; children? }`. 사이드바/탑바 메뉴 렌더 단위.
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
- 고객사·품목 등 자주 참조하는 마스터 데이터를 한 번 등록해 어느 화면에서든 공유 시그널로 쓰고, 그 데이터를 선택하는 드롭다운/버튼/리스트 컨트롤을 제공하는 군. 등록·항목 추가 절차는 `client-shared-data.md` 참조.
3
+ 고객사·품목 등 자주 참조하는 마스터 데이터를 한 번 등록해 어느 화면에서든 공유 시그널로 쓰고, 그 데이터를 선택하는 드롭다운/버튼/리스트 컨트롤을 제공하는 군. 등록·항목 추가 절차는 [client-shared-data.md](../manuals/client-shared-data.md) 참조.
4
4
 
5
- ## SdSharedDataProvider<T>
5
+ ## SdSharedDataProvider
6
6
 
7
- 마스터 데이터를 이름별로 등록·로드·공유하는 추상 프로바이더. 앱에서 상속해 `initialize()` 안에서 `register`. `@Injectable()`.
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
- - `loadingCount: WritableSignal<number>`로딩 항목 수. 0 보다 크면 로드 진행 중(`sd-base-container` 대기).
10
- - `abstract initialize(): void` — 앱이 override `register` 들을 호출.
11
- - `register<K>(name, info: SharedDataInfo): void` — 이름으로 데이터 항목 등록. 재호출 기존 리스너/세대 갱신(이전 이벤트 무시).
12
- - `getHandle<K>(name): SharedDataHandle<T[K]>` 핸들 조회. 최초 조회lazy 로드 + 변경 이벤트 리스너 등록. 미등록 이름이면 throw.
13
- - `emitAsync<K>(name, changeKeys?): Promise<void>` 데이터 변경을 다른 클라이언트/탭에 알림(`changeKeys` 지정 부분 갱신, 미지정 전체 리로드).
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
- `SdSharedDataChangeEvent` `defineEvent` 로 정의된 공유 데이터 변경 이벤트(서비스 서버 경유 브로드캐스트).
24
+ ### 관련 타입
17
25
 
18
- ## 타입 (shared-data.provider)
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<TKey>` — 모든 공유 항목이 상속할 매직 필드: `__valueKey: TKey`(키), `__searchText: string`(검색 텍스트), `__isHidden: boolean`(숨김 여부), `__parentKey?: TKey`(트리 부모 ).
21
- - `SharedDataInfo<T>` — `register` 옵션. `serviceKey: string`(연결 키), `getter: (changeKeys?) => Promise<T[]>`(데이터 로더, `changeKeys` 주어지면 그 키만 재조회), `filter?: unknown`(이벤트 필터), `orderBy?: (item) => 정렬키`(정렬).
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
- override initialize() {
26
- this.register("고객사", {
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
- ## SdSharedDataSelect
41
+ ## 선택 컨트롤
35
42
 
36
- 공유 데이터를 드롭다운으로 선택하는 컨트롤. selector `sd-shared-data-select`. 검색·트리(`__parentKey`)·관리/선택 모달 지원.
43
+ ### SdSharedDataSelect `<sd-shared-data-select>`
37
44
 
38
- - `value: model<단일|배열>` — 선택된 키(들). `selectMode` 에 따라 단건/배열.
39
- - `items: input.required<TItem[]>` 공유 데이터 항목 배열(보통 `sharedX.items()`).
40
- - `selectMode: "single"|"multi"` — 선택 모드(기본 `"single"`). 다중 선택이면 `"multi"`.
41
- - `required`/`disabled`/`useUndefined`/`inset`/`inline: boolean` — 필수/비활성/미지정 항목 노출/inset 스타일/인라인.
42
- - `size: "sm"|"lg"` 컨트롤 크기.
43
- - `filterFn: (item, index, ...params) => boolean` + `filterFnParams: any[]` — 표시 항목 필터(추가 파라미터 주입).
44
- - `modal: SdSelectModalInfo<TModal>` 관리·선택 모달. 열릴 때 `selectMode`/현재 선택키가 주입되고 닫힘 결과로 선택 갱신.
45
- - `editModal: SdModalInfo<SdModalContentDef<boolean>>` — 관리 전용 모달(선택을 바꾸지 않음).
46
- - `getIsHiddenFn`/`getSearchTextFn: (item, index) => ...` 숨김 판정·검색 텍스트(기본 `__isHidden`/`__searchText`).
47
- - `displayOrderByFn: (item) => 정렬키` — 표시 정렬.
48
- - 컨텐츠: `<ng-template [itemOf]="...">` 항목 렌더, `#undefinedTpl` 로 미지정 항목 표시.
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]="sharedCustomers.items()" [(value)]="data().customerId" [required]="true">
52
- <ng-template [itemOf]="sharedCustomers.items()" let-item="item">{{ item.name }}</ng-template>
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
- ## SdSharedDataSelectButton
69
+ ### SdSharedDataSelectButton — `<sd-shared-data-select-button>`
57
70
 
58
- 공유 데이터를 모달로만 선택하는 버튼형 컨트롤. selector `sd-shared-data-select-button`.
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
- - `value: model<단일|배열>` 선택 ().
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
- ## SdSharedDataSelectList
80
+ ### SdSharedDataSelectList — `<sd-shared-data-select-list>`
67
81
 
68
- 좌측 마스터 목록형 선택 컨트롤(항목 객체를 모델로). selector `sd-shared-data-select-list`. 공유데이터+detail 합성 화면의 좌측에 사용.
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: model<TItem>` 선택된 **항목 객체**(키가 아니라 객체).
71
- - `items: input.required<TItem[]>` 항목 배열.
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
- [header]="'역할'" [modal]="{ type: RoleList, title: '역할', inputs: {} }">
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
- ## matchesSearchText
101
+ ### matchesSearchText
88
102
 
89
- 공백 분리 AND 검색 일치 판정 유틸(선택 컨트롤이 내부 사용).
103
+ ```ts
104
+ matchesSearchText(itemText: string, searchQuery: string | undefined): boolean;
105
+ ```
90
106
 
91
- - `matchesSearchText(itemText: string, searchQuery: string | undefined): boolean` `searchQuery` 공백으로 나눈 모든 단어가(대소문자 무시) `itemText` 포함되면 true. 검색어 없으면 항상 true.
107
+ - 공백 분리 AND 부분일치(대소문자 무시). 쿼리면 true. 선택 컨트롤이 내부 검색에 사용하며, 커스텀 필터에서 재사용 가능.