@simplysm/sd-claude 14.0.91 → 14.0.93

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 (93) hide show
  1. package/claude/references/sd-simplysm14/README.md +7 -6
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +59 -39
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +119 -186
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +70 -31
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +55 -57
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +86 -105
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +48 -57
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +37 -47
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +82 -74
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +61 -50
  11. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +74 -57
  12. package/claude/references/sd-simplysm14/apis/angular/sheet.md +63 -72
  13. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +23 -18
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +21 -19
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +23 -18
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +72 -32
  17. package/claude/references/sd-simplysm14/apis/core-browser/README.md +18 -18
  18. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +29 -29
  19. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +41 -41
  20. package/claude/references/sd-simplysm14/apis/core-common/README.md +97 -90
  21. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +75 -51
  22. package/claude/references/sd-simplysm14/apis/core-common/collection-ext.md +81 -0
  23. package/claude/references/sd-simplysm14/apis/core-common/errors.md +27 -29
  24. package/claude/references/sd-simplysm14/apis/core-common/obj.md +44 -45
  25. package/claude/references/sd-simplysm14/apis/core-common/serialization.md +34 -33
  26. package/claude/references/sd-simplysm14/apis/core-common/value-types.md +86 -0
  27. package/claude/references/sd-simplysm14/apis/core-node/README.md +6 -6
  28. package/claude/references/sd-simplysm14/apis/core-node/consola.md +3 -0
  29. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +2 -2
  30. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +1 -1
  31. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +2 -2
  32. package/claude/references/sd-simplysm14/apis/core-node/worker.md +6 -3
  33. package/claude/references/sd-simplysm14/apis/excel/README.md +10 -10
  34. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +4 -2
  35. package/claude/references/sd-simplysm14/apis/excel/utils.md +1 -1
  36. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +6 -6
  37. package/claude/references/sd-simplysm14/apis/lint/README.md +6 -32
  38. package/claude/references/sd-simplysm14/apis/lint/recommended.md +60 -0
  39. package/claude/references/sd-simplysm14/apis/lint/rules.md +17 -17
  40. package/claude/references/sd-simplysm14/apis/orm-common/README.md +15 -6
  41. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +68 -102
  42. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +75 -89
  43. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +87 -99
  44. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +110 -147
  45. package/claude/references/sd-simplysm14/apis/orm-common/types.md +48 -51
  46. package/claude/references/sd-simplysm14/apis/orm-node/README.md +8 -13
  47. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +5 -5
  48. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +9 -6
  49. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +9 -8
  50. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +23 -19
  51. package/claude/references/sd-simplysm14/apis/service-client/README.md +20 -12
  52. package/claude/references/sd-simplysm14/apis/service-client/orm.md +6 -6
  53. package/claude/references/sd-simplysm14/apis/service-client/transport.md +1 -1
  54. package/claude/references/sd-simplysm14/apis/service-common/README.md +35 -32
  55. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +23 -22
  56. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +23 -23
  57. package/claude/references/sd-simplysm14/apis/service-server/README.md +51 -43
  58. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +6 -6
  59. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +31 -21
  60. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +8 -8
  61. package/claude/references/sd-simplysm14/apis/storage/README.md +55 -49
  62. package/claude/references/sd-simplysm14/manuals/client-component.md +843 -740
  63. package/claude/references/sd-simplysm14/manuals/client-crud.md +8 -0
  64. package/claude/references/sd-simplysm14/manuals/client-demo.md +6 -16
  65. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +26 -0
  66. package/claude/references/sd-simplysm14/manuals/logging.md +1 -1
  67. package/claude/references/sd-simplysm14/manuals/orm.md +15 -1
  68. package/claude/rules/sd-design-rules.md +7 -0
  69. package/claude/sd-system-prompt.md +5 -8
  70. package/claude/skills/sd-debug/SKILL.md +43 -0
  71. package/claude/skills/sd-debug/workflow.js +390 -0
  72. package/claude/skills/sd-demo/SKILL.md +18 -20
  73. package/claude/skills/sd-dev/SKILL.md +127 -24
  74. package/claude/skills/sd-docs/SKILL.md +5 -3
  75. package/claude/skills/sd-docs/references/subagent-prompt.md +2 -3
  76. package/claude/skills/sd-impl/SKILL.md +18 -18
  77. package/claude/skills/sd-manual/SKILL.md +1 -0
  78. package/claude/skills/sd-review/SKILL.md +24 -18
  79. package/claude/skills/sd-review/workflow.js +324 -0
  80. package/claude/skills/sd-spec/SKILL.md +96 -679
  81. package/claude/skills/sd-spec/references/example-spec.md +28 -50
  82. package/claude/skills/sd-spec/references/format-analyze.md +232 -0
  83. package/claude/skills/sd-spec/references/format-design.md +248 -0
  84. package/claude/skills/sd-spec/workflow-analyze.js +615 -0
  85. package/claude/skills/sd-spec/workflow-design.js +667 -0
  86. package/claude/skills/sd-unpack/scripts/handlers/office_com.py +5 -1
  87. package/package.json +1 -1
  88. package/scripts/postinstall.mjs +157 -18
  89. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +0 -68
  90. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +0 -77
  91. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +0 -86
  92. package/claude/skills/sd-skill/SKILL.md +0 -245
  93. package/claude/skills/sd-skill/scripts/run_eval.py +0 -380
@@ -1,79 +1,90 @@
1
- # @simplysm/angular — 라우팅 / 앱 구조(메뉴·권한)
1
+ # @simplysm/angular — 라우팅·메뉴·권한(app-structure)
2
2
 
3
3
  라우터 링크·현재 페이지 식별·뷰 컨텍스트(page/control/modal)·이탈 가드, 그리고 앱 구조 트리에서 메뉴·권한을 파생하는 군. 화면 컴포넌트의 표준 시그널 `viewType`, 권한 가드 `injectPermsSignal`, 사이드바/탑바 메뉴가 이 군에 의존.
4
4
 
5
- ## injectViewTypeSignal / SdViewType
5
+ ## 페이지 코드·뷰 타입·제목 시그널
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]` 입력에 그대로 전달.
6
13
 
7
14
  ```ts
