@simplysm/sd-claude 14.0.89 → 14.0.91

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 (79) hide show
  1. package/claude/references/sd-simplysm14/README.md +16 -17
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +52 -30
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +200 -38
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +41 -53
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +66 -22
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +127 -40
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +60 -43
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +56 -20
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +74 -74
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +50 -40
  11. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +55 -15
  12. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +59 -42
  13. package/claude/references/sd-simplysm14/apis/angular/sheet.md +77 -62
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +8 -7
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +71 -43
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +22 -14
  17. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +19 -19
  18. package/claude/references/sd-simplysm14/apis/core-browser/README.md +17 -17
  19. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +28 -28
  20. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +37 -37
  21. package/claude/references/sd-simplysm14/apis/core-common/README.md +87 -219
  22. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +54 -98
  23. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +57 -99
  24. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +60 -103
  25. package/claude/references/sd-simplysm14/apis/core-common/errors.md +42 -47
  26. package/claude/references/sd-simplysm14/apis/core-common/obj.md +42 -88
  27. package/claude/references/sd-simplysm14/apis/core-common/serialization.md +55 -0
  28. package/claude/references/sd-simplysm14/apis/core-node/README.md +6 -7
  29. package/claude/references/sd-simplysm14/apis/core-node/consola.md +17 -12
  30. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +14 -13
  31. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +9 -8
  32. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +14 -13
  33. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +4 -8
  34. package/claude/references/sd-simplysm14/apis/core-node/worker.md +14 -12
  35. package/claude/references/sd-simplysm14/apis/excel/README.md +22 -22
  36. package/claude/references/sd-simplysm14/apis/excel/cell.md +37 -29
  37. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +29 -15
  38. package/claude/references/sd-simplysm14/apis/excel/style.md +33 -27
  39. package/claude/references/sd-simplysm14/apis/excel/utils.md +29 -19
  40. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +78 -55
  41. package/claude/references/sd-simplysm14/apis/excel/wrapper.md +42 -45
  42. package/claude/references/sd-simplysm14/apis/orm-common/README.md +6 -8
  43. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +118 -67
  44. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +83 -86
  45. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +102 -93
  46. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +138 -81
  47. package/claude/references/sd-simplysm14/apis/orm-common/types.md +49 -44
  48. package/claude/references/sd-simplysm14/apis/orm-node/README.md +42 -42
  49. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +44 -33
  50. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +11 -10
  51. package/claude/references/sd-simplysm14/apis/service-client/README.md +56 -52
  52. package/claude/references/sd-simplysm14/apis/service-client/orm.md +33 -28
  53. package/claude/references/sd-simplysm14/apis/service-client/transport.md +23 -21
  54. package/claude/references/sd-simplysm14/apis/service-common/README.md +83 -48
  55. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +126 -34
  56. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +109 -54
  57. package/claude/references/sd-simplysm14/apis/service-server/README.md +69 -81
  58. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +46 -43
  59. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +63 -37
  60. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +40 -30
  61. package/claude/references/sd-simplysm14/apis/storage/README.md +17 -17
  62. package/claude/references/sd-simplysm14/manuals/client-app-structure.md +135 -140
  63. package/claude/references/sd-simplysm14/manuals/client-orm.md +1 -1
  64. package/claude/references/sd-simplysm14/manuals/client-service.md +19 -7
  65. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +2 -2
  66. package/claude/references/sd-simplysm14/manuals/client-system-log.md +16 -4
  67. package/claude/references/sd-simplysm14/manuals/data-log.md +0 -1
  68. package/claude/references/sd-simplysm14/manuals/orm.md +16 -0
  69. package/claude/rules/sd-design-rules.md +10 -0
  70. package/claude/skills/sd-demo/SKILL.md +0 -6
  71. package/claude/skills/sd-docs/SKILL.md +60 -0
  72. package/claude/{workflows/sd-docs.rules.md → skills/sd-docs/references/subagent-prompt.md} +118 -103
  73. package/claude/skills/sd-impl/SKILL.md +7 -4
  74. package/claude/skills/sd-spec/SKILL.md +842 -15
  75. package/claude/skills/sd-spec/references/example-spec.md +26 -36
  76. package/package.json +1 -1
  77. package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +0 -53
  78. package/claude/skills/sd-spec/references/spec-authoring.md +0 -519
  79. package/claude/workflows/sd-docs.js +0 -84
@@ -1,103 +1,103 @@
1
- # @simplysm/angular — 모달·토스트·Busy·인쇄 (오버레이)
1
+ # @simplysm/angular — 모달·토스트·Busy·인쇄 (오버레이/전역 피드백)
2
2
 
3
- 화면 위에 띄우는 피드백·오버레이 묶음. 프로그래밍 방식으로 모달/토스트를 띄우거나 작업 진행 표시, 화면 인쇄/PDF 필요할 때 함께 읽힘.
3
+ 화면에서 프로그래밍 방식으로 모달을 띄우거나, 토스트로 알림·진행률을 표시하거나, busy 인디케이터·인쇄/PDF 출력을 호출할 때 함께 읽히는 군. provider 는 모두 `providedIn: "root"`, 컴포넌트는 provider 가 동적으로 body 에 attach 하므로 템플릿에 직접 둘 일은 거의 없음.
4
4
 
5
5
  ## SdModalProvider
6
6
 
7
- `@Injectable({providedIn:"root"})`. 컴포넌트를 모달로 동적 생성.
8
-
9
- - showAsync<T>(modal: SdModalInfo<T>, options?: SdModalOptions): Promise<close결과 | undefined> — 모달 표시. 컨텐츠 컴포넌트의 `close.emit(result)` 시 result 로 resolve, 배경클릭/ESC/닫기버튼 시 undefined 로 resolve. 첫 탭 가능 컨트롤에 자동 포커스.
10
- - modalCount: Signal<number> — 현재 열린 모달 수.
11
-
12
- `SdModalInfo<T, X>` = `{ title: string; type: Type<T>; inputs: ... }`. inputs 는 컨텐츠 컴포넌트의 input 중 `initialized`/`close`/`actionTplRef`/`_optionalModalInputs` 와 X 로 지정한 키를 제외한 것. `_optionalModalInputs` 로 선언한 키는 optional.
13
-
14
- `SdModalContentDef<O>` — 모달 컨텐츠 컴포넌트가 구현할 인터페이스:
15
- - initialized: Signal<boolean> — 초기화 완료 신호(인쇄/대기 동기화에 사용).
16
- - close: OutputEmitterRef<O | undefined> — 결과 emit. O 가 모달 반환 타입.
17
- - actionTplRef?: TemplateRef — 헤더 우측 액션 영역에 투영할 템플릿(있으면 모달 헤더로 브릿지됨).
18
- - _optionalModalInputs?: string — optional 로 둘 input 키들의 유니온 타입 표식(런타임 값 아님).
19
-
20
- `SdModalOptions`:
21
- - key?: string — 지정 시 크기·위치를 `SdSystemConfigProvider` 에 영속(`sd-modal.<key>`).
22
- - hideHeader?: boolean — 헤더(제목·닫기버튼) 숨김.
23
- - hideCloseButton?: boolean — 우상단 닫기 버튼만 숨김.
24
- - headerStyle?: string — 헤더 인라인 스타일.
25
- - useCloseByBackdrop?: boolean — 배경 클릭으로 닫기 허용(기본 true). 입력 확인 모달이면 false.
26
- - useCloseByEscapeKey?: boolean — ESC 로 닫기 허용(기본 true).
27
- - float?: boolean — 배경(backdrop) 없이 떠 있는 모달. 비차단 패널이면 true.
28
- - fill?: boolean — 화면 가득 채움(전체화면 모달).
29
- - resizable?: boolean — 8방향 리사이즈 핸들 표시.
30
- - movable?: boolean — 헤더 드래그로 이동.
31
- - position?: "bottom-right" | "top-right" — 고정 위치(알림형 모달).
32
- - minHeightPx/minWidthPx/heightPx/widthPx?: number — 최소·초기 크기(px).
33
- - noFirstControlFocusing?: boolean — 첫 컨트롤 자동 포커스 비활성(다이얼로그 자체에 포커스).
7
+ 컴포넌트를 모달 셸(`SdModal`) 안에 동적 생성해 body 에 띄움. `close.emit(payload)` 또는 닫기(X/배경/ESC)로 종료.
34
8
 
