@simplysm/sd-claude 14.0.76 → 14.0.77

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 (66) hide show
  1. package/claude/output-styles/sd-tone.md +128 -0
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +28 -89
  3. package/claude/references/sd-simplysm14/apis/angular/app-structure.md +75 -32
  4. package/claude/references/sd-simplysm14/apis/angular/buttons.md +65 -29
  5. package/claude/references/sd-simplysm14/apis/angular/crud.md +86 -21
  6. package/claude/references/sd-simplysm14/apis/angular/forms.md +168 -42
  7. package/claude/references/sd-simplysm14/apis/angular/infrastructure.md +200 -49
  8. package/claude/references/sd-simplysm14/apis/angular/kanban.md +64 -20
  9. package/claude/references/sd-simplysm14/apis/angular/layout.md +75 -30
  10. package/claude/references/sd-simplysm14/apis/angular/modal.md +92 -40
  11. package/claude/references/sd-simplysm14/apis/angular/routing.md +86 -25
  12. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +72 -41
  13. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +113 -21
  14. package/claude/references/sd-simplysm14/apis/angular/sheet.md +108 -33
  15. package/claude/references/sd-simplysm14/apis/angular/toast.md +81 -30
  16. package/claude/references/sd-simplysm14/apis/angular/visual.md +140 -32
  17. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +46 -43
  18. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +59 -48
  19. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +17 -7
  20. package/claude/references/sd-simplysm14/apis/core-common/README.md +43 -116
  21. package/claude/references/sd-simplysm14/apis/core-common/extensions.md +74 -109
  22. package/claude/references/sd-simplysm14/apis/core-common/features.md +40 -35
  23. package/claude/references/sd-simplysm14/apis/core-common/types.md +80 -106
  24. package/claude/references/sd-simplysm14/apis/core-common/utils.md +142 -111
  25. package/claude/references/sd-simplysm14/apis/core-node/README.md +7 -16
  26. package/claude/references/sd-simplysm14/apis/core-node/consola.md +33 -38
  27. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +25 -33
  28. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +27 -38
  29. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +32 -60
  30. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +14 -45
  31. package/claude/references/sd-simplysm14/apis/core-node/worker.md +35 -81
  32. package/claude/references/sd-simplysm14/apis/excel/README.md +178 -80
  33. package/claude/references/sd-simplysm14/apis/lint/README.md +5 -0
  34. package/claude/references/sd-simplysm14/apis/orm-node/README.md +1 -1
  35. package/claude/references/sd-simplysm14/apis/sd-claude/README.md +28 -5
  36. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +1 -1
  37. package/claude/references/sd-simplysm14/apis/service-client/README.md +57 -50
  38. package/claude/references/sd-simplysm14/apis/service-server/README.md +8 -15
  39. package/claude/references/sd-simplysm14/apis/service-server/auth.md +24 -16
  40. package/claude/references/sd-simplysm14/apis/service-server/builtin-services.md +55 -31
  41. package/claude/references/sd-simplysm14/apis/service-server/define-service.md +28 -44
  42. package/claude/references/sd-simplysm14/apis/service-server/internals.md +59 -18
  43. package/claude/references/sd-simplysm14/apis/service-server/server.md +37 -46
  44. package/claude/references/sd-simplysm14/manuals/client-component.md +3 -1
  45. package/claude/references/sd-simplysm14/manuals/logging.md +9 -8
  46. package/claude/rules/sd-base-rules.md +380 -219
  47. package/claude/settings.json +1 -0
  48. package/claude/skills/sd-commit/SKILL.md +31 -8
  49. package/claude/skills/sd-docs/SKILL.md +15 -10
  50. package/claude/skills/sd-docs/references/subagent-prompt.md +26 -8
  51. package/claude/skills/sd-impl/SKILL.md +1 -1
  52. package/claude/skills/sd-skill/references/skill-authoring.md +1 -1
  53. package/claude/skills/sd-spec/SKILL.md +22 -13
  54. package/claude/skills/sd-spec/references/spec-authoring.md +1 -1
  55. package/claude/skills/sd-unpack/SKILL.md +150 -26
  56. package/claude/skills/sd-unpack/scripts/handlers/__pycache__/_common.cpython-314.pyc +0 -0
  57. package/claude/skills/sd-unpack/scripts/handlers/__pycache__/eml_handler.cpython-314.pyc +0 -0
  58. package/claude/skills/sd-unpack/scripts/handlers/__pycache__/office_com.cpython-314.pyc +0 -0
  59. package/claude/skills/sd-unpack/scripts/handlers/__pycache__/pdf_handler.cpython-314.pyc +0 -0
  60. package/claude/skills/sd-unpack/scripts/handlers/_common.py +17 -2
  61. package/claude/skills/sd-unpack/scripts/handlers/eml_handler.py +100 -24
  62. package/claude/skills/sd-unpack/scripts/handlers/msg_handler.py +140 -27
  63. package/claude/skills/sd-unpack/scripts/handlers/office_com.py +698 -107
  64. package/claude/skills/sd-unpack/scripts/handlers/office_worker.py +34 -26
  65. package/claude/skills/sd-unpack/scripts/handlers/pdf_handler.py +130 -8
  66. package/package.json +1 -1
@@ -1,36 +1,80 @@
1
1
  # @simplysm/angular — kanban
2
2
 