8
- function injectViewTypeSignal(): Signal<SdViewType>
9
- type SdViewType = "page" | "modal" | "control"
15
+ viewType = injectViewTypeSignal();
16
+ viewTitle = injectViewTitleSignal();
10
17
  ```
11
18
 
12
- 현재 컴포넌트가 어느 컨텍스트에서 동작 중인지 판정하는 signal. `"page"` = 라우팅 진입 화면, `"modal"` = 모달로 열림, `"control"` = 다른 화면 안에 임베드된 자식. 판정 기준: 모달 컨텍스트면 modal, 라우트 컴포넌트의 selector 가 현재 엘리먼트 태그와 일치하고 full/current 페이지 코드가 같으면 page, 그 외 control. 매뉴얼 표준 시그널 `viewType = injectViewTypeSignal()` 로 받아 `sd-base-container [viewType]` 에 전달.
19
+ ## setupCanDeactivate
13
20
 
14
- ## injectViewTitleSignal
21
+ 라우터/모달 이탈 시점에 호출되는 가드를 등록. injection 컨텍스트에서 호출.
15
22
 
16
- - `function injectViewTitleSignal(): Signal<string>`현재 뷰의 표시 제목. 모달이면 모달 컴포넌트 `title`, 아니면 구조에서 현재 페이지 코드로 찾은 제목(`[상위 > 경로] 현재`). 찾으면 `""`. `sd-base-container` 가 page 탑바 제목에 사용.
23
+ - `setupCanDeactivate(fn: () => boolean): void``fn()` `false` 이탈 차단. 모달이면 `SdActivatedModalProvider.canDeactivateFn` 설정, 페이지면 route `canDeactivate` push(파괴 자동 제거). detail 화면의 변경사항 가드에 사용.
17
24
 
18
- ## injectFullPageCodeSignal / injectCurrentPageCodeSignal
25
+ ```ts
26
+ setupCanDeactivate(() => this._orgData == null || obj.equal(this.data(), this._orgData) || confirm("변경사항이 있습니다. 진행할까요?"));
27
+ ```
19
28
 
20
- - `function injectFullPageCodeSignal(): Signal<string>` — 라우터 URL 전체에서 파생한 페이지 코드(`/` → `.` 로 합침, 앞 2세그먼트 제외, `;`/`?` 이후 제거). 메뉴 선택 상태·뷰 타입 판정에 사용.
21
- - `function injectCurrentPageCodeSignal(): Signal<string> | undefined` — 현재 `ActivatedRoute` 기준 상대 페이지 코드. 라우트 컨텍스트 없으면 `undefined`. 중첩 라우트에서 자기 위치 코드가 필요할 때.
29
+ ## SdNavigateWindowProvider
22
30
 
23
- ## setupCanDeactivate
31
+ 창/탭으로 라우트를 여는 프로바이더. 현재 컨텍스트가 창 모드인지 판별.
24
32
 
25
- - `function setupCanDeactivate(fn: () => boolean): void` — 라우터 이탈/모달 닫기 시점에 `fn()` false 면 이탈/닫기를 차단. 모달 컨텍스트면 `SdActivatedModalProvider.canDeactivateFn` 에, 라우트 컨텍스트면 해당 route 의 `canDeactivate` 에 등록(파괴 시 자동 해제). detail 화면의 변경 가드 표준: `setupCanDeactivate(() => this._checkIgnoreChanges())`(client-component.md).
33
+ - `isWindow: boolean` — 현재가 `window=true` 쿼리로 열린 창인지.
34
+ - `open(navigate: string, params?: Record<string,string>, features?: string): void` — 창 모드이거나 `features` 가 있으면 `window.open` 으로 새 창(beforeunload 시 자식 창 정리), 아니면 `_blank` 새 탭으로 라우트 열기.
26
35
 
27
- ## SdRouterLink (`[sdRouterLink]`)
36
+ ## SdRouterLink
28
37
 
29
- 라우터 이동을 호스트 클릭에 붙이는 디렉티브. Ctrl/Shift 클릭·window 모드면 새 창/탭으로 분기.
38
+ `[sdRouterLink]` 디렉티브. 클릭 옵션에 따라 라우팅/새 창/아웃렛 이동.
30
39
 
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)` 결과를 이 입력에 바인딩.
40
+ - `sdRouterLink` (option) — `{ link, params?, window?: { width?, height? }, outletName?, queryParams? }`. `link` 가 이동 경로, `params` 가 matrix 파라미터, `window` 가 창 크기, `outletName` 이 named outlet, `queryParams` 쿼리. Ctrl/Shift+클릭이면 새 창, 창 모드면 팝업 창, 아니면 `Router.navigate`. Alt+클릭은 무시.
37
41
 
38
- ## SdNavigateWindowProvider
42
+ ```html
43
+ <a [sdRouterLink]="{ link: '/home/goods/detail', params: { id: '5' } }">상세</a>
44
+ ```
39
45
 
40
- - `isWindow: boolean` — 현재 문서가 `window=true` 로 열린 팝업인지(해시 파라미터 검사).
41
- - `open(navigate: string, params?: Record<string,string>, features?: string): void` — 새 창/탭으로 라우트 열기. window 컨텍스트이거나 `features` 가 있으면 `window=true` 팝업으로(부모 unload 시 자동 close), 아니면 `_blank` 탭으로. `SdRouterLink` 내부 + 화면에서 보조 창을 띄울 때.
46
+ ## 메뉴 유틸 (menu-utils)
42
47
 
43
- ## SdAppStructureProvider<TModule> / injectPermsSignal
48
+ `SdMenu` 로부터 라우터 링크 옵션·선택 여부를 계산. 사이드바/탑바 메뉴가 사용.
44
49
 
45
- 구조 트리(`AppStructureItem[]`, `@simplysm/service-common`)에서 메뉴·권한을 파생하는 root provider. 화면은 보통 `injectPermsSignal` 직접 씀.
50
+ - `getMenuRouterLinkOption(menu: SdMenu): { link, queryParams } | undefined` — leaf 메뉴면 `"/home/"+코드체인` 링크와 쿼리파라미터 반환, 그룹/외부 url 메뉴면 `undefined`.
51
+ - `getIsMenuSelected(menu, fullPageCode?, customFn?): boolean` — `customFn` 있으면 그것으로, 없으면 현재 fullPageCode 와 메뉴 코드체인 일치 여부.
46
52
 
47
- ```ts
48
- function injectPermsSignal<K extends string>(viewCodes: string[], keys: K[]): Signal<K[]>
49
- ```
53
+ ## SdAppStructureProvider<TModule>
54
+
55
+ 앱 메뉴·권한 트리(`AppStructureItem[]`)에서 사용 가능 메뉴·권한을 파생하는 전역 프로바이더. `providedIn: "root"`.
50
56
 
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)` — 코드로 제목·항목 체인 조회.
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` — 코드 체인·권한 트리 조회.
60
65
 
61
- ## SdAppStructureUtils (static 유틸)
66
+ ## injectPermsSignal
62
67
 
63
- `SdAppStructureProvider` 내부적으로 쓰는 순수 함수 모음(abstract 클래스의 static). 트리 → 메뉴/권한/제목 변환을 provider 밖에서 직접 할 때만 사용.
68
+ 화면 컴포넌트가 권한을 시그널로 받는 헬퍼. injection 컨텍스트에서 호출.
69
+
70
+ - `injectPermsSignal<K>(viewCodes: string[], keys: K[]): Signal<K[]>` — `viewCodes` 권한 path 들에서 사용자가 보유한 `keys` 만 담은 배열을 반환. 템플릿/코드에서 `perms().includes("edit")` 식으로 인라인 가드.
71
+
72
+ ```ts
73
+ perms = injectPermsSignal(["inventory.outbound"], ["use", "edit"]);
74
+ // this.perms().includes("use")
75
+ ```
64
76
 
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 동명 메서드의 구현.
77
+ ## SdAppStructureUtils
69
78
 