35
9
  ```ts
36
- const result = await sdModal.showAsync(
37
- { title: "사용자 선택", type: UserSelectModal, inputs: { teamId } },
38
- { resizable: true, key: "user-select" },
39
- );
10
+ showAsync<T extends SdModalContentDef<any>>(
11
+ modal: SdModalInfo<T>, options?: SdModalOptions,
12
+ ): Promise<Parameters<T["close"]["emit"]>[0] | undefined>
40
13
  ```
41
14
 
42
- ## SdModal
15
+ - `modal.type: Type<T>` — `SdModalContentDef<O>` 를 구현한 컴포넌트 클래스(`SdModal` 자체가 아님). `O` 가 close 페이로드 타입.
16
+ - `modal.title: string` — 모달 헤더 제목.
17
+ - `modal.inputs` — 모달 컴포넌트가 받을 input 값. `initialized`/`close`/`actionTplRef` 와 `_optionalModalInputs` 로 표시된 키는 제외/optional 처리됨. 없으면 `{}`.
18
+ - 반환값 — 컴포넌트가 `close.emit` 한 페이로드. 닫기/취소로 닫히면 `undefined`. 매뉴얼 패턴: `const r = await this._sdModal.showAsync({...}); if (!r) return;`.
19
+ - `modalCount: WritableSignal<number>` — 현재 열린 모달 수.
43
20
 
44
- `<sd-modal>` `SdModalProvider` 가 내부적으로 생성하는 셸 컴포넌트. 직접 템플릿에 쓰기보다 provider 경유 권장. 위 `SdModalOptions` 와 동일한 input 들 + `open = model(false)`, `closeRequest = output<void>()`, `title`/`actionTplRef` input 보유.
21
+ ### SdModalContentDef<O> (모달 컴포넌트가 구현)
45
22
 
46
- ## SdActivatedModalProvider
23
+ - `initialized: Signal<boolean>` — 초기화 완료 여부. busy 표시 해제 기준.
24
+ - `close: OutputEmitterRef<O | undefined>` — 결과 emit. `O` 가 `showAsync` 반환 타입.
25
+ - `actionTplRef?: TemplateRef<any>` — 헤더 우측에 끼울 액션 영역 템플릿(있으면 모달 헤더로 브릿지됨).
26
+ - `_optionalModalInputs?: string` — (타입 전용 마커) 이 컴포넌트의 input 중 optional 로 둘 키 이름 리터럴. 런타임 값 아님.
47
27
 
48
- 모달 컨텐츠 컴포넌트 내부에서 inject. 현재 모달 제어.
49
- - modalComponent: Signal<SdModal | undefined> / contentComponent: Signal<T | undefined>.
50
- - canDeactivateFn: () => boolean — 닫기 차단 판정. `setupCanDeactivate` 가 설정. true 가 아니면 배경/ESC/버튼 닫기 무시.
28
+ ### SdModalOptions (showAsync 2번째 인자)
51
29
 
52
- ## SdPromptModal / SdConfirmModal
30
+ - `key?: string` — 설정 저장 키. 지정 시 사용자가 조정한 width/height/위치를 `SdSystemConfigProvider` 에 영속·복원.
31
+ - `hideHeader?: boolean` — true 면 제목/닫기 헤더 숨김. 헤더 없는 풀커스텀 모달용.
32
+ - `hideCloseButton?: boolean` — true 면 헤더 X 버튼만 숨김.
33
+ - `headerStyle?: string` — 헤더 영역 인라인 스타일.
34
+ - `useCloseByBackdrop?: boolean` — 배경 클릭으로 닫기 허용(기본 동작상 컴포넌트 기본 true). false 면 배경 클릭 무시.
35
+ - `useCloseByEscapeKey?: boolean` — ESC 로 닫기 허용. false 면 ESC 무시.
36
+ - `float?: boolean` — true 면 배경(backdrop) 없는 떠있는 패널. 비모달 보조 패널용.
37
+ - `fill?: boolean` — true 면 화면 전체를 채움(풀스크린 모달).
38
+ - `resizable?: boolean` — true 면 8방향 리사이즈 핸들 표시.
39
+ - `movable?: boolean` — true 면 헤더 드래그로 이동.
40
+ - `position?: "bottom-right" | "top-right"` — 고정 위치. 토스트성 알림 모달에 사용.
41
+ - `minHeightPx?/minWidthPx?/heightPx?/widthPx?: number` — 최소/초기 크기.
42
+ - `noFirstControlFocusing?: boolean` — true 면 첫 입력 요소 자동 포커스를 끔(다이얼로그 자체에 포커스).
53
43
 
54
- provider 바로 넘길 수 있는 범용 모달 컴포넌트.
55
- - SdPromptModal: `SdModalContentDef<string>`. `message = input.required<string>()`. 텍스트 입력(필수) 후 확인 시 입력값, 취소 시 undefined emit.
56
- - SdConfirmModal: `SdModalContentDef<boolean>`. `message = input.required<string>()`. 확인 시 `true`, 취소 시 undefined emit.
44
+ ### 내장 모달 컴포넌트
57
45
 
58
- ```ts
59
- const name = await sdModal.showAsync({ title: "이름", type: SdPromptModal, inputs: { message: "이름?" } });
60
- ```
46
+ - `SdPromptModal` (`SdModalContentDef<string>`) — 메시지 + 텍스트 입력 후 확인/취소. `message: input.required<string>` (innerHTML). 확인 시 입력값, 취소/닫기 시 `undefined`. 입력은 required 라 빈 값이면 네이티브 검증으로 차단.
47
+ - `SdConfirmModal` (`SdModalContentDef<boolean>`) 메시지 + 확인/취소. `message: input.required<string>`. 확인 `true`, 취소/닫기 `undefined`.
48
+ - 사용: `const ok = await this._sdModal.showAsync({ type: SdConfirmModal, title: "확인", inputs: { message: "삭제할까요?" } }); if (!ok) return;`.
61
49
 
62
- ## SdToastProvider
50
+ ### SdActivatedModalProvider
63
51
 
64
- `@Injectable({providedIn:"root"})`. 토스트 알림.
52
+ 모달 컴포넌트 내부에서 inject. 자기 모달의 셸/콘텐츠 참조와 이탈 가드를 보유.
65
53
 
66
- - info/success/warning/danger(message: string, useProgress?: boolean) 심각도별 토스트. useProgress=true 면 진행률 토스트가 되어 `WritableSignal<number>`(0~100) 반환, 100 도달 1초 뒤 자동 해제. useProgress 없거나 false 면 3초 후 자동 해제(호버 중 지연), 반환 void.
67
- - notify<T>(input: SdToastInput<T>): Promise<close결과 | undefined> 커스텀 컴포넌트 토스트. `SdToastInput<T>` = `{ type: Type<T>; inputs }`(컨텐츠는 `SdToastContentDef<O>` = `{ close: OutputEmitterRef<O|undefined> }` 구현). 5초 후 자동 해제.
68
- - try<R>(fn, messageFn?): Promise<R | undefined> — fn 실행 중 Error 발생 시 danger 토스트 + 시스템로그 기록 후 undefined 반환(Error 아닌 throw 는 재throw). 화면 액션 핸들러를 감싸는 표준 패턴.
69
- - alertThemes: WritableSignal<SdToastSeverity[]> — 이 심각도들은 토스트 대신 `window.alert`.
70
- - overlap: WritableSignal<boolean> — true 면 새 토스트가 기존 토스트 모두 제거 후 표시(겹침 방지).
71
- - beforeShowFn?: (theme) => void — 토스트 표시 직전 콜백(예: 사운드).
54
+ - `modalComponent: Signal<SdModal | undefined>` / `contentComponent: Signal<T | undefined>` 셸/콘텐츠 컴포넌트 참조.
55
+ - `canDeactivateFn: () => boolean`닫기 시도 false 닫힘 차단. `setupCanDeactivate`(routing-appstructure.md) 모달 컨텍스트에서 이걸 설정.
72
56
 
