@hjmds/design-contracts 1.1.0 → 1.2.0

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 (198) hide show
  1. package/dist/agreement.d.ts +183 -0
  2. package/dist/agreement.d.ts.map +1 -0
  3. package/dist/agreement.js +137 -0
  4. package/dist/agreement.js.map +1 -0
  5. package/dist/alert-dialog.d.ts +1 -1
  6. package/dist/alert-dialog.d.ts.map +1 -1
  7. package/dist/alert-dialog.js.map +1 -1
  8. package/dist/anchor.d.ts +62 -0
  9. package/dist/anchor.d.ts.map +1 -0
  10. package/dist/anchor.js +43 -0
  11. package/dist/anchor.js.map +1 -0
  12. package/dist/asset.d.ts +94 -0
  13. package/dist/asset.d.ts.map +1 -0
  14. package/dist/asset.js +60 -0
  15. package/dist/asset.js.map +1 -0
  16. package/dist/base-recipes.d.ts +19 -0
  17. package/dist/base-recipes.d.ts.map +1 -1
  18. package/dist/base-recipes.js +17 -0
  19. package/dist/base-recipes.js.map +1 -1
  20. package/dist/behaviors.d.ts +346 -3
  21. package/dist/behaviors.d.ts.map +1 -1
  22. package/dist/behaviors.js +35 -1
  23. package/dist/behaviors.js.map +1 -1
  24. package/dist/bottom-info.d.ts +58 -0
  25. package/dist/bottom-info.d.ts.map +1 -0
  26. package/dist/bottom-info.js +48 -0
  27. package/dist/bottom-info.js.map +1 -0
  28. package/dist/carousel.d.ts +3 -3
  29. package/dist/carousel.js +1 -1
  30. package/dist/carousel.js.map +1 -1
  31. package/dist/catalog.d.ts +1294 -239
  32. package/dist/catalog.d.ts.map +1 -1
  33. package/dist/catalog.js +65 -25
  34. package/dist/catalog.js.map +1 -1
  35. package/dist/collapsible.d.ts +56 -0
  36. package/dist/collapsible.d.ts.map +1 -0
  37. package/dist/collapsible.js +37 -0
  38. package/dist/collapsible.js.map +1 -0
  39. package/dist/component-definitions.d.ts +16 -0
  40. package/dist/component-definitions.d.ts.map +1 -1
  41. package/dist/component-definitions.js +16 -0
  42. package/dist/component-definitions.js.map +1 -1
  43. package/dist/component-recipes.d.ts +76 -4
  44. package/dist/component-recipes.d.ts.map +1 -1
  45. package/dist/component-recipes.js +38 -1
  46. package/dist/component-recipes.js.map +1 -1
  47. package/dist/context-menu.d.ts +53 -0
  48. package/dist/context-menu.d.ts.map +1 -0
  49. package/dist/context-menu.js +44 -0
  50. package/dist/context-menu.js.map +1 -0
  51. package/dist/counter-badge-recipe.d.ts +5 -0
  52. package/dist/counter-badge-recipe.d.ts.map +1 -1
  53. package/dist/counter-badge-recipe.js +5 -0
  54. package/dist/counter-badge-recipe.js.map +1 -1
  55. package/dist/dataviz.d.ts +76 -0
  56. package/dist/dataviz.d.ts.map +1 -0
  57. package/dist/dataviz.js +58 -0
  58. package/dist/dataviz.js.map +1 -0
  59. package/dist/date-range.d.ts +56 -0
  60. package/dist/date-range.d.ts.map +1 -0
  61. package/dist/date-range.js +79 -0
  62. package/dist/date-range.js.map +1 -0
  63. package/dist/design-system-provider.d.ts +24 -0
  64. package/dist/design-system-provider.d.ts.map +1 -1
  65. package/dist/design-system-provider.js +21 -0
  66. package/dist/design-system-provider.js.map +1 -1
  67. package/dist/floating-action-button.d.ts +1 -1
  68. package/dist/floating-action-button.d.ts.map +1 -1
  69. package/dist/floating-action-button.js +3 -1
  70. package/dist/floating-action-button.js.map +1 -1
  71. package/dist/formatters.d.ts +37 -0
  72. package/dist/formatters.d.ts.map +1 -0
  73. package/dist/formatters.js +69 -0
  74. package/dist/formatters.js.map +1 -0
  75. package/dist/heading.d.ts +82 -0
  76. package/dist/heading.d.ts.map +1 -0
  77. package/dist/heading.js +48 -0
  78. package/dist/heading.js.map +1 -0
  79. package/dist/index.d.ts +19 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +19 -0
  82. package/dist/index.js.map +1 -1
  83. package/dist/menubar.d.ts +133 -0
  84. package/dist/menubar.d.ts.map +1 -0
  85. package/dist/menubar.js +80 -0
  86. package/dist/menubar.js.map +1 -0
  87. package/dist/native-platform.d.ts +88 -0
  88. package/dist/native-platform.d.ts.map +1 -0
  89. package/dist/native-platform.js +70 -0
  90. package/dist/native-platform.js.map +1 -0
  91. package/dist/popover.d.ts +19 -4
  92. package/dist/popover.d.ts.map +1 -1
  93. package/dist/popover.js +3 -0
  94. package/dist/popover.js.map +1 -1
  95. package/dist/progress-recipe.d.ts +14 -0
  96. package/dist/progress-recipe.d.ts.map +1 -1
  97. package/dist/progress-recipe.js +14 -1
  98. package/dist/progress-recipe.js.map +1 -1
  99. package/dist/provider-button.d.ts +140 -0
  100. package/dist/provider-button.d.ts.map +1 -0
  101. package/dist/provider-button.js +83 -0
  102. package/dist/provider-button.js.map +1 -0
  103. package/dist/recipes.d.ts +15 -1
  104. package/dist/recipes.d.ts.map +1 -1
  105. package/dist/recipes.js +14 -0
  106. package/dist/recipes.js.map +1 -1
  107. package/dist/sheet.d.ts +16 -0
  108. package/dist/sheet.d.ts.map +1 -1
  109. package/dist/sheet.js +33 -0
  110. package/dist/sheet.js.map +1 -1
  111. package/dist/sidebar.d.ts +168 -0
  112. package/dist/sidebar.d.ts.map +1 -0
  113. package/dist/sidebar.js +98 -0
  114. package/dist/sidebar.js.map +1 -0
  115. package/dist/skip-nav.d.ts +67 -0
  116. package/dist/skip-nav.d.ts.map +1 -0
  117. package/dist/skip-nav.js +47 -0
  118. package/dist/skip-nav.js.map +1 -0
  119. package/dist/tags-input.d.ts +157 -0
  120. package/dist/tags-input.d.ts.map +1 -0
  121. package/dist/tags-input.js +108 -0
  122. package/dist/tags-input.js.map +1 -0
  123. package/dist/text-formats.d.ts +85 -0
  124. package/dist/text-formats.d.ts.map +1 -0
  125. package/dist/text-formats.js +43 -0
  126. package/dist/text-formats.js.map +1 -0
  127. package/dist/toggle-group.d.ts +111 -0
  128. package/dist/toggle-group.d.ts.map +1 -0
  129. package/dist/toggle-group.js +78 -0
  130. package/dist/toggle-group.js.map +1 -0
  131. package/dist/top.d.ts +113 -0
  132. package/dist/top.d.ts.map +1 -0
  133. package/dist/top.js +72 -0
  134. package/dist/top.js.map +1 -0
  135. package/dist/version.d.ts +1 -1
  136. package/dist/version.js +1 -1
  137. package/dist/version.js.map +1 -1
  138. package/docs/agreement.md +33 -0
  139. package/docs/anchor.md +59 -59
  140. package/docs/ant-design-coverage.md +5 -3
  141. package/docs/architecture.md +10 -0
  142. package/docs/asset.md +26 -0
  143. package/docs/bottom-info.md +15 -0
  144. package/docs/breadcrumb.md +15 -15
  145. package/docs/button-label.md +20 -0
  146. package/docs/calendar.md +82 -154
  147. package/docs/carousel.md +27 -2
  148. package/docs/cascader.md +17 -0
  149. package/docs/chart.md +33 -0
  150. package/docs/clipboard.md +13 -0
  151. package/docs/collapsible.md +19 -0
  152. package/docs/command-palette.md +19 -0
  153. package/docs/confirm-popover.md +11 -1
  154. package/docs/context-menu.md +23 -0
  155. package/docs/data-table.md +22 -0
  156. package/docs/date-range.md +31 -0
  157. package/docs/density.md +23 -0
  158. package/docs/expansion-roadmap.md +16 -3
  159. package/docs/floating-action-button.md +35 -3
  160. package/docs/formatters.md +18 -0
  161. package/docs/generated/component-maturity.md +39 -23
  162. package/docs/generated/renderer-evidence.json +2921 -642
  163. package/docs/generated/renderer-evidence.md +53 -6
  164. package/docs/generated/showcase-manifest.json +1087 -64
  165. package/docs/heading.md +25 -0
  166. package/docs/identity.md +5 -0
  167. package/docs/layout.md +1 -1
  168. package/docs/library-gap-analysis.md +29 -9
  169. package/docs/list-row.md +14 -0
  170. package/docs/mentions.md +18 -0
  171. package/docs/menubar.md +22 -0
  172. package/docs/native-platform.md +36 -0
  173. package/docs/overlay-stack.md +24 -0
  174. package/docs/pagination.md +16 -8
  175. package/docs/popover.md +42 -13
  176. package/docs/product-audit-2026-09-15.md +88 -0
  177. package/docs/progress.md +16 -0
  178. package/docs/provider-button.md +33 -0
  179. package/docs/rating.md +19 -0
  180. package/docs/react-native-completion.md +347 -0
  181. package/docs/screen-chrome.md +52 -0
  182. package/docs/side-panel.md +23 -2
  183. package/docs/sidebar.md +30 -0
  184. package/docs/skip-nav.md +20 -0
  185. package/docs/splitter.md +22 -2
  186. package/docs/stable-promotion.md +51 -0
  187. package/docs/tags-input.md +25 -0
  188. package/docs/text-formats.md +15 -0
  189. package/docs/theming.md +84 -0
  190. package/docs/time-picker.md +22 -1
  191. package/docs/toast.md +18 -0
  192. package/docs/toggle-group.md +21 -0
  193. package/docs/top.md +32 -0
  194. package/docs/tour.md +23 -2
  195. package/docs/transfer-list.md +20 -0
  196. package/docs/tree-select.md +18 -0
  197. package/docs/tree.md +21 -0
  198. package/package.json +120 -6