70
- ## menu-utils
79
+ `AppStructureItem[]` 에서 메뉴/권한/제목을 계산하는 정적 메서드 모음(추상 클래스). 대개 `SdAppStructureProvider` 가 내부에서 호출하지만 직접 쓸 수도 있음.
71
80
 
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(".")`.
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 와 동일 동작의 정적 버전.
74
85
 
75
- ## 타입
86
+ ## 타입 (sd-app-structure.types)
76
87
 
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` 입력 타입.
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`) 입력 단위.
@@ -1,74 +1,91 @@
1
- # @simplysm/angular — 공유 마스터 데이터 + 선택 컨트롤
1
+ # @simplysm/angular — 공유 마스터 데이터
2
2
 
3
- 고객사·품목 등 자주 참조하는 마스터 데이터를 한 번 등록해 어느 화면에서든 공유 signal 쓰고, 그 데이터를 선택하는 드롭다운/버튼/리스트 컨트롤을 제공하는 군. 등록·항목 추가 절차는 `client-shared-data.md` 참조.
3
+ 고객사·품목 등 자주 참조하는 마스터 데이터를 한 번 등록해 어느 화면에서든 공유 시그널로 쓰고, 그 데이터를 선택하는 드롭다운/버튼/리스트 컨트롤을 제공하는 군. 등록·항목 추가 절차는 `client-shared-data.md` 참조.
4
4
 
5
- ## SdSharedDataProvider<T> (abstract)
5
+ ## SdSharedDataProvider<T>
6
6
 
7
- 마스터 데이터를 이름별로 등록·로드·이벤트 동기화하는 root provider. 앱은 이걸 상속한 `AppSharedDataProvider` 만들고 `useSharedSignal` 헬퍼를 함께 export(client-shared-data.md).
7
+ 마스터 데이터를 이름별로 등록·로드·공유하는 추상 프로바이더. 앱에서 상속해 `initialize()` 안에서 `register`. `@Injectable()`.
8
8
 
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>` — 진행 로드 수.
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` 까지 대기.
15
15
 
16
- ### 타입
16
+ `SdSharedDataChangeEvent` — `defineEvent` 로 정의된 공유 데이터 변경 이벤트(서비스 서버 경유 브로드캐스트).
17
17
 
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`.
18
+ ## 타입 (shared-data.provider)
22
19
 
23
- 사용(화면): `sharedCustomers = useSharedSignal("고객사"); sharedCustomers.items(); sharedCustomers.get(id)`.
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`(키로 단건 조회).
24
23
 
25
- ## 선택 컨트롤
24
+ ```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
+ }
32
+ ```
26
33
 
27
- 공유 데이터(또는 `SharedDataBase` 호환 배열)를 항목으로 받아 선택. 매직 필드(`__searchText`/`__isHidden`/`__parentKey`)를 자동 활용(검색·숨김·트리).
34
+ ## SdSharedDataSelect
28
35
 
29
- ### SdSharedDataSelect (`sd-shared-data-select`)
36
+ 공유 데이터를 드롭다운으로 선택하는 컨트롤. selector `sd-shared-data-select`. 검색·트리(`__parentKey`)·관리/선택 모달 지원.
30
37
 
31
- 드롭다운 셀렉트(검색창·트리·미지정 항목·모달 연동 내장).
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` 로 미지정 항목 표시.
32
49
 
33
- - `value: model<...>` — 선택 키(single) 또는 키 배열(multi). 미지정은 `undefined`.
34
- - `items: input.required<TItem[]>` — 공유 항목 배열(`SharedDataBase` 상속).
50
+ ```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>
53
+ </sd-shared-data-select>
54
+ ```
55
+
56
+ ## SdSharedDataSelectButton
57
+
58
+ 공유 데이터를 모달로만 선택하는 버튼형 컨트롤. selector `sd-shared-data-select-button`.
59
+
60
+ - `value: model<단일|배열>` — 선택 키(들).
61
+ - `items: input<TItem[]>` — 항목 배열.
62
+ - `modal: input.required<SdSelectModalInfo<TModal>>` — 선택 모달(필수).
35
63
  - `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` — 추가 필터.
64
+ - `disabled`/`required`/`inset: boolean`, `size: "sm"|"lg"` 상태/크기.
65
+
66
+ ## SdSharedDataSelectList
67
+
68
+ 좌측 마스터 목록형 선택 컨트롤(항목 객체를 모델로). selector `sd-shared-data-select-list`. 공유데이터+detail 합성 화면의 좌측에 사용.
69
+
70
+ - `selectedItem: model<TItem>` — 선택된 **항목 객체**(키가 아니라 객체).
71
+ - `items: input.required<TItem[]>`항목 배열.
72
+ - `canChangeFn: (item) => boolean | Promise<boolean>` 선택 변경 허용 가드.
66
73
  - `selectedIcon: string` — 선택 표시 아이콘.
67
- - `pageItemCount: number` — 페이지당 항목 수(지정 시 페이지네이션).
68
- - `modal: SdSelectModalInfo<TModal>` 우상단 외부 링크로 띄울 모달.
69
- - `header: string`상단 헤더 텍스트.
70
- - 템플릿: `#headerTpl`(헤더 우측), `#filterTpl`(검색창 대체), `<ng-template [itemOf]>`(항목), `#undefinedTpl`(미지정).
74
+ - `useUndefined: boolean` — 미지정 항목 노출.
75
+ - `filterFn: (item, index) => boolean` 표시 필터.
76
+ - `modal: SdSelectModalInfo<TModal>`관리·선택 모달(목록 화면 재사용).
77
+ - `header: string` 목록 헤더 라벨.
78
+ - `pageItemCount: number` — 페이지당 항목 수.
79
+
80
+ ```html
81
+ <sd-shared-data-select-list [items]="sharedRoles.items()" [(selectedItem)]="selectedRole"
82
+ [header]="'역할'" [modal]="{ type: RoleList, title: '역할', inputs: {} }">
83
+ <ng-template [itemOf]="sharedRoles.items()" let-item="item">{{ item.name }}</ng-template>
84
+ </sd-shared-data-select-list>
85
+ ```
71
86
 
72
87
  ## matchesSearchText
73
88
 