3
- 드래그·드롭 칸반 보드.
3
+ 드래그·드롭 칸반 보드. lane 간 카드 이동·다중 선택.
4
+
5
+ ## SdKanbanBoard — `<sd-kanban-board>`
6
+
7
+ ```ts
8
+ class SdKanbanBoard<L, T>
9
+ selectedValues = model<T[]>([]); // 선택된 카드 value 목록
10
+ drop = output<SdKanbanBoardDropInfo<L, T>>();
11
+
12
+ interface SdKanbanBoardDropInfo<L, T> {
13
+ sourceKanbanValue?: T;
14
+ targetLaneValue?: L;
15
+ targetKanbanValue?: T; // 특정 카드 위에 떨어뜨렸을 때
16
+ }
17
+ ```
18
+
19
+ - 자식으로 `<sd-kanban-lane>` 들. 카드를 lane 또는 다른 카드 위로 드롭하면 `drop` 발화. 호출자는 데이터 reorder 책임.
20
+ - `selectedValues` — `<sd-kanban selectable>` 의 클릭 시 선택 토글.
4
21
 
5
22
  ```html
6
- <sd-kanban-board [(selectedValues)]="selected" (drop)="onDrop($event)">
7
- <sd-kanban-lane [value]="laneA" [busy]="loading">
8
- <ng-template #titleTpl>레인 A</ng-template>
9
- <sd-kanban [value]="card" [draggable]="true" [selectable]="true">{{ card.title }}</sd-kanban>
10
- </sd-kanban-lane>
23
+ <sd-kanban-board (drop)="onDrop($event)">
24
+ @for (lane of lanes; track lane.id) {
25
+ <sd-kanban-lane [value]="lane">
26
+ <div sd-kanban-lane-title>{{ lane.title }}</div>
27
+ @for (card of lane.cards; track card.id) {
28
+ <sd-kanban [value]="card" draggable>{{ card.title }}</sd-kanban>
29
+ }
30
+ </sd-kanban-lane>
31
+ }
11
32
  </sd-kanban-board>
12
33
  ```
13
34
 
14
- ## `<sd-kanban-board<L, T>>`
15
-
16
- - `selectedValues = model<T[]>([])`.
17
- - `drop = output<SdKanbanBoardDropInfo<L, T>>` (`{ sourceKanbanValue?, targetLaneValue?, targetKanbanValue? }`).
18
- - `dragKanban = signal<SdKanbanDragRef | undefined>` — 자식이 드래그 시작 시 설정. document `dragend` 시 자동 해제.
35
+ ## SdKanbanLane — `<sd-kanban-lane>`
19
36
 
20
- ## `<sd-kanban-lane<L, T>>`
37
+ ```ts
38
+ class SdKanbanLane<L, T>
39
+ value = input<L>(); // lane 식별 데이터
40
+ busy = input(false); // lane 영역에 SdBusyContainer 효과
41
+ useCollapse = input(false); // 접기 버튼 노출
42
+ collapse = model(false); // 접힌 상태
43
+ ```
21
44
 
22
- `value: L`, `busy`, `useCollapse`, `collapse` (model, default `false`). drop target 구현. `useCollapse=true` 면 토글 아이콘 노출. 자식 `kanbanControls.filter(selectable)` 있으면 전체선택 체크박스 노출.
45
+ - `value` `drop.targetLaneValue` 사용.
46
+ - `useCollapse=true` + `collapse(true)` 면 컨텐츠 숨김(헤더만).
23
47
 
24
- content templates: `<ng-template #titleTpl>` (헤더 영역), `<ng-template #toolTpl>` (전체선택 우측 도구 영역).
48
+ ## SdKanban `<sd-kanban>`
25
49
 
26
- ## `<sd-kanban<L, T>>`
50
+ ```ts
51
+ class SdKanban<L, T> implements SdKanbanDragRef<L, T>, SdKanbanDropTarget<L, T>
52
+ value = input<T>();
53
+ selectable = input(false);
54
+ draggable = input(false);
55
+ contentClass = input<string>();
56
+ ```
27
57
 
28
- `value: T`, `draggable`, `selectable`, `contentClass`. drag ref + drop target 둘 다 구현(카드 위에 드롭 가능). Shift+클릭 시 `selectable=true` 경우 `selectedValues` 토글.
58
+ - `draggable=true` 드래그 핸들 활성. `selectable=true` 클릭 `SdKanbanBoard.selectedValues` 토글.
59
+ - `value` 는 `drop.sourceKanbanValue`/`targetKanbanValue` 로 사용.
60
+ - `<ng-content>` 가 카드 본문.
29
61
 
30
62
  ## 타입
31
63
 
32
- ```typescript
33
- interface SdKanbanBoardDropInfo<L, T> { sourceKanbanValue?: T; targetLaneValue?: L; targetKanbanValue?: T }
34
- interface SdKanbanDragRef<_L, T> { value(): T | undefined; heightOnDrag(): number }
35
- interface SdKanbanDropTarget<L, T> { targetLaneValue(): L | undefined; targetKanbanValue?(): T | undefined }
64
+ ```ts
65
+ interface SdKanbanDragRef<_L, T> {
66
+ value(): T|undefined;
67
+ heightOnDrag(): number;
68
+ }
69
+ interface SdKanbanDropTarget<L, T> {
70
+ targetLaneValue(): L|undefined;
71
+ targetKanbanValue?(): T|undefined;
72
+ }
36
73
  ```
74
+
75
+ - 보드 내부에서 드래그 소스/드롭 타겟 구분에 사용. 컴포넌트 클래스가 직접 구현. 외부에서 직접 구현할 일은 거의 없음.
76
+
77
+ ## 주의
78
+
79
+ - 드롭 후 데이터 반영은 호출자가 해야 함. `drop` 이벤트만으로 자동 재정렬되지 않음.
80
+ - 같은 카드를 자기 자신에 드롭하면 `sourceKanbanValue === targetKanbanValue`. 호출자가 무시 처리.
@@ -1,47 +1,92 @@
1
1
  # @simplysm/angular — layout
2
2
 
3
- ## 사이드바
3
+ 셸 레이아웃. 사이드바·탑바 컨테이너와 메뉴 위젯.
4
4
 
5
- ```html
6
- <sd-sidebar-container>
7
- <sd-sidebar>
8
- <sd-sidebar-user [userMenu]="userMenu">유저영역 컨텐츠</sd-sidebar-user>
9
- <sd-sidebar-menu [menus]="menus" [layout]="'accordion'" [getMenuIsSelectedFn]="isSel" />
10
- </sd-sidebar>
11
- <ng-content />
12
- </sd-sidebar-container>
5
+ ## SdSidebarContainer — `<sd-sidebar-container>`
6
+
7
+ ```ts
8
+ toggle: WritableSignal<boolean>; // 사이드바 표시 여부
9
+ ```
10
+
11
+ - 사이드바 + 메인 영역 컨테이너. `Router` 네비게이션 시작 시 `toggle` 자동 false(모바일에서 메뉴 자동 닫힘).
12
+ - 자식: `<sd-sidebar>` + 본문 컨텐츠.
13
+
14
+ ## SdSidebar — `<sd-sidebar>`
15
+
16
+ ```ts
17
+ toggle = computed(() => parent.toggle());
18
+ ```
19
+
20
+ - 사이드바 패널. 부모 `SdSidebarContainer.toggle` 에 연동되어 슬라이드 표시.
21
+ - `<ng-content>` 가 사이드바 내부 컨텐츠(보통 `<sd-sidebar-user>` + `<sd-sidebar-menu>`).
22
+
23
+ ## SdSidebarMenu — `<sd-sidebar-menu>`
24
+
25
+ ```ts
26
+ menus = input<SdMenu[]>([]);
27
+ layout = input<"accordion"|"flat">();
28
+ getMenuIsSelectedFn = input<(menu: SdMenu) => boolean>();
13
29
  ```