73
- 타입: `SdToastSeverity = "info"|"success"|"warning"|"danger"`, `SdToastTheme = "primary"|"secondary"|SdToastSeverity|"gray"|"blue-gray"`.
57
+ ### SdModal (모달 셸)
74
58
 
75
- ```ts
76
- await sdToast.try(async () => { await save(); sdToast.success("저장됨"); });
77
- ```
59
+ `SdModalProvider` 가 내부적으로 생성하는 셸 컴포넌트. 상속·직접 배치 대상 아님. `<ng-content>` 로 콘텐츠를 투영하고 위 `SdModalOptions` 와 동일한 input(`title`/`open`/`resizable`/... `closeRequest` output) 을 보유.
60
+
61
+ ## SdToastProvider
62
+
63
+ 화면 우상단(또는 overlap 모드)에 토스트를 띄움. 알림·비동기 에러 가드·진행률에 사용.
64
+
65
+ - `info/success/warning/danger(message: string): void` — 심각도별 토스트 1개 표시(3초 후 자동 해제, hover 중이면 지연). `info`/`success` 는 `aria-live=polite`, `warning`/`danger` 는 `assertive`. 심각도 의미는 `sd-design-rules` 의 분류를 따름(error=문제 발생).
66
+ - `info/...(message: string, useProgress: true): WritableSignal<number>` — progress 모드. 반환된 signal 에 0~100 을 set 하면 진행바 갱신, 100 도달 1초 후 자동 해제. 업로드/다운로드 진행률 표시에 사용.
67
+ - `try<R>(fn: () => Promise<R> | R, messageFn?: (err: Error) => string): Promise<R | undefined>` — `fn` 실행 중 `Error` 가 throw 되면 잡아서 `danger` 토스트 + 시스템로그 `error` 적재 후 `undefined` 반환(에러를 외부로 전파하지 않음). `Error` 가 아닌 throw 는 그대로 재전파. 매뉴얼의 비동기 작업 표준 가드: `await this._sdToast.try(async () => { ... })`.
68
+ - `notify<T extends SdToastContentDef<any>>(input: SdToastInput<T>): Promise<...>` — 커스텀 컴포넌트를 토스트로 띄우고 `close` 페이로드를 반환(5초 후 자동 `undefined`).
69
+ - `alertThemes: WritableSignal<SdToastSeverity[]>` — 여기 든 심각도는 토스트 대신 `window.alert` 로 표시(키오스크 등 강제 확인 필요 화면).
70
+ - `overlap: WritableSignal<boolean>` — true 면 새 토스트가 기존 토스트를 제거하고 단독 표시.
71
+ - `beforeShowFn?: (theme: SdToastSeverity) => void` — 토스트 표시 직전 콜백(사운드 등).
78
72
 
79
- ## SdToast / SdToastContainer
73
+ 타입:
80
74
 
81
- `SdToastProvider` 가 내부 생성. 직접 쓸 일 드묾. SdToast: `open`/`progress`/`message` model, `useProgress`/`theme` input, severity 에 따라 `role`/`aria-live` 자동(info·success=status/polite, warning·danger=alert/assertive). SdToastContainer: `overlap` input.
75
+ - `SdToastSeverity = "info"|"success"|"warning"|"danger"`.
76
+ - `SdToastTheme = "primary"|"secondary"|SdToastSeverity|"gray"|"blue-gray"` — `sd-toast` 컴포넌트 `theme` 입력 범위.
77
+ - `SdToastContentDef<O> = { close: OutputEmitterRef<O | undefined> }` — `notify` 커스텀 컴포넌트 규약.
78
+ - `SdToastInput<T> = { type: Type<T>; inputs: Omit<DirectiveInputSignals<T>, "close"> }`.
82
79
 
83
- ## SdBusyProvider
80
+ `SdToast`/`SdToastContainer` 는 provider 가 동적 생성하는 표시 컴포넌트(직접 배치 불필요).
84
81
 
85
- `@Injectable({providedIn:"root"})`. 전역 로딩 표시.
86
- - globalBusyCount: WritableSignal<number> — >0 이면 전역 busy 오버레이 표시(라우팅·인쇄가 자동 증감). 직접 작업 감쌀 때 update 로 증감.
87
- - type: WritableSignal<SdBusyType> — 기본 표시 유형. `SdBusyType = "spinner"|"bar"|"cube"`.
82
+ ## SdBusyProvider / SdBusyContainer
88
83
 
89
- ## SdBusyContainer
84
+ 영역 단위 busy 오버레이.
90
85
 
91
- `<sd-busy-container [busy]="..." />` 영역 단위 busy 오버레이. 내부 컨텐츠 위에 표시.
92
- - busy: booleantrue 오버레이 표시 + 영역 키입력 차단.
93
- - message?: string표시 메시지.
94
- - type?: "spinner"|"bar"|"cube"미지정 `SdBusyProvider.type` 사용.
95
- - progressPercent?: number지정 상단 진행 바 표시(0~100).
86
+ - `SdBusyProvider.type: WritableSignal<SdBusyType>` — 전역 기본 인디케이터 종류. `SdBusyType = "spinner"|"bar"|"cube"`. `"bar"` = 상단 가는 진행바, `"spinner"` = 회전 원, `"cube"` = 큐브 애니메이션.
87
+ - `SdBusyProvider.globalBusyCount: WritableSignal<number>`전역 busy 카운트(>0 이면 화면 전체 busy). 라우팅·인쇄가 ±1. 직접 ±1 해 전역 차단 가능.
88
+ - `SdBusyContainer` (`sd-busy-container`)자식 영역에 busy 오버레이를 씌우는 컨테이너 컴포넌트.
89
+ - `busy: boolean`true 오버레이 표시 + 영역 내 키입력 차단.
90
+ - `message: string`인디케이터 옆/아래 표시 메시지.
91
+ - `type: SdBusyType` — 이 영역만의 인디케이터 종류(미지정 시 provider 기본값).
92
+ - `progressPercent: number` — 지정 시 상단 진행바(0~100). 결정형 작업 진행률에 사용.
93
+ - 사용: `<sd-busy-container [busy]="busyCount() > 0">...</sd-busy-container>` (단, `sd-base-container` 가 이미 내장).
96
94
 
97
95
  ## SdPrintProvider
98
96
 
99
- `@Injectable({providedIn:"root"})`. 컴포넌트를 인쇄/PDF 렌더.
100
- - printAsync<T>(template: SdPrintInput<T>, options?: { size?: string; margin?: string }): Promise<void> — 컴포넌트를 body 에 임시 렌더 후 `window.print()`. size 기본 `"A4 auto"`, margin 기본 `"0"`. 인쇄 동안 글로벌 busy.
101
- - getPdfBufferAsync<T>(template, options?: { orientation?: "portrait"|"landscape"; pageSize?: string }): Promise<Uint8Array> — `.page` 엘리먼트별로 캔버스 변환(jsPDF) 후 PDF 버퍼 반환. pageSize 기본 `"a4"`, orientation 기본 portrait.
97
+ 인쇄 템플릿 컴포넌트를 동적 생성해 `window.print()` 하거나 PDF 버퍼로 변환. 호출 동안 `globalBusyCount` ±1.
102
98
 