74
- - `function matchesSearchText(itemText: string, searchQuery: string | undefined): boolean` — 공백 구분 다중 검색어 AND 매칭(대소문자 무시). 쿼리면 true. 위 선택 컨트롤들이 내부 검색에 사용. 커스텀 목록에서 동일 검색 동작이 필요할 때 직접 호출.
89
+ 공백 분리 AND 검색 일치 판정 유틸(선택 컨트롤이 내부 사용).
90
+
91
+ - `matchesSearchText(itemText: string, searchQuery: string | undefined): boolean` — `searchQuery` 를 공백으로 나눈 모든 단어가(대소문자 무시) `itemText` 에 포함되면 true. 검색어 없으면 항상 true.
@@ -1,88 +1,79 @@
1
- # @simplysm/angular — 시트 (sd-sheet)
1
+ # @simplysm/angular — 시트(sd-sheet)
2
2
 
3
3
  다건 목록·편집 표(그리드). 컬럼 디렉티브 + 셀 템플릿으로 구성하며, 선택·정렬·페이지·트리펼침·셀 편집·컬럼 고정/리사이즈/설정저장을 내장. `sd-crud-list` 는 이 시트를 감싼 표준 골격(crud.md). 셀 본문·정렬·폭 규약은 client-component.md "시트 컬럼·셀 표준" 을 따름.
4
4
 
5
- ## SdSheet<TItem> (`sd-sheet`)
6
-
7
- ### 입력
8
-
9
- - `key: string` — 시트 설정(컬럼 폭/숨김/고정/순서) 저장 키. 지정하면 설정 버튼 + `SdSystemConfigProvider` 영속·복원. 없으면 설정 비활성.
10
- - `items: TItem[]` — 표시할 데이터.
11
- - `trackByFn: (item, index) => unknown` — 행 추적/선택 키 함수(기본 item 자체). 선택은 이 반환값을 키로 사용.
12
- - `selectMode: "single"|"multi"` 선택 모드. 미지정이면 선택 비활성. single 화살표, multi 는 체크박스(전체선택 헤더).
13
- - `autoSelect: "click"|"focus"` — 자동 선택 트리거. `"click"` = 행/셀 클릭 선택, `"focus"` = 포커스 이동만으로 선택. 키보드 위주 화면이면 `"focus"`.
14
- - `getItemSelectableFn: (item) => boolean | string` 선택 가능 여부. `false`/문자열(사유) 선택 불가(문자열은 툴팁으로 표시).
15
- - `getChildrenFn: (item, index) => TItem[] | undefined` — 트리 자식 함수. 지정펼침 기능 활성(들여쓰기 + 토글).
16
- - `useAutoSort: boolean` 클라이언트 정렬. true `sorts` 변경 시트가 직접 `items` 정렬. 서버 페이징/정렬이면 false(외부에서 재조회). `sd-crud-list` `totalPageCount===0` 때만 true.
17
- - `totalPageCount: number` — 서버 페이징 페이지 수. >0 이면 서버 페이징 모드(시트는 slice 함).
18
- - `itemsPerPage: number` — 클라이언트 페이징 페이지당 수. >0 이면 시트가 직접 slice. `totalPageCount` 와 택일.
19
- - `visiblePageCount: number` — 페이지네이터 표시 번호 개수(기본 10).
20
- - `focusMode: "row"|"cell"` — 키보드 포커스 단위. `"cell"`(기본) = 단위 이동·셀 포커스 표시, `"row"` = 행 단위(셀 표시 없음). 셀 편집/복사 화면이면 `"cell"`.
21
- - `inset: boolean` — 테두리·radius 제거(컨테이너 내장).
22
- - `contentStyle: string` — 스크롤 컨테이너 인라인 스타일.
23
- - `getItemCellClassFn: (item, colKey) => string` / `getItemCellStyleFn: (item, colKey) => string | undefined` 셀별 클래스/스타일(삭제행 취소선 등).
24
- - `hideConfigBar: boolean` 상단 설정/페이지 숨김.
25
- - `columnControlsInput: readonly SdSheetColumn[]` — 템플릿 외부에서 컬럼 디렉티브를 주입(투영 컬럼과 합쳐짐). `sd-crud-list` 가 투영 컬럼을 시트로 전달할 때 사용.
26
-
27
- ### 출력·모델
28
-
29
- - `selectedKeys: model<unknown[]>` — 선택된 키 배열(`trackByFn` 반환값). single 도 배열(길이 0/1).
30
- - `expandedItems: model<TItem[]>` — 펼쳐진 트리 항목.
31
- - `sorts: model<SortingDef[]>` — 정렬 상태(`{ key; desc }[]`). 헤더 클릭으로 토글(Shift=다중). `useAutoSort` 면 직접 정렬, 아니면 외부 재조회 트리거.
32
- - `currentPage: model<number>` — 현재 페이지(0-based).
33
- - `itemKeydown: output<SdSheetItemKeydownEventParam<TItem>>`행에서 키 입력(`{ item; event }`).
34
- - `cellKeydown: output<SdSheetCellKeydownEventParam<TItem>>` — 셀에서 키 입력(`{ item; key; event }`. key=컬럼 key).
35
-
36
- ### 컬럼·셀
37
-
38
- #### SdSheetColumn<T> (`sd-sheet-column`)
39
-
40
- 컬럼 정의 디렉티브. `<sd-sheet>`(또는 `sd-crud-list`) 직속 자식으로 둠.
41
-
42
- - `key: input.required<string>` — 컬럼 식별 키(설정 저장·셀 키).
43
- - `header: string | string[]` — 헤더 텍스트(배열이면 다단 헤더로 그룹).
44
- - `headerStyle: string` — 헤더 셀 스타일.
45
- - `tooltip: string` — 헤더 도움말(? 표시).
46
- - `width: string` — 컬럼 폭(미지정=자동). px 지정은 명시 지시 시만(client-component.md).
47
- - `fixed: boolean` — 좌측 고정 컬럼.
48
- - `hidden: boolean` — 숨김.
49
- - `collapse: boolean` — 접힘 컬럼.
50
- - `disableSorting: boolean` — 헤더 클릭 정렬 비활성.
51
- - `disableResizing: boolean` — 폭 드래그 리사이즈 비활성.
52
- - `ordering: number` — 컬럼 정렬 순서.
53
- - 템플릿: `<ng-template [cell]="items()" let-item="item">`(필수, 셀 본문), `#headerTpl`(헤더 커스텀), `#summaryTpl`(요약 행).
54
- - `SdSheetCellContext<T> = { $implicit; item: T; index: number; depth: number; edit: boolean }` — 셀 컨텍스트. `let-edit="edit"` 로 편집 모드 여부.
55
-
56
- #### SdSheetColumnCellTemplate<T> (`ng-template[cell]`)
5
+ ## SdSheet<TItem>
6
+
7
+ selector `sd-sheet`. 직속 자식으로 `<sd-sheet-column>` 들을 둠.
8
+
9
+ 입력:
10
+ - `key: string` — 컬럼 구성(폭/숨김/순서) 영속화 키. 지정 시 `injectSdSystemConfigResource` 로 저장/복원. 사용자별 컬럼 설정을 유지하려면 지정.
11
+ - `items: TItem[]` — 행 데이터.
12
+ - `trackByFn: (item, index) => unknown` 함수(기본은 item 자체). 선택 키·재렌더 추적.
13
+ - `selectMode: "single"|"multi"` — 선택 모드. 미지정선택 비활성. 다중 선택 화면이면 `"multi"`.
14
+ - `autoSelect: "click"|"focus"` 자동 선택 트리거. `"click"` = 클릭 선택, `"focus"` = 포커스 이동만으로 선택. 키보드 위주 화면이면 `"focus"`.
15
+ - `getItemSelectableFn: (item) => boolean | string` — 선택 가능 여부(문자열 반환 불가 사유).
16
+ - `getChildrenFn: (item, index) => TItem[] | undefined` 자식 반환(트리 모드). 값이 있으면 펼침 토글 표시.
17
+ - `useAutoSort: boolean` — 클라이언트 정렬. true `sorts` 변경 시트가 `items` 직접 정렬. 서버측 정렬/페이징이면 false 로 두고 외부에서 재조회.
18
+ - `visiblePageCount: number` — 페이지네이터가 번에 보이는 페이지 번호 수(기본 10).
19
+ - `totalPageCount: number` — 서버 페이징 페이지 수(서버 페이징 시 지정, `itemsPerPage` 와 택일).
20
+ - `itemsPerPage: number` — 클라이언트 페이징 페이지당 수(0 이면 페이징 ).
21
+ - `focusMode: "row"|"cell"` — 키보드 포커스 단위(기본 `"cell"`). `"row"` = 행 전체 이동, `"cell"` = 셀 단위 이동. 셀 편집·복사 화면이면 `"cell"`.
22
+ - `inset: boolean` — 테두리/모서리 없는 inset 스타일.
23
+ - `contentStyle: string` — 본문 인라인 스타일.
24
+ - `getItemCellClassFn`/`getItemCellStyleFn: (item, colKey) => ...` 셀별 클래스/스타일.
25
+ - `hideConfigBar: boolean` — 상단 컬럼 설정 숨김.
26
+
27
+ 출력/모델:
28
+ - `itemKeydown: output<{ item, event }>` — 행 단위 키다운.
29
+ - `cellKeydown: output<{ item, key, event }>` — 단위 키다운(`key` 컬럼 ).
30
+ - `selectedKeys: model<unknown[]>` — 선택된 키 배열(`trackByFn` 결과).
31
+ - `expandedItems: model<TItem[]>` — 펼쳐진 트리 행.
32
+ - `sorts: model<SortingDef[]>` — 정렬 상태(`{ key, desc }[]`).
33
+ - `currentPage: model<number>`현재 페이지(0 기반).
57
34
 
