@hjmds/design-contracts 0.8.2

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 (353) hide show
  1. package/README.md +208 -0
  2. package/dist/alert-dialog.d.ts +102 -0
  3. package/dist/alert-dialog.d.ts.map +1 -0
  4. package/dist/alert-dialog.js +136 -0
  5. package/dist/alert-dialog.js.map +1 -0
  6. package/dist/base-recipes.d.ts +184 -0
  7. package/dist/base-recipes.d.ts.map +1 -0
  8. package/dist/base-recipes.js +129 -0
  9. package/dist/base-recipes.js.map +1 -0
  10. package/dist/behaviors.d.ts +1254 -0
  11. package/dist/behaviors.d.ts.map +1 -0
  12. package/dist/behaviors.js +972 -0
  13. package/dist/behaviors.js.map +1 -0
  14. package/dist/bottom-navigation-defaults.d.ts +13 -0
  15. package/dist/bottom-navigation-defaults.d.ts.map +1 -0
  16. package/dist/bottom-navigation-defaults.js +10 -0
  17. package/dist/bottom-navigation-defaults.js.map +1 -0
  18. package/dist/bottom-navigation.d.ts +114 -0
  19. package/dist/bottom-navigation.d.ts.map +1 -0
  20. package/dist/bottom-navigation.js +223 -0
  21. package/dist/bottom-navigation.js.map +1 -0
  22. package/dist/breadcrumb.d.ts +97 -0
  23. package/dist/breadcrumb.d.ts.map +1 -0
  24. package/dist/breadcrumb.js +99 -0
  25. package/dist/breadcrumb.js.map +1 -0
  26. package/dist/calendar.d.ts +285 -0
  27. package/dist/calendar.d.ts.map +1 -0
  28. package/dist/calendar.js +297 -0
  29. package/dist/calendar.js.map +1 -0
  30. package/dist/card.d.ts +39 -0
  31. package/dist/card.d.ts.map +1 -0
  32. package/dist/card.js +50 -0
  33. package/dist/card.js.map +1 -0
  34. package/dist/carousel.d.ts +180 -0
  35. package/dist/carousel.d.ts.map +1 -0
  36. package/dist/carousel.js +172 -0
  37. package/dist/carousel.js.map +1 -0
  38. package/dist/catalog.d.ts +7158 -0
  39. package/dist/catalog.d.ts.map +1 -0
  40. package/dist/catalog.js +220 -0
  41. package/dist/catalog.js.map +1 -0
  42. package/dist/collection.d.ts +123 -0
  43. package/dist/collection.d.ts.map +1 -0
  44. package/dist/collection.js +211 -0
  45. package/dist/collection.js.map +1 -0
  46. package/dist/color-references.d.ts +46 -0
  47. package/dist/color-references.d.ts.map +1 -0
  48. package/dist/color-references.js +39 -0
  49. package/dist/color-references.js.map +1 -0
  50. package/dist/colors.d.ts +56 -0
  51. package/dist/colors.d.ts.map +1 -0
  52. package/dist/colors.js +90 -0
  53. package/dist/colors.js.map +1 -0
  54. package/dist/command-palette.d.ts +240 -0
  55. package/dist/command-palette.d.ts.map +1 -0
  56. package/dist/command-palette.js +97 -0
  57. package/dist/command-palette.js.map +1 -0
  58. package/dist/component-contracts.d.ts +137 -0
  59. package/dist/component-contracts.d.ts.map +1 -0
  60. package/dist/component-contracts.js +53 -0
  61. package/dist/component-contracts.js.map +1 -0
  62. package/dist/component-definitions.d.ts +135 -0
  63. package/dist/component-definitions.d.ts.map +1 -0
  64. package/dist/component-definitions.js +142 -0
  65. package/dist/component-definitions.js.map +1 -0
  66. package/dist/component-recipes.d.ts +3583 -0
  67. package/dist/component-recipes.d.ts.map +1 -0
  68. package/dist/component-recipes.js +1503 -0
  69. package/dist/component-recipes.js.map +1 -0
  70. package/dist/component-references.d.ts +416 -0
  71. package/dist/component-references.d.ts.map +1 -0
  72. package/dist/component-references.js +140 -0
  73. package/dist/component-references.js.map +1 -0
  74. package/dist/content-state.d.ts +104 -0
  75. package/dist/content-state.d.ts.map +1 -0
  76. package/dist/content-state.js +116 -0
  77. package/dist/content-state.js.map +1 -0
  78. package/dist/counter-badge-recipe.d.ts +80 -0
  79. package/dist/counter-badge-recipe.d.ts.map +1 -0
  80. package/dist/counter-badge-recipe.js +44 -0
  81. package/dist/counter-badge-recipe.js.map +1 -0
  82. package/dist/counter-badge.d.ts +12 -0
  83. package/dist/counter-badge.d.ts.map +1 -0
  84. package/dist/counter-badge.js +21 -0
  85. package/dist/counter-badge.js.map +1 -0
  86. package/dist/data-table.d.ts +203 -0
  87. package/dist/data-table.d.ts.map +1 -0
  88. package/dist/data-table.js +182 -0
  89. package/dist/data-table.js.map +1 -0
  90. package/dist/date-picker.d.ts +268 -0
  91. package/dist/date-picker.d.ts.map +1 -0
  92. package/dist/date-picker.js +168 -0
  93. package/dist/date-picker.js.map +1 -0
  94. package/dist/description-list.d.ts +68 -0
  95. package/dist/description-list.d.ts.map +1 -0
  96. package/dist/description-list.js +85 -0
  97. package/dist/description-list.js.map +1 -0
  98. package/dist/design-system-provider.d.ts +80 -0
  99. package/dist/design-system-provider.d.ts.map +1 -0
  100. package/dist/design-system-provider.js +147 -0
  101. package/dist/design-system-provider.js.map +1 -0
  102. package/dist/evidence.d.ts +49 -0
  103. package/dist/evidence.d.ts.map +1 -0
  104. package/dist/evidence.js +133 -0
  105. package/dist/evidence.js.map +1 -0
  106. package/dist/file-picker.d.ts +183 -0
  107. package/dist/file-picker.d.ts.map +1 -0
  108. package/dist/file-picker.js +224 -0
  109. package/dist/file-picker.js.map +1 -0
  110. package/dist/floating-action-button.d.ts +143 -0
  111. package/dist/floating-action-button.d.ts.map +1 -0
  112. package/dist/floating-action-button.js +149 -0
  113. package/dist/floating-action-button.js.map +1 -0
  114. package/dist/form.d.ts +143 -0
  115. package/dist/form.d.ts.map +1 -0
  116. package/dist/form.js +206 -0
  117. package/dist/form.js.map +1 -0
  118. package/dist/foundations.d.ts +300 -0
  119. package/dist/foundations.d.ts.map +1 -0
  120. package/dist/foundations.js +238 -0
  121. package/dist/foundations.js.map +1 -0
  122. package/dist/grid.d.ts +75 -0
  123. package/dist/grid.d.ts.map +1 -0
  124. package/dist/grid.js +133 -0
  125. package/dist/grid.js.map +1 -0
  126. package/dist/icon-button-recipe.d.ts +99 -0
  127. package/dist/icon-button-recipe.d.ts.map +1 -0
  128. package/dist/icon-button-recipe.js +53 -0
  129. package/dist/icon-button-recipe.js.map +1 -0
  130. package/dist/icon.d.ts +41 -0
  131. package/dist/icon.d.ts.map +1 -0
  132. package/dist/icon.js +147 -0
  133. package/dist/icon.js.map +1 -0
  134. package/dist/image.d.ts +82 -0
  135. package/dist/image.d.ts.map +1 -0
  136. package/dist/image.js +100 -0
  137. package/dist/image.js.map +1 -0
  138. package/dist/index.d.ts +58 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +66 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/layout.d.ts +118 -0
  143. package/dist/layout.d.ts.map +1 -0
  144. package/dist/layout.js +118 -0
  145. package/dist/layout.js.map +1 -0
  146. package/dist/link.d.ts +57 -0
  147. package/dist/link.d.ts.map +1 -0
  148. package/dist/link.js +142 -0
  149. package/dist/link.js.map +1 -0
  150. package/dist/load-more.d.ts +64 -0
  151. package/dist/load-more.d.ts.map +1 -0
  152. package/dist/load-more.js +124 -0
  153. package/dist/load-more.js.map +1 -0
  154. package/dist/mentions.d.ts +67 -0
  155. package/dist/mentions.d.ts.map +1 -0
  156. package/dist/mentions.js +107 -0
  157. package/dist/mentions.js.map +1 -0
  158. package/dist/number-field.d.ts +208 -0
  159. package/dist/number-field.d.ts.map +1 -0
  160. package/dist/number-field.js +247 -0
  161. package/dist/number-field.js.map +1 -0
  162. package/dist/otp-field.d.ts +152 -0
  163. package/dist/otp-field.d.ts.map +1 -0
  164. package/dist/otp-field.js +117 -0
  165. package/dist/otp-field.js.map +1 -0
  166. package/dist/pagination.d.ts +181 -0
  167. package/dist/pagination.d.ts.map +1 -0
  168. package/dist/pagination.js +226 -0
  169. package/dist/pagination.js.map +1 -0
  170. package/dist/password-field.d.ts +187 -0
  171. package/dist/password-field.d.ts.map +1 -0
  172. package/dist/password-field.js +120 -0
  173. package/dist/password-field.js.map +1 -0
  174. package/dist/popover.d.ts +152 -0
  175. package/dist/popover.d.ts.map +1 -0
  176. package/dist/popover.js +137 -0
  177. package/dist/popover.js.map +1 -0
  178. package/dist/progress-recipe.d.ts +43 -0
  179. package/dist/progress-recipe.d.ts.map +1 -0
  180. package/dist/progress-recipe.js +16 -0
  181. package/dist/progress-recipe.js.map +1 -0
  182. package/dist/recipes.d.ts +31 -0
  183. package/dist/recipes.d.ts.map +1 -0
  184. package/dist/recipes.js +46 -0
  185. package/dist/recipes.js.map +1 -0
  186. package/dist/responsive.d.ts +27 -0
  187. package/dist/responsive.d.ts.map +1 -0
  188. package/dist/responsive.js +66 -0
  189. package/dist/responsive.js.map +1 -0
  190. package/dist/result.d.ts +111 -0
  191. package/dist/result.d.ts.map +1 -0
  192. package/dist/result.js +97 -0
  193. package/dist/result.js.map +1 -0
  194. package/dist/selection-helpers.d.ts +16 -0
  195. package/dist/selection-helpers.d.ts.map +1 -0
  196. package/dist/selection-helpers.js +52 -0
  197. package/dist/selection-helpers.js.map +1 -0
  198. package/dist/semantic-colors.d.ts +275 -0
  199. package/dist/semantic-colors.d.ts.map +1 -0
  200. package/dist/semantic-colors.js +84 -0
  201. package/dist/semantic-colors.js.map +1 -0
  202. package/dist/sheet.d.ts +51 -0
  203. package/dist/sheet.d.ts.map +1 -0
  204. package/dist/sheet.js +69 -0
  205. package/dist/sheet.js.map +1 -0
  206. package/dist/showcase.d.ts +153 -0
  207. package/dist/showcase.d.ts.map +1 -0
  208. package/dist/showcase.js +210 -0
  209. package/dist/showcase.js.map +1 -0
  210. package/dist/side-panel.d.ts +199 -0
  211. package/dist/side-panel.d.ts.map +1 -0
  212. package/dist/side-panel.js +111 -0
  213. package/dist/side-panel.js.map +1 -0
  214. package/dist/slider.d.ts +138 -0
  215. package/dist/slider.d.ts.map +1 -0
  216. package/dist/slider.js +150 -0
  217. package/dist/slider.js.map +1 -0
  218. package/dist/splitter.d.ts +113 -0
  219. package/dist/splitter.d.ts.map +1 -0
  220. package/dist/splitter.js +99 -0
  221. package/dist/splitter.js.map +1 -0
  222. package/dist/statistic.d.ts +41 -0
  223. package/dist/statistic.d.ts.map +1 -0
  224. package/dist/statistic.js +76 -0
  225. package/dist/statistic.js.map +1 -0
  226. package/dist/steps.d.ts +196 -0
  227. package/dist/steps.d.ts.map +1 -0
  228. package/dist/steps.js +160 -0
  229. package/dist/steps.js.map +1 -0
  230. package/dist/tag.d.ts +128 -0
  231. package/dist/tag.d.ts.map +1 -0
  232. package/dist/tag.js +93 -0
  233. package/dist/tag.js.map +1 -0
  234. package/dist/timeline.d.ts +147 -0
  235. package/dist/timeline.d.ts.map +1 -0
  236. package/dist/timeline.js +127 -0
  237. package/dist/timeline.js.map +1 -0
  238. package/dist/toast.d.ts +164 -0
  239. package/dist/toast.d.ts.map +1 -0
  240. package/dist/toast.js +529 -0
  241. package/dist/toast.js.map +1 -0
  242. package/dist/tokens.d.ts +9 -0
  243. package/dist/tokens.d.ts.map +1 -0
  244. package/dist/tokens.js +9 -0
  245. package/dist/tokens.js.map +1 -0
  246. package/dist/tooltip.d.ts +44 -0
  247. package/dist/tooltip.d.ts.map +1 -0
  248. package/dist/tooltip.js +88 -0
  249. package/dist/tooltip.js.map +1 -0
  250. package/dist/tour.d.ts +218 -0
  251. package/dist/tour.d.ts.map +1 -0
  252. package/dist/tour.js +211 -0
  253. package/dist/tour.js.map +1 -0
  254. package/dist/transfer-list.d.ts +207 -0
  255. package/dist/transfer-list.d.ts.map +1 -0
  256. package/dist/transfer-list.js +193 -0
  257. package/dist/transfer-list.js.map +1 -0
  258. package/dist/tree-select.d.ts +78 -0
  259. package/dist/tree-select.d.ts.map +1 -0
  260. package/dist/tree-select.js +132 -0
  261. package/dist/tree-select.js.map +1 -0
  262. package/dist/tree.d.ts +206 -0
  263. package/dist/tree.d.ts.map +1 -0
  264. package/dist/tree.js +223 -0
  265. package/dist/tree.js.map +1 -0
  266. package/dist/upload-item.d.ts +173 -0
  267. package/dist/upload-item.d.ts.map +1 -0
  268. package/dist/upload-item.js +156 -0
  269. package/dist/upload-item.js.map +1 -0
  270. package/dist/version.d.ts +3 -0
  271. package/dist/version.d.ts.map +1 -0
  272. package/dist/version.js +3 -0
  273. package/dist/version.js.map +1 -0
  274. package/docs/affix.md +63 -0
  275. package/docs/anchor.md +59 -0
  276. package/docs/ant-design-coverage.md +119 -0
  277. package/docs/app-provider.md +50 -0
  278. package/docs/app-rn-adoption.md +227 -0
  279. package/docs/architecture.md +320 -0
  280. package/docs/authoring-brief.md +87 -0
  281. package/docs/border-beam.md +66 -0
  282. package/docs/bottom-navigation.md +127 -0
  283. package/docs/breadcrumb.md +82 -0
  284. package/docs/calendar.md +154 -0
  285. package/docs/carousel.md +130 -0
  286. package/docs/cascader.md +93 -0
  287. package/docs/catalog-decision-status.md +306 -0
  288. package/docs/color-picker.md +72 -0
  289. package/docs/command-palette.md +116 -0
  290. package/docs/confirm-popover.md +93 -0
  291. package/docs/consistency-audit.md +383 -0
  292. package/docs/consumer-release-gate.md +90 -0
  293. package/docs/content-state.md +156 -0
  294. package/docs/context-panel.md +88 -0
  295. package/docs/cross-platform-core-normalization.md +118 -0
  296. package/docs/data-table.md +79 -0
  297. package/docs/date-picker.md +84 -0
  298. package/docs/description-list.md +75 -0
  299. package/docs/design-system-provider.md +139 -0
  300. package/docs/dropdown.md +78 -0
  301. package/docs/expansion-roadmap.md +285 -0
  302. package/docs/file-picker.md +59 -0
  303. package/docs/floating-action-button.md +111 -0
  304. package/docs/form.md +134 -0
  305. package/docs/generated/component-maturity.md +103 -0
  306. package/docs/generated/renderer-evidence.json +5597 -0
  307. package/docs/generated/renderer-evidence.md +132 -0
  308. package/docs/generated/showcase-manifest.json +3732 -0
  309. package/docs/icon.md +22 -0
  310. package/docs/identity.md +121 -0
  311. package/docs/image.md +84 -0
  312. package/docs/implementation-0.5.md +85 -0
  313. package/docs/layout-primitives.md +107 -0
  314. package/docs/layout.md +83 -0
  315. package/docs/library-reference-decisions.md +123 -0
  316. package/docs/link.md +67 -0
  317. package/docs/load-more.md +31 -0
  318. package/docs/mentions.md +82 -0
  319. package/docs/migration-0.2.md +108 -0
  320. package/docs/migration-0.3.md +72 -0
  321. package/docs/migration-0.5.md +82 -0
  322. package/docs/migration-0.6.md +197 -0
  323. package/docs/notification.md +54 -0
  324. package/docs/number-field.md +82 -0
  325. package/docs/otp-field.md +103 -0
  326. package/docs/pagination.md +139 -0
  327. package/docs/password-field.md +98 -0
  328. package/docs/popover.md +124 -0
  329. package/docs/promotion-candidates.md +169 -0
  330. package/docs/qr-code.md +69 -0
  331. package/docs/rating.md +58 -0
  332. package/docs/responsive-grid.md +90 -0
  333. package/docs/result.md +96 -0
  334. package/docs/showcase.md +97 -0
  335. package/docs/side-panel.md +71 -0
  336. package/docs/slider.md +79 -0
  337. package/docs/splitter.md +55 -0
  338. package/docs/statistic.md +16 -0
  339. package/docs/steps.md +115 -0
  340. package/docs/tag.md +57 -0
  341. package/docs/time-picker.md +88 -0
  342. package/docs/timeline.md +146 -0
  343. package/docs/toast.md +127 -0
  344. package/docs/tooltip.md +58 -0
  345. package/docs/tour.md +136 -0
  346. package/docs/transfer-list.md +94 -0
  347. package/docs/tree-select.md +101 -0
  348. package/docs/tree.md +123 -0
  349. package/docs/upload-item.md +68 -0
  350. package/docs/utility.md +53 -0
  351. package/docs/virtual-list.md +70 -0
  352. package/docs/watermark.md +58 -0
  353. package/package.json +402 -0