14
30
 
15
- - `SdSidebarContainer`: `toggle = signal(false)`. 데스크탑은 토글 시 본문 left padding 제거, 모바일(`max-width:520px`)에서는 사이드바가 슬라이드. Router `NavigationStart` 이벤트에 자동 false (페이지 이동 시 자동 닫힘).
16
- - `SdSidebar`: 부모 container의 toggle 추종. content projection.
17
- - `SdSidebarMenu`: `menus: SdMenu[]`, `layout: "accordion"|"flat"` (미지정 `menus.length <= 3` 이면 `"flat"`, 아니면 `"accordion"` 자동), `getMenuIsSelectedFn?: (menu) => boolean`. 자식 메뉴는 항상 `"accordion"`. `menu.url != null` 이면 클릭 시 새창 open.
18
- - `SdSidebarUser`: `userMenu?: SdSidebarUserMenu` + 상단 영역 content projection. userMenu.title 클릭 시 메뉴 항목 펼침/접기.
31
+ - `menus` 보통 `sdAppStructure.usableMenus()` 결과.
32
+ - `layout` — `accordion`: 그룹 메뉴 펼침/접힘, `flat`: 상시 전개. 미지정 시 메뉴 ≤3 → `flat`, 초과 → `accordion`.
33
+ - `getMenuIsSelectedFn` 현재 선택 메뉴 판정 커스텀. 미지정 `fullPageCode === menu.codeChain.join(".")`.
34
+
35
+ ## SdSidebarUser — `<sd-sidebar-user>`
36
+
37
+ ```ts
38
+ userMenu = input<SdSidebarUserMenu>();
19
39
 
20
- ```typescript
21
40
  interface SdSidebarUserMenu {
22
41
  title: string;
23
42
  menus: { title: string; onClick: () => void }[];
24
43
  }
25
44
  ```
26
45
 
27
- ## 탑바
46
+ - 사이드바 상단 사용자 정보 + 드롭다운 메뉴(로그아웃 등). `<ng-content>` 가 사용자 표시 영역(아바타·이름).
28
47
 
29
- ```html
30
- <sd-topbar-container>
31
- <sd-topbar>
32
- <h4>{{ title }}</h4>
33
- <sd-topbar-menu [menus]="menus" />
34
- <sd-topbar-user [menus]="userMenus">유저표시</sd-topbar-user>
35
- </sd-topbar>
36
- <ng-content />
37
- </sd-topbar-container>
48
+ ## SdTopbarContainer — `<sd-topbar-container>`
49
+
50
+ - 탑바 + 본문 컨테이너. inputs 없음. 자식: `<sd-topbar>` + 본문.
51
+
52
+ ## SdTopbar — `<sd-topbar>`
53
+
54
+ ```ts
55
+ sidebarContainer = input<SdSidebarContainer>();
38
56
  ```
39
57
 
40
- - `SdTopbarContainer`: flex-column 100% 컨테이너.
41
- - `SdTopbar`: `sidebarContainer?: SdSidebarContainer` 입력(미지정 시 inject 시도). 사이드바 있으면 햄버거 버튼 노출 → 클릭 시 `sc.toggle` 토글.
42
- - `SdTopbarMenu`: `menus: SdMenu[]`, `getMenuIsSelectedFn?`. 각 최상위 menu는 dropdown 으로 노출. leaf 클릭 후 dropdown 자동 닫힘.
43
- - `SdTopbarUser`: `menus: SdTopbarUserMenu[]` (required, `{ title, onClick }[]`) + 트리거 영역 content projection. dropdown 으로 menus 표시, 클릭 후 자동 close.
58
+ - 상단 바. 햄버거 버튼 클릭 시 사이드바 토글. `sidebarContainer` 명시 하면 ancestor inject 자동 탐색.
59
+
60
+ ## SdTopbarMenu `<sd-topbar-menu>`
61
+
62
+ ```ts
63
+ menus = input<SdMenu[]>([]);
64
+ getMenuIsSelectedFn = input<(menu: SdMenu) => boolean>();
65
+ ```
44
66
 
45
- ## 메뉴 데이터
67
+ - 탑바 가로 메뉴. 그룹 메뉴는 드롭다운 자동.
68
+
69
+ ## SdTopbarUser — `<sd-topbar-user>`
70
+
71
+ ```ts
72
+ menus = input.required<SdTopbarUserMenu[]>();
73
+
74
+ interface SdTopbarUserMenu { title: string; onClick: () => void }
75
+ ```
46
76
 