58
- 셀 본문 템플릿 디렉티브. `cell: input.required<T[]>` 는 타입 추론용 더미(실제 데이터는 시트 `items`). `let-item`/`let-index`/`let-depth`/`let-edit` 컨텍스트 제공.
59
-
60
- 사용:
61
35
  ```html
62
- <sd-sheet [items]="items()" [(selectedKeys)]="selectedKeys" selectMode="single" [trackByFn]="trackByFn">
36
+ <sd-sheet [items]="items()" [(selectedKeys)]="selectedKeys" selectMode="single"
37
+ [trackByFn]="trackByFn" [(currentPage)]="page" [totalPageCount]="pageLength()">
63
38
  <sd-sheet-column [key]="'name'" [header]="'이름'">
64
- <ng-template [cell]="items()" let-item="item">
65
- <div class="p-xs-sm">{{ item.name }}</div>
66
- </ng-template>
39
+ <ng-template [cell]="items()" let-item="item"><div class="p-xs-sm">{{ item.name }}</div></ng-template>
67
40
  </sd-sheet-column>
68
41
  </sd-sheet>
69
42
  ```
70
43
 
71
- #### SdSheetConfigModal (`sd-sheet-config-modal`)
44
+ ## SdSheetColumn<T>
45
+
46
+ `sd-sheet-column` 디렉티브. 컬럼 1개를 정의.
47
+
48
+ - `key: input.required<string>` — 컬럼 식별 키(정렬·셀 키다운·구성 저장 단위).
49
+ - `header: string | string[]` — 헤더 텍스트. 배열이면 다단(그룹) 헤더.
50
+ - `headerStyle: string` — 헤더 셀 인라인 스타일.
51
+ - `tooltip: string` — 헤더 툴팁.
52
+ - `width: string` — 컬럼 폭(미지정이 기본=자동). px 지정은 명시 지시 시에만.
53
+ - `fixed: boolean` — 좌측 고정 컬럼.
54
+ - `hidden: boolean` — 숨김.
55
+ - `collapse: boolean` — 접힘(헤더만, 폭 최소).
56
+ - `disableSorting: boolean` — 정렬 비활성.
57
+ - `disableResizing: boolean` — 폭 리사이즈 비활성.
58
+ - `ordering: number` — 컬럼 표시 순서.
59
+ - 컨텐츠: `<ng-template [cell]="items()" let-item="item">`(셀 본문), `#headerTpl`(커스텀 헤더), `#summaryTpl`(요약 행).
60
+
61
+ 셀 컨텍스트(`SdSheetCellContext`): `$implicit`/`item`/`index`/`depth`(트리 깊이)/`edit`(편집 모드 여부).
62
+
63
+ ## SdSheetColumnCellTemplate<T>
72
64
 
73
- 컬럼 폭/순서/고정/숨김을 사용자가 조정하는 설정 모달(`SdModalContentDef<SdSheetConfig | undefined>`). `key` 있는 시트의 설정 버튼이 자동으로 띄움 → 직접 호출 불필요.
65
+ `ng-template[cell]` 디렉티브. 본문 템플릿에 타입 가드를 부여.
74
66
 
75
- - `sheetKey: input.required<string>` / `controls: input.required<readonly SdSheetColumn[]>` / `config: input.required<SdSheetConfig | undefined>` 대상 시트 키·컬럼들·현재 설정.
76
- - `close: output<SdSheetConfig | undefined>` — 변경된 설정(취소 시 undefined).
67
+ - `cell: input.required<T[]>` 타입 추론용 더미(보통 `items()` 전달). 실제 데이터는 `<sd-sheet>` `items` 보유.
77
68
 
78
- ### 타입 (data/sheet/types)
69
+ ## SdSheetConfigModal
79
70
 