103
- `SdPrintInput<T, X>` = `{ type: Type<T>; inputs }`. 컨텐츠는 `SdPrint`(= `{ initialized: Signal<boolean>; _optionalPrintInputs?: string }`) 구현. `initialized()` true 되고 이미지 로드 완료까지 대기 후 인쇄.
99
+ - `printAsync<T extends SdPrint>(template: SdPrintInput<T>, options?: { size?: string; margin?: string }): Promise<void>` 템플릿을 숨겨 붙인 뒤 `@media print` CSS 로 그것만 출력. `size` 기본 `"A4 auto"`, `margin` 기본 `"0"`. 이미지 로드 완료를 기다린 후 인쇄.
100
+ - `getPdfBufferAsync<T extends SdPrint>(template: SdPrintInput<T>, options?: { orientation?: "portrait"|"landscape"; pageSize?: string }): Promise<Uint8Array>` — `.page` 요소들(없으면 루트)을 캔버스로 렌더해 jsPDF 로 PDF 바이트 생성. `orientation` 기본 portrait, `pageSize` 기본 `"a4"`. 첨부/저장용 PDF 가 필요할 때.
101
+ - `SdPrint = { initialized: Signal<boolean>; _optionalPrintInputs?: string }` — 인쇄 템플릿 컴포넌트 규약. `initialized` 가 true 가 되어야 인쇄 진행(데이터 로드 대기).
102
+ - `SdPrintInput<T> = { type: Type<T>; inputs: ... }` — 인쇄 컴포넌트 + input 값(`_optionalPrintInputs` 표시 키는 optional).
103
+ - 사용: `await this._sdPrint.printAsync({ type: OutboundPrintTemplate, inputs: { id } })`. 인쇄 템플릿 파일은 `<domain>.print-template.ts`(client-component.md).
@@ -1,69 +1,79 @@
1
1
  # @simplysm/angular — 라우팅 / 앱 구조(메뉴·권한)
2
2
 
3
- 현재 페이지 식별·뷰 제목/타입 판정, 라우터 링크, 메뉴/권한 트리 구성을 다룰 함께 읽힘. 구조 데이터 원본은 `@simplysm/service-common` `AppStructureItem`.
3
+ 라우터 링크·현재 페이지 식별·뷰 컨텍스트(page/control/modal)·이탈 가드, 그리고구조 트리에서 메뉴·권한을 파생하는 군. 화면 컴포넌트의 표준 시그널 `viewType`, 권한 가드 `injectPermsSignal`, 사이드바/탑바 메뉴가 이 군에 의존.
4
4
 
5
- ## injectFullPageCodeSignal
5
+ ## injectViewTypeSignal / SdViewType
6
6
 
7
- `injectFullPageCodeSignal(): Signal<string>` — 현재 라우터 URL 에서 앞 2 세그먼트(`/home` 등)를 제외한 나머지를 `.` 으로 이은 페이지 코드. 네비게이션 종료마다 갱신. 메뉴 선택 표시·뷰 제목 조회에 사용.
8
-
9
- ## injectCurrentPageCodeSignal
7
+ ```ts
8
+ function injectViewTypeSignal(): Signal<SdViewType>
9
+ type SdViewType = "page" | "modal" | "control"
10
+ ```
10
11
 
11
- `injectCurrentPageCodeSignal(): Signal<string> | undefined` 현재 `ActivatedRoute` pathFromRoot 기준 코드(중첩 outlet/모달 안에서 자기 자신 경로). ActivatedRoute 없으면 undefined. fullPageCode 비교해 page/control 판정에 사용.
12
+ 현재 컴포넌트가 어느 컨텍스트에서 동작 중인지 판정하는 signal. `"page"` = 라우팅 진입 화면, `"modal"` = 모달로 열림, `"control"` = 다른 화면 안에 임베드된 자식. 판정 기준: 모달 컨텍스트면 modal, 라우트 컴포넌트의 selector 현재 엘리먼트 태그와 일치하고 full/current 페이지 코드가 같으면 page, 그 외 control. 매뉴얼 표준 시그널 `viewType = injectViewTypeSignal()` 로 받아 `sd-base-container [viewType]` 에 전달.
12
13
 
13
14
  ## injectViewTitleSignal
14
15
 
15
- `injectViewTitleSignal(): Signal<string>` — 모달 안이면 모달 제목, 아니면 앱 구조에서 현재 페이지 코드로 찾은 제목(`[상위 > 상위] 현재`). 못 찾으면 `""`. 화면 컨테이너 제목 표시에 사용.
16
+ - `function injectViewTitleSignal(): Signal<string>` — 현재 뷰의 표시 제목. 모달이면 모달 컴포넌트 `title`, 아니면 앱 구조에서 현재 페이지 코드로 찾은 제목(`[상위 > 경로] 현재`). 못 찾으면 `""`. `sd-base-container` page 탑바 제목에 사용.
16
17
 
17
- ## injectViewTypeSignal
18
+ ## injectFullPageCodeSignal / injectCurrentPageCodeSignal
18
19
 
19
- `injectViewTypeSignal(): Signal<SdViewType>` — 현재 컴포넌트가 어떤 맥락으로 떠 있는지 판정. `SdViewType = "page"|"modal"|"control"`. 모달 안이면 "page"가 아닌 "modal", 라우트의 페이지 컴포넌트로 직접 있고 fullPageCode===currPageCode "page", "control"(다른 화면에 박힌 재사용 컴포넌트). CRUD 컨테이너의 `viewType` input 그대로 전달.
20
+ - `function injectFullPageCodeSignal(): Signal<string>` — 라우터 URL 전체에서 파생한 페이지 코드(`/` `.` 합침, 2세그먼트 제외, `;`/`?` 이후 제거). 메뉴 선택 상태·뷰 타입 판정에 사용.
21
+ - `function injectCurrentPageCodeSignal(): Signal<string> | undefined` — 현재 `ActivatedRoute` 기준 상대 페이지 코드. 라우트 컨텍스트 없으면 `undefined`. 중첩 라우트에서 자기 위치 코드가 필요할 때.
20
22
 
21
23
  ## setupCanDeactivate
22
24
 
23
- `setupCanDeactivate(fn: () => boolean): void` — 컴포넌트 호출. 모달 안이면 `SdActivatedModalProvider.canDeactivateFn` 에, 라우트면 해당 route 의 `canDeactivate` 가드에 fn 을 등록(컴포넌트 파괴 시 해제). fn false 이탈/닫기 차단. 미저장 변경 보호에 사용.
25
+ - `function setupCanDeactivate(fn: () => boolean): void` — 라우터 이탈/모달 닫기 시점에 `fn()` 이 false 면 이탈/닫기를 차단. 모달 컨텍스트면 `SdActivatedModalProvider.canDeactivateFn` 에, 라우트 컨텍스트면 해당 route 의 `canDeactivate` 등록(파괴 시 자동 해제). detail 화면의 변경 가드 표준: `setupCanDeactivate(() => this._checkIgnoreChanges())`(client-component.md).
24
26
 
25
- ```ts
26
- setupCanDeactivate(() => !this.dirty() || confirm("저장하지 않고 나갈까요?"));
27
- ```
27
+ ## SdRouterLink (`[sdRouterLink]`)
28
28
 
29
- ## SdRouterLink
29
+ 라우터 이동을 호스트 클릭에 붙이는 디렉티브. Ctrl/Shift 클릭·window 모드면 새 창/탭으로 분기.
30
30
 