47
- `SdMenu` (`./app-structure.md` 참조)을 그대로 입력. 선택 상태는 `getIsMenuSelected(menu, fullPageCode, customFn?)` 또는 `getMenuIsSelectedFn` 으로. leaf 메뉴는 `getMenuRouterLinkOption(menu)` 으로 `[sdRouterLink]` 옵션 자동 생성.
77
+ - 탑바 우측 사용자 드롭다운(아바타 메뉴). `<ng-content>` 트리거 표시 영역.
78
+
79
+ ## 사용 예
80
+
81
+ ```html
82
+ <sd-sidebar-container>
83
+ <sd-sidebar>
84
+ <sd-sidebar-user [userMenu]="userMenu()">{{ user().name }}</sd-sidebar-user>
85
+ <sd-sidebar-menu [menus]="appStructure.usableMenus()" />
86
+ </sd-sidebar>
87
+ <sd-topbar-container>
88
+ <sd-topbar><h1>{{ viewTitle() }}</h1><sd-topbar-user [menus]="topbarMenus()" /></sd-topbar>
89
+ <router-outlet />
90
+ </sd-topbar-container>
91
+ </sd-sidebar-container>
92
+ ```
@@ -1,63 +1,115 @@
1
1
  # @simplysm/angular — modal
2
2
 
3
- 선언형 `<sd-modal>` 프로그래밍 방식 `SdModalProvider` 둘 다 지원.
4
-
5
- ## 프로그래밍 방식
6
-
7
- ```typescript
8
- const result = await inject(SdModalProvider).showAsync(
9
- {
10
- title: "주문 선택",
11
- type: OrderSelectModal, // SdModalContentDef<TOutput> 구현
12
- inputs: { mode: "single" }, // DirectiveInputSignals<T> (close/initialized 등 제외)
13
- },
14
- { fill: true, resizable: true, key: "order-select" },
15
- );
16
- ```
3
+ `SdModalProvider.showAsync` 로 프로그래밍 호출, 또는 `<sd-modal>` 컴포넌트 직접 배치.
17
4
 
18
- - `showAsync<T>(info, options?)` → `Promise<TOutput | undefined>`. `close.emit(value)` 시 resolve.
19
- - `SdModalProvider.modalCount = signal(0)` (열린 모달 수).
20
- - `key`를 주면 `SdSystemConfigProvider` 통해 width/height/위치 영속화.
21
- - `noFirstControlFocusing: true`면 첫 tabbable 요소가 아닌 dialog 자체 포커스.
5
+ ## SdModalProvider (root)
22
6
 
23
- ### 모달 컨텐츠 컴포넌트 인터페이스
7
+ ```ts
8
+ modalCount: WritableSignal<number>;
9
+ showAsync<T extends SdModalContentDef<any>>(modal: SdModalInfo<T>, options?: SdModalOptions): Promise<O|undefined>;
24
10
 
25
- ```typescript
26
11
  interface SdModalContentDef<O> {
27
12
  initialized: Signal<boolean>;
28
- close: OutputEmitterRef<O | undefined>;
13
+ close: OutputEmitterRef<O|undefined>;
29
14
  actionTplRef?: TemplateRef<any>;
30
- readonly _optionalModalInputs?: string; // union 해당 input들이 optional 처리
15
+ readonly _optionalModalInputs?: string; // type-level marker, optional inputs 이름 union
16
+ }
17
+
18
+ interface SdModalInfo<T, X = ""> {
19
+ title: string;
20
+ type: Type<T>;
21
+ inputs: ...; // T 의 InputSignal prop 들 (close/initialized 등 제외, _optionalModalInputs 마킹 prop 은 optional)
31
22
  }