80
- - `SdSheetColumnDef` 내부 계산된 컬럼 정의(`key/header/headerStyle/tooltip/width/fixed/hidden/collapse/disableSorting/disableResizing/ordering`).
81
- - `SdSheetHeaderDef` — 헤더 셀 렌더 정의(`text/colspan/rowspan/isLastRow/fixed/colDef/colIndex`).
82
- - `SdSheetConfig = { columnRecord: Record<string, { width?; hidden?; fixed?; ordering? }> }` — 영속되는 시트 설정.
83
- - `SdSheetItemKeydownEventParam<T> = { item: T; event: KeyboardEvent }`.
84
- - `SdSheetCellKeydownEventParam<T> = { item: T; key: string; event: KeyboardEvent }`.
71
+ 컬럼 폭/숨김/고정/순서를 사용자에게 편집시키는 모달(`SdModalContentDef<SdSheetConfig>`). 시트 설정 바에서 자동 호출되므로 직접 띄울 일은 드묾.
85
72
 
86
- ### 요약
73
+ ## 타입 (types)
87
74
 
88
- 컬럼 중 하나라도 `#summaryTpl` 가지면 헤더 하단에 요약 행이 고정 렌더(warning 배경). 집계 값은 시트가 계산하지 않으므로 화면에서 `computed` 로 만들어 넣음(client-component.md).
75
+ - `SdSheetColumnDef` 컬럼 해석 결과(`key`/`header`/`width`/`fixed`/`hidden`/`collapse`/`disableSorting`/`disableResizing`/`ordering` ).
76
+ - `SdSheetHeaderDef` — 다단 헤더 셀 정의(`text`/`colspan`/`rowspan`/`isLastRow`/`fixed`/`colDef`/`colIndex`).
77
+ - `SdSheetConfig` — 영속화되는 컬럼별 사용자 설정(`columnRecord[key] = { width?, hidden?, fixed?, ordering? }`).
78
+ - `SdSheetItemKeydownEventParam<T>` — `{ item, event }`(행 키다운 페이로드).
79
+ - `SdSheetCellKeydownEventParam<T>` — `{ item, key, event }`(셀 키다운 페이로드).
@@ -1,27 +1,27 @@
1
1
  # @simplysm/capacitor-plugin-auto-update
2
2
 
3
- Capacitor 앱(Android)에서 APK 설치 인텐트를 실행하고, 서버 또는 외부 저장소의 최신 APK 를 받아 자동 업데이트하는 플러그인. 공개 심볼은 모두 `static` 멤버만 가진 abstract 클래스이거나 타입이라 인스턴스 없이 클래스명으로 직접 호출. 비-Android(웹) 환경에서는 `ApkInstallerWeb` 폴백으로 설치는 no-op, 권한은 항상 통과로 동작.
3
+ Android APK 자동 업데이트 Capacitor 플러그인. 부팅 시 서버 또는 외부 저장소의 최신 APK 를 받아 설치하는 오케스트레이터(`AutoUpdate`)와, APK 설치·설치 권한·앱 버전 조회를 다루는 저수준 정적 클래스(`ApkInstaller`)·타입을 제공. 공개 심볼은 모두 `static` 멤버만 가진 abstract 클래스이거나 인터페이스라 인스턴스 없이 클래스명으로 직접 호출. 비-Android(웹)에서는 폴백 구현으로 설치는 no-op, 권한은 항상 통과.
4
4
 
5
5
  ## 사용 트리거 인덱스
6
6
 
7
- - **AutoUpdate** — 앱 부팅 "최신 확인 → 권한 → 다운로드 → 설치 → 앱 멈춤" 까지 한 번에 돌리는 자동 업데이트 오케스트레이터. 서버 기반(`run`) 또는 외부 저장소 기반(`runByExternalStorage`). 부트스트랩에서 1회 호출.
8
- - **ApkInstaller** — APK 설치/권한 확인·요청/현재 버전 조회를 직접 호출하는 저수준 정적 클래스. 자동 업데이트 흐름을 직접 짜거나 단건 설치·권한 처리만 필요할 때.
9
- - **ApkInstallerPlugin / VersionInfo** — Capacitor 네이티브 브리지 인터페이스 버전 정보 타입. 직접 호출보다는 `ApkInstaller` 가 감싸므로 반환 타입 참조·웹 구현체 작성 시 참조용.
7
+ - **AutoUpdate** — 앱 부팅 시점에 "최신 확인 → 권한 → 다운로드 → 설치 → 앱 멈춤" 까지 한 번에 도는 진입점. 서버(`ServiceClient`) 연동 업데이트면 `run`, USB/외장 저장소 폴더에서 가져오면 `runByExternalStorage`.
8
+ - **ApkInstaller** — `AutoUpdate` 거치지 않고 설치 권한 확인/요청, 특정 APK 설치, 현재 버전 직접 조회가 필요할 쓰는 저수준 정적 클래스.
9
+ - **ApkInstallerPlugin / VersionInfo** — Capacitor 네이티브 브리지 인터페이스와 버전 정보 형태. `ApkInstaller` 가 감싸므로 직접 호출보다는 반환 타입 참조용.
10
10
 
11
11
  ## AutoUpdate
12
12
 
13
- `abstract class AutoUpdate` — 정적 메서드만 가진 고수준 오케스트레이터. 두 진입점 모두 내부에서 Android 여부 확인 → 설치 권한 확인/요청 → 버전 비교 → 다운로드/설치 → 무한 freeze 순으로 일괄 처리. 진행 단계는 `log` 콜백으로 HTML 문자열을 흘려보내고, 도중 발생한 모든 예외를 잡아 `log` 로 에러 메시지를 표시한 뒤 영원히 resolve 되지 않는 무한 대기로 진입해 구버전 실행을 막는다. Android 가 아니면(`navigator.userAgent` 에 "android" 없음) `"Android만 지원됩니다."` 로 throw 되어 catch 에서 표시됨.
13
+ `abstract class AutoUpdate` — 정적 메서드만 가진 부트 시 업데이트 오케스트레이터. 두 진입점 모두 Android 여부 확인 → 설치 권한 확인/요청 → 버전 비교 → 다운로드/설치 → 무한 freeze 순으로 일괄 처리. 진행 단계는 `log` 콜백으로 HTML 문자열을 흘려보내고, 도중 발생한 모든 예외를 잡아 `log` 로 오류 메시지를 표시한 뒤 영원히 resolve 되지 않는 무한 대기로 진입해 구버전 실행을 막음.
14
14
 
15
15
  ```typescript
16
16
  static run(opt: { log: (messageHtml: string) => void; serviceClient: ServiceClient }): Promise<void>
17
17
  static runByExternalStorage(opt: { log: (messageHtml: string) => void; dirPath: string }): Promise<void>
18
18
  ```
19
19
 