31
- `[sdRouterLink]="option"` 디렉티브. 클릭 option 으로 라우터 이동(또는 새 창).
32
- - option: `{ link: string; params?; window?: {width?;height?}; outletName?: string; queryParams? } | undefined` — undefined 동작 없음(커서도 기본). 현재가 window 모드거나 Ctrl/Shift+클릭이면 새 창으로, outletName 있으면 named outlet 으로 이동. Alt+클릭은 무시.
31
+ - `sdRouterLink: { link: string; params?: Record<string,string>; window?: { width?; height? }; outletName?: string; queryParams?: Record<string,string> } | undefined` — 이동 옵션.
32
+ - `link` — 라우트 경로. `outletName` 있으면 named outlet 이동.
33
+ - `params` — 매트릭스 파라미터, `queryParams` — 쿼리 파라미터.
34
+ - `window` — Ctrl/Shift 클릭 또는 window 컨텍스트일 때 팝업 창 크기(기본 800x800).
35
+ - 미지정(`undefined`) 이면 클릭 무시 + 커서 기본. 메뉴 항목이 leaf 가 아닐 때 등.
36
+ - Alt+클릭은 무시. 사이드바/탑바 메뉴가 `getMenuRouterLinkOption(menu)` 결과를 이 입력에 바인딩.
33
37
 
34
38
  ## SdNavigateWindowProvider
35
39
 
36
- `@Injectable({providedIn:"root"})`. 창/팝업 네비게이션.
37
- - isWindow: boolean현재 URL 해시에 `window=true` 가 있는지(팝업으로 열린 창인지).
38
- - open(navigate: string, params?, features?): void — window 모드거나 features 지정 시 `window.open` 팝업(닫힐 때 부모와 함께 정리), 아니면 `_blank` 탭.
40
+ - `isWindow: boolean` 현재 문서가 `window=true` 로 열린 팝업인지(해시 파라미터 검사).
41
+ - `open(navigate: string, params?: Record<string,string>, features?: string): void` 창/탭으로 라우트 열기. window 컨텍스트이거나 `features` 가 있으면 `window=true` 팝업으로(부모 unload 시 자동 close), 아니면 `_blank` 탭으로. `SdRouterLink` 내부 + 화면에서 보조 창을 띄울 때.
39
42
 
40
- ## getMenuRouterLinkOption / getIsMenuSelected
43
+ ## SdAppStructureProvider<TModule> / injectPermsSignal
44
+
45
+ 앱 구조 트리(`AppStructureItem[]`, `@simplysm/service-common`)에서 메뉴·권한을 파생하는 root provider. 화면은 보통 `injectPermsSignal` 만 직접 씀.
46
+
47
+ ```ts
48
+ function injectPermsSignal<K extends string>(viewCodes: string[], keys: K[]): Signal<K[]>
49
+ ```
41
50
 
42
- - `getMenuRouterLinkOption(menu: SdMenu): { link; queryParams } | undefined` — leaf 메뉴(children/url 없음)면 `/home/<코드체인>` 링크 + 쿼리파라미터 옵션, undefined. `SdRouterLink` 바로 전달.
43
- - `getIsMenuSelected(menu, fullPageCode, customFn?): boolean` — customFn 있으면 그 결과, 없으면 `fullPageCode === menu.codeChain.join(".")`. 메뉴 선택 강조에 사용.
51
+ - `viewCodes` 권한 path 목록(도메인 트리 좌표). `keys`확인할 action 목록. 반환 signal 사용자가 보유한 action 들의 배열. 매뉴얼 패턴: `perms = injectPermsSignal(["inventory.outbound"], ["use","edit"])``this.perms().includes("use")` 인라인 가드.
52
+ - provider 멤버:
53
+ - `usableModules: WritableSignal<TModule[] | undefined>` / `permRecord: WritableSignal<Record<string, boolean> | undefined>` — 인증 후 주입하는 활성 모듈·권한 레코드. 메뉴/권한 계산의 입력.
54
+ - `items: WritableSignal<AppStructureItem<TModule>[]>` / `initialize(items): void` — 앱 구조 트리 세팅.
55
+ - `usableMenus: Signal<SdMenu[]>` — 권한·모듈 필터를 적용한 트리형 메뉴(사이드바용).
56
+ - `usableFlatMenus: Signal<SdFlatMenu<TModule>[]>` — 평탄화된 메뉴 목록(검색/전체메뉴용).
57
+ - `getPermsByFullCode<K>(fullCodes, permKeys): K[]` — 코드 목록에 대해 보유 action 계산(`injectPermsSignal` 내부). 권한 정의 자체가 없는 항목은 모든 action 허용으로 간주.
58
+ - `getPermissionsByStructure(items, codeChain?)` — 권한표(`sd-permission-table`)용 `SdPermission` 트리 생성.
59
+ - `findTitleByFullCode(fullCode): string | undefined` / `getTitleByFullCode(fullCode): string`(못 찾으면 throw) / `getItemChainByFullCode(fullCode)` — 코드로 제목·항목 체인 조회.
44
60
 
45
- ## SdAppStructureProvider<TModule>
61
+ ## SdAppStructureUtils (static 유틸)
46
62
 
47
- `@Injectable({providedIn:"root"})`. 메뉴·권한 트리의 단일 소스.
48
- - usableModules: Signal<TModule[]|undefined> — 활성 모듈 목록. 메뉴/권한 필터에 사용(미설정 시 전체 허용 취급).
49
- - permRecord: Signal<Record<string,boolean>|undefined> — `<코드>.<권한키>` → 허용 여부 맵.
50
- - items: Signal<AppStructureItem<TModule>[]> — 구조 원본.
51
- - initialize(items) — 구조 주입.
52
- - usableMenus: Signal<SdMenu[]> — 모듈·권한 통과한 메뉴 트리(사이드바/탑바용).
53
- - usableFlatMenus: Signal<SdFlatMenu<TModule>[]> — 평탄화된 메뉴 목록.
54
- - getPermissionsByStructure(items, codeChain?) — 권한 편집표용 `SdPermission` 트리 생성.
55
- - getTitleByFullCode(fullCode) / findTitleByFullCode(fullCode) — 전자는 못 찾으면 throw, 후자는 undefined(결측 보존).
56
- - getItemChainByFullCode(fullCode) — 코드 체인에 해당하는 항목 배열(없으면 빈 배열).
57
- - getPermsByFullCode(fullCodes, permKeys) — 해당 코드들에서 보유한 권한 키 목록.
63
+ `SdAppStructureProvider` 가 내부적으로 쓰는 순수 함수 모음(abstract 클래스의 static). 트리 메뉴/권한/제목 변환을 provider 밖에서 직접 할 때만 사용.
58
64
 
59
- `injectPermsSignal<K>(viewCodes: string[], keys: K[]): Signal<K[]>`현재 보유 권한 키를 computed signal 로. 화면에서 편집/사용 권한 분기에 사용.
65
+ - `getMenus(items, codeChain, usableModules, permRecord): SdMenu[]`모듈·`use` 권한 필터 적용 트리 메뉴. `isNotMenu` 항목·빈 그룹 제외.
66
+ - `getFlatMenus(items, usableModules, permRecord): SdFlatMenu[]` — BFS 평탄화 메뉴.
67
+ - `getPermissions(items, codeChain, usableModules): SdPermission[]` — 권한표용 트리(leaf 의 `perms`/`subPerms` 포함).
68
+ - `getFlatPermissions(items, usableModules)` / `findTitleByFullCode` / `getTitleByFullCode` / `getItemChainByFullCode` / `getPermsByFullCode` — provider 동명 메서드의 구현.
60
69
 
61
- ## SdAppStructureUtils
70
+ ## menu-utils
62
71
 