23
+
24
+ interface SdModalOptions {
25
+ key?: string; hideHeader?: boolean; hideCloseButton?: boolean;
26
+ headerStyle?: string;
27
+ useCloseByBackdrop?: boolean; useCloseByEscapeKey?: boolean;
28
+ float?: boolean; fill?: boolean;
29
+ resizable?: boolean; movable?: boolean;
30
+ position?: "bottom-right"|"top-right";
31
+ minHeightPx?: number; minWidthPx?: number;
32
+ heightPx?: number; widthPx?: number;
33
+ noFirstControlFocusing?: boolean;
34
+ }
35
+ ```
36
+
37
+ - `showAsync` — 컴포넌트 동적 생성·body 부착·열림 애니메이션·포커스 캡처. 컨텐츠 컴포넌트가 `close.emit(result)` 또는 배경/ESC/닫기버튼으로 `closeRequest` 발화하면 close 진행, Promise 가 result 로 resolve(취소는 undefined).
38
+ - `modalCount` — 동시 열린 모달 수. 키보드 이벤트 핸들러 등에서 활용.
39
+ - `SdModalContentDef.initialized` — 모달 컴포넌트가 자기 준비 끝났을 때 `true` 로 set. `SdPrintProvider` 등은 이걸 기다림(`SdModalProvider` 는 직접 사용 안 함).
40
+ - `SdModalContentDef.actionTplRef` — 모달 헤더 우측 추가 액션 영역에 띄울 `TemplateRef`. set 하면 자동으로 `<sd-modal>` 의 `actionTplRef` 로 브릿지.
41
+ - `_optionalModalInputs` — `SdModalInfo.inputs` 의 일부 prop 을 optional 로 만들 때 사용하는 type-level 마커(`= "fieldA" | "fieldB"`). 런타임 값 없음.
42
+ - `SdModalOptions` 필드별:
43
+ - `key` — 지정 시 `SdSystemConfigProvider` 키 `sd-modal.<key>` 로 width/height/left/top 자동 저장·복원.
44
+ - `hideHeader`/`hideCloseButton` — 헤더 영역 전체/닫기 버튼만 숨김.
45
+ - `headerStyle` — 헤더 인라인 style 문자열.
46
+ - `useCloseByBackdrop`/`useCloseByEscapeKey` — 기본 true. false 면 배경 클릭/ESC 무시.
47
+ - `float` — true 면 배경 어둡지 않고 그림자만(비차단 플로팅).
48
+ - `fill` — true 면 전체 화면. 헤더 투명·테두리 없음.
49
+ - `resizable` — true 면 8방향 리사이즈 핸들.
50
+ - `movable` — true 면 헤더 드래그로 이동.
51
+ - `position` — `bottom-right`/`top-right` 절대 배치. 미지정 = 상단 가운데.
52
+ - `minWidthPx`/`minHeightPx`/`widthPx`/`heightPx` — 사이즈 제약·고정.
53
+ - `noFirstControlFocusing` — true 면 첫 탭가능 요소가 아니라 dialog 박스 자체에 포커스.
54
+
55
+ ```ts
56
+ const result = await sdModal.showAsync({ title: "직원선택", type: EmpSelectModal, inputs: {} }, { resizable: true, key: "emp-select" });
32
57
  ```
33
58
 
34
- `SdModalInfo<T, X>`: `X`는 inputs에서 제외할 추가 키 union (예: `SdSelectModalInfo`가 `selectMode|selectedKeys` 제외).
59
+ ## SdModal `<sd-modal>`
60
+
61
+ `SdModalProvider` 가 내부에서 사용. 직접 템플릿에 두려면:
62
+
63
+ ```ts
64
+ open = model(false); key = input<string|undefined>();
65
+ title = input(""); hideHeader = input(false); hideCloseButton = input(false);
66
+ headerStyle = input<string|undefined>();
67
+ useCloseByBackdrop = input(true); useCloseByEscapeKey = input(true);
68
+ float = input(false); fill = input(false);
69
+ resizable = input(false); movable = input(false);
70
+ position = input<"bottom-right"|"top-right"|undefined>();
71
+ minHeightPx/minWidthPx/heightPx/widthPx = input<number|undefined>();
72
+ actionTplRef = input<TemplateRef<any>|undefined>();
73
+ closeRequest = output<void>();
74
+ ```
35
75
 
36
- ### `SdModalOptions`
76
+ - 의미는 위 `SdModalOptions` 와 1:1. `closeRequest` 발화 시 부모가 `open.set(false)` 책임.
37
77
 
38
- `key`, `hideHeader`, `hideCloseButton`, `headerStyle`, `useCloseByBackdrop`, `useCloseByEscapeKey`, `float`, `fill`, `resizable`, `movable`, `position: "bottom-right"|"top-right"`, `minHeightPx`, `minWidthPx`, `heightPx`, `widthPx`, `noFirstControlFocusing`.
78
+ ## SdActivatedModalProvider (inject inside modal content)
39
79
 
40
- ## 선언형 `<sd-modal>`
80
+ ```ts
81
+ modalComponent: WritableSignal<SdModal|undefined>;
82
+ contentComponent: WritableSignal<T|undefined>;
83
+ canDeactivateFn: () => boolean; // 닫기 차단 시 false 반환
84
+ ```
41
85
 
42
- `open` model, `title`/`key`/`hideHeader`/`hideCloseButton`/`headerStyle`/`useCloseByBackdrop`/`useCloseByEscapeKey`/`float`/`fill`/`resizable`/`movable`/`position`/`minHeightPx`/`minWidthPx`/`heightPx`/`widthPx`/`actionTplRef` input. `closeRequest` output (배경 클릭/ESC/닫기 버튼).
86
+ - 컨텐츠 컴포넌트 내부에서 `inject(SdActivatedModalProvider)` 자기 모달 참조 가져오기. `canDeactivateFn` 함수로 덮어쓰면 ESC/배경/닫기버튼이 false 시 닫힘 차단(저장 안된 변경 사항 보호 패턴).
87
+ - `setupCanDeactivate(fn)` 헬퍼가 이걸 set 함 ([routing.md](./routing.md)).
43
88
 
44
- ## 내부에서 모달 정보 사용
89
+ ## SdPromptModal / SdConfirmModal (사전 정의 컨텐츠)
45
90
 
46
- ```typescript
47
- const am = inject(SdActivatedModalProvider, { optional: true });
48
- am?.modalComponent(); // signal<SdModal> (title() 등)
49
- am?.contentComponent(); // signal<T> 컨텐츠 인스턴스
50
- am.canDeactivateFn = () => isClean(); // 닫기 차단 (false 반환 시)
91
+ ```ts
92
+ class SdPromptModal implements SdModalContentDef<string> { message = input.required<string>(); }
93
+ class SdConfirmModal implements SdModalContentDef<boolean> { message = input.required<string>(); }
51
94
  ```
52
95
 
53
- ## 내장 컨텐츠 컴포넌트
96
+ - 확인 prompt 는 입력값, confirm 은 `true` emit. 취소는 둘 다 `undefined`.
97
+
98
+ ```ts
99
+ const input = await sdModal.showAsync({ title: "입력", type: SdPromptModal, inputs: { message: "이름?" } });
100
+ const ok = await sdModal.showAsync({ title: "확인", type: SdConfirmModal, inputs: { message: "삭제할까요?" } });
101
+ ```
102
+
103
+ ## SelectModalOutputResult<TKey>
104
+
105
+ ```ts
106
+ interface SelectModalOutputResult<TKey = any> { selectedKeys: TKey[]; }
107
+ ```
54
108
 