20
- - log: (messageHtml: string) => void — 진행/오류 상태를 HTML 문자열로 받는 콜백. 매 단계마다 여러 번 호출되며 `"최신 버전 확인 중..."`, `"권한 확인 중..."`, 다운로드 진행률 `(NN.NN%)`, 권한 활성화·재시도 버튼 HTML, 오류 메시지 등이 인자로 들어옴. 부팅 스플래시 등에 그대로 `innerHTML` 로 렌더하는 용도. 버튼 인터랙티브 HTML 포함되므로 텍스트가 아닌 HTML 로 렌더해야 재시도/다운로드 링크가 동작.
21
- - serviceClient: ServiceClient (`run` 전용) — `@simplysm/service-client` 의 서비스 클라이언트. 내부에서 `getService<AutoUpdateService>("AutoUpdate").getLastVersion("android")` 로 최신 버전·다운로드 경로를 조회하고, `serviceClient.hostUrl + downloadPath` 로 `fetchUrlBytes` 다운로드. 서버가 버전 정보를 안 주면(`undefined`) 무동작 반환(업데이트 없음). 서버 연동 배포일 때 사용.
22
- - dirPath: string (`runByExternalStorage` 전용) — 외부 저장소(`FileSystem.getStoragePath("external")`) 기준 상대 디렉토리 경로. 이 폴더의 비-디렉토리 항목 중 확장자가 `.apk` 이고 파일명(확장자 제외)이 `^[0-9.]*$`(숫자·점) 인 것을 버전으로 보고 `semver.maxSatisfying(..., "*")` 로 최신을 선정. 서버 없이 USB/SD 등으로 사이드로딩 배포할 때 사용.
20
+ - opt.log: (messageHtml: string) => void — 진행/오류 상태를 HTML 문자열로 받는 콜백. 매 단계마다 여러 번 호출되며 `"최신 버전 확인 중..."`, `"권한 확인 중..."`, 다운로드 진행률 `(NN.NN%)`, 권한 활성화·재시도 버튼 HTML, 오류 메시지 등이 인자로 들어옴. 부팅 스플래시 등에 그대로 `innerHTML` 로 렌더하는 용도 재시도/다운로드 버튼이 HTML 이므로 텍스트가 아닌 HTML 로 렌더해야 동작.
21
+ - opt.serviceClient: ServiceClient (`run` 전용) — `@simplysm/service-client` 의 서비스 클라이언트. 내부에서 `getService<AutoUpdateService>("AutoUpdate").getLastVersion("android")` 로 최신 버전·다운로드 경로를 조회하고 `serviceClient.hostUrl + downloadPath` 로 `fetchUrlBytes` 다운로드. 서버 연동 배포일 때 사용.
22
+ - opt.dirPath: string (`runByExternalStorage` 전용) — `external` 저장소(`FileSystem.getStoragePath("external")`) 루트 기준 상대 디렉토리 경로. 이 폴더의 비-디렉토리 항목 중 확장자가 `.apk` 이고 파일명(확장자 제외)이 `^[0-9.]*$`(숫자·점) 인 것을 버전으로 보고 `semver.maxSatisfying(..., "*")` 로 최신을 선정. 서버 없이 USB/SD 등으로 사이드로딩 배포할 때 사용.
23
23
 
24
- 동작 차이: `run` 은 다운로드한 바이트를 `appCache` 저장소의 `latest.apk` 로 써서 설치하고, `runByExternalStorage` 는 외부 저장소의 `<dirPath>/<version>.apk` 를 직접 설치 대상으로 삼는다. 두 경로 모두 현재 버전(`ApkInstaller.getVersionInfo().versionName`)과 비교해 `semver.gt(최신, 현재)` 가 아니면(이미 최신·동일·낮음) 반환하고, 어느 한쪽이라도 유효한 semver 가 아니면 업데이트 확인을 건너뛴다.
24
+ 동작 차이: `run` 은 다운로드한 바이트를 `appCache` 저장소의 `latest.apk` 로 써서 설치하고, `runByExternalStorage` 는 외부 저장소의 `<dirPath>/<version>.apk` 를 직접 설치 대상으로 삼음. 두 경로 모두 현재 버전(`ApkInstaller.getVersionInfo().versionName`)과 비교해 `semver.gt(최신, 현재)` 가 아니면(이미 최신·동일·낮음) 반환하고, 어느 한쪽이라도 유효한 semver 가 아니면 업데이트 확인을 건너뜀.
25
25
 
26
26
  ```typescript
27
27
  // 앱 부트스트랩에서 (서버 기반)
@@ -30,11 +30,16 @@ await AutoUpdate.run({ log: (h) => (statusEl.innerHTML = h), serviceClient });
30
30
  await AutoUpdate.runByExternalStorage({ log: (h) => (statusEl.innerHTML = h), dirPath: "myapp-apks" });
31
31
  ```
32
32
 
33
- 주의: 업데이트가 없으면 그냥 반환하지만, 설치를 진행하거나 오류가 나면 무한 대기로 들어가 영원히 resolve 되지 않으므로 호출 후속 코드에 의존하지 말 것 — 후속 코드는 "업데이트 없음" 경로에서만 실행된다. 권한 미승인 시 설정 화면으로 보낸 뒤 최대 5분(1초 간격 300회) 동안 권한 부여를 폴링. APK manifest 에 `REQUEST_INSTALL_PACKAGES` 가 없거나(`manifest:false`) 권한 확인 자체가 실패하면 "APK 파일을 다시 다운로드하여 설치해야 합니다(코드)." 안내(코드 1·2, 다운로드 링크 버튼 포함)와 함께 throw.
33
+ 주의사항:
34
+
35
+ - 업데이트가 없거나 이미 최신이면 그냥 반환(freeze 안 함)하므로 후속 정상 부트로 이어가면 됨. 반대로 설치를 진행하거나 오류가 나면 무한 대기로 들어가 영원히 resolve 되지 않으니 호출 후속 코드에 의존하지 말 것 — 후속 코드는 "업데이트 없음" 경로에서만 실행됨.
36
+ - Android 외 환경(`navigator.userAgent` 에 "android" 없음)이면 `"Android만 지원됩니다."` throw → catch 에서 표시 후 freeze.
37
+ - 권한 미승인 시 설정 화면으로 보낸 뒤 최대 5분(1초 간격 300회) 동안 권한 부여를 폴링 대기.
38
+ - manifest 에 `REQUEST_INSTALL_PACKAGES` 가 없거나(`manifest:false`) 권한 확인 자체가 실패하면 "APK 파일을 다시 다운로드하여 설치해야 합니다(코드)." 안내(코드 1·2, `run` 은 다운로드 링크 버튼 포함)와 함께 throw.
34
39
 
35
40
  ## ApkInstaller
36
41
 
37
- `abstract class ApkInstaller` — APK 설치 관련 네이티브 호출을 감싼 저수준 정적 클래스. Android 는 실제 인텐트/권한을 다루고, 브라우저(web)는 `ApkInstallerWeb` 가 대체 구현(설치 시 미지원 alert 후 정상 반환, 권한은 항상 `granted:true`/`manifest:true`). `AutoUpdate` 가 내부에서 쓰며, 자동 업데이트 UX 를 직접 구성하거나 권한·설치만 단독으로 다룰 때 직접 호출.
42
+ `abstract class ApkInstaller` — APK 설치 관련 네이티브 호출을 감싼 저수준 정적 클래스. Android 는 실제 인텐트/권한을 다루고, 브라우저(web)는 폴백 구현(설치 시 미지원 alert 후 정상 반환, 권한은 항상 `granted:true`/`manifest:true`, 버전은 `__VER__` env 또는 `0.0.0`). `AutoUpdate` 가 내부에서 쓰며, 자동 업데이트 UX 를 직접 구성하거나 권한·설치만 단독으로 다룰 때 직접 호출.
38
43
 