63
- `abstract class`. `SdAppStructureProvider` 내부 사용하는 정적 유틸 모음(직접 호출 가능): `getMenus`/`getFlatMenus`/`getPermissions`/`getFlatPermissions`/`getTitleByFullCode`/`findTitleByFullCode`/`getItemChainByFullCode`/`getPermsByFullCode`. 시그니처는 provider 메서드와 대응(추가로 items/usableModules/permRecord 인자로 받음).
72
+ - `getMenuRouterLinkOption(menu: SdMenu): { link: string; queryParams: Record<string,string> | undefined } | undefined` — 메뉴를 `sdRouterLink` 옵션으로 변환. children 있거나 `url`(외부 링크) 이면 `undefined`(이동 불가 = leaf 아님). `codeChain` 으로 `/home/<코드/...>` 링크 + 쿼리 분리.
73
+ - `getIsMenuSelected(menu: SdMenu, fullPageCode: string | undefined, customFn?: (menu) => boolean): boolean` — 메뉴 선택 여부. `customFn` 있으면 위임, 없으면 `fullPageCode === menu.codeChain.join(".")`.
64
74
 
65
75
  ## 타입
66
76
 
67
- - **SdMenu** `{ title; codeChain: string[]; url?; icon?; children?: SdMenu[] }`. 메뉴 트리 노드.
68
- - **SdFlatMenu<TModule>** `{ titleChain: string[]; codeChain: string[]; modulesChain: TModule[][] }`. 평탄 메뉴.
69
- - **SdPermission<TModule>** `{ title; codeChain; modules; perms: ("use"|"edit")[]|undefined; children }`. 권한 트리 노드(권한표 입력).
77
+ - `SdMenu = { title; codeChain: string[]; url?; icon?; children?: SdMenu[] }` 트리 메뉴 항목. `url` 있으면 외부 링크, `children` 있으면 그룹.
78
+ - `SdFlatMenu<TModule> = { titleChain: string[]; codeChain: string[]; modulesChain: TModule[][] }` 평탄 메뉴(경로 누적).
79
+ - `SdPermission<TModule> = { title; codeChain; modules; perms: ("use"|"edit")[] | undefined; children }` 권한표 노드. `perms` 가 undefined 면 그룹(권한 없음). `sd-permission-table` 의 `items` 입력 타입.
@@ -1,28 +1,68 @@
1
- # @simplysm/angular — 선택·정렬·확장 매니저(use* 컴포저블)
1
+ # @simplysm/angular — selection/sorting/expanding 매니저 (use* 컴포저블)
2
2
 
3
- 커스텀 리스트/그리드 컴포넌트에 선택·정렬·트리 확장 로직을 붙이는 함수형 컴포저블. signal 바인딩을 넘기면 파생 signal 조작 메서드를 돌려줌. `sd-sheet` 가 내부적으로 이들을 조합함.
3
+ 커스텀 목록 컴포넌트에서 선택·정렬·트리펼침 상태 로직을 signal 기반으로 합성하는 함수 컴포저블 군. `sd-sheet` 가 이들을 조합해 만들어졌고, 직접 그리드/리스트를 만들 때 같은 로직을 재사용. 모두 함수 호출로 signal·메서드 묶음을 반환(컴포넌트 필드에 보관).
4
4
 
5
5
  ## useSelectionManager<TItem, TKey>
6
6
 
7
- 다중/단일 선택 상태 관리.
8
- - options: `{ displayItems: Signal<TItem[]>; selectedKeys: WritableSignal<TKey[]>; selectMode: Signal<"single"|"multi"|undefined>; getItemSelectableFn: Signal<((item) => boolean|string)|undefined>; trackByFn: Signal<(item,index) => TKey> }` — selectMode 미지정이면 선택 비활성, getItemSelectableFn 이 string 반환 시 그 사유로 불가, trackByFn 으로 키 비교(`obj.equal` 깊은 비교).
9
- - 반환: `hasSelectable`/`isAllSelected`(Signal), `getSelectable(item): true|string|undefined`, `getCanChangeFn(item)`, `select`/`deselect`/`toggle`/`toggleAll`/`isSelected`. single 은 1개로 교체, multi 는 누적.
7
+ 선택(single/multi)·전체선택·선택 가능 여부 로직.
8
+
9
+ ```ts
10
+ useSelectionManager<TItem, TKey>(options: {
11
+ displayItems: Signal<TItem[]>;
12
+ selectedKeys: WritableSignal<TKey[]>;
13
+ selectMode: Signal<"single" | "multi" | undefined>;
14
+ getItemSelectableFn: Signal<((item: TItem) => boolean | string) | undefined>;
15
+ trackByFn: Signal<(item: TItem, index: number) => TKey>;
16
+ }): { ... }
17
+ ```
18
+
19
+ - `options.displayItems` — 현재 표시 항목. `selectedKeys` — 선택 키(WritableSignal, 키는 `trackByFn` 반환값). `selectMode` — 모드(undefined 면 선택 비활성). `getItemSelectableFn` — 행별 선택 가능: `true`=가능, `false`=불가, 문자열=불가+사유. `trackByFn` — 항목→키.
20
+ - 반환:
21
+ - `hasSelectable: Signal<boolean>` — 선택 모드가 켜졌는지.
22
+ - `isAllSelected: Signal<boolean>` — 선택 가능한 항목이 모두 선택됐는지(전체선택 체크 상태).
23
+ - `getSelectable(item): true | string | undefined` — 항목 선택 가능 여부(문자열=사유 툴팁).
24
+ - `getCanChangeFn(item): () => boolean` — 체크박스 `canChangeFn` 에 넘길 가드.
25
+ - `select`/`deselect`/`toggle(item)` — 선택 조작(single 은 단일 키로 대체).
26
+ - `toggleAll()` — 선택 가능 항목 전체 토글.
27
+ - `isSelected(item): boolean`.
28
+ - 키 비교는 `===` 후 `obj.equal`(복합 키 지원).
10
29
 
11
30
  ## useSortingManager
12
31
 
13
- 헤더 클릭 정렬 상태 + 정렬 실행.
14
- - options: `{ sorts: WritableSignal<SortingDef[]> }`.
15
- - 반환: `defMap: Signal<Map<key, { indexText?; desc }>>`(다중 정렬 시 순번 표시), `toggle(key, multiple)`(단일/다중 토글: 없음→오름차순→내림차순→해제 순환), `sort<T>(items): T[]`(null 은 앞, 문자열 localeCompare, 숫자 차이 기준).
16
- - **SortingDef** — `{ key: string; desc: boolean }`. 정렬 1건.
32
+ 정렬 상태(다중 컬럼) 토글·적용. `sd-sheet` 의 `sorts` 와 `SortingDef` 를 공유.
33
+
34
+ ```ts
35
+ useSortingManager(options: { sorts: WritableSignal<SortingDef[]> }): {
36
+ defMap: Signal<Map<string, { indexText?: string; desc: boolean }>>;
37
+ toggle(key: string, multiple: boolean): void;
38
+ sort<T>(items: T[]): T[];
39
+ }
40
+ ```
41
+
42
+ - `SortingDef = { key: string; desc: boolean }` — 한 정렬 기준. `key`=컬럼 키, `desc`=내림차순 여부.
43
+ - `defMap` — 키별 정렬 상태(헤더 아이콘 표시용; `indexText` 는 다중 정렬 시 순번).
44
+ - `toggle(key, multiple)` — 정렬 토글. 한 키를 누를 때마다 없음→오름차순→내림차순→해제 순환. `multiple`(Shift) true 면 기존 정렬 유지하고 추가, false 면 단일 정렬로 대체.
45
+ - `sort<T>(items)` — 현재 정렬을 적용한 새 배열 반환. null 은 가장 앞, 문자열은 localeCompare, 숫자는 수치 비교. 클라이언트 정렬 시 사용.
17
46
 
18
47
  ## useExpandingManager<T>
19
48
 