@@ -0,0 +1,183 @@
1
+ import { type CheckboxSelectionItem, type CheckboxState } from "./selection-helpers.js";
2
+ /**
3
+ * 가입·결제 앞에 서는 "약관 동의" 묶음이다. CheckboxGroup으로 매번 조립하던 것을
4
+ * 계약으로 올리는 이유는 세 가지가 **체크박스 목록이 아니라 법적 절차**이기 때문이다.
5
+ *
6
+ * 1. 필수/선택이 제출 가능 여부를 결정한다 — CheckboxGroup은 그 판정을 갖지 않는다.
7
+ * 2. 전체 동의는 자식의 합이지 별도 항목이 아니다(DataTable 머리글·TreeSelect 부모와
8
+ * 같은 파생 관계). 저장되는 것은 개별 항목뿐이다.
9
+ * 3. 각 항목은 "읽을 수 있어야" 한다 — 상세 보기 경로가 없는 동의는 동의가 아니다.
10
+ * 그래서 `detail`은 renderer가 장식으로 넣는 화살표가 아니라 계약된 행동이다.
11
+ *
12
+ * 문구·링크 주소·법적 유효성은 제품이 소유한다. 이 계약은 판정과 배치만 갖는다.
13
+ */
14
+ export type AgreementItemDescriptor<Id extends string = string> = CheckboxSelectionItem<Id> & Readonly<{
15
+ /** 필수 항목은 하나라도 비면 제출을 막는다. 기본값은 선택(false). */
16
+ required?: boolean;
17
+ /**
18
+ * 전문을 여는 경로. 제품이 링크(`href`)로 열지 시트로 열지는 renderer 밖의
19
+ * 결정이라 여기서는 "열 수 있다"는 사실만 계약한다.
20
+ */
21
+ detail?: Readonly<{
22
+ label: string;
23
+ href?: string;
24
+ }>;
25
+ }>;
26
+ export type AgreementDescriptor<Id extends string = string> = Readonly<{
27
+ /** 묶음 전체의 접근성 이름. "약관 동의"처럼 제품이 현지화한다. */
28
+ accessibilityLabel: string;
29
+ /** 전체 동의 행의 라벨. 필수이므로 renderer가 임의 문구를 만들지 않는다. */
30
+ allLabel: string;
31
+ items: readonly AgreementItemDescriptor<Id>[];
32
+ }>;
33
+ export declare function validateAgreementDescriptor<Id extends string>(descriptor: AgreementDescriptor<Id>): void;
34
+ export type AgreementState<Id extends string = string> = Readonly<{
35
+ /** 전체 동의 행이 그릴 tri-state. 개별 항목의 합에서만 나온다. */
36
+ all: CheckboxState;
37
+ /** 필수 항목이 모두 체크됐는가 — 제품의 제출 버튼이 읽는 값. */
38
+ satisfied: boolean;
39
+ /** 아직 체크되지 않은 필수 항목. 제품이 "무엇이 남았는지" 문장을 만든다. */
40
+ missingRequiredIds: readonly Id[];
41
+ }>;
42
+ /**
43
+ * 비활성 항목은 분모에서 뺀다 — 사용자가 바꿀 수 없는 것을 "덜 동의했다"고 셀 수
44
+ * 없기 때문이다(`resolveDataTableSelectAllState`·`resolveTreeCheckedStates`와 같은 규칙).
45
+ * 필수 항목은 비활성일 수 없으므로(위 validate) `satisfied` 판정에는 이 예외가 없다.
46
+ */
47
+ export declare function resolveAgreementState<Id extends string>(descriptor: AgreementDescriptor<Id>, checkedIds: ReadonlySet<Id>): AgreementState<Id>;
48
+ /** 개별 항목 토글. 비활성 항목 보호는 공용 helper가 이미 갖고 있다. */
49
+ export declare function toggleAgreementItem<Id extends string>(descriptor: AgreementDescriptor<Id>, checkedIds: ReadonlySet<Id>, id: Id): ReadonlySet<Id>;
50
+ /**
51
+ * 전체 동의는 "모두 체크됨"의 반대로 간다 — mixed에서 누르면 채운다(공용
52
+ * `getCheckboxNextState`의 기본값과 같은 관습). 비활성 항목의 현재 상태는 건드리지
53
+ * 않는다: 그 값은 사용자가 만든 것이 아니라 제품이 고정한 것이다.
54
+ */
55
+ export declare function toggleAgreementAll<Id extends string>(descriptor: AgreementDescriptor<Id>, checkedIds: ReadonlySet<Id>): ReadonlySet<Id>;
56
+ /** 목록에서 사라진 항목의 동의는 남기지 않는다. */
57
+ export declare function reconcileAgreementSelection<Id extends string>(descriptor: AgreementDescriptor<Id>, checkedIds: ReadonlySet<Id>): ReadonlySet<Id>;
58
+ /**
59
+ * 전체 동의 행은 개별 행보다 시각적으로 무겁다 — 목록의 제목 역할을 하기 때문이다.
60
+ * 나머지는 `collectionItemContract`를 그대로 쓴다(Menu·Tree·ListRow와 같은 행 chrome).
61
+ */
62
+ export declare const agreementRecipe: {
63
+ readonly slots: readonly ["root", "all", "list", "item", "label", "detail"];
64
+ readonly all: {
65
+ readonly minHeight: 44;
66
+ readonly gap: 12;
67
+ readonly paddingHorizontal: 16;
68
+ readonly paddingVertical: 12;
69
+ readonly background: Readonly<{
70
+ source: "theme";
71
+ key: "surfaceAlt";
72
+ alpha?: number;
73
+ }>;
74
+ readonly radius: "md";
75
+ readonly textVariant: "bodyLarge";
76
+ readonly color: Readonly<{
77
+ source: "theme";
78
+ key: "text";
79
+ alpha?: number;
80
+ }>;
81
+ };
82
+ readonly item: {
83
+ readonly minHeight: 44;
84
+ readonly paddingHorizontal: 12;
85
+ readonly gap: 12;
86
+ readonly radius: "md";
87
+ readonly label: {
88
+ readonly color: Readonly<{
89
+ source: "theme";
90
+ key: "textBody";
91
+ alpha?: number;
92
+ }>;
93
+ readonly textVariant: "body";
94
+ };
95
+ readonly description: {
96
+ readonly color: Readonly<{
97
+ source: "theme";
98
+ key: "textMuted";
99
+ alpha?: number;
100
+ }>;
101
+ readonly textVariant: "label";
102
+ };
103
+ readonly highlightedBackground: Readonly<{
104
+ source: "theme";
105
+ key: "text";
106
+ alpha?: number;
107
+ }>;
108
+ readonly focus: {
109
+ readonly color: Readonly<{
110
+ source: "theme";
111
+ key: "contentBrand";
112
+ alpha?: number;
113
+ }>;
114
+ readonly width: 2;
115
+ readonly offset: 2;
116
+ };
117
+ readonly selectedBackground: Readonly<{
118
+ source: "theme";
119
+ key: "primary";
120
+ alpha?: number;
121
+ }>;
122
+ readonly selectedIndicator: Readonly<{
123
+ source: "theme";
124
+ key: "contentBrand";
125
+ alpha?: number;
126
+ }>;
127
+ readonly danger: Readonly<{
128
+ source: "theme";
129
+ key: "danger";
130
+ alpha?: number;
131
+ }>;
132
+ };
133
+ readonly detail: {
134
+ readonly color: Readonly<{
135
+ source: "theme";
136
+ key: "textMuted";
137
+ alpha?: number;
138
+ }>;
139
+ readonly textVariant: "label";
140
+ readonly minHeight: 44;
141
+ };
142
+ readonly required: {
143
+ readonly color: Readonly<{
144
+ source: "theme";
145
+ key: "contentBrand";
146
+ alpha?: number;
147
+ }>;
148
+ readonly textVariant: "label";
149
+ };
150
+ readonly gap: 8;
151
+ readonly states: {
152
+ readonly focus: {
153
+ readonly color: Readonly<{
154
+ source: "theme";
155
+ key: "contentBrand";
156
+ alpha?: number;
157
+ }>;
158
+ readonly width: 2;
159
+ readonly offset: 2;
160
+ };
161
+ };
162
+ };
163
+ export declare const agreementBehavior: {
164
+ readonly controlled: readonly ["checkedIds", "defaultCheckedIds", "onCheckedIdsChange"];
165
+ readonly inputs: readonly ["items", "allLabel", "accessibilityLabel"];
166
+ readonly events: readonly ["onDetail"];
167
+ readonly stateAxes: {
168
+ readonly availability: readonly ["enabled", "disabled"];
169
+ readonly value: readonly ["unchecked", "checked", "mixed"];
170
+ };
171
+ readonly web: {
172
+ readonly roles: readonly ["group", "checkbox", "link", "button"];
173
+ readonly keyboard: readonly ["Tab", "Space", "Enter"];
174
+ readonly focus: "native";
175
+ };
176
+ readonly native: {
177
+ readonly roles: readonly ["checkbox", "link", "button"];
178
+ readonly states: readonly ["checked", "disabled"];
179
+ readonly actions: readonly ["toggle", "toggleAll", "openDetail"];
180
+ };
181
+ readonly scenarios: readonly ["all-agree-is-derived-from-the-items-never-stored-as-its-own-value", "checking-all-checks-every-enabled-item-and-leaves-disabled-ones-untouched", "required-items-alone-decide-whether-the-product-may-submit", "a-required-item-can-never-be-disabled-the-descriptor-is-rejected", "every-item-may-open-its-full-text-and-that-control-is-a-separate-tab-stop", "the-detail-control-never-toggles-the-agreement-it-only-opens-the-text", "optional-items-never-block-submission-and-are-announced-as-optional", "removing-an-item-drops-its-consent-instead-of-keeping-an-orphan-id"];
182
+ };
183
+ //# sourceMappingURL=agreement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agreement.d.ts","sourceRoot":"","sources":["../src/agreement.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EACnB,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,uBAAuB,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAC5D,qBAAqB,CAAC,EAAE,CAAC,GACvB,QAAQ,CAAC;IACP,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD,CAAC,CAAC;AAEP,MAAM,MAAM,mBAAmB,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACrE,0CAA0C;IAC1C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,uBAAuB,CAAC,EAAE,CAAC,EAAE,CAAC;CAC/C,CAAC,CAAC;AAQH,wBAAgB,2BAA2B,CAAC,EAAE,SAAS,MAAM,EAC3D,UAAU,EAAE,mBAAmB,CAAC,EAAE,CAAC,GAClC,IAAI,CAyBN;AAED,MAAM,MAAM,cAAc,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IAChE,8CAA8C;IAC9C,GAAG,EAAE,aAAa,CAAC;IACnB,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,gDAAgD;IAChD,kBAAkB,EAAE,SAAS,EAAE,EAAE,CAAC;CACnC,CAAC,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,SAAS,MAAM,EACrD,UAAU,EAAE,mBAAmB,CAAC,EAAE,CAAC,EACnC,UAAU,EAAE,WAAW,CAAC,EAAE,CAAC,GAC1B,cAAc,CAAC,EAAE,CAAC,CAapB;AAED,gDAAgD;AAChD,wBAAgB,mBAAmB,CAAC,EAAE,SAAS,MAAM,EACnD,UAAU,EAAE,mBAAmB,CAAC,EAAE,CAAC,EACnC,UAAU,EAAE,WAAW,CAAC,EAAE,CAAC,EAC3B,EAAE,EAAE,EAAE,GACL,WAAW,CAAC,EAAE,CAAC,CAGjB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAClD,UAAU,EAAE,mBAAmB,CAAC,EAAE,CAAC,EACnC,UAAU,EAAE,WAAW,CAAC,EAAE,CAAC,GAC1B,WAAW,CAAC,EAAE,CAAC,CASjB;AAED,gCAAgC;AAChC,wBAAgB,2BAA2B,CAAC,EAAE,SAAS,MAAM,EAC3D,UAAU,EAAE,mBAAmB,CAAC,EAAE,CAAC,EACnC,UAAU,EAAE,WAAW,CAAC,EAAE,CAAC,GAC1B,WAAW,CAAC,EAAE,CAAC,CAGjB;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsC3B,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;CA4BO,CAAC"}
@@ -0,0 +1,137 @@
1
+ import { collectionItemContract, focusIndicatorContract } from "./component-contracts.js";
2
+ import { control, spacing } from "./foundations.js";
3
+ import { semanticColors } from "./semantic-colors.js";
4
+ import { reconcileCheckboxSelection, toggleCheckboxSelection, } from "./selection-helpers.js";
5
+ function assertNonEmpty(value, field) {
6
+ if (typeof value !== "string" || value.trim().length === 0) {
7
+ throw new TypeError(`Agreement ${field} must not be empty`);
8
+ }
9
+ }
10
+ export function validateAgreementDescriptor(descriptor) {
11
+ assertNonEmpty(descriptor.accessibilityLabel, "accessibilityLabel");
12
+ assertNonEmpty(descriptor.allLabel, "allLabel");
13
+ if (!Array.isArray(descriptor.items) || descriptor.items.length === 0) {
14
+ throw new RangeError("Agreement must contain at least one item");
15
+ }
16
+ const ids = new Set();
17
+ for (const item of descriptor.items) {
18
+ assertNonEmpty(item.id, "item id");
19
+ assertNonEmpty(item.label, "item label");
20
+ if (ids.has(item.id))
21
+ throw new TypeError(`Duplicate Agreement item id: ${item.id}`);
22
+ ids.add(item.id);
23
+ if (item.detail !== undefined) {
24
+ assertNonEmpty(item.detail.label, `item ${item.id} detail label`);
25
+ }
26
+ if (item.required === true && item.disabled === true) {
27
+ /*
28
+ 비활성 필수 항목은 사용자가 영원히 제출할 수 없는 화면을 만든다. 조용히
29
+ 허용하면 "버튼이 왜 안 눌리는지" 알 수 없는 상태가 되므로 여기서 막는다.
30
+ */
31
+ throw new TypeError(`Agreement item ${item.id} cannot be both required and disabled`);
32
+ }
33
+ }
34
+ }
35
+ /**
36
+ * 비활성 항목은 분모에서 뺀다 — 사용자가 바꿀 수 없는 것을 "덜 동의했다"고 셀 수
37
+ * 없기 때문이다(`resolveDataTableSelectAllState`·`resolveTreeCheckedStates`와 같은 규칙).
38
+ * 필수 항목은 비활성일 수 없으므로(위 validate) `satisfied` 판정에는 이 예외가 없다.
39
+ */
40
+ export function resolveAgreementState(descriptor, checkedIds) {
41
+ validateAgreementDescriptor(descriptor);
42
+ const selectable = descriptor.items.filter((item) => !item.disabled);
43
+ const checkedCount = selectable.filter((item) => checkedIds.has(item.id)).length;
44
+ const all = selectable.length === 0 || checkedCount === 0
45
+ ? false
46
+ : checkedCount === selectable.length
47
+ ? true
48
+ : "mixed";
49
+ const missingRequiredIds = descriptor.items
50
+ .filter((item) => item.required === true && !checkedIds.has(item.id))
51
+ .map((item) => item.id);
52
+ return { all, satisfied: missingRequiredIds.length === 0, missingRequiredIds };
53
+ }
54
+ /** 개별 항목 토글. 비활성 항목 보호는 공용 helper가 이미 갖고 있다. */
55
+ export function toggleAgreementItem(descriptor, checkedIds, id) {
56
+ validateAgreementDescriptor(descriptor);
57
+ return toggleCheckboxSelection(descriptor.items, checkedIds, id);
58
+ }
59
+ /**
60
+ * 전체 동의는 "모두 체크됨"의 반대로 간다 — mixed에서 누르면 채운다(공용
61
+ * `getCheckboxNextState`의 기본값과 같은 관습). 비활성 항목의 현재 상태는 건드리지
62
+ * 않는다: 그 값은 사용자가 만든 것이 아니라 제품이 고정한 것이다.
63
+ */
64
+ export function toggleAgreementAll(descriptor, checkedIds) {
65
+ const { all } = resolveAgreementState(descriptor, checkedIds);
66
+ const next = new Set(checkedIds);
67
+ for (const item of descriptor.items) {
68
+ if (item.disabled)
69
+ continue;
70
+ if (all === true)
71
+ next.delete(item.id);
72
+ else
73
+ next.add(item.id);
74
+ }
75
+ return next;
76
+ }
77
+ /** 목록에서 사라진 항목의 동의는 남기지 않는다. */
78
+ export function reconcileAgreementSelection(descriptor, checkedIds) {
79
+ validateAgreementDescriptor(descriptor);
80
+ return reconcileCheckboxSelection(descriptor.items, checkedIds);
81
+ }
82
+ /**
83
+ * 전체 동의 행은 개별 행보다 시각적으로 무겁다 — 목록의 제목 역할을 하기 때문이다.
84
+ * 나머지는 `collectionItemContract`를 그대로 쓴다(Menu·Tree·ListRow와 같은 행 chrome).
85
+ */
86
+ export const agreementRecipe = {
87
+ slots: ["root", "all", "list", "item", "label", "detail"],
88
+ all: {
89
+ minHeight: control.minTouchTarget,
90
+ gap: spacing.sm,
91
+ paddingHorizontal: spacing.md,
92
+ paddingVertical: spacing.sm,
93
+ background: semanticColors.surface.sunken,
94
+ radius: "md",
95
+ textVariant: "bodyLarge",
96
+ color: semanticColors.content.primary,
97
+ },
98
+ item: collectionItemContract,
99
+ detail: {
100
+ color: semanticColors.content.secondary,
101
+ textVariant: "label",
102
+ minHeight: control.minTouchTarget,
103
+ },
104
+ required: { color: semanticColors.content.brand, textVariant: "label" },
105
+ gap: spacing.xs,
106
+ states: { focus: focusIndicatorContract },
107
+ };
108
+ export const agreementBehavior = {
109
+ controlled: ["checkedIds", "defaultCheckedIds", "onCheckedIdsChange"],
110
+ inputs: ["items", "allLabel", "accessibilityLabel"],
111
+ events: ["onDetail"],
112
+ stateAxes: {
113
+ availability: ["enabled", "disabled"],
114
+ value: ["unchecked", "checked", "mixed"],
115
+ },
116
+ web: {
117
+ roles: ["group", "checkbox", "link", "button"],
118
+ keyboard: ["Tab", "Space", "Enter"],
119
+ focus: "native",
120
+ },
121
+ native: {
122
+ roles: ["checkbox", "link", "button"],
123
+ states: ["checked", "disabled"],
124
+ actions: ["toggle", "toggleAll", "openDetail"],
125
+ },
126
+ scenarios: [
127
+ "all-agree-is-derived-from-the-items-never-stored-as-its-own-value",
128
+ "checking-all-checks-every-enabled-item-and-leaves-disabled-ones-untouched",
129
+ "required-items-alone-decide-whether-the-product-may-submit",
130
+ "a-required-item-can-never-be-disabled-the-descriptor-is-rejected",
131
+ "every-item-may-open-its-full-text-and-that-control-is-a-separate-tab-stop",
132
+ "the-detail-control-never-toggles-the-agreement-it-only-opens-the-text",
133
+ "optional-items-never-block-submission-and-are-announced-as-optional",
134
+ "removing-an-item-drops-its-consent-instead-of-keeping-an-orphan-id",
135
+ ],
136
+ };
137
+ //# sourceMappingURL=agreement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agreement.js","sourceRoot":"","sources":["../src/agreement.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { collectionItemContract, focusIndicatorContract } from \"./component-contracts.js\";\nimport { control, spacing } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport {\n reconcileCheckboxSelection,\n toggleCheckboxSelection,\n type CheckboxSelectionItem,\n type CheckboxState,\n} from \"./selection-helpers.js\";\n\n/**\n * 가입·결제 앞에 서는 \"약관 동의\" 묶음이다. CheckboxGroup으로 매번 조립하던 것을\n * 계약으로 올리는 이유는 세 가지가 **체크박스 목록이 아니라 법적 절차**이기 때문이다.\n *\n * 1. 필수/선택이 제출 가능 여부를 결정한다 — CheckboxGroup은 그 판정을 갖지 않는다.\n * 2. 전체 동의는 자식의 합이지 별도 항목이 아니다(DataTable 머리글·TreeSelect 부모와\n * 같은 파생 관계). 저장되는 것은 개별 항목뿐이다.\n * 3. 각 항목은 \"읽을 수 있어야\" 한다 — 상세 보기 경로가 없는 동의는 동의가 아니다.\n * 그래서 `detail`은 renderer가 장식으로 넣는 화살표가 아니라 계약된 행동이다.\n *\n * 문구·링크 주소·법적 유효성은 제품이 소유한다. 이 계약은 판정과 배치만 갖는다.\n */\nexport type AgreementItemDescriptor<Id extends string = string> =\n CheckboxSelectionItem<Id> &\n Readonly<{\n /** 필수 항목은 하나라도 비면 제출을 막는다. 기본값은 선택(false). */\n required?: boolean;\n /**\n * 전문을 여는 경로. 제품이 링크(`href`)로 열지 시트로 열지는 renderer 밖의\n * 결정이라 여기서는 \"열 수 있다\"는 사실만 계약한다.\n */\n detail?: Readonly<{ label: string; href?: string }>;\n }>;\n\nexport type AgreementDescriptor<Id extends string = string> = Readonly<{\n /** 묶음 전체의 접근성 이름. \"약관 동의\"처럼 제품이 현지화한다. */\n accessibilityLabel: string;\n /** 전체 동의 행의 라벨. 필수이므로 renderer가 임의 문구를 만들지 않는다. */\n allLabel: string;\n items: readonly AgreementItemDescriptor<Id>[];\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Agreement ${field} must not be empty`);\n }\n}\n\nexport function validateAgreementDescriptor<Id extends string>(\n descriptor: AgreementDescriptor<Id>,\n): void {\n assertNonEmpty(descriptor.accessibilityLabel, \"accessibilityLabel\");\n assertNonEmpty(descriptor.allLabel, \"allLabel\");\n if (!Array.isArray(descriptor.items) || descriptor.items.length === 0) {\n throw new RangeError(\"Agreement must contain at least one item\");\n }\n const ids = new Set<Id>();\n for (const item of descriptor.items) {\n assertNonEmpty(item.id, \"item id\");\n assertNonEmpty(item.label, \"item label\");\n if (ids.has(item.id)) throw new TypeError(`Duplicate Agreement item id: ${item.id}`);\n ids.add(item.id);\n if (item.detail !== undefined) {\n assertNonEmpty(item.detail.label, `item ${item.id} detail label`);\n }\n if (item.required === true && item.disabled === true) {\n /*\n 비활성 필수 항목은 사용자가 영원히 제출할 수 없는 화면을 만든다. 조용히\n 허용하면 \"버튼이 왜 안 눌리는지\" 알 수 없는 상태가 되므로 여기서 막는다.\n */\n throw new TypeError(\n `Agreement item ${item.id} cannot be both required and disabled`,\n );\n }\n }\n}\n\nexport type AgreementState<Id extends string = string> = Readonly<{\n /** 전체 동의 행이 그릴 tri-state. 개별 항목의 합에서만 나온다. */\n all: CheckboxState;\n /** 필수 항목이 모두 체크됐는가 — 제품의 제출 버튼이 읽는 값. */\n satisfied: boolean;\n /** 아직 체크되지 않은 필수 항목. 제품이 \"무엇이 남았는지\" 문장을 만든다. */\n missingRequiredIds: readonly Id[];\n}>;\n\n/**\n * 비활성 항목은 분모에서 뺀다 — 사용자가 바꿀 수 없는 것을 \"덜 동의했다\"고 셀 수\n * 없기 때문이다(`resolveDataTableSelectAllState`·`resolveTreeCheckedStates`와 같은 규칙).\n * 필수 항목은 비활성일 수 없으므로(위 validate) `satisfied` 판정에는 이 예외가 없다.\n */\nexport function resolveAgreementState<Id extends string>(\n descriptor: AgreementDescriptor<Id>,\n checkedIds: ReadonlySet<Id>,\n): AgreementState<Id> {\n validateAgreementDescriptor(descriptor);\n const selectable = descriptor.items.filter((item) => !item.disabled);\n const checkedCount = selectable.filter((item) => checkedIds.has(item.id)).length;\n const all: CheckboxState = selectable.length === 0 || checkedCount === 0\n ? false\n : checkedCount === selectable.length\n ? true\n : \"mixed\";\n const missingRequiredIds = descriptor.items\n .filter((item) => item.required === true && !checkedIds.has(item.id))\n .map((item) => item.id);\n return { all, satisfied: missingRequiredIds.length === 0, missingRequiredIds };\n}\n\n/** 개별 항목 토글. 비활성 항목 보호는 공용 helper가 이미 갖고 있다. */\nexport function toggleAgreementItem<Id extends string>(\n descriptor: AgreementDescriptor<Id>,\n checkedIds: ReadonlySet<Id>,\n id: Id,\n): ReadonlySet<Id> {\n validateAgreementDescriptor(descriptor);\n return toggleCheckboxSelection(descriptor.items, checkedIds, id);\n}\n\n/**\n * 전체 동의는 \"모두 체크됨\"의 반대로 간다 — mixed에서 누르면 채운다(공용\n * `getCheckboxNextState`의 기본값과 같은 관습). 비활성 항목의 현재 상태는 건드리지\n * 않는다: 그 값은 사용자가 만든 것이 아니라 제품이 고정한 것이다.\n */\nexport function toggleAgreementAll<Id extends string>(\n descriptor: AgreementDescriptor<Id>,\n checkedIds: ReadonlySet<Id>,\n): ReadonlySet<Id> {\n const { all } = resolveAgreementState(descriptor, checkedIds);\n const next = new Set(checkedIds);\n for (const item of descriptor.items) {\n if (item.disabled) continue;\n if (all === true) next.delete(item.id);\n else next.add(item.id);\n }\n return next;\n}\n\n/** 목록에서 사라진 항목의 동의는 남기지 않는다. */\nexport function reconcileAgreementSelection<Id extends string>(\n descriptor: AgreementDescriptor<Id>,\n checkedIds: ReadonlySet<Id>,\n): ReadonlySet<Id> {\n validateAgreementDescriptor(descriptor);\n return reconcileCheckboxSelection(descriptor.items, checkedIds);\n}\n\n/**\n * 전체 동의 행은 개별 행보다 시각적으로 무겁다 — 목록의 제목 역할을 하기 때문이다.\n * 나머지는 `collectionItemContract`를 그대로 쓴다(Menu·Tree·ListRow와 같은 행 chrome).\n */\nexport const agreementRecipe = {\n slots: [\"root\", \"all\", \"list\", \"item\", \"label\", \"detail\"] as const,\n all: {\n minHeight: control.minTouchTarget,\n gap: spacing.sm,\n paddingHorizontal: spacing.md,\n paddingVertical: spacing.sm,\n background: semanticColors.surface.sunken,\n radius: \"md\" as const,\n textVariant: \"bodyLarge\" as const,\n color: semanticColors.content.primary,\n },\n item: collectionItemContract,\n detail: {\n color: semanticColors.content.secondary,\n textVariant: \"label\" as const,\n minHeight: control.minTouchTarget,\n },\n required: { color: semanticColors.content.brand, textVariant: \"label\" as const },\n gap: spacing.xs,\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"all\", \"list\", \"item\", \"label\", \"detail\"];\n all: {\n minHeight: number;\n gap: number;\n paddingHorizontal: number;\n paddingVertical: number;\n background: ColorReference;\n radius: \"md\";\n textVariant: \"bodyLarge\";\n color: ColorReference;\n };\n item: typeof collectionItemContract;\n detail: { color: ColorReference; textVariant: \"label\"; minHeight: number };\n required: { color: ColorReference; textVariant: \"label\" };\n gap: number;\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const agreementBehavior = {\n controlled: [\"checkedIds\", \"defaultCheckedIds\", \"onCheckedIdsChange\"],\n inputs: [\"items\", \"allLabel\", \"accessibilityLabel\"],\n events: [\"onDetail\"],\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"unchecked\", \"checked\", \"mixed\"],\n },\n web: {\n roles: [\"group\", \"checkbox\", \"link\", \"button\"],\n keyboard: [\"Tab\", \"Space\", \"Enter\"],\n focus: \"native\",\n },\n native: {\n roles: [\"checkbox\", \"link\", \"button\"],\n states: [\"checked\", \"disabled\"],\n actions: [\"toggle\", \"toggleAll\", \"openDetail\"],\n },\n scenarios: [\n \"all-agree-is-derived-from-the-items-never-stored-as-its-own-value\",\n \"checking-all-checks-every-enabled-item-and-leaves-disabled-ones-untouched\",\n \"required-items-alone-decide-whether-the-product-may-submit\",\n \"a-required-item-can-never-be-disabled-the-descriptor-is-rejected\",\n \"every-item-may-open-its-full-text-and-that-control-is-a-separate-tab-stop\",\n \"the-detail-control-never-toggles-the-agreement-it-only-opens-the-text\",\n \"optional-items-never-block-submission-and-are-announced-as-optional\",\n \"removing-an-item-drops-its-consent-instead-of-keeping-an-orphan-id\",\n ],\n} as const satisfies BehaviorContract;\n"]}
@@ -32,7 +32,7 @@ type AlertDialogCopy = Readonly<{
32
32
  */
33
33
  export type AlertDialogRequest = (AlertDialogCopy & Readonly<{
34
34
  mode: "alert";
35
- tone?: Extract<AlertDialogTone, "attention">;
35
+ tone?: Exclude<AlertDialogTone, "danger">;
36
36
  cancelLabel?: never;
37
37
  onConfirm?: never;
38
38
  fallbackErrorMessage?: never;
@@ -1 +1 @@
1
- {"version":3,"file":"alert-dialog.d.ts","sourceRoot":"","sources":["../src/alert-dialog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,CAAC;AAClD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC3D,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAC9D,MAAM,MAAM,wBAAwB,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAClE,MAAM,MAAM,uBAAuB,GAC/B,eAAe,GACf,QAAQ,GACR,MAAM,GACN,cAAc,GACd,aAAa,CAAC;AAClB,MAAM,MAAM,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,EAAE,aAAa,CAAC,GAC/C,SAAS,CAAC;AACd,MAAM,MAAM,2BAA2B,GAAG,SAAS,GAAG,SAAS,GAAG,uBAAuB,CAAC;AAE1F,MAAM,MAAM,iBAAiB,GACzB,QAAQ,CAAC;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC,GAClC,QAAQ,CAAC;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,CAAC,CAAC;AAExE,KAAK,sBAAsB,GACvB,QAAQ,CAAC;IACP,SAAS,EAAE,wBAAwB,CAAC;IACpC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;CAClD,CAAC,GACF,QAAQ,CAAC;IACP,SAAS,CAAC,EAAE,KAAK,CAAC;IAClB,oBAAoB,CAAC,EAAE,KAAK,CAAC;IAC7B,mBAAmB,CAAC,EAAE,KAAK,CAAC;CAC7B,CAAC,CAAC;AAEP,KAAK,eAAe,GAAG,QAAQ,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAC1B,CAAC,eAAe,GACd,QAAQ,CAAC;IACP,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;IAC7C,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,SAAS,CAAC,EAAE,KAAK,CAAC;IAClB,oBAAoB,CAAC,EAAE,KAAK,CAAC;IAC7B,mBAAmB,CAAC,EAAE,KAAK,CAAC;CAC7B,CAAC,CAAC,GACL,CAAC,eAAe,GACd,QAAQ,CAAC;IACP,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,GACF,sBAAsB,CAAC,CAAC;AAE9B,MAAM,MAAM,oBAAoB,GAC5B,QAAQ,CAAC;IACP,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,YAAY,CACV,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,QAAQ,CAAC;QAAE,MAAM,EAAE,2BAA2B,CAAA;KAAE,CAAC,GACxD,IAAI,CAAC;CACT,CAAC,GACF,QAAQ,CAAC;IACP,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,CACb,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,QAAQ,CAAC;QAAE,MAAM,EAAE,2BAA2B,CAAA;KAAE,CAAC,KACtD,IAAI,CAAC;CACX,CAAC,CAAC;AAEP,eAAO,MAAM,2BAA2B;;;;;;;;CAUtC,CAAC;AAQH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAY5E;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,eAAe,GACpB,uBAAuB,CAEzB;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,wBAAwB,EAChC,IAAI,EAAE,OAAO,GACZ,OAAO,CAIT;AAED,MAAM,MAAM,gBAAgB,GACxB,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAC5B,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAC5B,QAAQ,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAC9C,QAAQ,CAAC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAC,GAC1D,QAAQ,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAC,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACnC,WAAW,IAAI,gBAAgB,CAAC;IAChC,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,+EAA+E;IAC/E,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,uBAAuB,EAAE,aAAa,CAAC,GAAG,OAAO,CAAC;IACzE,qBAAqB,IAAI,KAAK,CAAC;IAC/B,YAAY,IAAI,OAAO,CAAC;IACxB,SAAS,IAAI,OAAO,CAAC;CACtB,CAAC,CAAC;AAeH;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,kBAAkB,GAC1B,kBAAkB,CAgFpB"}
1
+ {"version":3,"file":"alert-dialog.d.ts","sourceRoot":"","sources":["../src/alert-dialog.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"alert-dialog.js","sourceRoot":"","sources":["../src/alert-dialog.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AlertDialogTone } from \"./component-recipes.js\";\n\nexport type AlertDialogMode = \"alert\" | \"confirm\";\nexport type AlertDialogInitialFocus = \"cancel\" | \"confirm\";\nexport type AlertDialogAsyncState = \"idle\" | \"busy\" | \"error\";\nexport type AlertDialogConfirmAction = () => void | Promise<void>;\nexport type AlertDialogCancelReason =\n | \"cancel-action\"\n | \"escape\"\n | \"back\"\n | \"programmatic\"\n | \"interrupted\";\nexport type AlertDialogDismissReason =\n | Exclude<AlertDialogCancelReason, \"interrupted\">\n | \"outside\";\nexport type AlertDialogOpenChangeReason = \"trigger\" | \"confirm\" | AlertDialogCancelReason;\n\nexport type AlertDialogResult =\n | Readonly<{ outcome: \"confirmed\" }>\n | Readonly<{ outcome: \"cancelled\"; reason: AlertDialogCancelReason }>;\n\ntype AlertDialogAsyncAction =\n | Readonly<{\n onConfirm: AlertDialogConfirmAction;\n fallbackErrorMessage: string;\n resolveErrorMessage?: (error: unknown) => string;\n }>\n | Readonly<{\n onConfirm?: never;\n fallbackErrorMessage?: never;\n resolveErrorMessage?: never;\n }>;\n\ntype AlertDialogCopy = Readonly<{\n title: string;\n description: string;\n confirmLabel: string;\n}>;\n\n/**\n * Renderer-neutral copy and action contract. Products own localized copy;\n * renderers own modal isolation, focus, motion, and visual translation.\n */\nexport type AlertDialogRequest =\n | (AlertDialogCopy &\n Readonly<{\n mode: \"alert\";\n tone?: Extract<AlertDialogTone, \"attention\">;\n cancelLabel?: never;\n onConfirm?: never;\n fallbackErrorMessage?: never;\n resolveErrorMessage?: never;\n }>)\n | (AlertDialogCopy &\n Readonly<{\n mode: \"confirm\";\n tone?: AlertDialogTone;\n cancelLabel: string;\n }> &\n AlertDialogAsyncAction);\n\nexport type AlertDialogOpenState =\n | Readonly<{\n open: boolean;\n defaultOpen?: never;\n onOpenChange(\n open: boolean,\n detail: Readonly<{ reason: AlertDialogOpenChangeReason }>,\n ): void;\n }>\n | Readonly<{\n open?: never;\n defaultOpen?: boolean;\n onOpenChange?: (\n open: boolean,\n detail: Readonly<{ reason: AlertDialogOpenChangeReason }>,\n ) => void;\n }>;\n\nexport const alertDialogBehaviorDefaults = {\n initialFocus: { alert: \"confirm\", confirm: \"cancel\" },\n outsideDismiss: false,\n escapeOrBackOutcome: \"cancelled\",\n dismissWhileBusy: false,\n} as const satisfies Readonly<{\n initialFocus: Record<AlertDialogMode, AlertDialogInitialFocus>;\n outsideDismiss: false;\n escapeOrBackOutcome: \"cancelled\";\n dismissWhileBusy: false;\n}>;\n\nfunction assertCopy(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`AlertDialog ${field} must not be empty`);\n }\n}\n\nexport function validateAlertDialogRequest(request: AlertDialogRequest): void {\n assertCopy(request.title, \"title\");\n assertCopy(request.description, \"description\");\n assertCopy(request.confirmLabel, \"confirmLabel\");\n if (request.mode === \"alert\") {\n if (request.tone === (\"danger\" as AlertDialogTone)) {\n throw new TypeError(\"AlertDialog danger tone requires confirm mode\");\n }\n return;\n }\n assertCopy(request.cancelLabel, \"cancelLabel\");\n if (request.onConfirm) assertCopy(request.fallbackErrorMessage, \"fallbackErrorMessage\");\n}\n\nexport function getAlertDialogInitialFocus(\n mode: AlertDialogMode,\n): AlertDialogInitialFocus {\n return alertDialogBehaviorDefaults.initialFocus[mode];\n}\n\nexport function canDismissAlertDialog(\n reason: AlertDialogDismissReason,\n busy: boolean,\n): boolean {\n if (busy && !alertDialogBehaviorDefaults.dismissWhileBusy) return false;\n if (reason === \"outside\") return alertDialogBehaviorDefaults.outsideDismiss;\n return true;\n}\n\nexport type AlertDialogPhase =\n | Readonly<{ status: \"idle\" }>\n | Readonly<{ status: \"busy\" }>\n | Readonly<{ status: \"error\"; message: string }>\n | Readonly<{ status: \"closing\"; result: AlertDialogResult }>\n | Readonly<{ status: \"closed\"; result: AlertDialogResult }>;\n\nexport type AlertDialogSession = Readonly<{\n request: AlertDialogRequest;\n result: Promise<AlertDialogResult>;\n getSnapshot(): AlertDialogPhase;\n subscribe(listener: () => void): () => void;\n /** Returns true only for the attempt that moved the session toward closing. */\n confirm(): Promise<boolean>;\n cancel(reason: Exclude<AlertDialogCancelReason, \"interrupted\">): boolean;\n attemptOutsideDismiss(): false;\n completeExit(): boolean;\n interrupt(): boolean;\n}>;\n\nfunction resolveFailureMessage(\n request: Extract<AlertDialogRequest, { mode: \"confirm\" }>,\n error: unknown,\n): string {\n if (!request.onConfirm) return \"\";\n try {\n const resolved = request.resolveErrorMessage?.(error).trim();\n return resolved || request.fallbackErrorMessage;\n } catch {\n return request.fallbackErrorMessage;\n }\n}\n\n/**\n * Owns a complete AlertDialog lifecycle, including side-effect de-duplication\n * and settling only after the renderer reports that exit motion completed.\n */\nexport function createAlertDialogSession(\n request: AlertDialogRequest,\n): AlertDialogSession {\n validateAlertDialogRequest(request);\n\n let phase: AlertDialogPhase = { status: \"idle\" };\n let settleResult!: (result: AlertDialogResult) => void;\n let settled = false;\n const listeners = new Set<() => void>();\n const result = new Promise<AlertDialogResult>((resolve) => {\n settleResult = resolve;\n });\n\n const notify = () => {\n for (const listener of listeners) listener();\n };\n const transition = (next: AlertDialogPhase) => {\n phase = next;\n notify();\n };\n const settle = (next: AlertDialogResult): boolean => {\n if (settled) return false;\n settled = true;\n settleResult(next);\n return true;\n };\n\n const session: AlertDialogSession = {\n request,\n result,\n getSnapshot: () => phase,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n async confirm() {\n if (phase.status !== \"idle\" && phase.status !== \"error\") return false;\n\n if (request.mode === \"alert\" || !request.onConfirm) {\n transition({ status: \"closing\", result: { outcome: \"confirmed\" } });\n return true;\n }\n\n transition({ status: \"busy\" });\n try {\n await request.onConfirm();\n if ((phase as AlertDialogPhase).status !== \"busy\") return false;\n transition({ status: \"closing\", result: { outcome: \"confirmed\" } });\n return true;\n } catch (error) {\n if ((phase as AlertDialogPhase).status !== \"busy\") return false;\n transition({ status: \"error\", message: resolveFailureMessage(request, error) });\n return false;\n }\n },\n cancel(reason) {\n if (!canDismissAlertDialog(reason, phase.status === \"busy\")) return false;\n if (phase.status !== \"idle\" && phase.status !== \"error\") return false;\n transition({ status: \"closing\", result: { outcome: \"cancelled\", reason } });\n return true;\n },\n attemptOutsideDismiss() {\n return false;\n },\n completeExit() {\n if (phase.status !== \"closing\") return false;\n const finalResult = phase.result;\n transition({ status: \"closed\", result: finalResult });\n return settle(finalResult);\n },\n interrupt() {\n if (phase.status === \"closed\") return false;\n const interrupted: AlertDialogResult = {\n outcome: \"cancelled\",\n reason: \"interrupted\",\n };\n transition({ status: \"closed\", result: interrupted });\n return settle(interrupted);\n },\n };\n\n return session;\n}\n"]}
1
+ {"version":3,"file":"alert-dialog.js","sourceRoot":"","sources":["../src/alert-dialog.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AlertDialogTone } from \"./component-recipes.js\";\n\nexport type AlertDialogMode = \"alert\" | \"confirm\";\nexport type AlertDialogInitialFocus = \"cancel\" | \"confirm\";\nexport type AlertDialogAsyncState = \"idle\" | \"busy\" | \"error\";\nexport type AlertDialogConfirmAction = () => void | Promise<void>;\nexport type AlertDialogCancelReason =\n | \"cancel-action\"\n | \"escape\"\n | \"back\"\n | \"programmatic\"\n | \"interrupted\";\nexport type AlertDialogDismissReason =\n | Exclude<AlertDialogCancelReason, \"interrupted\">\n | \"outside\";\nexport type AlertDialogOpenChangeReason = \"trigger\" | \"confirm\" | AlertDialogCancelReason;\n\nexport type AlertDialogResult =\n | Readonly<{ outcome: \"confirmed\" }>\n | Readonly<{ outcome: \"cancelled\"; reason: AlertDialogCancelReason }>;\n\ntype AlertDialogAsyncAction =\n | Readonly<{\n onConfirm: AlertDialogConfirmAction;\n fallbackErrorMessage: string;\n resolveErrorMessage?: (error: unknown) => string;\n }>\n | Readonly<{\n onConfirm?: never;\n fallbackErrorMessage?: never;\n resolveErrorMessage?: never;\n }>;\n\ntype AlertDialogCopy = Readonly<{\n title: string;\n description: string;\n confirmLabel: string;\n}>;\n\n/**\n * Renderer-neutral copy and action contract. Products own localized copy;\n * renderers own modal isolation, focus, motion, and visual translation.\n */\nexport type AlertDialogRequest =\n | (AlertDialogCopy &\n Readonly<{\n mode: \"alert\";\n // Every tone except `danger` can announce without a choice. A danger\n // dialog with no cancel would let a destructive act through on one key.\n tone?: Exclude<AlertDialogTone, \"danger\">;\n cancelLabel?: never;\n onConfirm?: never;\n fallbackErrorMessage?: never;\n resolveErrorMessage?: never;\n }>)\n | (AlertDialogCopy &\n Readonly<{\n mode: \"confirm\";\n tone?: AlertDialogTone;\n cancelLabel: string;\n }> &\n AlertDialogAsyncAction);\n\nexport type AlertDialogOpenState =\n | Readonly<{\n open: boolean;\n defaultOpen?: never;\n onOpenChange(\n open: boolean,\n detail: Readonly<{ reason: AlertDialogOpenChangeReason }>,\n ): void;\n }>\n | Readonly<{\n open?: never;\n defaultOpen?: boolean;\n onOpenChange?: (\n open: boolean,\n detail: Readonly<{ reason: AlertDialogOpenChangeReason }>,\n ) => void;\n }>;\n\nexport const alertDialogBehaviorDefaults = {\n initialFocus: { alert: \"confirm\", confirm: \"cancel\" },\n outsideDismiss: false,\n escapeOrBackOutcome: \"cancelled\",\n dismissWhileBusy: false,\n} as const satisfies Readonly<{\n initialFocus: Record<AlertDialogMode, AlertDialogInitialFocus>;\n outsideDismiss: false;\n escapeOrBackOutcome: \"cancelled\";\n dismissWhileBusy: false;\n}>;\n\nfunction assertCopy(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`AlertDialog ${field} must not be empty`);\n }\n}\n\nexport function validateAlertDialogRequest(request: AlertDialogRequest): void {\n assertCopy(request.title, \"title\");\n assertCopy(request.description, \"description\");\n assertCopy(request.confirmLabel, \"confirmLabel\");\n if (request.mode === \"alert\") {\n if (request.tone === (\"danger\" as AlertDialogTone)) {\n throw new TypeError(\"AlertDialog danger tone requires confirm mode\");\n }\n return;\n }\n assertCopy(request.cancelLabel, \"cancelLabel\");\n if (request.onConfirm) assertCopy(request.fallbackErrorMessage, \"fallbackErrorMessage\");\n}\n\nexport function getAlertDialogInitialFocus(\n mode: AlertDialogMode,\n): AlertDialogInitialFocus {\n return alertDialogBehaviorDefaults.initialFocus[mode];\n}\n\nexport function canDismissAlertDialog(\n reason: AlertDialogDismissReason,\n busy: boolean,\n): boolean {\n if (busy && !alertDialogBehaviorDefaults.dismissWhileBusy) return false;\n if (reason === \"outside\") return alertDialogBehaviorDefaults.outsideDismiss;\n return true;\n}\n\nexport type AlertDialogPhase =\n | Readonly<{ status: \"idle\" }>\n | Readonly<{ status: \"busy\" }>\n | Readonly<{ status: \"error\"; message: string }>\n | Readonly<{ status: \"closing\"; result: AlertDialogResult }>\n | Readonly<{ status: \"closed\"; result: AlertDialogResult }>;\n\nexport type AlertDialogSession = Readonly<{\n request: AlertDialogRequest;\n result: Promise<AlertDialogResult>;\n getSnapshot(): AlertDialogPhase;\n subscribe(listener: () => void): () => void;\n /** Returns true only for the attempt that moved the session toward closing. */\n confirm(): Promise<boolean>;\n cancel(reason: Exclude<AlertDialogCancelReason, \"interrupted\">): boolean;\n attemptOutsideDismiss(): false;\n completeExit(): boolean;\n interrupt(): boolean;\n}>;\n\nfunction resolveFailureMessage(\n request: Extract<AlertDialogRequest, { mode: \"confirm\" }>,\n error: unknown,\n): string {\n if (!request.onConfirm) return \"\";\n try {\n const resolved = request.resolveErrorMessage?.(error).trim();\n return resolved || request.fallbackErrorMessage;\n } catch {\n return request.fallbackErrorMessage;\n }\n}\n\n/**\n * Owns a complete AlertDialog lifecycle, including side-effect de-duplication\n * and settling only after the renderer reports that exit motion completed.\n */\nexport function createAlertDialogSession(\n request: AlertDialogRequest,\n): AlertDialogSession {\n validateAlertDialogRequest(request);\n\n let phase: AlertDialogPhase = { status: \"idle\" };\n let settleResult!: (result: AlertDialogResult) => void;\n let settled = false;\n const listeners = new Set<() => void>();\n const result = new Promise<AlertDialogResult>((resolve) => {\n settleResult = resolve;\n });\n\n const notify = () => {\n for (const listener of listeners) listener();\n };\n const transition = (next: AlertDialogPhase) => {\n phase = next;\n notify();\n };\n const settle = (next: AlertDialogResult): boolean => {\n if (settled) return false;\n settled = true;\n settleResult(next);\n return true;\n };\n\n const session: AlertDialogSession = {\n request,\n result,\n getSnapshot: () => phase,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n async confirm() {\n if (phase.status !== \"idle\" && phase.status !== \"error\") return false;\n\n if (request.mode === \"alert\" || !request.onConfirm) {\n transition({ status: \"closing\", result: { outcome: \"confirmed\" } });\n return true;\n }\n\n transition({ status: \"busy\" });\n try {\n await request.onConfirm();\n if ((phase as AlertDialogPhase).status !== \"busy\") return false;\n transition({ status: \"closing\", result: { outcome: \"confirmed\" } });\n return true;\n } catch (error) {\n if ((phase as AlertDialogPhase).status !== \"busy\") return false;\n transition({ status: \"error\", message: resolveFailureMessage(request, error) });\n return false;\n }\n },\n cancel(reason) {\n if (!canDismissAlertDialog(reason, phase.status === \"busy\")) return false;\n if (phase.status !== \"idle\" && phase.status !== \"error\") return false;\n transition({ status: \"closing\", result: { outcome: \"cancelled\", reason } });\n return true;\n },\n attemptOutsideDismiss() {\n return false;\n },\n completeExit() {\n if (phase.status !== \"closing\") return false;\n const finalResult = phase.result;\n transition({ status: \"closed\", result: finalResult });\n return settle(finalResult);\n },\n interrupt() {\n if (phase.status === \"closed\") return false;\n const interrupted: AlertDialogResult = {\n outcome: \"cancelled\",\n reason: \"interrupted\",\n };\n transition({ status: \"closed\", result: interrupted });\n return settle(interrupted);\n },\n };\n\n return session;\n}\n"]}
@@ -0,0 +1,62 @@
1
+ /** IDs refer to same-document sections, never arbitrary external destinations. */
2
+ export type AnchorItem = Readonly<{
3
+ id: string;
4
+ label: string;
5
+ }>;
6
+ export declare function resolveAnchorItems(items: readonly AnchorItem[]): any[];
7
+ /** Positions use one viewport's coordinates. DOM reads and scroll ownership stay in the renderer. */
8
+ export declare function getAnchorCurrentId(positions: readonly Readonly<{
9
+ id: string;
10
+ top: number;
11
+ }>[], offset?: number, atEnd?: boolean): string | undefined;
12
+ export declare const anchorRecipe: {
13
+ readonly slots: readonly ["root", "list", "link", "indicator"];
14
+ readonly gap: 4;
15
+ readonly link: {
16
+ readonly padding: 8;
17
+ readonly color: Readonly<{
18
+ source: "theme";
19
+ key: "textMuted";
20
+ alpha?: number;
21
+ }>;
22
+ };
23
+ readonly current: {
24
+ readonly color: Readonly<{
25
+ source: "theme";
26
+ key: "contentBrand";
27
+ alpha?: number;
28
+ }>;
29
+ readonly border: Readonly<{
30
+ source: "theme";
31
+ key: "contentBrand";
32
+ alpha?: number;
33
+ }>;
34
+ readonly width: 2;
35
+ };
36
+ readonly focus: {
37
+ readonly color: Readonly<{
38
+ source: "theme";
39
+ key: "contentBrand";
40
+ alpha?: number;
41
+ }>;
42
+ readonly width: 2;
43
+ readonly offset: 2;
44
+ };
45
+ };
46
+ export declare const anchorBehavior: {
47
+ readonly controlled: readonly [];
48
+ readonly inputs: readonly ["items", "offset", "container", "historyMode"];
49
+ readonly stateAxes: {};
50
+ readonly web: {
51
+ readonly roles: readonly ["navigation", "list", "listitem", "link"];
52
+ readonly keyboard: readonly ["Tab", "Enter"];
53
+ readonly focus: "native";
54
+ };
55
+ readonly native: {
56
+ readonly roles: readonly [];
57
+ readonly states: readonly [];
58
+ readonly actions: readonly [];
59
+ };
60
+ readonly scenarios: readonly ["scroll-position-selects-one-current-location", "activation-focuses-target-and-preserves-offset", "reduced-motion-jumps-instantly", "browser-history-restores-a-section", "missing-targets-are-not-current"];
61
+ };
62
+ //# sourceMappingURL=anchor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchor.d.ts","sourceRoot":"","sources":["../src/anchor.ts"],"names":[],"mappings":"AAKA,kFAAkF;AAClF,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACjE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,SAU9D;AAED,qGAAqG;AACrG,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,EAAE,MAAM,SAAI,EAAE,KAAK,UAAQ,GAAG,MAAM,GAAG,SAAS,CAM7I;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAMf,CAAC;AACX,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;CAKU,CAAC"}
package/dist/anchor.js ADDED
@@ -0,0 +1,43 @@
1
+ import { spacing, stroke } from "./foundations.js";
2
+ import { semanticColors } from "./semantic-colors.js";
3
+ import { focusIndicatorContract } from "./component-contracts.js";
4
+ export function resolveAnchorItems(items) {
5
+ if (!Array.isArray(items) || items.length === 0)
6
+ throw new TypeError("Anchor items must not be empty");
7
+ const ids = new Set();
8
+ return items.map((item) => {
9
+ if (!item || typeof item.id !== "string" || !item.id || /\s/u.test(item.id))
10
+ throw new TypeError("Anchor id must be a non-empty HTML id without whitespace");
11
+ if (typeof item.label !== "string" || !item.label.trim())
12
+ throw new TypeError("Anchor label must not be empty");
13
+ if (ids.has(item.id))
14
+ throw new TypeError(`Duplicate Anchor id: ${item.id}`);
15
+ ids.add(item.id);
16
+ return { ...item, href: `#${encodeURIComponent(item.id)}` };
17
+ });
18
+ }
19
+ /** Positions use one viewport's coordinates. DOM reads and scroll ownership stay in the renderer. */
20
+ export function getAnchorCurrentId(positions, offset = 0, atEnd = false) {
21
+ if (!Number.isFinite(offset) || offset < 0)
22
+ throw new RangeError("Anchor offset must be non-negative");
23
+ if (positions.some(({ top }) => !Number.isFinite(top)))
24
+ throw new TypeError("Anchor positions must be finite");
25
+ const ordered = [...positions].sort((a, b) => a.top - b.top);
26
+ if (atEnd)
27
+ return ordered.at(-1)?.id;
28
+ return ordered.filter(({ top }) => top <= offset).at(-1)?.id ?? ordered[0]?.id;
29
+ }
30
+ export const anchorRecipe = {
31
+ slots: ["root", "list", "link", "indicator"],
32
+ gap: spacing.xxs,
33
+ link: { padding: spacing.xs, color: semanticColors.content.secondary },
34
+ current: { color: semanticColors.content.brand, border: semanticColors.border.focus, width: stroke.strong },
35
+ focus: focusIndicatorContract,
36
+ };
37
+ export const anchorBehavior = {
38
+ controlled: [], inputs: ["items", "offset", "container", "historyMode"], stateAxes: {},
39
+ web: { roles: ["navigation", "list", "listitem", "link"], keyboard: ["Tab", "Enter"], focus: "native" },
40
+ native: { roles: [], states: [], actions: [] },
41
+ scenarios: ["scroll-position-selects-one-current-location", "activation-focuses-target-and-preserves-offset", "reduced-motion-jumps-instantly", "browser-history-restores-a-section", "missing-targets-are-not-current"],
42
+ };
43
+ //# sourceMappingURL=anchor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchor.js","sourceRoot":"","sources":["../src/anchor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport { spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport { focusIndicatorContract } from \"./component-contracts.js\";\n\n/** IDs refer to same-document sections, never arbitrary external destinations. */\nexport type AnchorItem = Readonly<{ id: string; label: string }>;\nexport function resolveAnchorItems(items: readonly AnchorItem[]) {\n if (!Array.isArray(items) || items.length === 0) throw new TypeError(\"Anchor items must not be empty\");\n const ids = new Set<string>();\n return items.map((item) => {\n if (!item || typeof item.id !== \"string\" || !item.id || /\\s/u.test(item.id)) throw new TypeError(\"Anchor id must be a non-empty HTML id without whitespace\");\n if (typeof item.label !== \"string\" || !item.label.trim()) throw new TypeError(\"Anchor label must not be empty\");\n if (ids.has(item.id)) throw new TypeError(`Duplicate Anchor id: ${item.id}`);\n ids.add(item.id);\n return { ...item, href: `#${encodeURIComponent(item.id)}` };\n });\n}\n\n/** Positions use one viewport's coordinates. DOM reads and scroll ownership stay in the renderer. */\nexport function getAnchorCurrentId(positions: readonly Readonly<{ id: string; top: number }>[], offset = 0, atEnd = false): string | undefined {\n if (!Number.isFinite(offset) || offset < 0) throw new RangeError(\"Anchor offset must be non-negative\");\n if (positions.some(({ top }) => !Number.isFinite(top))) throw new TypeError(\"Anchor positions must be finite\");\n const ordered = [...positions].sort((a, b) => a.top - b.top);\n if (atEnd) return ordered.at(-1)?.id;\n return ordered.filter(({ top }) => top <= offset).at(-1)?.id ?? ordered[0]?.id;\n}\n\nexport const anchorRecipe = {\n slots: [\"root\", \"list\", \"link\", \"indicator\"] as const,\n gap: spacing.xxs,\n link: { padding: spacing.xs, color: semanticColors.content.secondary },\n current: { color: semanticColors.content.brand, border: semanticColors.border.focus, width: stroke.strong },\n focus: focusIndicatorContract,\n} as const;\nexport const anchorBehavior = {\n controlled: [], inputs: [\"items\", \"offset\", \"container\", \"historyMode\"], stateAxes: {},\n web: { roles: [\"navigation\", \"list\", \"listitem\", \"link\"], keyboard: [\"Tab\", \"Enter\"], focus: \"native\" },\n native: { roles: [], states: [], actions: [] },\n scenarios: [\"scroll-position-selects-one-current-location\", \"activation-focuses-target-and-preserves-offset\", \"reduced-motion-jumps-instantly\", \"browser-history-restores-a-section\", \"missing-targets-are-not-current\"],\n} as const satisfies BehaviorContract;\n"]}