55
- - `SdPromptModal` (`SdModalContentDef<string>`): `message` input. Enter/확인 `close.emit(value)` (빈 값이면 emit X), 취소 `undefined`.
56
- - `SdConfirmModal` (`SdModalContentDef<boolean>`): `message` input. 확인 → `true`, 취소 → `undefined`.
57
- - `SdAddressSearchModal` (`SdModalContentDef<Address>`): Daum Postcode 스크립트 자동 로드. `Address = { postNumber, address, buildingName }`.
109
+ - `SdModalSelectButton`/`SdSharedDataSelect*` 선택 모달을 호출할 모달이 emit 표준 출력 형태.
58
110
 
59
111
  ## 주의
60
112
 
61
- - `SdModalContentDef` `initialized` signal 컨텐츠 준비 `true` (인쇄/모달 등이 대기).
62
- - 모달은 body에 직접 attach (z-index 자동 할당, 최상위로 끌어올림). focus trap 적용.
63
- - 닫힘 애니메이션 transition duration 대기 destroy.
113
+ - 컨텐츠 컴포넌트가 `close.emit(value)` 호출해야 Promise resolve. 닫기 버튼/ESC/배경 클릭은 `closeRequest` `undefined` resolve.
114
+ - 컨텐츠 안에 `<ng-template #actionTpl>...</ng-template>` 두고 컴포넌트 클래스에서 `actionTplRef = viewChild('actionTpl')` set 하면 헤더 액션 영역 표시됨.
115
+ - `resizable`/`movable`/`key` 조합 사용자 조정 사이즈·위치가 `sd-modal.<key>` 키로 `SdSystemConfigProvider` 에 저장됨.
@@ -1,46 +1,107 @@
1
1
  # @simplysm/angular — routing
2
2
 
3
- Angular Router 위에 페이지 코드(`a.b.c` 형식)·뷰 타입·새창 네비게이션을 얹는 헬퍼.
3
+ Angular Router 위에 페이지 코드(`a.b.c` 점 표기)·뷰 타입·새창 네비게이션 헬퍼.
4
+
5
+ ## SdRouterLink — `[sdRouterLink]` (Directive)
6
+
7
+ ```ts
8
+ option = input<{
9
+ link: string;
10
+ params?: Record<string, string>;
11
+ window?: { width?: number; height?: number }; // 새창 띄울 때 크기
12
+ outletName?: string;
13
+ queryParams?: Record<string, string>;
14
+ } | undefined>(undefined, { alias: "sdRouterLink" });
15
+ ```
4
16
 
5
- ## `SdRouterLink` 디렉티브
17
+ - click 처리. `Alt+click` 무시(브라우저 기본 다운로드 거동 보존). 새창 모드(`SdNavigateWindowProvider.isWindow=true` 또는 `Ctrl/Shift+click`)면 `window.open` 으로 새창 띄움. 일반 모드면 `Router.navigate`.
18
+ - `link` — 라우터 경로. `window` 옵션 지정 시 width/height (기본 800x800) 으로 새창.
19
+ - `outletName` — 보조 outlet 라우팅용. 미지정 시 primary.
20
+ - `params`/`queryParams` — 라우터 매트릭스 파라미터/쿼리 파라미터.
6
21
 
7
22
  ```html
8
- <a [sdRouterLink]="{ link: '/home/order/list', params: { id }, queryParams, outletName, window: { width: 800, height: 600 } }">go</a>
23
+ <sd-anchor [sdRouterLink]="{ link: '/home/sales/invoice', params: { id: '1' } }">송장</sd-anchor>
24
+ ```
25
+
26
+ ## SdNavigateWindowProvider (root)
27
+
28
+ ```ts
29
+ get isWindow: boolean; // 현재 URL hash 의 ;window=true 여부
30
+ open(navigate: string, params?: Record<string, string>, features?: string): void;
31
+ ```
32
+
33
+ - 현재 창이 simplysm 새창 모드인지 판단(`location.hash` 의 `;window=true`).
34
+ - `open` — 새창이거나 `features` 가 주어지면 `window.open(... features)`. 일반 모드면 `_blank` 탭. 부모창 close 시 자식들도 일괄 종료.
35
+
36
+ ## injectCurrentPageCodeSignal
37
+
38
+ ```ts
39
+ function injectCurrentPageCodeSignal(): Signal<string> | undefined
9
40
  ```
10
41
 
11
- - 일반 클릭 → `router.navigate`. Ctrl/Shift 클릭 또는 현재가 새창 컨텍스트(`SdNavigateWindowProvider.isWindow`)일 때 → `SdNavigateWindowProvider.open` 으로 창. Alt+click 무시.
12
- - `outletName` 지정 시 named outlet 으로 navigate.
13
- - `window: { width, height }` 옵션은 isWindow 컨텍스트에서 새 창 features 로만 사용 (Ctrl/Shift 새창에는 미적용).
42
+ - `ActivatedRoute.pathFromRoot.slice(2)` url segments `.` 으로 join. 라우터 컨텍스트 없으면 undefined.
43
+ - 예: `/home/sales/invoice` `"sales.invoice"`.
14
44
 
15
- ## `SdNavigateWindowProvider`
45
+ ## injectFullPageCodeSignal
16
46
 