20
- 트리 펼침 상태 + 가시 항목 평탄화.
21
- - binding: `{ items: Signal<T[]>; expandedItems: WritableSignal<T[]>; getChildrenFn: Signal<((item,index) => T[]|undefined)|undefined>; sort: (items) => T[] }` — getChildrenFn 으로 자식 조회, sort 로 각 레벨 정렬.
22
- - 반환: `displayItems`/`hasExpandable`/`isAllExpanded`(Signal), `toggle(item)`/`toggleAll()`, `isVisible(item)`(조상이 모두 펼쳐졌는지), `def(item): ExpandItemDef<T>`(못 찾으면 throw).
23
- - **ExpandItemDef<T>** — `{ item; parentDef: ExpandItemDef<T>|undefined; hasChildren; depth }`. 항목의 트리 위치 정의.
49
+ 트리 항목 펼침/접힘 + 표시 항목 평탄화.
24
50
 
25
51
  ```ts
26
- const sorting = useSortingManager({ sorts });
27
- const selection = useSelectionManager({ displayItems, selectedKeys, selectMode, getItemSelectableFn, trackByFn });
52
+ useExpandingManager<T>(binding: {
53
+ items: Signal<T[]>;
54
+ expandedItems: WritableSignal<T[]>;
55
+ getChildrenFn: Signal<((item: T, index: number) => T[] | undefined) | undefined>;
56
+ sort: (items: T[]) => T[];
57
+ }): { ... }
28
58
  ```
59
+
60
+ - `binding.items` — 루트 항목. `expandedItems` — 펼쳐진 항목(WritableSignal). `getChildrenFn` — 자식 조회(undefined 면 자식 없음). `sort` — 각 레벨 정렬 함수(보통 `useSortingManager.sort`).
61
+ - 반환:
62
+ - `displayItems: Signal<T[]>` — 펼침 상태를 반영해 평탄화·정렬된 표시 항목.
63
+ - `hasExpandable: Signal<boolean>` — 펼칠 수 있는 항목이 있는지(토글 컬럼 표시 기준).
64
+ - `isAllExpanded: Signal<boolean>` — 전체 펼침 상태.
65
+ - `toggle(item)` / `toggleAll()` — 펼침 토글.
66
+ - `isVisible(item): boolean` — 조상이 모두 펼쳐져 보이는지.
67
+ - `def(item): ExpandItemDef<T>` — 항목 메타(못 찾으면 throw).
68
+ - `ExpandItemDef<T> = { item: T; parentDef: ExpandItemDef<T> | undefined; hasChildren: boolean; depth: number }` — 항목의 부모·자식유무·깊이. 들여쓰기·토글 렌더에 사용.
@@ -1,57 +1,74 @@
1
- # @simplysm/angular — 공유 데이터(shared-data)
1
+ # @simplysm/angular — 공유 마스터 데이터 + 선택 컨트롤
2
2
 
3
- 서버의 마스터 데이터(코드·거래처 등)를 클라이언트 전역에서 캐시·구독하고, 데이터를 고른 select/list/button UI노출하는 묶음. 데이터 변경은 서비스 이벤트로 전파되어 자동 갱신됨.
3
+ 고객사·품목 자주 참조하는 마스터 데이터를 등록해 어느 화면에서든 공유 signal 쓰고, 데이터를 선택하는 드롭다운/버튼/리스트 컨트롤을 제공하는 군. 등록·항목 추가 절차는 `client-shared-data.md` 참조.
4
4
 
5
- ## 데이터 규약 타입
5
+ ## SdSharedDataProvider<T> (abstract)
6
6
 
7
- - **SharedDataBase<TKey>** 공유 데이터 항목의 베이스. `{ __valueKey: TKey; __searchText: string; __isHidden: boolean; __parentKey?: TKey }`. `__searchText` 검색, `__isHidden` 으로 목록 숨김, `__parentKey` 있으면 트리 구성. 도메인 타입이 이 인터페이스를 확장.
8
- - **SharedDataInfo<T>** — 등록 정보. `{ serviceKey: string; getter: (changeKeys?) => Promise<T[]>; filter?: unknown; orderBy?: (item) => 비교키 }`. getter 는 전체/부분(changeKeys) 로드, filter 는 이벤트 매칭, orderBy 는 정렬키.
9
- - **SharedDataHandle<T>** — `{ items: Signal<T[]>; get(key): T | undefined }`. 화면에서 데이터 소비 핸들.
7
+ 마스터 데이터를 이름별로 등록·로드·이벤트 동기화하는 root provider. 앱은 이걸 상속한 `AppSharedDataProvider` 만들고 `useSharedSignal` 헬퍼를 함께 export(client-shared-data.md).
10
8
 
11
- ## SdSharedDataProvider<T>
9
+ - `abstract initialize(): void` — 여기서 `register(name, info)` 로 항목 등록(앱이 구현).
10
+ - `register<K>(name: K, info: SharedDataInfo<T[K]>): void` — 항목 등록. 재호출 시 기존 리스너 정리 + generation 증가로 이전 결과 무시 후 재로드.
11
+ - `getHandle<K>(name: K): SharedDataHandle<T[K]>` — 항목 핸들 반환(첫 접근 시 lazy 로드 + 변경 이벤트 리스너 등록). 미등록 이름이면 throw. `useSharedSignal` 이 이걸 감쌈.
12
+ - `emitAsync<K>(name: K, changeKeys?: (string|number)[]): Promise<void>` — 변경 브로드캐스트. `changeKeys` 주면 해당 키만 부분 갱신, 없으면 전체 리로드(다른 클라이언트 포함).
13
+ - `wait(): Promise<void>` — 진행 중 로드가 끝날 때까지 대기. `sd-base-container` 가 ready 전에 호출.
14
+ - `loadingCount: WritableSignal<number>` — 진행 중 로드 수.
12
15
 
13
- `@Injectable()` abstract. 앱별로 상속해 `initialize()` 구현. T 는 `{ name: SharedDataBase 파생 }` 맵.
14
- - loadingCount: WritableSignal<number> — 로딩 중 카운트.
15
- - abstract initialize(): void — 앱 시작 시 각 데이터 register.
16
- - register<K>(name, info: SharedDataInfo<T[K]>) — 데이터 등록(재호출 시 generation 증가로 이전 이벤트 무시·재로드).
17
- - getHandle<K>(name): SharedDataHandle<T[K]> — 핸들 획득(첫 호출 시 lazy 로드 + 이벤트 리스너 등록). 미등록이면 throw.
18
- - emitAsync<K>(name, changeKeys?) — 변경 이벤트 발행(같은 name·filter 구독자 갱신). changeKeys 지정 시 부분 갱신.
19
- - wait(): Promise<void> — loadingCount 가 0 이 될 때까지 대기(화면 진입 전 데이터 준비).
16
+ ### 타입
20
17
 
21
- **SdSharedDataChangeEvent**`defineEvent` 정의된 서비스 이벤트(`{ name; filter }`, 페이로드 `(string|number)[] | undefined`). provider 내부 사용.
18
+ - `SharedDataBase<TKey extends string|number>` 모든 공유 항목이 상속할 베이스. 매직 필드: `__valueKey: TKey`(항목 키), `__searchText: string`(검색용 텍스트), `__isHidden: boolean`(숨김), `__parentKey?: TKey`(트리 부모). getter select 결과에 빠짐없이 포함.
19
+ - `SharedDataInfo<T>` — 등록 정보. `serviceKey: string`(이벤트 채널), `getter: (changeKeys?) => Promise<T[]>`(조회; changeKeys 주면 부분), `filter?: unknown`(이벤트 필터 매칭), `orderBy?: (item) => string|number|DateOnly|DateTime|Time|undefined`(정렬 키).
20
+ - `SharedDataHandle<T>` — `{ items: Signal<T[]>; get(key): T | undefined }`. 화면이 `useSharedSignal(name)` 으로 받아 `.items()`·`.get(id)` 사용.
21
+ - `SdSharedDataChangeEvent` — 변경 동기화에 쓰이는 `defineEvent`. payload `{ name; filter }`, data `(string|number)[] | undefined`.
22
22
 