@@ -0,0 +1,143 @@
1
+ import type { SemanticIconName } from "./icon.js";
2
+ /**
3
+ * FloatingActionButton is not a smaller BottomCTA. BottomCTA is the screen's
4
+ * conclusion (full-width, end of a flow, `bottomCtaRecipe`'s full-bleed
5
+ * anatomy). FloatingActionButton is a single create action that floats above
6
+ * scrollable content the user is still browsing — it never spans the width
7
+ * and never claims to be the screen's only action. That is the entire
8
+ * justification for a second contract instead of a BottomCTA variant.
9
+ */
10
+ export type FloatingActionButtonLayoutMode = "expanded" | "collapsed";
11
+ /**
12
+ * The renderer owns scroll listening and thresholding (same boundary as
13
+ * LoadMore's IntersectionObserver/onEndReached and Popover's AnchoredOverlay —
14
+ * this package never touches a scroll container). It only reports a discrete
15
+ * signal; `resolveFloatingActionButtonLayoutMode` turns that into the next
16
+ * mode without the renderer re-deriving the collapse/expand rule itself.
17
+ */
18
+ export type FloatingActionButtonScrollSignal = "away-from-start" | "toward-start" | "idle";
19
+ export type FloatingActionButtonIconDescriptor<IconName extends string = SemanticIconName> = Readonly<{
20
+ name: IconName;
21
+ decorative?: true;
22
+ accessibilityLabel?: never;
23
+ size?: never;
24
+ tone?: never;
25
+ weight?: never;
26
+ directionality?: never;
27
+ }>;
28
+ export type ResolvedFloatingActionButtonIconDescriptor<IconName extends string = SemanticIconName> = Readonly<{
29
+ name: IconName;
30
+ decorative: true;
31
+ }>;
32
+ /**
33
+ * `label` is required even though `collapsed` mode shows only the icon.
34
+ * The action's name must never shrink to nothing just because its visible
35
+ * chrome did — `resolveFloatingActionButtonDescriptor` always reports the
36
+ * full label as the accessible name in both layout modes.
37
+ */
38
+ export type FloatingActionButtonDescriptor<IconName extends string = SemanticIconName> = Readonly<{
39
+ icon: FloatingActionButtonIconDescriptor<IconName>;
40
+ label: string;
41
+ layoutMode?: FloatingActionButtonLayoutMode;
42
+ }>;
43
+ export type ResolvedFloatingActionButtonDescriptor<IconName extends string = SemanticIconName> = Readonly<{
44
+ icon: ResolvedFloatingActionButtonIconDescriptor<IconName>;
45
+ label: string;
46
+ layoutMode: FloatingActionButtonLayoutMode;
47
+ resolvedAccessibilityLabel: string;
48
+ }>;
49
+ export declare const floatingActionButtonBehaviorDefaults: {
50
+ readonly layoutMode: "expanded";
51
+ };
52
+ export declare function validateFloatingActionButtonDescriptor<IconName extends string>(descriptor: FloatingActionButtonDescriptor<IconName>): void;
53
+ /**
54
+ * The label is the accessible name in every mode — `collapsed` never falls
55
+ * back to the icon name, because an icon-only glyph is not a stable
56
+ * accessible name across products or locales.
57
+ */
58
+ export declare function resolveFloatingActionButtonDescriptor<IconName extends string>(descriptor: FloatingActionButtonDescriptor<IconName>): ResolvedFloatingActionButtonDescriptor<IconName>;
59
+ /**
60
+ * Collapsing on `away-from-start` and expanding on `toward-start` mirrors the
61
+ * common list-scroll convention (hide the label while the user reads,
62
+ * reintroduce it once they scroll back toward the top). `idle` intentionally
63
+ * keeps whatever mode is already active instead of guessing, so a renderer
64
+ * that reports "no delta since last frame" cannot cause the button to flicker.
65
+ */
66
+ export declare function resolveFloatingActionButtonLayoutMode(signal: FloatingActionButtonScrollSignal, previousMode?: FloatingActionButtonLayoutMode): FloatingActionButtonLayoutMode;
67
+ /**
68
+ * Answers "who reserves the content's bottom padding" so both platform
69
+ * renderers compute the same number instead of each re-deriving
70
+ * diameter + margin arithmetic independently and drifting apart. The safe-area
71
+ * inset is additive, matching every other bottom-anchored recipe in this
72
+ * package (BottomNavigation, Sheet, Toast) rather than a `max()` clamp.
73
+ */
74
+ export declare function resolveFloatingActionButtonContentClearance(safeAreaBottomInset: number): number;
75
+ /**
76
+ * Both layout modes reuse Button/IconButton's `large` tier so the control
77
+ * never changes height while morphing between a circle and a pill — only
78
+ * shape and content change, per the brief's "don't fork the Button recipe."
79
+ */
80
+ export declare const floatingActionButtonRecipe: {
81
+ readonly slots: readonly ["root", "icon", "label"];
82
+ readonly defaults: {
83
+ readonly layoutMode: "expanded";
84
+ };
85
+ readonly circle: {
86
+ readonly diameter: 52;
87
+ readonly hitSlop: 0;
88
+ readonly glyph: "lg";
89
+ };
90
+ readonly tone: {
91
+ readonly background: Readonly<{
92
+ source: "theme";
93
+ key: "primary";
94
+ alpha?: number;
95
+ }>;
96
+ readonly content: Readonly<{
97
+ source: "theme";
98
+ key: "onPrimary";
99
+ alpha?: number;
100
+ }>;
101
+ readonly border: null;
102
+ };
103
+ readonly shape: "full";
104
+ /**
105
+ * Button의 large 티어와 같은 값이다. 그런데 `buttonRecipe`를 import하지는 않는다 —
106
+ * 그것은 `recipes.ts`에 있고 그 파일은 `component-recipes.ts`를 import하는 **하류**라,
107
+ * 여기서 거슬러 올라가면 순환이 된다(ESM은 재수출을 본문보다 먼저 평가해서
108
+ * `buttonRecipe`가 undefined인 채로 이 모듈이 실행된다).
109
+ *
110
+ * 대신 같은 foundations 토큰을 직접 읽고, **두 값이 어긋나지 않는다는 것은
111
+ * `test/floating-action-button.test.ts`의 불변식으로 강제한다.** 이 저장소가 규칙을
112
+ * 다루는 방식대로 — 지켜야 할 것은 import가 아니라 테스트로 묶는다.
113
+ */
114
+ readonly expandedLabel: {
115
+ readonly textVariant: "bodyLarge";
116
+ readonly paddingHorizontal: 20;
117
+ };
118
+ readonly margin: 16;
119
+ readonly shadow: {
120
+ readonly color: "#000000";
121
+ readonly opacity: 0.12;
122
+ readonly radius: 12;
123
+ readonly offsetY: 4;
124
+ };
125
+ /**
126
+ * `micro` (120ms, instant under Reduce Motion) rather than `enter`/`exit`:
127
+ * this transition can fire on every scroll-direction change, and a state
128
+ * driven by continuous scrolling should use the fastest state-change tier
129
+ * so it cannot visibly queue behind fast scroll gestures.
130
+ */
131
+ readonly transition: {
132
+ readonly duration: 120;
133
+ readonly easing: "standard";
134
+ readonly reducedMotion: "instant";
135
+ };
136
+ };
137
+ /**
138
+ * Literal scenario names for behaviorRegistry.floatingActionButton (lead
139
+ * wires into src/behaviors.ts). Kept here, not there, so this module stays
140
+ * self-contained per the authoring brief.
141
+ */
142
+ export declare const floatingActionButtonBehaviorScenarios: readonly ["collapses-on-scroll-away-from-start-without-losing-the-44-unit-target", "expands-on-scroll-toward-start", "idle-scroll-signal-does-not-change-the-current-mode", "accessible-name-is-the-full-label-in-both-layout-modes", "content-clearance-accounts-for-the-safe-area-inset-additively", "root-sits-after-scrollable-content-in-reading-and-tab-order", "reduced-motion-crossfades-icon-and-label-without-a-shape-tween"];
143
+ //# sourceMappingURL=floating-action-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floating-action-button.d.ts","sourceRoot":"","sources":["../src/floating-action-button.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,MAAM,8BAA8B,GAAG,UAAU,GAAG,WAAW,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,MAAM,gCAAgC,GACxC,iBAAiB,GACjB,cAAc,GACd,MAAM,CAAC;AAEX,MAAM,MAAM,kCAAkC,CAC5C,QAAQ,SAAS,MAAM,GAAG,gBAAgB,IACxC,QAAQ,CAAC;IACX,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB,kBAAkB,CAAC,EAAE,KAAK,CAAC;IAC3B,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,cAAc,CAAC,EAAE,KAAK,CAAC;CACxB,CAAC,CAAC;AAEH,MAAM,MAAM,0CAA0C,CACpD,QAAQ,SAAS,MAAM,GAAG,gBAAgB,IACxC,QAAQ,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,8BAA8B,CACxC,QAAQ,SAAS,MAAM,GAAG,gBAAgB,IACxC,QAAQ,CAAC;IACX,IAAI,EAAE,kCAAkC,CAAC,QAAQ,CAAC,CAAC;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,8BAA8B,CAAC;CAC7C,CAAC,CAAC;AAEH,MAAM,MAAM,sCAAsC,CAChD,QAAQ,SAAS,MAAM,GAAG,gBAAgB,IACxC,QAAQ,CAAC;IACX,IAAI,EAAE,0CAA0C,CAAC,QAAQ,CAAC,CAAC;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,8BAA8B,CAAC;IAC3C,0BAA0B,EAAE,MAAM,CAAC;CACpC,CAAC,CAAC;AAEH,eAAO,MAAM,oCAAoC;;CAE4B,CAAC;AAoC9E,wBAAgB,sCAAsC,CAAC,QAAQ,SAAS,MAAM,EAC5E,UAAU,EAAE,8BAA8B,CAAC,QAAQ,CAAC,GACnD,IAAI,CAsBN;AAED;;;;GAIG;AACH,wBAAgB,qCAAqC,CAAC,QAAQ,SAAS,MAAM,EAC3E,UAAU,EAAE,8BAA8B,CAAC,QAAQ,CAAC,GACnD,sCAAsC,CAAC,QAAQ,CAAC,CASlD;AAED;;;;;;GAMG;AACH,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,gCAAgC,EACxC,YAAY,GAAE,8BAAgF,GAC7F,8BAA8B,CAYhC;AAED;;;;;;GAMG;AACH,wBAAgB,2CAA2C,CACzD,mBAAmB,EAAE,MAAM,GAC1B,MAAM,CAWR;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;IAMrC;;;;;;;;;OASG;;;;;;;;;;;;IAOH;;;;;OAKG;;;;;;CAEK,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,yaAQxC,CAAC"}
@@ -0,0 +1,149 @@
1
+ import { iconButtonRecipe } from "./icon-button-recipe.js";
2
+ import { motionPreset, shadow, spacing } from "./foundations.js";
3
+ export const floatingActionButtonBehaviorDefaults = {
4
+ layoutMode: "expanded",
5
+ };
6
+ function validateTrimmedCopy(value, field) {
7
+ if (typeof value !== "string" || value.trim().length === 0) {
8
+ throw new TypeError(`${field} must not be empty`);
9
+ }
10
+ if (value !== value.trim()) {
11
+ throw new TypeError(`${field} must not start or end with whitespace`);
12
+ }
13
+ }
14
+ function isObject(value) {
15
+ return value !== null && typeof value === "object" && !Array.isArray(value);
16
+ }
17
+ function rejectUnknownKeys(value, allowed, field) {
18
+ for (const key of Object.keys(value)) {
19
+ if (!allowed.has(key)) {
20
+ throw new TypeError(`Unsupported FloatingActionButton ${field} field: ${key}`);
21
+ }
22
+ }
23
+ }
24
+ const descriptorKeys = new Set(["icon", "label", "layoutMode"]);
25
+ const iconKeys = new Set(["name", "decorative"]);
26
+ const layoutModes = new Set(["expanded", "collapsed"]);
27
+ const scrollSignals = new Set([
28
+ "away-from-start",
29
+ "toward-start",
30
+ "idle",
31
+ ]);
32
+ export function validateFloatingActionButtonDescriptor(descriptor) {
33
+ if (!isObject(descriptor)) {
34
+ throw new TypeError("FloatingActionButton descriptor must be an object");
35
+ }
36
+ rejectUnknownKeys(descriptor, descriptorKeys, "descriptor");
37
+ validateTrimmedCopy(descriptor.label, "FloatingActionButton label");
38
+ if (!isObject(descriptor.icon)) {
39
+ throw new TypeError("FloatingActionButton icon must be an object");
40
+ }
41
+ rejectUnknownKeys(descriptor.icon, iconKeys, "icon");
42
+ if (descriptor.icon.decorative !== undefined && descriptor.icon.decorative !== true) {
43
+ throw new TypeError("FloatingActionButton icon must be decorative");
44
+ }
45
+ validateTrimmedCopy(descriptor.icon.name, "FloatingActionButton icon name");
46
+ if (descriptor.layoutMode !== undefined &&
47
+ !layoutModes.has(descriptor.layoutMode)) {
48
+ throw new TypeError(`Unsupported FloatingActionButton layoutMode: ${String(descriptor.layoutMode)}`);
49
+ }
50
+ }
51
+ /**
52
+ * The label is the accessible name in every mode — `collapsed` never falls
53
+ * back to the icon name, because an icon-only glyph is not a stable
54
+ * accessible name across products or locales.
55
+ */
56
+ export function resolveFloatingActionButtonDescriptor(descriptor) {
57
+ validateFloatingActionButtonDescriptor(descriptor);
58
+ return {
59
+ icon: { name: descriptor.icon.name, decorative: true },
60
+ label: descriptor.label,
61
+ layoutMode: descriptor.layoutMode ?? floatingActionButtonBehaviorDefaults.layoutMode,
62
+ resolvedAccessibilityLabel: descriptor.label,
63
+ };
64
+ }
65
+ /**
66
+ * Collapsing on `away-from-start` and expanding on `toward-start` mirrors the
67
+ * common list-scroll convention (hide the label while the user reads,
68
+ * reintroduce it once they scroll back toward the top). `idle` intentionally
69
+ * keeps whatever mode is already active instead of guessing, so a renderer
70
+ * that reports "no delta since last frame" cannot cause the button to flicker.
71
+ */
72
+ export function resolveFloatingActionButtonLayoutMode(signal, previousMode = floatingActionButtonBehaviorDefaults.layoutMode) {
73
+ if (!scrollSignals.has(signal)) {
74
+ throw new TypeError(`Unsupported FloatingActionButton scroll signal: ${String(signal)}`);
75
+ }
76
+ if (!layoutModes.has(previousMode)) {
77
+ throw new TypeError(`Unsupported FloatingActionButton layoutMode: ${String(previousMode)}`);
78
+ }
79
+ if (signal === "away-from-start")
80
+ return "collapsed";
81
+ if (signal === "toward-start")
82
+ return "expanded";
83
+ return previousMode;
84
+ }
85
+ /**
86
+ * Answers "who reserves the content's bottom padding" so both platform
87
+ * renderers compute the same number instead of each re-deriving
88
+ * diameter + margin arithmetic independently and drifting apart. The safe-area
89
+ * inset is additive, matching every other bottom-anchored recipe in this
90
+ * package (BottomNavigation, Sheet, Toast) rather than a `max()` clamp.
91
+ */
92
+ export function resolveFloatingActionButtonContentClearance(safeAreaBottomInset) {
93
+ if (!Number.isFinite(safeAreaBottomInset) || safeAreaBottomInset < 0) {
94
+ throw new RangeError("FloatingActionButton safeAreaBottomInset must be a non-negative finite number");
95
+ }
96
+ return (floatingActionButtonRecipe.circle.diameter +
97
+ floatingActionButtonRecipe.margin * 2 +
98
+ safeAreaBottomInset);
99
+ }
100
+ /**
101
+ * Both layout modes reuse Button/IconButton's `large` tier so the control
102
+ * never changes height while morphing between a circle and a pill — only
103
+ * shape and content change, per the brief's "don't fork the Button recipe."
104
+ */
105
+ export const floatingActionButtonRecipe = {
106
+ slots: ["root", "icon", "label"],
107
+ defaults: { layoutMode: "expanded" },
108
+ circle: iconButtonRecipe.sizes.large,
109
+ tone: iconButtonRecipe.tones.primary,
110
+ shape: iconButtonRecipe.shapes.circle,
111
+ /**
112
+ * Button의 large 티어와 같은 값이다. 그런데 `buttonRecipe`를 import하지는 않는다 —
113
+ * 그것은 `recipes.ts`에 있고 그 파일은 `component-recipes.ts`를 import하는 **하류**라,
114
+ * 여기서 거슬러 올라가면 순환이 된다(ESM은 재수출을 본문보다 먼저 평가해서
115
+ * `buttonRecipe`가 undefined인 채로 이 모듈이 실행된다).
116
+ *
117
+ * 대신 같은 foundations 토큰을 직접 읽고, **두 값이 어긋나지 않는다는 것은
118
+ * `test/floating-action-button.test.ts`의 불변식으로 강제한다.** 이 저장소가 규칙을
119
+ * 다루는 방식대로 — 지켜야 할 것은 import가 아니라 테스트로 묶는다.
120
+ */
121
+ expandedLabel: {
122
+ textVariant: "bodyLarge",
123
+ paddingHorizontal: spacing.lg,
124
+ },
125
+ margin: spacing.md,
126
+ shadow: shadow.floating,
127
+ /**
128
+ * `micro` (120ms, instant under Reduce Motion) rather than `enter`/`exit`:
129
+ * this transition can fire on every scroll-direction change, and a state
130
+ * driven by continuous scrolling should use the fastest state-change tier
131
+ * so it cannot visibly queue behind fast scroll gestures.
132
+ */
133
+ transition: motionPreset.micro,
134
+ };
135
+ /**
136
+ * Literal scenario names for behaviorRegistry.floatingActionButton (lead
137
+ * wires into src/behaviors.ts). Kept here, not there, so this module stays
138
+ * self-contained per the authoring brief.
139
+ */
140
+ export const floatingActionButtonBehaviorScenarios = [
141
+ "collapses-on-scroll-away-from-start-without-losing-the-44-unit-target",
142
+ "expands-on-scroll-toward-start",
143
+ "idle-scroll-signal-does-not-change-the-current-mode",
144
+ "accessible-name-is-the-full-label-in-both-layout-modes",
145
+ "content-clearance-accounts-for-the-safe-area-inset-additively",
146
+ "root-sits-after-scrollable-content-in-reading-and-tab-order",
147
+ "reduced-motion-crossfades-icon-and-label-without-a-shape-tween",
148
+ ];
149
+ //# sourceMappingURL=floating-action-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floating-action-button.js","sourceRoot":"","sources":["../src/floating-action-button.ts"],"names":[],"mappings":"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","sourcesContent":["import { iconButtonRecipe } from \"./icon-button-recipe.js\";\nimport { motionPreset, shadow, spacing } from \"./foundations.js\";\nimport type { SemanticIconName } from \"./icon.js\";\n\n/**\n * FloatingActionButton is not a smaller BottomCTA. BottomCTA is the screen's\n * conclusion (full-width, end of a flow, `bottomCtaRecipe`'s full-bleed\n * anatomy). FloatingActionButton is a single create action that floats above\n * scrollable content the user is still browsing — it never spans the width\n * and never claims to be the screen's only action. That is the entire\n * justification for a second contract instead of a BottomCTA variant.\n */\nexport type FloatingActionButtonLayoutMode = \"expanded\" | \"collapsed\";\n\n/**\n * The renderer owns scroll listening and thresholding (same boundary as\n * LoadMore's IntersectionObserver/onEndReached and Popover's AnchoredOverlay —\n * this package never touches a scroll container). It only reports a discrete\n * signal; `resolveFloatingActionButtonLayoutMode` turns that into the next\n * mode without the renderer re-deriving the collapse/expand rule itself.\n */\nexport type FloatingActionButtonScrollSignal =\n | \"away-from-start\"\n | \"toward-start\"\n | \"idle\";\n\nexport type FloatingActionButtonIconDescriptor<\n IconName extends string = SemanticIconName,\n> = Readonly<{\n name: IconName;\n decorative?: true;\n accessibilityLabel?: never;\n size?: never;\n tone?: never;\n weight?: never;\n directionality?: never;\n}>;\n\nexport type ResolvedFloatingActionButtonIconDescriptor<\n IconName extends string = SemanticIconName,\n> = Readonly<{ name: IconName; decorative: true }>;\n\n/**\n * `label` is required even though `collapsed` mode shows only the icon.\n * The action's name must never shrink to nothing just because its visible\n * chrome did — `resolveFloatingActionButtonDescriptor` always reports the\n * full label as the accessible name in both layout modes.\n */\nexport type FloatingActionButtonDescriptor<\n IconName extends string = SemanticIconName,\n> = Readonly<{\n icon: FloatingActionButtonIconDescriptor<IconName>;\n label: string;\n layoutMode?: FloatingActionButtonLayoutMode;\n}>;\n\nexport type ResolvedFloatingActionButtonDescriptor<\n IconName extends string = SemanticIconName,\n> = Readonly<{\n icon: ResolvedFloatingActionButtonIconDescriptor<IconName>;\n label: string;\n layoutMode: FloatingActionButtonLayoutMode;\n resolvedAccessibilityLabel: string;\n}>;\n\nexport const floatingActionButtonBehaviorDefaults = {\n layoutMode: \"expanded\",\n} as const satisfies Readonly<{ layoutMode: FloatingActionButtonLayoutMode }>;\n\nfunction validateTrimmedCopy(value: unknown, field: string): asserts value is string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`${field} must not be empty`);\n }\n if (value !== value.trim()) {\n throw new TypeError(`${field} must not start or end with whitespace`);\n }\n}\n\nfunction isObject(value: unknown): value is Readonly<Record<string, unknown>> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction rejectUnknownKeys(\n value: Readonly<Record<string, unknown>>,\n allowed: ReadonlySet<string>,\n field: string,\n): void {\n for (const key of Object.keys(value)) {\n if (!allowed.has(key)) {\n throw new TypeError(`Unsupported FloatingActionButton ${field} field: ${key}`);\n }\n }\n}\n\nconst descriptorKeys = new Set([\"icon\", \"label\", \"layoutMode\"]);\nconst iconKeys = new Set([\"name\", \"decorative\"]);\nconst layoutModes = new Set<FloatingActionButtonLayoutMode>([\"expanded\", \"collapsed\"]);\nconst scrollSignals = new Set<FloatingActionButtonScrollSignal>([\n \"away-from-start\",\n \"toward-start\",\n \"idle\",\n]);\n\nexport function validateFloatingActionButtonDescriptor<IconName extends string>(\n descriptor: FloatingActionButtonDescriptor<IconName>,\n): void {\n if (!isObject(descriptor)) {\n throw new TypeError(\"FloatingActionButton descriptor must be an object\");\n }\n rejectUnknownKeys(descriptor, descriptorKeys, \"descriptor\");\n validateTrimmedCopy(descriptor.label, \"FloatingActionButton label\");\n if (!isObject(descriptor.icon)) {\n throw new TypeError(\"FloatingActionButton icon must be an object\");\n }\n rejectUnknownKeys(descriptor.icon, iconKeys, \"icon\");\n if (descriptor.icon.decorative !== undefined && descriptor.icon.decorative !== true) {\n throw new TypeError(\"FloatingActionButton icon must be decorative\");\n }\n validateTrimmedCopy(descriptor.icon.name, \"FloatingActionButton icon name\");\n if (\n descriptor.layoutMode !== undefined &&\n !layoutModes.has(descriptor.layoutMode)\n ) {\n throw new TypeError(\n `Unsupported FloatingActionButton layoutMode: ${String(descriptor.layoutMode)}`,\n );\n }\n}\n\n/**\n * The label is the accessible name in every mode — `collapsed` never falls\n * back to the icon name, because an icon-only glyph is not a stable\n * accessible name across products or locales.\n */\nexport function resolveFloatingActionButtonDescriptor<IconName extends string>(\n descriptor: FloatingActionButtonDescriptor<IconName>,\n): ResolvedFloatingActionButtonDescriptor<IconName> {\n validateFloatingActionButtonDescriptor(descriptor);\n return {\n icon: { name: descriptor.icon.name, decorative: true },\n label: descriptor.label,\n layoutMode:\n descriptor.layoutMode ?? floatingActionButtonBehaviorDefaults.layoutMode,\n resolvedAccessibilityLabel: descriptor.label,\n };\n}\n\n/**\n * Collapsing on `away-from-start` and expanding on `toward-start` mirrors the\n * common list-scroll convention (hide the label while the user reads,\n * reintroduce it once they scroll back toward the top). `idle` intentionally\n * keeps whatever mode is already active instead of guessing, so a renderer\n * that reports \"no delta since last frame\" cannot cause the button to flicker.\n */\nexport function resolveFloatingActionButtonLayoutMode(\n signal: FloatingActionButtonScrollSignal,\n previousMode: FloatingActionButtonLayoutMode = floatingActionButtonBehaviorDefaults.layoutMode,\n): FloatingActionButtonLayoutMode {\n if (!scrollSignals.has(signal)) {\n throw new TypeError(`Unsupported FloatingActionButton scroll signal: ${String(signal)}`);\n }\n if (!layoutModes.has(previousMode)) {\n throw new TypeError(\n `Unsupported FloatingActionButton layoutMode: ${String(previousMode)}`,\n );\n }\n if (signal === \"away-from-start\") return \"collapsed\";\n if (signal === \"toward-start\") return \"expanded\";\n return previousMode;\n}\n\n/**\n * Answers \"who reserves the content's bottom padding\" so both platform\n * renderers compute the same number instead of each re-deriving\n * diameter + margin arithmetic independently and drifting apart. The safe-area\n * inset is additive, matching every other bottom-anchored recipe in this\n * package (BottomNavigation, Sheet, Toast) rather than a `max()` clamp.\n */\nexport function resolveFloatingActionButtonContentClearance(\n safeAreaBottomInset: number,\n): number {\n if (!Number.isFinite(safeAreaBottomInset) || safeAreaBottomInset < 0) {\n throw new RangeError(\n \"FloatingActionButton safeAreaBottomInset must be a non-negative finite number\",\n );\n }\n return (\n floatingActionButtonRecipe.circle.diameter +\n floatingActionButtonRecipe.margin * 2 +\n safeAreaBottomInset\n );\n}\n\n/**\n * Both layout modes reuse Button/IconButton's `large` tier so the control\n * never changes height while morphing between a circle and a pill — only\n * shape and content change, per the brief's \"don't fork the Button recipe.\"\n */\nexport const floatingActionButtonRecipe = {\n slots: [\"root\", \"icon\", \"label\"] as const,\n defaults: { layoutMode: \"expanded\" } as const,\n circle: iconButtonRecipe.sizes.large,\n tone: iconButtonRecipe.tones.primary,\n shape: iconButtonRecipe.shapes.circle,\n /**\n * Button의 large 티어와 같은 값이다. 그런데 `buttonRecipe`를 import하지는 않는다 —\n * 그것은 `recipes.ts`에 있고 그 파일은 `component-recipes.ts`를 import하는 **하류**라,\n * 여기서 거슬러 올라가면 순환이 된다(ESM은 재수출을 본문보다 먼저 평가해서\n * `buttonRecipe`가 undefined인 채로 이 모듈이 실행된다).\n *\n * 대신 같은 foundations 토큰을 직접 읽고, **두 값이 어긋나지 않는다는 것은\n * `test/floating-action-button.test.ts`의 불변식으로 강제한다.** 이 저장소가 규칙을\n * 다루는 방식대로 — 지켜야 할 것은 import가 아니라 테스트로 묶는다.\n */\n expandedLabel: {\n textVariant: \"bodyLarge\",\n paddingHorizontal: spacing.lg,\n },\n margin: spacing.md,\n shadow: shadow.floating,\n /**\n * `micro` (120ms, instant under Reduce Motion) rather than `enter`/`exit`:\n * this transition can fire on every scroll-direction change, and a state\n * driven by continuous scrolling should use the fastest state-change tier\n * so it cannot visibly queue behind fast scroll gestures.\n */\n transition: motionPreset.micro,\n} as const;\n\n/**\n * Literal scenario names for behaviorRegistry.floatingActionButton (lead\n * wires into src/behaviors.ts). Kept here, not there, so this module stays\n * self-contained per the authoring brief.\n */\nexport const floatingActionButtonBehaviorScenarios = [\n \"collapses-on-scroll-away-from-start-without-losing-the-44-unit-target\",\n \"expands-on-scroll-toward-start\",\n \"idle-scroll-signal-does-not-change-the-current-mode\",\n \"accessible-name-is-the-full-label-in-both-layout-modes\",\n \"content-clearance-accounts-for-the-safe-area-inset-additively\",\n \"root-sits-after-scrollable-content-in-reading-and-tab-order\",\n \"reduced-motion-crossfades-icon-and-label-without-a-shape-tween\",\n] as const;\n"]}
package/dist/form.d.ts ADDED
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Form owns two things only: the vertical rhythm between already-framed
3
+ * Fields, and the repeatable submit session (idle → submitting →
4
+ * succeeded/failed). Field values, registration, dependencies, validateTrigger
5
+ * policy and Form.List-style repeating groups stay product-owned (React Hook
6
+ * Form or equivalent) — see docs/form.md for the full boundary.
7
+ */
8
+ export type FormSubmitStatus = "idle" | "submitting" | "succeeded" | "failed";
9
+ export type FormSubmitPhase = Readonly<{
10
+ status: "idle";
11
+ }> | Readonly<{
12
+ status: "submitting";
13
+ }> | Readonly<{
14
+ status: "succeeded";
15
+ }> | Readonly<{
16
+ status: "failed";
17
+ message: string;
18
+ }>;
19
+ export type FormSubmitOutcome = Readonly<{
20
+ outcome: "succeeded";
21
+ }> | Readonly<{
22
+ outcome: "failed";
23
+ message: string;
24
+ }> | Readonly<{
25
+ outcome: "blocked";
26
+ reason: "already-submitting";
27
+ }> | Readonly<{
28
+ outcome: "interrupted";
29
+ }>;
30
+ /** Rejects to report a submission failure; resolving never implies success. */
31
+ export type FormSubmitHandler<Values> = (values: Values) => void | Promise<void>;
32
+ export type FormSubmitSessionOptions<Values> = Readonly<{
33
+ onSubmit: FormSubmitHandler<Values>;
34
+ /** Localized fallback shown when `resolveErrorMessage` is absent, throws, or returns empty copy. */
35
+ fallbackErrorMessage: string;
36
+ resolveErrorMessage?: (error: unknown) => string;
37
+ }>;
38
+ /**
39
+ * A single submit session is reused across many attempts — unlike
40
+ * AlertDialog's one-shot confirm, a form typically stays mounted and gets
41
+ * resubmitted (edit → retry, or a settings screen saved again later). Each
42
+ * attempt still settles its own returned Promise exactly once, and
43
+ * `submitting` blocks every concurrent attempt the same way AlertDialog's
44
+ * `busy` blocks a second confirm.
45
+ */
46
+ export type FormSubmitSession<Values> = Readonly<{
47
+ getSnapshot(): FormSubmitPhase;
48
+ subscribe(listener: () => void): () => void;
49
+ submit(values: Values): Promise<FormSubmitOutcome>;
50
+ /** Moves a resting `succeeded`/`failed` phase back to `idle`. No-op while submitting. */
51
+ reset(): boolean;
52
+ dispose(): boolean;
53
+ }>;
54
+ export declare function validateFormSubmitSessionOptions<Values>(options: FormSubmitSessionOptions<Values>): void;
55
+ /**
56
+ * Owns submit re-entrancy and exactly-once settlement. Does not own field
57
+ * values (the caller passes `values` at call time), field-level validation,
58
+ * or when submission is allowed to start (the product decides that from its
59
+ * own dirty/valid state before calling `submit`).
60
+ */
61
+ export declare function createFormSubmitSession<Values>(options: FormSubmitSessionOptions<Values>): FormSubmitSession<Values>;
62
+ /**
63
+ * Form does not register fields or run validation, so it cannot find the
64
+ * first invalid field on its own. The product supplies its own field render
65
+ * order and the ids it currently considers invalid (e.g. React Hook Form's
66
+ * `formState.errors` keys); Form only fixes which one wins the race — first
67
+ * in render order — so every product answers this the same way.
68
+ */
69
+ export declare function validateFormFieldOrder(fieldOrder: readonly string[]): void;
70
+ /**
71
+ * Web renderers call `.focus()` on the resolved field's control; React Native
72
+ * renderers move accessibility focus to it (e.g. `AccessibilityInfo.sendAccessibilityEvent`
73
+ * or a focus-trap ref). Returns `null` when there are no fields yet or none
74
+ * are invalid, in which case a failed submit has no field to focus and the
75
+ * form-level error slot below owns the announcement instead.
76
+ */
77
+ export declare function resolveFirstInvalidFieldFocusTarget(fieldOrder: readonly string[], invalidFieldIds: ReadonlySet<string> | readonly string[]): string | null;
78
+ export type FormDensity = "compact" | "comfortable";
79
+ export declare const formDefaults: {
80
+ readonly density: "comfortable";
81
+ };
82
+ /**
83
+ * Anatomy is fixed, not a configurable prop: fields, then the form-level
84
+ * error, then actions. `field` frame styling (background, border, its own
85
+ * label/hint/error gap) belongs to `fieldRecipe`; this recipe only sets the
86
+ * gap *between* one field and the next. `formError` renders as the existing
87
+ * Notice `tone="danger"` contract — Form fixes only its slot position, it
88
+ * does not redeclare Notice's colors. `actions` has no dedicated layout here;
89
+ * composing Button/Stack in that slot is the product's job, same reasoning
90
+ * the roadmap already applies to Stack.
91
+ */
92
+ export declare const formRecipe: {
93
+ readonly slots: readonly ["root", "field", "formError", "actions"];
94
+ readonly defaults: {
95
+ readonly density: "comfortable";
96
+ };
97
+ readonly density: {
98
+ readonly compact: {
99
+ readonly fieldGap: 12;
100
+ };
101
+ readonly comfortable: {
102
+ readonly fieldGap: 20;
103
+ };
104
+ };
105
+ readonly formError: {
106
+ readonly position: "beforeActions";
107
+ readonly gap: 12;
108
+ };
109
+ };
110
+ export declare const formBehaviorDefaults: {
111
+ /** Reuses Toast's own `normal | high` vocabulary: a blocked submission is `high`/assertive. */
112
+ readonly errorAnnouncementPriority: "high";
113
+ };
114
+ /**
115
+ * Kept beside the Form contract as the source value wired by
116
+ * `behaviorRegistry.form`; this lets the registry reuse the contract without
117
+ * redeclaring Form's inputs, events, states, or scenarios.
118
+ */
119
+ export declare const formBehaviorSpec: {
120
+ readonly controlled: readonly [];
121
+ readonly inputs: readonly ["status", "formError", "fieldOrder"];
122
+ readonly events: readonly ["onSubmit"];
123
+ readonly defaults: {
124
+ /** Reuses Toast's own `normal | high` vocabulary: a blocked submission is `high`/assertive. */
125
+ readonly errorAnnouncementPriority: "high";
126
+ };
127
+ readonly stateAxes: {
128
+ readonly availability: readonly ["enabled", "busy"];
129
+ readonly content: readonly ["idle", "loading", "error"];
130
+ };
131
+ readonly web: {
132
+ readonly roles: readonly ["alert"];
133
+ readonly keyboard: readonly ["Tab", "Enter"];
134
+ readonly focus: "native";
135
+ };
136
+ readonly native: {
137
+ readonly roles: readonly ["alert"];
138
+ readonly states: readonly ["busy"];
139
+ readonly actions: readonly ["submit"];
140
+ };
141
+ readonly scenarios: readonly ["submitting-blocks-every-concurrent-submit-attempt", "each-submit-attempt-settles-its-result-exactly-once", "failed-submit-returns-to-a-resting-state-that-still-allows-retry", "succeeded-submit-remains-resting-and-allows-resubmission", "dispose-during-submit-settles-the-pending-attempt-as-interrupted", "disposed-session-rejects-further-submit-attempts", "form-level-error-never-carries-a-per-field-message", "first-invalid-field-in-render-order-receives-focus-on-submit-failure", "form-level-error-is-announced-when-no-field-target-exists"];
142
+ };
143
+ //# sourceMappingURL=form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAMH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE9E,MAAM,MAAM,eAAe,GACvB,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAC5B,QAAQ,CAAC;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC,GAClC,QAAQ,CAAC;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC,GACjC,QAAQ,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEpD,MAAM,MAAM,iBAAiB,GACzB,QAAQ,CAAC;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC,GAClC,QAAQ,CAAC;IAAE,OAAO,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAChD,QAAQ,CAAC;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAC,GAC9D,QAAQ,CAAC;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,CAAC,CAAC;AAEzC,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEjF,MAAM,MAAM,wBAAwB,CAAC,MAAM,IAAI,QAAQ,CAAC;IACtD,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACpC,oGAAoG;IACpG,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;CAClD,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,CAAC,MAAM,IAAI,QAAQ,CAAC;IAC/C,WAAW,IAAI,eAAe,CAAC;IAC/B,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACnD,yFAAyF;IACzF,KAAK,IAAI,OAAO,CAAC;IACjB,OAAO,IAAI,OAAO,CAAC;CACpB,CAAC,CAAC;AAQH,wBAAgB,gCAAgC,CAAC,MAAM,EACrD,OAAO,EAAE,wBAAwB,CAAC,MAAM,CAAC,GACxC,IAAI,CAKN;AAcD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAC5C,OAAO,EAAE,wBAAwB,CAAC,MAAM,CAAC,GACxC,iBAAiB,CAAC,MAAM,CAAC,CAqE3B;AAMD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAW1E;AAED;;;;;;GAMG;AACH,wBAAgB,mCAAmC,CACjD,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,GACvD,MAAM,GAAG,IAAI,CAQf;AAMD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,aAAa,CAAC;AAEpD,eAAO,MAAM,YAAY;;CAE8B,CAAC;AAExD;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;CAgBrB,CAAC;AAMH,eAAO,MAAM,oBAAoB;IAC/B,+FAA+F;;CAElB,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;QAT3B,+FAA+F;;;;;;;;;;;;;;;;;;CAsCvF,CAAC"}