17
- ```typescript
18
- const nav = inject(SdNavigateWindowProvider);
19
- nav.open("/home/order/list", { id }, "width=800,height=600");
20
- nav.isWindow; // 현재 컨텍스트가 팝업 윈도우인지
47
+ ```ts
48
+ function injectFullPageCodeSignal(): Signal<string>
21
49
  ```
22
50
 
23
- URL hash 끝에 `;window=true` 있으면 `isWindow=true`. 부모 unload 자기가 자동 close.
51
+ - `Router.events` NavigationEnd URL segment 2개부터 `.` join. matrix/query 제거.
52
+ - "전체 페이지 코드" 로 메뉴 선택 상태 판정에 사용.
53
+
54
+ ## injectViewTitleSignal
24
55
 
25
- ## Page Code Signal
56
+ ```ts
57
+ function injectViewTitleSignal(): Signal<string>
58
+ ```
59
+
60
+ - 모달 안이면 `SdActivatedModalProvider.modalComponent().title()`.
61
+ - 그 외엔 `SdAppStructureProvider.getTitleByFullCode(currentPageCode ?? fullPageCode)`.
62
+ - 페이지/모달 상단에 표시할 제목.
26
63
 
27
- 페이지 코드 = activated route URL segment 를 `.` 으로 join.
64
+ ## injectViewTypeSignal
28
65
 
29
- - `injectCurrentPageCodeSignal()`: 현재 라우트의 segment 신호 (`undefined` if no ActivatedRoute).
30
- - `injectFullPageCodeSignal()`: router.url 기반 풀 코드.
31
- - `injectViewTitleSignal()`: 모달이면 `modalComponent.title()`, 아니면 `SdAppStructureProvider.getTitleByFullCode`.
32
- - `injectViewTypeSignal(): Signal<SdViewType>`. `SdViewType = "page" | "modal" | "control"`. 모달 컨텍스트면 `"modal"`, page-level route component면 `"page"`, 그 외 `"control"`.
66
+ ```ts
67
+ function injectViewTypeSignal(): Signal<SdViewType>;
68
+ type SdViewType = "page" | "modal" | "control";
69
+ ```
33
70
 
34
- ## `setupCanDeactivate(fn: () => boolean)`
71
+ - `page`: 라우터 진입 페이지 컴포넌트, `modal`: 모달 컨텐츠, `control`: 그 외(다른 컴포넌트의 자식). 컴포넌트가 자기 컨텍스트별로 다르게 그릴 때.
35
72
 
36
- constructor 내 호출. 모달 컨텍스트면 `SdActivatedModalProvider.canDeactivateFn` 설정, 라우트 컨텍스트면 `route.routeConfig.canDeactivate` 에 push (destroy 시 제거).
73
+ ## setupCanDeactivate
37
74
 
38
- ## 메뉴 유틸
75
+ ```ts
76
+ function setupCanDeactivate(fn: () => boolean): void
77
+ ```
78
+
79
+ - 모달 안: `SdActivatedModalProvider.canDeactivateFn = fn` 으로 ESC/배경/닫기 차단.
80
+ - 라우터 페이지: route config 에 `CanDeactivate` 가드 추가(컴포넌트 파괴 시 자동 제거).
81
+ - 저장 안된 변경 사항 보호용.
82
+
83
+ ```ts
84
+ setupCanDeactivate(() => !isDirty() || confirm("변경 사항이 있습니다. 나가시겠습니까?"));
85
+ ```
86
+
87
+ ## getMenuRouterLinkOption
88
+
89
+ ```ts
90
+ function getMenuRouterLinkOption(menu: SdMenu): { link: string; queryParams: Record<string, string>|undefined } | undefined
91
+ ```
92
+
93
+ - leaf 메뉴(`children` 도 `url` 도 없음)만 결과 반환. 그룹/외부URL 메뉴는 undefined.
94
+ - 반환 link 는 `/home/<codeChain join "/">`. 마지막 segment 에 `?key=val` 포함 시 분리해 queryParams 로.
95
+
96
+ ## getIsMenuSelected
97
+
98
+ ```ts
99
+ function getIsMenuSelected(menu: SdMenu, fullPageCode: string|undefined, customFn?: (menu) => boolean): boolean
100
+ ```
39
101
 
40
- - `getMenuRouterLinkOption(menu: SdMenu)`: leaf 메뉴를 `SdRouterLink` 옵션(`{ link, queryParams }`)으로 변환. children/url 있으면 `undefined`.
41
- - `getIsMenuSelected(menu, fullPageCode, customFn?)`: 현재 페이지가 메뉴와 일치하는지.
102
+ - 커스텀 함수 있으면 그것 사용. 없으면 `fullPageCode === menu.codeChain.join(".")`.
42
103
 
43
104
  ## 주의
44
105
 
45
- - 페이지 코드는 hash router 기준 `/home/<code>` 구조 가정.
46
- - `injectCurrentPageCodeSignal` `pathFromRoot.slice(2)` 사용 root + `home` 레벨 라우트 컴포넌트에서 의미 있음.
106
+ - 페이지 코드는 `/home/` 다음의 segment `.` join. 라우터를 이 컨벤션에 맞춰 설계해야 메뉴 선택 표시·뷰 타이틀 자동 동작.
107
+ - `SdRouterLink` `link` 라우터 절대경로 또는 상대경로. `link` `?queryString` 직접 쓰지 말고 `queryParams` 객체로 분리.
@@ -1,51 +1,82 @@
1
1
  # @simplysm/angular — selection-managers
2
2
 
3
- `<sd-sheet>`/`<sd-select>` 등이 내부에서 쓰는 선택·확장·정렬 로직을 외부 컴포넌트에서도 재사용할 수 있도록 추출된 함수 훅들. signal 바인딩.
4
-
5
- ## `useSelectionManager<TItem, TKey>(options)`
6
-
7
- ```typescript
8
- const sm = useSelectionManager({
9
- displayItems, // Signal<TItem[]>
10
- selectedKeys, // WritableSignal<TKey[]>
11
- selectMode, // Signal<"single"|"multi"|undefined>
12
- getItemSelectableFn, // Signal<((item) => boolean|string) | undefined>
13
- trackByFn, // Signal<(item, idx) => TKey>
14
- });
15
- sm.hasSelectable; sm.isAllSelected;
16
- sm.getSelectable(item); // true | string(reason) | undefined
17
- sm.select(item); sm.deselect(item); sm.toggle(item); sm.toggleAll();
18
- sm.isSelected(item); sm.getCanChangeFn(item); // () => boolean (체크박스 canChangeFn 으로 직결)
3
+ 리스트 컴포넌트 내부에서 쓰는 선택·확장·정렬 로직 함수 훅. signal 바인딩.
4
+
5
+ ## useSelectionManager
6
+
7
+ ```ts
8
+ function useSelectionManager<TItem, TKey>(options: {
9
+ displayItems: Signal<TItem[]>;
10
+ selectedKeys: WritableSignal<TKey[]>;
11
+ selectMode: Signal<"single"|"multi"|undefined>;
12
+ getItemSelectableFn: Signal<((item: TItem) => boolean|string)|undefined>;
13
+ trackByFn: Signal<(item, index) => TKey>;
14
+ }): {
15
+ hasSelectable: Signal<boolean>;
16
+ isAllSelected: Signal<boolean>;
17
+ getSelectable(item): true|string|undefined;
18
+ getCanChangeFn(item): () => boolean;
19
+ select(item): void;
20
+ deselect(item): void;
21
+ toggle(item): void;
22
+ toggleAll(): void;
23
+ isSelected(item): boolean;
24
+ };
19
25
  ```