23
- ## SdSharedDataSelect<TItem, TMode, TModal>
23
+ 사용(화면): `sharedCustomers = useSharedSignal("고객사"); sharedCustomers.items(); sharedCustomers.get(id)`.
24
24
 
25
- `<sd-shared-data-select [items]="...">` — 공유 데이터 드롭다운 선택(검색·트리·모달 연동).
26
- - value = model<...>() — 선택값(single=키, multi=키 배열). 키는 `TItem["__valueKey"] | undefined`.
27
- - items: input.required<TItem[]> — 후보(보통 handle.items()).
28
- - selectMode: "single"|"multi"(기본 single).
29
- - disabled/required/inset/inline/size — 컨트롤 공통.
30
- - useUndefined: boolean — multi 에서도 "미지정" 항목 노출.
31
- - filterFn?/filterFnParams? — 후보 필터(item,index,...params).
32
- - modal?: SdSelectModalInfo — 우측 검색 버튼으로 띄울 선택 모달.
33
- - editModal?: SdModalInfo<SdModalContentDef<boolean>> — 편집 버튼으로 띄울 모달.
34
- - selectClass?/multiSelectionDisplayDirection?("vertical").
35
- - getIsHiddenFn?(item,index)/getSearchTextFn?(item,index)/displayOrderByFn?(item) — 기본은 `__isHidden`/`__searchText`/없음. 검색·표시·정렬 재정의.
36
- - (contentChild) `itemOf` 템플릿 — 항목 렌더. `undefinedTpl` 로 "미지정" 표시 커스텀.
37
- - `__parentKey` 있으면 자동 트리(자식은 부모 펼침 시 노출).
25
+ ## 선택 컨트롤
38
26
 
39
- ## SdSharedDataSelectButton<TItem, TMode, TModal>
27
+ 공유 데이터(또는 `SharedDataBase` 호환 배열)를 항목으로 받아 선택. 매직 필드(`__searchText`/`__isHidden`/`__parentKey`)를 자동 활용(검색·숨김·트리).
40
28
 
41
- `<sd-shared-data-select-button [modal]="...">` — 모달 선택 버튼 + 선택 항목 인라인 표시(`SdModalSelectButton` 래퍼).
42
- - value = model<...>(), items: TItem[](선택값→표시용 매핑), modal: input.required<SdSelectModalInfo>, selectMode(기본 single), disabled/required/inset/size.
43
- - (contentChild) `itemOf` 템플릿 필수 — 선택된 항목 표시.
29
+ ### SdSharedDataSelect (`sd-shared-data-select`)
44
30
 
45
- ## SdSharedDataSelectList<TItem, TModal>
31
+ 드롭다운 셀렉트(검색창·트리·미지정 항목·모달 연동 내장).
46
32
 
47
- `<sd-shared-data-select-list [items]="...">`단일 선택 리스트(검색·페이징·모달 연동).
48
- - selectedItem = model<TItem>(), canChangeFn?(item) 선택 변경 가드.
49
- - items: input.required<TItem[]>, selectedIcon?, useUndefined(미지정 항목), filterFn?(item,index).
50
- - modal?우상단 외부창 버튼으로 띄울 선택 모달.
51
- - header?: string상단 헤더 텍스트.
52
- - pageItemCount?: number >0 이면 페이지당 항목 수로 페이징.
53
- - (contentChild) `itemOf`/`headerTpl`/`filterTpl`/`undefinedTpl` 템플릿.
33
+ - `value: model<...>` — 선택 (single) 또는 키 배열(multi). 미지정은 `undefined`.
34
+ - `items: input.required<TItem[]>`공유 항목 배열(`SharedDataBase` 상속).
35
+ - `selectMode: "single"|"multi"` 선택 모드(기본 single).
36
+ - `required: boolean` 값이면 invalid.
37
+ - `useUndefined: boolean`multi 에서도 "미지정" 항목 노출(single 은 required 아니면 자동 노출).
38
+ - `filterFn: (item, index, ...params) => boolean` + `filterFnParams: any[]` — 표시 항목 필터.
39
+ - `getIsHiddenFn: (item, index) => boolean` — 숨김 판정(기본 `__isHidden`; 숨김 항목은 취소선 + 검색 시에만 표시).
40
+ - `getSearchTextFn: (item, index) => string` — 검색 대상 텍스트(기본 `__searchText`).
41
+ - `displayOrderByFn: (item) => ...` — 표시 정렬 키.
42
+ - `modal: SdSelectModalInfo<TModal>` — 검색 버튼으로 띄울 선택 모달. `editModal: SdModalInfo<...>` — 편집 버튼 모달.
43
+ - `multiSelectionDisplayDirection: "vertical"` — multi 표시 세로 나열.
44
+ - `disabled`/`inset`/`inline`/`size`/`selectClass` — 공통/스타일.
45
+ - 항목 템플릿: `<ng-template [itemOf]="items()" let-item="item">`, 미지정 표시 `#undefinedTpl`.
46
+ - 사용: `<sd-shared-data-select [items]="sharedCustomers.items()" [(value)]="data().customerId"><ng-template [itemOf]="sharedCustomers.items()" let-item="item">{{ item.name }}</ng-template></sd-shared-data-select>`.
47
+
48
+ ### SdSharedDataSelectButton (`sd-shared-data-select-button`)
49
+
50
+ 값 표시 + 모달 검색 버튼(드롭다운 없이 모달 전용). 항목 수가 많아 드롭다운이 부적합할 때.
51
+
52
+ - `value: model<...>` — 선택 키/키배열.
53
+ - `items: TItem[]` — 표시명 매핑용 항목 배열.
54
+ - `modal: input.required<SdSelectModalInfo<TModal>>` — 띄울 선택 모달.
55
+ - `selectMode: "single"|"multi"` / `disabled` / `required` / `inset` / `size` — 공통.
56
+ - 선택 항목 표시 템플릿: `<ng-template [itemOf]>`(필수).
57
+
58
+ ### SdSharedDataSelectList (`sd-shared-data-select-list`)
59
+
60
+ 검색창 + 리스트로 단건 선택(좌측 마스터 리스트 패널 등). `flex-column fill`.
61
+
62
+ - `selectedItem: model<TItem>` — 선택된 항목(키 아닌 항목 객체). `canChangeFn: (item|undefined) => boolean|Promise<boolean>` — 변경 가드.
63
+ - `items: input.required<TItem[]>` — 항목 배열(`__isHidden` 항목 자동 제외).
64
+ - `useUndefined: boolean` — "미지정" 항목 노출.
65
+ - `filterFn: (item, index) => boolean` — 추가 필터.
66
+ - `selectedIcon: string` — 선택 표시 아이콘.
67
+ - `pageItemCount: number` — 페이지당 항목 수(지정 시 페이지네이션).
68
+ - `modal: SdSelectModalInfo<TModal>` — 우상단 외부 링크로 띄울 모달.
69
+ - `header: string` — 상단 헤더 텍스트.
70
+ - 템플릿: `#headerTpl`(헤더 우측), `#filterTpl`(검색창 대체), `<ng-template [itemOf]>`(항목), `#undefinedTpl`(미지정).
54
71
 
55
72
  ## matchesSearchText
56
73
 
57
- `matchesSearchText(itemText: string, searchQuery: string | undefined): boolean` — 검색어를 공백으로 나눈 모든 토큰이 itemText(소문자)에 포함되면 true(AND 매칭). 빈 검색어면 true. 위 select/list 내부 사용하나 커스텀 검색에도 활용 가능.
74
+ - `function matchesSearchText(itemText: string, searchQuery: string | undefined): boolean` — 공백 구분 다중 검색어 AND 매칭(대소문자 무시). 빈 쿼리면 true. 위 선택 컨트롤들이 내부 검색에 사용. 커스텀 목록에서 동일 검색 동작이 필요할 때 직접 호출.