39
44
  ```typescript
40
45
  static checkPermissions(): Promise<{ granted: boolean; manifest: boolean }>
@@ -43,10 +48,10 @@ static install(apkUri: string): Promise<void>
43
48
  static getVersionInfo(): Promise<VersionInfo>
44
49
  ```
45
50
 
46
- - checkPermissions() { granted, manifest } — 설치 권한 상태 조회. granted: boolean = `REQUEST_INSTALL_PACKAGES` 권한이 사용자에게 승인되었는지(false 면 `requestPermissions` 로 유도), manifest: boolean = 해당 권한이 앱 manifest 에 선언되어 있는지(false 면 권한 요청 자체가 불가 → APK 재설치 필요). 설치 전 사전 점검·재시도 폴링에 사용. 웹에서는 둘 다 true.
47
- - requestPermissions() void — 설치 권한 요청. Android 에서는 시스템 설정 화면으로 이동시키며 즉시 부여되지 않으므로 이후 `checkPermissions` 폴링으로 승인 여부를 확인해야 함. 웹에서는 동작 없음.
48
- - install(apkUri: string) void — APK 설치 인텐트 실행. apkUri 는 로컬 경로가 아니라 `content://` FileProvider URI (보통 `@simplysm/capacitor-plugin-file-system` 의 `FileSystem.getUri(파일경로)` 결과). 내부에서 `{ uri: apkUri }` 로 래핑해 플러그인에 전달. 웹에서는 미지원 alert 후 반환.
49
- - getVersionInfo() VersionInfo — 현재 설치된 앱 버전 조회. 서버/외부 버전과 비교할 현재 버전 기준값. 웹 구현은 `versionName` 을 빌드 시 주입된 `env("__VER__")`(없으면 `"0.0.0"`), `versionCode` 를 `"0"` 으로 응답.
51
+ - checkPermissions(): Promise<{ granted: boolean; manifest: boolean }> — 설치 권한 상태 조회. `granted` = `REQUEST_INSTALL_PACKAGES` 권한이 사용자에게 승인되었는지(false 면 `requestPermissions` 로 유도), `manifest` = 해당 권한이 앱 manifest 에 선언되어 있는지(false 면 권한 요청 자체가 불가 → APK 재설치 필요). 설치 전 사전 점검·재시도 폴링에 사용. 웹에서는 둘 다 true.
52
+ - requestPermissions(): Promise<void> — 설치 권한 요청. Android 에서는 시스템 설정 화면으로 이동시키며 즉시 부여되지 않으므로 이후 `checkPermissions` 폴링으로 승인 여부를 확인해야 함. 웹에서는 동작 없음.
53
+ - install(apkUri: string): Promise<void> — APK 설치 인텐트 실행. `apkUri` 는 로컬 파일 경로가 아니라 `content://` FileProvider URI (보통 `@simplysm/capacitor-plugin-file-system` 의 `FileSystem.getUri(파일경로)` 결과). 내부에서 `{ uri: apkUri }` 로 래핑해 플러그인에 전달. 웹에서는 미지원 alert 후 반환.
54
+ - getVersionInfo(): Promise<VersionInfo> — 현재 설치된 앱 버전 조회. 서버/외부 버전과 비교할 현재 버전 기준값. 웹 구현은 `versionName` 을 빌드 시 주입된 `env("__VER__")`(없으면 `"0.0.0"`), `versionCode` 를 `"0"` 으로 응답.
50
55
 
51
56
  ```typescript
52
57
  const { granted, manifest } = await ApkInstaller.checkPermissions();
@@ -57,7 +62,7 @@ await ApkInstaller.install(await FileSystem.getUri(apkFilePath));
57
62
 
58
63
  ## ApkInstallerPlugin / VersionInfo
59
64
 
60
- Capacitor `registerPlugin("ApkInstaller")` 로 등록되는 네이티브 브리지 인터페이스와 버전 정보 타입. `ApkInstaller` 의 정적 메서드가 이 인터페이스 구현체(네이티브 또는 `ApkInstallerWeb`)에 위임하므로, 직접 구현·호출할 일은 드물고 주로 `getVersionInfo` 반환 형태 참조·웹 구현체 작성 시에만 본다.
65
+ Capacitor `registerPlugin("ApkInstaller")` 로 등록되는 네이티브 브리지 인터페이스와 버전 정보 타입. `ApkInstaller` 의 정적 메서드가 이 인터페이스 구현체(네이티브 또는 웹 폴백)에 위임하므로, 직접 구현·호출할 일은 드물고 주로 `getVersionInfo` 반환 형태 참조·웹 구현체 작성 본다.
61
66
 
62
67
  ```typescript
63
68
  interface VersionInfo {
@@ -75,5 +80,5 @@ interface ApkInstallerPlugin {
75
80
 
76
81
  - VersionInfo.versionName: string — 사람이 읽는 표시 버전(semver, 예 `"1.2.3"`). `AutoUpdate` 의 버전 비교(`semver.gt`/`semver.valid`)는 이 값을 semver 로 사용.
77
82
  - VersionInfo.versionCode: string — Android 빌드 버전 코드의 문자열 표현. 웹 구현에서는 `"0"` 고정. 현재 업데이트 판정 로직은 `versionName` 만 사용하며 이 값은 표시·식별용.
78
- - ApkInstallerPlugin.install(options: { uri: string }) — content URI APK 설치. `ApkInstaller.install(apkUri)` 가 `{ uri: apkUri }` 형태로 래핑해 전달하며 uri 는 `content://` FileProvider URI.
79
- - ApkInstallerPlugin.checkPermissions / requestPermissions / getVersionInfo — 각각 `ApkInstaller` 의 동명 정적 메서드가 그대로 위임하는 원본 브리지 메서드(권한 조회 / 권한 요청 / 현재 버전 조회).
83
+ - ApkInstallerPlugin.install(options: { uri: string }): Promise<void> — content URI APK 설치(네이티브 측 진입점). `ApkInstaller.install(apkUri)` 가 `{ uri: apkUri }` 래핑해 호출하며 uri 는 `content://` FileProvider URI.
84
+ - ApkInstallerPlugin.checkPermissions / requestPermissions / getVersionInfo — 각각 `ApkInstaller` 의 동명 정적 메서드가 그대로 위임하는 원본 브리지 메서드(권한 조회 / 권한 요청 / 현재 버전 조회). 의미는 위 `ApkInstaller` 항목과 동일.