20
26
 
21
- - `single`이면 select 기존 키 대체. `multi` 면 추가.
22
- - 키 비교는 `obj.equal` (`@simplysm/core-common`).
23
- - `trackByFn` 반환이 `null`이면 선택 불가.
24
- - `hasSelectable`은 `selectMode != null` 여부 (선택 가능 모드인지). 선택 가능한 아이템 존재 여부는 `isAllSelected` 로직에서 자동 처리.
25
-
26
- ## `useExpandingManager<T>(binding)`
27
-
28
- ```typescript
29
- interface ExpandItemDef<T> { item; parentDef?; hasChildren; depth }
30
- const em = useExpandingManager({
31
- items, expandedItems,
32
- getChildrenFn, // Signal<((item, idx) => T[] | undefined) | undefined>
33
- sort, // (items: T[]) => T[]
34
- });
35
- em.displayItems; em.hasExpandable; em.isAllExpanded;
36
- em.toggle(item); em.toggleAll(); em.isVisible(item); em.def(item);
27
+ - `selectMode=single` 이면 `select` 기존 키 덮어씀, `multi` 면 추가.
28
+ - `getItemSelectableFn` — true: 선택 가능, false: 불가, string: 불가 + 사유.
29
+ - `getSelectable` 반환 `true` (선택 가능), `string` (사유), `undefined` (해당 없음).
30
+ - `isAllSelected` selectable 항목 전체가 선택된 상태.
31
+ - 키 비교는 `obj.equal` (deep equal).
32
+
33
+ ## useExpandingManager
34
+
35
+ ```ts
36
+ function useExpandingManager<T>(binding: {
37
+ items: Signal<T[]>;
38
+ expandedItems: WritableSignal<T[]>;
39
+ getChildrenFn: Signal<((item: T, index: number) => T[]|undefined)|undefined>;
40
+ sort: (items: T[]) => T[];
41
+ }): {
42
+ displayItems: Signal<T[]>; // 트리 평탄화 + 정렬 적용
43
+ hasExpandable: Signal<boolean>;
44
+ isAllExpanded: Signal<boolean>;
45
+ toggle(item): void;
46
+ toggleAll(): void;
47
+ isVisible(item): boolean; // 조상 모두 expanded 인지
48
+ def(item): ExpandItemDef<T>;
49
+ };
50
+
51
+ interface ExpandItemDef<T> {
52
+ item: T;
53
+ parentDef: ExpandItemDef<T>|undefined;
54
+ hasChildren: boolean;
55
+ depth: number;
56
+ }
37
57
  ```
38
58
 
39
- 부모가 collapsed면 자식은 `isVisible=false`.
59
+ - `getChildrenFn` 으로 트리 워킹 → 깊이·부모 정보 포함한 def 배열 생성.
60
+ - `sort` — 각 depth 별 자식들에 적용할 정렬 함수(보통 `useSortingManager.sort`).
61
+ - `isVisible` — 항목이 화면에 보일 조건(모든 조상이 expanded).
62
+
63
+ ## useSortingManager
40
64
 
41
- ## `useSortingManager(options)`
65
+ ```ts
66
+ function useSortingManager(options: { sorts: WritableSignal<SortingDef[]> }): {
67
+ defMap: Signal<Map<string, { indexText?: string; desc: boolean }>>;
68
+ toggle(key: string, multiple: boolean): void;
69
+ sort<T>(items: T[]): T[];
70
+ };
42
71
 
43
- ```typescript
44
- interface SortingDef { key: string; desc: boolean }
45
- const sm = useSortingManager({ sorts });
46
- sm.defMap; // Signal<Map<key, { indexText?, desc }>>
47
- sm.toggle(key, multiple); // multiple=true면 multi-key sort, 아니면 단일
48
- sm.sort(items); // null < non-null, string localeCompare
72
+ interface SortingDef { key: string; desc: boolean; }
49
73
  ```
50
74
 
51
- `toggle` 토글 순서: 없음 asc → desc → 제거.
75
+ - 컬럼 클릭 시 `toggle(key, ctrlKey)`. 단일 정렬은 `asc → desc → 없음` 순환, 다중(`multiple=true`)은 같은 키 누적 + 마지막에 제거.
76
+ - `defMap.indexText` — 다중 정렬일 때 컬럼 헤더에 표시할 순번 ("1", "2"…). 단일이면 undefined.
77
+ - `sort` — `key` 별 prop 값 비교. string 은 localeCompare, number 는 산술, 그 외는 String 변환 localeCompare. null/undefined 는 최소값.
78
+
79
+ ## 주의
80
+
81
+ - 세 훅 모두 컴포넌트의 inject 컨텍스트 없이 호출 가능(순수 함수). 단 signal 바인딩이므로 reactive 컨텍스트에서 사용.
82
+ - `<sd-sheet>` 가 내부적으로 셋 다 사용. 새 리스트 컴포넌트 만들 때 같은 동작 원하면 재사용.