@hjmds/design-contracts 1.7.0 → 1.9.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 (250) hide show
  1. package/THIRD_PARTY_NOTICES.md +58 -0
  2. package/dist/affix.d.ts +8 -0
  3. package/dist/affix.d.ts.map +1 -0
  4. package/dist/affix.js +7 -0
  5. package/dist/affix.js.map +1 -0
  6. package/dist/agreement.d.ts +3 -3
  7. package/dist/agreement.js +1 -1
  8. package/dist/agreement.js.map +1 -1
  9. package/dist/asset.d.ts +1 -1
  10. package/dist/asset.js +1 -1
  11. package/dist/asset.js.map +1 -1
  12. package/dist/base-recipes.d.ts +12 -12
  13. package/dist/base-recipes.d.ts.map +1 -1
  14. package/dist/base-recipes.js +15 -14
  15. package/dist/base-recipes.js.map +1 -1
  16. package/dist/behaviors.d.ts +1 -1
  17. package/dist/catalog.d.ts +445 -651
  18. package/dist/catalog.d.ts.map +1 -1
  19. package/dist/catalog.js +149 -101
  20. package/dist/catalog.js.map +1 -1
  21. package/dist/color-picker.d.ts +11 -0
  22. package/dist/color-picker.d.ts.map +1 -0
  23. package/dist/color-picker.js +20 -0
  24. package/dist/color-picker.js.map +1 -0
  25. package/dist/command-palette.d.ts +3 -3
  26. package/dist/component-contracts.d.ts +4 -4
  27. package/dist/component-contracts.d.ts.map +1 -1
  28. package/dist/component-contracts.js +5 -4
  29. package/dist/component-contracts.js.map +1 -1
  30. package/dist/component-definitions.d.ts +1 -9
  31. package/dist/component-definitions.d.ts.map +1 -1
  32. package/dist/component-definitions.js +1 -9
  33. package/dist/component-definitions.js.map +1 -1
  34. package/dist/component-recipes.d.ts +76 -83
  35. package/dist/component-recipes.d.ts.map +1 -1
  36. package/dist/component-recipes.js +29 -49
  37. package/dist/component-recipes.js.map +1 -1
  38. package/dist/component-references.d.ts +10 -25
  39. package/dist/component-references.d.ts.map +1 -1
  40. package/dist/component-references.js +5 -8
  41. package/dist/component-references.js.map +1 -1
  42. package/dist/data-table.d.ts +3 -3
  43. package/dist/data-table.js +1 -1
  44. package/dist/data-table.js.map +1 -1
  45. package/dist/date-picker.d.ts +2 -2
  46. package/dist/icon-button-recipe.d.ts +2 -2
  47. package/dist/icon-button-recipe.js +1 -1
  48. package/dist/icon-button-recipe.js.map +1 -1
  49. package/dist/image.d.ts +2 -2
  50. package/dist/image.js +2 -2
  51. package/dist/image.js.map +1 -1
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +1 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/interaction-adapters.d.ts +53 -0
  57. package/dist/interaction-adapters.d.ts.map +1 -0
  58. package/dist/interaction-adapters.js +45 -0
  59. package/dist/interaction-adapters.js.map +1 -0
  60. package/dist/internal/thinking-orb/braid.d.ts +3 -0
  61. package/dist/internal/thinking-orb/braid.d.ts.map +1 -0
  62. package/dist/internal/thinking-orb/braid.js +47 -0
  63. package/dist/internal/thinking-orb/braid.js.map +1 -0
  64. package/dist/internal/thinking-orb/core.d.ts +60 -0
  65. package/dist/internal/thinking-orb/core.d.ts.map +1 -0
  66. package/dist/internal/thinking-orb/core.js +86 -0
  67. package/dist/internal/thinking-orb/core.js.map +1 -0
  68. package/dist/internal/thinking-orb/lattice.d.ts +5 -0
  69. package/dist/internal/thinking-orb/lattice.d.ts.map +1 -0
  70. package/dist/internal/thinking-orb/lattice.js +187 -0
  71. package/dist/internal/thinking-orb/lattice.js.map +1 -0
  72. package/dist/internal/thinking-orb/morph.d.ts +3 -0
  73. package/dist/internal/thinking-orb/morph.d.ts.map +1 -0
  74. package/dist/internal/thinking-orb/morph.js +127 -0
  75. package/dist/internal/thinking-orb/morph.js.map +1 -0
  76. package/dist/internal/thinking-orb/orbits.d.ts +3 -0
  77. package/dist/internal/thinking-orb/orbits.d.ts.map +1 -0
  78. package/dist/internal/thinking-orb/orbits.js +68 -0
  79. package/dist/internal/thinking-orb/orbits.js.map +1 -0
  80. package/dist/internal/thinking-orb/presets.d.ts +21 -0
  81. package/dist/internal/thinking-orb/presets.d.ts.map +1 -0
  82. package/dist/internal/thinking-orb/presets.js +77 -0
  83. package/dist/internal/thinking-orb/presets.js.map +1 -0
  84. package/dist/internal/thinking-orb/profiles.d.ts +8 -0
  85. package/dist/internal/thinking-orb/profiles.d.ts.map +1 -0
  86. package/dist/internal/thinking-orb/profiles.js +158 -0
  87. package/dist/internal/thinking-orb/profiles.js.map +1 -0
  88. package/dist/internal/thinking-orb/registry.d.ts +9 -0
  89. package/dist/internal/thinking-orb/registry.d.ts.map +1 -0
  90. package/dist/internal/thinking-orb/registry.js +27 -0
  91. package/dist/internal/thinking-orb/registry.js.map +1 -0
  92. package/dist/internal/thinking-orb/ribbon.d.ts +3 -0
  93. package/dist/internal/thinking-orb/ribbon.d.ts.map +1 -0
  94. package/dist/internal/thinking-orb/ribbon.js +87 -0
  95. package/dist/internal/thinking-orb/ribbon.js.map +1 -0
  96. package/dist/internal/thinking-orb/types.d.ts +15 -0
  97. package/dist/internal/thinking-orb/types.d.ts.map +1 -0
  98. package/dist/internal/thinking-orb/types.js +4 -0
  99. package/dist/internal/thinking-orb/types.js.map +1 -0
  100. package/dist/internal/thinking-orb/web.d.ts +3 -0
  101. package/dist/internal/thinking-orb/web.d.ts.map +1 -0
  102. package/dist/internal/thinking-orb/web.js +92 -0
  103. package/dist/internal/thinking-orb/web.js.map +1 -0
  104. package/dist/masonry.d.ts +17 -0
  105. package/dist/masonry.d.ts.map +1 -0
  106. package/dist/masonry.js +18 -0
  107. package/dist/masonry.js.map +1 -0
  108. package/dist/menubar.d.ts +3 -3
  109. package/dist/menubar.js +1 -1
  110. package/dist/menubar.js.map +1 -1
  111. package/dist/number-field.d.ts +2 -2
  112. package/dist/otp-field.d.ts +1 -1
  113. package/dist/password-field.d.ts +2 -2
  114. package/dist/popover.d.ts +7 -8
  115. package/dist/popover.d.ts.map +1 -1
  116. package/dist/popover.js +2 -0
  117. package/dist/popover.js.map +1 -1
  118. package/dist/qr-code-recipe.d.ts +7 -0
  119. package/dist/qr-code-recipe.d.ts.map +1 -0
  120. package/dist/qr-code-recipe.js +2 -0
  121. package/dist/qr-code-recipe.js.map +1 -0
  122. package/dist/qr-code.d.ts +16 -0
  123. package/dist/qr-code.d.ts.map +1 -0
  124. package/dist/qr-code.js +37 -0
  125. package/dist/qr-code.js.map +1 -0
  126. package/dist/semantic-colors.d.ts +1 -1
  127. package/dist/semantic-colors.js +1 -1
  128. package/dist/semantic-colors.js.map +1 -1
  129. package/dist/showcase.d.ts +5 -1
  130. package/dist/showcase.d.ts.map +1 -1
  131. package/dist/showcase.js +53 -12
  132. package/dist/showcase.js.map +1 -1
  133. package/dist/sidebar.d.ts +3 -3
  134. package/dist/sidebar.js +1 -1
  135. package/dist/sidebar.js.map +1 -1
  136. package/dist/tag.d.ts +4 -4
  137. package/dist/tag.js +3 -3
  138. package/dist/tag.js.map +1 -1
  139. package/dist/tags-input.d.ts +3 -3
  140. package/dist/tags-input.js +1 -1
  141. package/dist/tags-input.js.map +1 -1
  142. package/dist/text-formats.d.ts +2 -2
  143. package/dist/text-formats.js +2 -2
  144. package/dist/text-formats.js.map +1 -1
  145. package/dist/thinking-orb-recipe.d.ts +15 -0
  146. package/dist/thinking-orb-recipe.d.ts.map +1 -0
  147. package/dist/thinking-orb-recipe.js +9 -0
  148. package/dist/thinking-orb-recipe.js.map +1 -0
  149. package/dist/thinking-orb.d.ts +25 -0
  150. package/dist/thinking-orb.d.ts.map +1 -0
  151. package/dist/thinking-orb.js +40 -0
  152. package/dist/thinking-orb.js.map +1 -0
  153. package/dist/toast-liquid.d.ts +116 -0
  154. package/dist/toast-liquid.d.ts.map +1 -0
  155. package/dist/toast-liquid.js +103 -0
  156. package/dist/toast-liquid.js.map +1 -0
  157. package/dist/toast.d.ts +7 -1
  158. package/dist/toast.d.ts.map +1 -1
  159. package/dist/toast.js +5 -0
  160. package/dist/toast.js.map +1 -1
  161. package/dist/toggle-group.d.ts +2 -2
  162. package/dist/toggle-group.js +3 -3
  163. package/dist/toggle-group.js.map +1 -1
  164. package/dist/transfer-list.d.ts +3 -3
  165. package/dist/transfer-list.js +1 -1
  166. package/dist/transfer-list.js.map +1 -1
  167. package/dist/tree.d.ts +2 -2
  168. package/dist/version.d.ts +1 -1
  169. package/dist/version.js +1 -1
  170. package/dist/version.js.map +1 -1
  171. package/dist/virtual-list.d.ts +13 -0
  172. package/dist/virtual-list.d.ts.map +1 -0
  173. package/dist/virtual-list.js +13 -0
  174. package/dist/virtual-list.js.map +1 -0
  175. package/dist/watermark.d.ts +15 -0
  176. package/dist/watermark.d.ts.map +1 -0
  177. package/dist/watermark.js +11 -0
  178. package/dist/watermark.js.map +1 -0
  179. package/docs/affix.md +27 -63
  180. package/docs/anchor.md +5 -4
  181. package/docs/ant-design-coverage.md +8 -12
  182. package/docs/architecture.md +6 -2
  183. package/docs/authoring-brief.md +3 -2
  184. package/docs/bottom-navigation.md +7 -6
  185. package/docs/breadcrumb.md +5 -2
  186. package/docs/calendar.md +6 -3
  187. package/docs/carousel.md +2 -2
  188. package/docs/cascader.md +11 -0
  189. package/docs/catalog-cleanup.md +7 -0
  190. package/docs/catalog-decision-status.md +8 -3
  191. package/docs/catalog-freeze.json +9 -7
  192. package/docs/collapsible.md +12 -0
  193. package/docs/color-picker.md +30 -72
  194. package/docs/command-palette.md +5 -3
  195. package/docs/confirm-popover.md +5 -0
  196. package/docs/consumer-policy.md +29 -12
  197. package/docs/context-menu.md +12 -2
  198. package/docs/data-table.md +11 -6
  199. package/docs/date-picker.md +6 -4
  200. package/docs/date-range.md +4 -2
  201. package/docs/description-list.md +8 -9
  202. package/docs/dialog.md +39 -0
  203. package/docs/expansion-roadmap.md +11 -7
  204. package/docs/file-picker.md +5 -3
  205. package/docs/form.md +16 -4
  206. package/docs/generated/component-maturity.md +91 -99
  207. package/docs/generated/renderer-evidence.json +2043 -1059
  208. package/docs/generated/renderer-evidence.md +156 -145
  209. package/docs/generated/showcase-manifest.json +476 -625
  210. package/docs/layout.md +8 -9
  211. package/docs/library-gap-analysis.md +1 -1
  212. package/docs/masonry.md +11 -0
  213. package/docs/mentions.md +14 -7
  214. package/docs/optional-adapters.md +126 -0
  215. package/docs/otp-field.md +2 -1
  216. package/docs/pagination.md +4 -1
  217. package/docs/password-field.md +2 -1
  218. package/docs/popover.md +9 -6
  219. package/docs/promotion-candidates.md +2 -1
  220. package/docs/qr-code.md +11 -65
  221. package/docs/rating.md +5 -0
  222. package/docs/react-native-completion.md +1 -1
  223. package/docs/result.md +8 -5
  224. package/docs/screen-chrome.md +2 -1
  225. package/docs/sheet.md +8 -2
  226. package/docs/showcase.md +10 -2
  227. package/docs/side-panel.md +12 -6
  228. package/docs/sidebar.md +3 -0
  229. package/docs/splitter.md +6 -6
  230. package/docs/stable-core.md +92 -0
  231. package/docs/stable-promotion.md +47 -43
  232. package/docs/tags-input.md +5 -2
  233. package/docs/theme-palette.md +27 -0
  234. package/docs/thinking-orb.md +124 -0
  235. package/docs/time-picker.md +5 -0
  236. package/docs/timeline.md +2 -3
  237. package/docs/toast.md +6 -0
  238. package/docs/tooltip.md +2 -1
  239. package/docs/tour.md +14 -8
  240. package/docs/transfer-list.md +24 -21
  241. package/docs/tree-select.md +7 -1
  242. package/docs/tree.md +12 -7
  243. package/docs/upload-item.md +7 -3
  244. package/docs/virtual-list.md +10 -67
  245. package/docs/watermark.md +26 -58
  246. package/package.json +54 -3
  247. package/docs/app-provider.md +0 -50
  248. package/docs/border-beam.md +0 -66
  249. package/docs/chart.md +0 -33
  250. package/docs/utility.md +0 -53
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../src/sidebar.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 type { LinkDestination } from \"./link.js\";\nimport { control, radius, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * 데스크톱 웹의 세로 내비게이션. 관리자 화면·문서·작업 도구처럼 넓은 화면에서 좌우로\n * 나뉜 셸의 왼쪽이다.\n *\n * `BottomNavigation`과 같은 문제를 푸는 다른 표면이 아니다 — 두 축이 다르다.\n * BottomNavigation은 **3~5개 최상위 목적지**를 모바일 하단에 고정하고 그룹이 없다.\n * Sidebar는 **그룹이 있는 긴 목록**이고 접힌다. 한 컴포넌트의 `orientation` 축으로\n * 합치면 \"그룹이 있는 하단 탭바\"라는 존재하지 않는 조합이 타입에 생긴다.\n *\n * `Layout`의 sidebar 영역과도 다르다. Layout은 **자리**(폭·순서·반응형 규칙)를 갖고\n * 내용을 모른다. 이 계약은 그 자리에 들어가는 **내비게이션 자체**다.\n */\nexport type SidebarItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n destination?: LinkDestination;\n /** 접힌 상태에서도 보이는 상태 수치. Badge/CounterBadge가 그린다. */\n badgeCount?: number;\n disabled?: boolean;\n}>;\n\nexport type SidebarGroupDescriptor<\n Id extends string = string,\n GroupId extends string = string,\n> = Readonly<{\n id: GroupId;\n /** 그룹 제목. 없으면 구분선만 있는 묶음이다. */\n label?: string;\n items: readonly SidebarItemDescriptor<Id>[];\n}>;\n\nexport type SidebarDescriptor<\n Id extends string = string,\n GroupId extends string = string,\n> = Readonly<{\n accessibilityLabel: string;\n groups: readonly SidebarGroupDescriptor<Id, GroupId>[];\n /** 현재 위치. 링크 내비게이션의 `aria-current`가 된다. */\n currentId: Id | null;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Sidebar ${field} must not be empty`);\n }\n}\n\nexport function validateSidebarDescriptor<Id extends string, GroupId extends string>(\n descriptor: SidebarDescriptor<Id, GroupId>,\n): void {\n assertNonEmpty(descriptor.accessibilityLabel, \"accessibilityLabel\");\n if (!Array.isArray(descriptor.groups) || descriptor.groups.length === 0) {\n throw new RangeError(\"Sidebar must contain at least one group\");\n }\n const itemIds = new Set<Id>();\n const groupIds = new Set<GroupId>();\n for (const group of descriptor.groups) {\n assertNonEmpty(group.id, \"group id\");\n if (groupIds.has(group.id)) throw new TypeError(`Duplicate Sidebar group id: ${group.id}`);\n groupIds.add(group.id);\n if (group.label !== undefined) assertNonEmpty(group.label, `group ${group.id} label`);\n if (!Array.isArray(group.items) || group.items.length === 0) {\n throw new RangeError(`Sidebar group ${group.id} must contain at least one item`);\n }\n for (const item of group.items) {\n assertNonEmpty(item.id, \"item id\");\n assertNonEmpty(item.label, `item ${item.id} label`);\n if (itemIds.has(item.id)) throw new TypeError(`Duplicate Sidebar item id: ${item.id}`);\n itemIds.add(item.id);\n if (item.badgeCount !== undefined && (!Number.isInteger(item.badgeCount) || item.badgeCount < 0)) {\n throw new RangeError(`Sidebar item ${item.id} badgeCount must be a non-negative integer`);\n }\n }\n }\n if (descriptor.currentId !== null && !itemIds.has(descriptor.currentId)) {\n throw new RangeError(`Sidebar currentId does not match any item: ${String(descriptor.currentId)}`);\n }\n}\n\n/**\n * 접힘은 **표시 밀도**이지 내용이 아니다. 접혀도 항목은 모두 남고 라벨만 숨는다 —\n * 접을 때 항목을 빼면 \"접었더니 메뉴가 사라졌다\"가 되고, 그건 다른 컴포넌트다.\n * 라벨이 숨으면 아이콘만으로는 이름을 알 수 없으므로 renderer가 접근성 이름을 유지한다.\n */\nexport const sidebarDefaults = {\n collapsed: false,\n} as const satisfies Readonly<{ collapsed: boolean }>;\n\nexport const sidebarRecipe = {\n slots: [\"root\", \"group\", \"groupLabel\", \"item\", \"label\", \"badge\", \"toggle\"] as const,\n defaults: sidebarDefaults,\n widths: { expanded: 260, collapsed: 72 },\n background: semanticColors.surface.sunken,\n border: semanticColors.border.default,\n borderWidth: stroke.default,\n paddingVertical: spacing.sm,\n paddingHorizontal: spacing.xs,\n gap: spacing.xxs,\n groupGap: spacing.md,\n groupLabel: {\n color: semanticColors.content.secondary,\n textVariant: \"label\" as const,\n paddingHorizontal: spacing.sm,\n paddingVertical: spacing.xxs,\n },\n item: collectionItemContract,\n itemRadius: radius.md,\n itemMinHeight: control.minTouchTarget,\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"group\", \"groupLabel\", \"item\", \"label\", \"badge\", \"toggle\"];\n defaults: { collapsed: boolean };\n widths: { expanded: number; collapsed: number };\n background: ColorReference;\n border: ColorReference;\n borderWidth: number;\n paddingVertical: number;\n paddingHorizontal: number;\n gap: number;\n groupGap: number;\n groupLabel: { color: ColorReference; textVariant: \"label\"; paddingHorizontal: number; paddingVertical: number };\n item: typeof collectionItemContract;\n itemRadius: number;\n itemMinHeight: number;\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const sidebarBehavior = {\n controlled: [\"collapsed\", \"defaultCollapsed\", \"onCollapsedChange\"],\n inputs: [\"groups\", \"currentId\", \"accessibilityLabel\"],\n events: [\"onNavigate\"],\n defaults: sidebarDefaults,\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"selected\"],\n interaction: [\"idle\", \"hover\", \"focusVisible\", \"pressed\"],\n },\n web: {\n roles: [\"navigation\", \"list\", \"listitem\", \"link\", \"button\"],\n keyboard: [\"Tab\", \"Enter\"],\n /** Each item is a link in document order; no roving focus and no arrow-key grid. */\n focus: \"native\",\n },\n /** Web-only: a phone uses BottomNavigation, and a native tablet split view is a navigator concern. */\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"the-current-item-is-announced-with-aria-current-not-only-painted\",\n \"collapsing-hides-labels-but-keeps-every-item-and-its-accessible-name\",\n \"groups-are-real-list-groupings-with-their-own-label-not-visual-dividers-only\",\n \"an-item-badge-stays-visible-while-collapsed-because-it-is-the-reason-to-look\",\n \"bottom-navigation-owns-the-three-to-five-destination-mobile-case-this-contract-never-adds-it\",\n \"layout-owns-where-the-sidebar-sits-this-contract-owns-what-is-inside-it\",\n ],\n} as const satisfies BehaviorContract;\n"]}
1
+ {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../src/sidebar.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 type { LinkDestination } from \"./link.js\";\nimport { control, radius, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * 데스크톱 웹의 세로 내비게이션. 관리자 화면·문서·작업 도구처럼 넓은 화면에서 좌우로\n * 나뉜 셸의 왼쪽이다.\n *\n * `BottomNavigation`과 같은 문제를 푸는 다른 표면이 아니다 — 두 축이 다르다.\n * BottomNavigation은 **3~5개 최상위 목적지**를 모바일 하단에 고정하고 그룹이 없다.\n * Sidebar는 **그룹이 있는 긴 목록**이고 접힌다. 한 컴포넌트의 `orientation` 축으로\n * 합치면 \"그룹이 있는 하단 탭바\"라는 존재하지 않는 조합이 타입에 생긴다.\n *\n * `Layout`의 sidebar 영역과도 다르다. Layout은 **자리**(폭·순서·반응형 규칙)를 갖고\n * 내용을 모른다. 이 계약은 그 자리에 들어가는 **내비게이션 자체**다.\n */\nexport type SidebarItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n destination?: LinkDestination;\n /** 접힌 상태에서도 보이는 상태 수치. Badge/CounterBadge가 그린다. */\n badgeCount?: number;\n disabled?: boolean;\n}>;\n\nexport type SidebarGroupDescriptor<\n Id extends string = string,\n GroupId extends string = string,\n> = Readonly<{\n id: GroupId;\n /** 그룹 제목. 없으면 구분선만 있는 묶음이다. */\n label?: string;\n items: readonly SidebarItemDescriptor<Id>[];\n}>;\n\nexport type SidebarDescriptor<\n Id extends string = string,\n GroupId extends string = string,\n> = Readonly<{\n accessibilityLabel: string;\n groups: readonly SidebarGroupDescriptor<Id, GroupId>[];\n /** 현재 위치. 링크 내비게이션의 `aria-current`가 된다. */\n currentId: Id | null;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Sidebar ${field} must not be empty`);\n }\n}\n\nexport function validateSidebarDescriptor<Id extends string, GroupId extends string>(\n descriptor: SidebarDescriptor<Id, GroupId>,\n): void {\n assertNonEmpty(descriptor.accessibilityLabel, \"accessibilityLabel\");\n if (!Array.isArray(descriptor.groups) || descriptor.groups.length === 0) {\n throw new RangeError(\"Sidebar must contain at least one group\");\n }\n const itemIds = new Set<Id>();\n const groupIds = new Set<GroupId>();\n for (const group of descriptor.groups) {\n assertNonEmpty(group.id, \"group id\");\n if (groupIds.has(group.id)) throw new TypeError(`Duplicate Sidebar group id: ${group.id}`);\n groupIds.add(group.id);\n if (group.label !== undefined) assertNonEmpty(group.label, `group ${group.id} label`);\n if (!Array.isArray(group.items) || group.items.length === 0) {\n throw new RangeError(`Sidebar group ${group.id} must contain at least one item`);\n }\n for (const item of group.items) {\n assertNonEmpty(item.id, \"item id\");\n assertNonEmpty(item.label, `item ${item.id} label`);\n if (itemIds.has(item.id)) throw new TypeError(`Duplicate Sidebar item id: ${item.id}`);\n itemIds.add(item.id);\n if (item.badgeCount !== undefined && (!Number.isInteger(item.badgeCount) || item.badgeCount < 0)) {\n throw new RangeError(`Sidebar item ${item.id} badgeCount must be a non-negative integer`);\n }\n }\n }\n if (descriptor.currentId !== null && !itemIds.has(descriptor.currentId)) {\n throw new RangeError(`Sidebar currentId does not match any item: ${String(descriptor.currentId)}`);\n }\n}\n\n/**\n * 접힘은 **표시 밀도**이지 내용이 아니다. 접혀도 항목은 모두 남고 라벨만 숨는다 —\n * 접을 때 항목을 빼면 \"접었더니 메뉴가 사라졌다\"가 되고, 그건 다른 컴포넌트다.\n * 라벨이 숨으면 아이콘만으로는 이름을 알 수 없으므로 renderer가 접근성 이름을 유지한다.\n */\nexport const sidebarDefaults = {\n collapsed: false,\n} as const satisfies Readonly<{ collapsed: boolean }>;\n\nexport const sidebarRecipe = {\n slots: [\"root\", \"group\", \"groupLabel\", \"item\", \"label\", \"badge\", \"toggle\"] as const,\n defaults: sidebarDefaults,\n widths: { expanded: 260, collapsed: 72 },\n background: semanticColors.canvas,\n border: semanticColors.border.default,\n borderWidth: stroke.default,\n paddingVertical: spacing.sm,\n paddingHorizontal: spacing.xs,\n gap: spacing.xxs,\n groupGap: spacing.md,\n groupLabel: {\n color: semanticColors.content.secondary,\n textVariant: \"label\" as const,\n paddingHorizontal: spacing.sm,\n paddingVertical: spacing.xxs,\n },\n item: collectionItemContract,\n itemRadius: radius.md,\n itemMinHeight: control.minTouchTarget,\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"group\", \"groupLabel\", \"item\", \"label\", \"badge\", \"toggle\"];\n defaults: { collapsed: boolean };\n widths: { expanded: number; collapsed: number };\n background: ColorReference;\n border: ColorReference;\n borderWidth: number;\n paddingVertical: number;\n paddingHorizontal: number;\n gap: number;\n groupGap: number;\n groupLabel: { color: ColorReference; textVariant: \"label\"; paddingHorizontal: number; paddingVertical: number };\n item: typeof collectionItemContract;\n itemRadius: number;\n itemMinHeight: number;\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const sidebarBehavior = {\n controlled: [\"collapsed\", \"defaultCollapsed\", \"onCollapsedChange\"],\n inputs: [\"groups\", \"currentId\", \"accessibilityLabel\"],\n events: [\"onNavigate\"],\n defaults: sidebarDefaults,\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"selected\"],\n interaction: [\"idle\", \"hover\", \"focusVisible\", \"pressed\"],\n },\n web: {\n roles: [\"navigation\", \"list\", \"listitem\", \"link\", \"button\"],\n keyboard: [\"Tab\", \"Enter\"],\n /** Each item is a link in document order; no roving focus and no arrow-key grid. */\n focus: \"native\",\n },\n /** Web-only: a phone uses BottomNavigation, and a native tablet split view is a navigator concern. */\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"the-current-item-is-announced-with-aria-current-not-only-painted\",\n \"collapsing-hides-labels-but-keeps-every-item-and-its-accessible-name\",\n \"groups-are-real-list-groupings-with-their-own-label-not-visual-dividers-only\",\n \"an-item-badge-stays-visible-while-collapsed-because-it-is-the-reason-to-look\",\n \"bottom-navigation-owns-the-three-to-five-destination-mobile-case-this-contract-never-adds-it\",\n \"layout-owns-where-the-sidebar-sits-this-contract-owns-what-is-inside-it\",\n ],\n} as const satisfies BehaviorContract;\n"]}
package/dist/tag.d.ts CHANGED
@@ -33,7 +33,7 @@ export declare const tagRecipe: {
33
33
  readonly neutral: {
34
34
  readonly background: Readonly<{
35
35
  source: "theme";
36
- key: "surfaceAlt";
36
+ key: "bg";
37
37
  alpha?: number;
38
38
  }>;
39
39
  readonly content: Readonly<{
@@ -46,7 +46,7 @@ export declare const tagRecipe: {
46
46
  readonly brand: {
47
47
  readonly background: Readonly<{
48
48
  source: "theme";
49
- key: "surfaceAccent";
49
+ key: "bg";
50
50
  alpha?: number;
51
51
  }>;
52
52
  readonly content: Readonly<{
@@ -58,8 +58,8 @@ export declare const tagRecipe: {
58
58
  };
59
59
  readonly info: {
60
60
  readonly background: Readonly<{
61
- source: "accent";
62
- key: "info";
61
+ source: "theme";
62
+ key: "bg";
63
63
  alpha?: number;
64
64
  }>;
65
65
  readonly content: Readonly<{
package/dist/tag.js CHANGED
@@ -41,17 +41,17 @@ export const tagRecipe = {
41
41
  defaults: tagDefaults,
42
42
  tones: {
43
43
  neutral: {
44
- background: semanticColors.surface.sunken,
44
+ background: semanticColors.canvas,
45
45
  content: semanticColors.content.secondary,
46
46
  border: null,
47
47
  },
48
48
  brand: {
49
- background: semanticColors.surface.brand,
49
+ background: semanticColors.canvas,
50
50
  content: semanticColors.content.brand,
51
51
  border: null,
52
52
  },
53
53
  info: {
54
- background: semanticColors.feedback.info.badgeBackground,
54
+ background: semanticColors.canvas,
55
55
  content: semanticColors.feedback.info.foreground,
56
56
  border: semanticColors.feedback.info.border,
57
57
  },
package/dist/tag.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tag.js","sourceRoot":"","sources":["../src/tag.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveColorReference,\n type ColorReferencePalette,\n} from \"./color-references.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport { fontWeight, spacing, stroke } from \"./foundations.js\";\n\nexport type TagTone = \"neutral\" | \"info\" | \"success\" | \"attention\" | \"brand\";\n\nexport const tagDefaults = {\n tone: \"neutral\",\n} as const satisfies Readonly<{ tone: TagTone }>;\n\n/**\n * A single piece of static metadata (a position, a grade, a season). Tag has\n * no interaction axis at all — no selected, no pressed, no closable. Removing\n * or toggling a tag is deselection, which belongs to `Chip`.\n */\nexport type TagDescriptor = Readonly<{\n label: string;\n tone?: TagTone;\n}>;\n\nexport type ResolvedTagDescriptor = Readonly<{\n label: string;\n tone: TagTone;\n}>;\n\nfunction assertCopy(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Tag ${field} must not be empty`);\n }\n}\n\nconst supportedTones: readonly TagTone[] = [\n \"neutral\",\n \"info\",\n \"success\",\n \"attention\",\n \"brand\",\n];\n\nexport function validateTagDescriptor(descriptor: TagDescriptor): void {\n assertCopy(descriptor.label, \"label\");\n if (\n descriptor.tone !== undefined &&\n !supportedTones.includes(descriptor.tone)\n ) {\n throw new TypeError(`Unsupported Tag tone: ${String(descriptor.tone)}`);\n }\n}\n\nexport function resolveTagDescriptor(\n descriptor: TagDescriptor,\n): ResolvedTagDescriptor {\n validateTagDescriptor(descriptor);\n return {\n label: descriptor.label,\n tone: descriptor.tone ?? tagDefaults.tone,\n };\n}\n\n/**\n * Rectangular, not the pill `radius.full` that `chipRecipe` and `badgeRecipe`\n * use, so a static Tag never reads as a pressable Chip or a status Badge at a\n * glance. No `states` and no `focus` contract: nothing here is ever pressed\n * or focused.\n */\nexport const tagRecipe = {\n slots: [\"root\", \"label\"] as const,\n defaults: tagDefaults,\n tones: {\n neutral: {\n background: semanticColors.surface.sunken,\n content: semanticColors.content.secondary,\n border: null,\n },\n brand: {\n background: semanticColors.surface.brand,\n content: semanticColors.content.brand,\n border: null,\n },\n info: {\n background: semanticColors.feedback.info.badgeBackground,\n content: semanticColors.feedback.info.foreground,\n border: semanticColors.feedback.info.border,\n },\n success: {\n background: semanticColors.feedback.success.badgeBackground,\n content: semanticColors.feedback.success.foreground,\n border: semanticColors.feedback.success.border,\n },\n attention: {\n background: semanticColors.feedback.attention.badgeBackground,\n content: semanticColors.feedback.attention.foreground,\n border: semanticColors.feedback.attention.border,\n },\n },\n size: {\n // Static metadata still participates in Dynamic Type. A fixed 20px frame\n // clips the label at accessibility sizes; minHeight preserves the compact\n // base shape while allowing the intrinsic line box to grow.\n minHeight: 20,\n paddingHorizontal: spacing.xxs,\n gap: spacing.xxs,\n textVariant: \"caption\",\n fontWeight: fontWeight.semibold,\n },\n radius: \"sm\",\n borderWidth: stroke.default,\n} as const;\n\nexport type ResolvedTagPresentation = Readonly<{\n background: string;\n content: string;\n border: string | null;\n}>;\n\n/** Resolve the same Tag recipe for non-CSS renderers. */\nexport function resolveTagPresentation(\n tone: TagTone,\n palette: ColorReferencePalette,\n): ResolvedTagPresentation {\n const contract = tagRecipe.tones[tone];\n return {\n background: resolveColorReference(contract.background, palette),\n content: resolveColorReference(contract.content, palette),\n border:\n contract.border === null\n ? null\n : resolveColorReference(contract.border, palette),\n };\n}\n"]}
1
+ {"version":3,"file":"tag.js","sourceRoot":"","sources":["../src/tag.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveColorReference,\n type ColorReferencePalette,\n} from \"./color-references.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport { fontWeight, spacing, stroke } from \"./foundations.js\";\n\nexport type TagTone = \"neutral\" | \"info\" | \"success\" | \"attention\" | \"brand\";\n\nexport const tagDefaults = {\n tone: \"neutral\",\n} as const satisfies Readonly<{ tone: TagTone }>;\n\n/**\n * A single piece of static metadata (a position, a grade, a season). Tag has\n * no interaction axis at all — no selected, no pressed, no closable. Removing\n * or toggling a tag is deselection, which belongs to `Chip`.\n */\nexport type TagDescriptor = Readonly<{\n label: string;\n tone?: TagTone;\n}>;\n\nexport type ResolvedTagDescriptor = Readonly<{\n label: string;\n tone: TagTone;\n}>;\n\nfunction assertCopy(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Tag ${field} must not be empty`);\n }\n}\n\nconst supportedTones: readonly TagTone[] = [\n \"neutral\",\n \"info\",\n \"success\",\n \"attention\",\n \"brand\",\n];\n\nexport function validateTagDescriptor(descriptor: TagDescriptor): void {\n assertCopy(descriptor.label, \"label\");\n if (\n descriptor.tone !== undefined &&\n !supportedTones.includes(descriptor.tone)\n ) {\n throw new TypeError(`Unsupported Tag tone: ${String(descriptor.tone)}`);\n }\n}\n\nexport function resolveTagDescriptor(\n descriptor: TagDescriptor,\n): ResolvedTagDescriptor {\n validateTagDescriptor(descriptor);\n return {\n label: descriptor.label,\n tone: descriptor.tone ?? tagDefaults.tone,\n };\n}\n\n/**\n * Rectangular, not the pill `radius.full` that `chipRecipe` and `badgeRecipe`\n * use, so a static Tag never reads as a pressable Chip or a status Badge at a\n * glance. No `states` and no `focus` contract: nothing here is ever pressed\n * or focused.\n */\nexport const tagRecipe = {\n slots: [\"root\", \"label\"] as const,\n defaults: tagDefaults,\n tones: {\n neutral: {\n background: semanticColors.canvas,\n content: semanticColors.content.secondary,\n border: null,\n },\n brand: {\n background: semanticColors.canvas,\n content: semanticColors.content.brand,\n border: null,\n },\n info: {\n background: semanticColors.canvas,\n content: semanticColors.feedback.info.foreground,\n border: semanticColors.feedback.info.border,\n },\n success: {\n background: semanticColors.feedback.success.badgeBackground,\n content: semanticColors.feedback.success.foreground,\n border: semanticColors.feedback.success.border,\n },\n attention: {\n background: semanticColors.feedback.attention.badgeBackground,\n content: semanticColors.feedback.attention.foreground,\n border: semanticColors.feedback.attention.border,\n },\n },\n size: {\n // Static metadata still participates in Dynamic Type. A fixed 20px frame\n // clips the label at accessibility sizes; minHeight preserves the compact\n // base shape while allowing the intrinsic line box to grow.\n minHeight: 20,\n paddingHorizontal: spacing.xxs,\n gap: spacing.xxs,\n textVariant: \"caption\",\n fontWeight: fontWeight.semibold,\n },\n radius: \"sm\",\n borderWidth: stroke.default,\n} as const;\n\nexport type ResolvedTagPresentation = Readonly<{\n background: string;\n content: string;\n border: string | null;\n}>;\n\n/** Resolve the same Tag recipe for non-CSS renderers. */\nexport function resolveTagPresentation(\n tone: TagTone,\n palette: ColorReferencePalette,\n): ResolvedTagPresentation {\n const contract = tagRecipe.tones[tone];\n return {\n background: resolveColorReference(contract.background, palette),\n content: resolveColorReference(contract.content, palette),\n border:\n contract.border === null\n ? null\n : resolveColorReference(contract.border, palette),\n };\n}\n"]}
@@ -76,12 +76,12 @@ export declare const tagsInputRecipe: {
76
76
  readonly gap: 8;
77
77
  readonly border: Readonly<{
78
78
  source: "theme";
79
- key: "textMuted";
79
+ key: "borderControl";
80
80
  alpha?: number;
81
81
  }>;
82
82
  readonly background: Readonly<{
83
83
  source: "theme";
84
- key: "surface";
84
+ key: "bg";
85
85
  alpha?: number;
86
86
  }>;
87
87
  };
@@ -91,7 +91,7 @@ export declare const tagsInputRecipe: {
91
91
  readonly gap: 4;
92
92
  readonly background: Readonly<{
93
93
  source: "theme";
94
- key: "surfaceAlt";
94
+ key: "bg";
95
95
  alpha?: number;
96
96
  }>;
97
97
  readonly color: Readonly<{
@@ -65,7 +65,7 @@ export const tagsInputRecipe = {
65
65
  minHeight: 28,
66
66
  paddingHorizontal: spacing.xs,
67
67
  gap: spacing.xxs,
68
- background: semanticColors.surface.sunken,
68
+ background: semanticColors.canvas,
69
69
  color: semanticColors.content.body,
70
70
  },
71
71
  /** 삭제 버튼은 태그보다 작아도 되지만 포인터 타깃은 공용 최소값을 지킨다. */
@@ -1 +1 @@
1
- {"version":3,"file":"tags-input.js","sourceRoot":"","sources":["../src/tags-input.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { fieldFrameContract, focusIndicatorContract } from \"./component-contracts.js\";\nimport { control, spacing } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * 자유 입력으로 여러 값을 모으는 필드 — 해시태그, 초대할 사람, 검색 필터.\n *\n * Combobox와 다르다. Combobox는 **주어진 목록에서 고른다**. TagsInput은 목록이 없거나\n * 있어도 목록 밖의 값을 만들 수 있다. 그래서 Combobox에 `multiple` 축을 붙이는 대신\n * 별도로 둔다 — 합치면 \"목록에 없는 값을 고를 수 있는 Select\"라는 모순이 생긴다.\n * (제품이 후보 목록을 함께 보여 주고 싶으면 이 필드와 Combobox의 listbox를 조합한다.)\n *\n * 이 계약이 소유하는 것은 **언제 태그가 확정되는가**와 **빈 입력에서 지우면 무엇이\n * 지워지는가** 둘이다. 중복 허용 여부·최대 개수·글자 규칙은 제품 정책이라 판정 결과를\n * 받기만 한다.\n */\nexport type TagsInputCommitKey = \"Enter\" | \"Comma\" | \"Space\" | \"Blur\";\n\n/**\n * 후보 목록. Combobox의 listbox 계약을 **이 필드 안에서** 쓰는 형태다.\n *\n * \"Combobox에 multiple을 붙이자\"가 아니라 여기로 온 이유: Combobox의 값은 목록 안의\n * 키 하나이고 이 필드의 값은 문자열 배열이다. 값 모양이 다른 둘을 한 컴포넌트에 넣으면\n * 모든 소비자가 매번 좁히기를 해야 한다. 자유 입력이 이미 여기 있으므로 후보를 여기에\n * 더하는 쪽이 값 하나로 끝난다.\n *\n * 필터링은 제품이 한다 — 이 계약은 \"무엇을 보여줄지\"가 아니라 \"고른 것을 어떻게\n * 확정할지\"만 안다.\n */\nexport type TagsInputSuggestion = Readonly<{\n id: string;\n label: string;\n /** 확정될 문자열. 생략하면 `label`. */\n value?: string;\n disabled?: boolean;\n}>;\n\n/**\n * 활성 후보를 방향키로 옮긴 결과. 목록 끝에서 순환한다 — 후보가 보통 짧아서 끝에\n * 막히면 반대 끝까지 다시 내려가야 한다.\n */\nexport function resolveTagsInputActiveSuggestion(\n suggestions: readonly TagsInputSuggestion[],\n currentId: string | null,\n direction: \"next\" | \"previous\",\n): string | null {\n const enabled = suggestions.filter((item) => item.disabled !== true);\n if (enabled.length === 0) return null;\n const index = enabled.findIndex((item) => item.id === currentId);\n if (index === -1) return direction === \"next\" ? enabled[0]!.id : enabled.at(-1)!.id;\n const next = direction === \"next\" ? index + 1 : index - 1;\n return enabled[(next + enabled.length) % enabled.length]!.id;\n}\n\nexport const tagsInputBehaviorDefaults = {\n /**\n * Enter만 기본이다. 쉼표·공백은 언어에 따라 값의 일부다(한국어 태그에 공백이 흔하다).\n * 제품이 자기 도메인을 알 때 켠다.\n */\n commitKeys: [\"Enter\"] as readonly TagsInputCommitKey[],\n /** 빈 입력에서 Backspace는 마지막 태그를 **선택**만 한다. 한 번 더 눌러야 지운다. */\n backspaceRemovesLastTag: false,\n} as const;\n\nexport type TagsInputRejectionReason = \"empty\" | \"duplicate\" | \"limit\" | \"invalid\";\n\nexport type TagsInputCommitResult = Readonly<{\n accepted: boolean;\n /** 정리된 값(앞뒤 공백 제거). 거절돼도 제품이 문구에 쓸 수 있게 돌려준다. */\n value: string;\n reason?: TagsInputRejectionReason;\n}>;\n\nexport type TagsInputPolicy = Readonly<{\n /** 같은 값을 다시 넣을 수 있는가. 기본은 막는다. */\n allowDuplicates?: boolean;\n maxTags?: number;\n /** 제품 도메인 규칙. 실패 사유는 `invalid` 하나로 합쳐 돌려준다. */\n isValid?: (value: string) => boolean;\n}>;\n\n/**\n * 태그 하나를 확정할 수 있는지 판정한다. 값을 바꾸지 않고 결과만 돌려준다 —\n * 실제 상태 갱신은 renderer가, 사용자에게 보여 줄 문장은 제품이 만든다.\n */\nexport function resolveTagsInputCommit(\n current: readonly string[],\n rawValue: string,\n policy: TagsInputPolicy = {},\n): TagsInputCommitResult {\n const value = rawValue.trim();\n if (value.length === 0) return { accepted: false, value, reason: \"empty\" };\n if (policy.allowDuplicates !== true && current.includes(value)) {\n return { accepted: false, value, reason: \"duplicate\" };\n }\n if (policy.maxTags !== undefined && current.length >= policy.maxTags) {\n return { accepted: false, value, reason: \"limit\" };\n }\n if (policy.isValid !== undefined && !policy.isValid(value)) {\n return { accepted: false, value, reason: \"invalid\" };\n }\n return { accepted: true, value };\n}\n\n/** 태그 하나를 뺀 목록. 같은 값이 여러 개면 해당 위치 하나만 지운다. */\nexport function removeTagAt(current: readonly string[], index: number): readonly string[] {\n if (!Number.isInteger(index) || index < 0 || index >= current.length) {\n throw new RangeError(`TagsInput index out of range: ${String(index)}`);\n }\n return [...current.slice(0, index), ...current.slice(index + 1)];\n}\n\nexport const tagsInputRecipe = {\n slots: [\"root\", \"frame\", \"tag\", \"remove\", \"input\"] as const,\n defaults: { commitKeys: tagsInputBehaviorDefaults.commitKeys },\n frame: {\n minHeight: fieldFrameContract.minHeight,\n paddingHorizontal: fieldFrameContract.paddingHorizontal,\n paddingVertical: spacing.xxs,\n gap: spacing.xs,\n border: fieldFrameContract.border,\n background: fieldFrameContract.background,\n },\n tag: {\n minHeight: 28,\n paddingHorizontal: spacing.xs,\n gap: spacing.xxs,\n background: semanticColors.surface.sunken,\n color: semanticColors.content.body,\n },\n /** 삭제 버튼은 태그보다 작아도 되지만 포인터 타깃은 공용 최소값을 지킨다. */\n remove: { minTouchTarget: control.minTouchTarget, color: semanticColors.content.secondary },\n selectedTagOutline: semanticColors.border.focus,\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"frame\", \"tag\", \"remove\", \"input\"];\n defaults: { commitKeys: readonly TagsInputCommitKey[] };\n frame: {\n minHeight: number;\n paddingHorizontal: number;\n paddingVertical: number;\n gap: number;\n border: ColorReference;\n background: ColorReference;\n };\n tag: { minHeight: number; paddingHorizontal: number; gap: number; background: ColorReference; color: ColorReference };\n remove: { minTouchTarget: number; color: ColorReference };\n selectedTagOutline: ColorReference;\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const tagsInputBehavior = {\n controlled: [\"tags\", \"defaultTags\", \"onTagsChange\"],\n inputs: [\"policy\", \"commitKeys\", \"label\", \"removeLabel\"],\n /**\n * `onDraftChange`가 없으면 제품이 후보를 거를 수 없다 — 무엇을 입력 중인지 모르기\n * 때문이다. 후보 목록을 계약에 넣는 순간 이 이벤트도 함께 필요하다.\n */\n events: [\"onReject\", \"onDraftChange\"],\n defaults: { backspaceRemovesLastTag: tagsInputBehaviorDefaults.backspaceRemovesLastTag },\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"empty\", \"filled\"],\n validation: [\"valid\", \"invalid\"],\n },\n web: {\n roles: [\"combobox\", \"listbox\", \"option\", \"button\", \"list\", \"listitem\"],\n keyboard: [\"Enter\", \"Backspace\", \"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\", \"Escape\", \"Tab\"],\n focus: \"native\",\n },\n native: { roles: [\"button\"], states: [\"disabled\"], actions: [\"commit\", \"remove\"] },\n scenarios: [\n \"enter-commits-the-trimmed-value-and-clears-the-input\",\n \"an-empty-or-whitespace-only-value-is-never-committed\",\n \"duplicates-are-rejected-by-default-and-the-reason-is-reported-not-swallowed\",\n \"backspace-in-an-empty-input-selects-the-last-tag-before-a-second-press-removes-it\",\n \"every-tag-carries-its-own-remove-control-with-a-localized-name\",\n \"the-policy-judges-and-the-product-writes-the-sentence-the-user-reads\",\n \"a-candidate-list-lives-in-this-field-because-the-value-shape-is-this-fields-not-comboboxs\",\n \"enter-commits-the-active-candidate-when-one-is-highlighted-and-the-typed-text-otherwise\",\n \"candidate-filtering-belongs-to-the-product-the-contract-only-commits-the-choice\",\n ],\n} as const satisfies BehaviorContract;\n"]}
1
+ {"version":3,"file":"tags-input.js","sourceRoot":"","sources":["../src/tags-input.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { fieldFrameContract, focusIndicatorContract } from \"./component-contracts.js\";\nimport { control, spacing } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * 자유 입력으로 여러 값을 모으는 필드 — 해시태그, 초대할 사람, 검색 필터.\n *\n * Combobox와 다르다. Combobox는 **주어진 목록에서 고른다**. TagsInput은 목록이 없거나\n * 있어도 목록 밖의 값을 만들 수 있다. 그래서 Combobox에 `multiple` 축을 붙이는 대신\n * 별도로 둔다 — 합치면 \"목록에 없는 값을 고를 수 있는 Select\"라는 모순이 생긴다.\n * (제품이 후보 목록을 함께 보여 주고 싶으면 이 필드와 Combobox의 listbox를 조합한다.)\n *\n * 이 계약이 소유하는 것은 **언제 태그가 확정되는가**와 **빈 입력에서 지우면 무엇이\n * 지워지는가** 둘이다. 중복 허용 여부·최대 개수·글자 규칙은 제품 정책이라 판정 결과를\n * 받기만 한다.\n */\nexport type TagsInputCommitKey = \"Enter\" | \"Comma\" | \"Space\" | \"Blur\";\n\n/**\n * 후보 목록. Combobox의 listbox 계약을 **이 필드 안에서** 쓰는 형태다.\n *\n * \"Combobox에 multiple을 붙이자\"가 아니라 여기로 온 이유: Combobox의 값은 목록 안의\n * 키 하나이고 이 필드의 값은 문자열 배열이다. 값 모양이 다른 둘을 한 컴포넌트에 넣으면\n * 모든 소비자가 매번 좁히기를 해야 한다. 자유 입력이 이미 여기 있으므로 후보를 여기에\n * 더하는 쪽이 값 하나로 끝난다.\n *\n * 필터링은 제품이 한다 — 이 계약은 \"무엇을 보여줄지\"가 아니라 \"고른 것을 어떻게\n * 확정할지\"만 안다.\n */\nexport type TagsInputSuggestion = Readonly<{\n id: string;\n label: string;\n /** 확정될 문자열. 생략하면 `label`. */\n value?: string;\n disabled?: boolean;\n}>;\n\n/**\n * 활성 후보를 방향키로 옮긴 결과. 목록 끝에서 순환한다 — 후보가 보통 짧아서 끝에\n * 막히면 반대 끝까지 다시 내려가야 한다.\n */\nexport function resolveTagsInputActiveSuggestion(\n suggestions: readonly TagsInputSuggestion[],\n currentId: string | null,\n direction: \"next\" | \"previous\",\n): string | null {\n const enabled = suggestions.filter((item) => item.disabled !== true);\n if (enabled.length === 0) return null;\n const index = enabled.findIndex((item) => item.id === currentId);\n if (index === -1) return direction === \"next\" ? enabled[0]!.id : enabled.at(-1)!.id;\n const next = direction === \"next\" ? index + 1 : index - 1;\n return enabled[(next + enabled.length) % enabled.length]!.id;\n}\n\nexport const tagsInputBehaviorDefaults = {\n /**\n * Enter만 기본이다. 쉼표·공백은 언어에 따라 값의 일부다(한국어 태그에 공백이 흔하다).\n * 제품이 자기 도메인을 알 때 켠다.\n */\n commitKeys: [\"Enter\"] as readonly TagsInputCommitKey[],\n /** 빈 입력에서 Backspace는 마지막 태그를 **선택**만 한다. 한 번 더 눌러야 지운다. */\n backspaceRemovesLastTag: false,\n} as const;\n\nexport type TagsInputRejectionReason = \"empty\" | \"duplicate\" | \"limit\" | \"invalid\";\n\nexport type TagsInputCommitResult = Readonly<{\n accepted: boolean;\n /** 정리된 값(앞뒤 공백 제거). 거절돼도 제품이 문구에 쓸 수 있게 돌려준다. */\n value: string;\n reason?: TagsInputRejectionReason;\n}>;\n\nexport type TagsInputPolicy = Readonly<{\n /** 같은 값을 다시 넣을 수 있는가. 기본은 막는다. */\n allowDuplicates?: boolean;\n maxTags?: number;\n /** 제품 도메인 규칙. 실패 사유는 `invalid` 하나로 합쳐 돌려준다. */\n isValid?: (value: string) => boolean;\n}>;\n\n/**\n * 태그 하나를 확정할 수 있는지 판정한다. 값을 바꾸지 않고 결과만 돌려준다 —\n * 실제 상태 갱신은 renderer가, 사용자에게 보여 줄 문장은 제품이 만든다.\n */\nexport function resolveTagsInputCommit(\n current: readonly string[],\n rawValue: string,\n policy: TagsInputPolicy = {},\n): TagsInputCommitResult {\n const value = rawValue.trim();\n if (value.length === 0) return { accepted: false, value, reason: \"empty\" };\n if (policy.allowDuplicates !== true && current.includes(value)) {\n return { accepted: false, value, reason: \"duplicate\" };\n }\n if (policy.maxTags !== undefined && current.length >= policy.maxTags) {\n return { accepted: false, value, reason: \"limit\" };\n }\n if (policy.isValid !== undefined && !policy.isValid(value)) {\n return { accepted: false, value, reason: \"invalid\" };\n }\n return { accepted: true, value };\n}\n\n/** 태그 하나를 뺀 목록. 같은 값이 여러 개면 해당 위치 하나만 지운다. */\nexport function removeTagAt(current: readonly string[], index: number): readonly string[] {\n if (!Number.isInteger(index) || index < 0 || index >= current.length) {\n throw new RangeError(`TagsInput index out of range: ${String(index)}`);\n }\n return [...current.slice(0, index), ...current.slice(index + 1)];\n}\n\nexport const tagsInputRecipe = {\n slots: [\"root\", \"frame\", \"tag\", \"remove\", \"input\"] as const,\n defaults: { commitKeys: tagsInputBehaviorDefaults.commitKeys },\n frame: {\n minHeight: fieldFrameContract.minHeight,\n paddingHorizontal: fieldFrameContract.paddingHorizontal,\n paddingVertical: spacing.xxs,\n gap: spacing.xs,\n border: fieldFrameContract.border,\n background: fieldFrameContract.background,\n },\n tag: {\n minHeight: 28,\n paddingHorizontal: spacing.xs,\n gap: spacing.xxs,\n background: semanticColors.canvas,\n color: semanticColors.content.body,\n },\n /** 삭제 버튼은 태그보다 작아도 되지만 포인터 타깃은 공용 최소값을 지킨다. */\n remove: { minTouchTarget: control.minTouchTarget, color: semanticColors.content.secondary },\n selectedTagOutline: semanticColors.border.focus,\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"frame\", \"tag\", \"remove\", \"input\"];\n defaults: { commitKeys: readonly TagsInputCommitKey[] };\n frame: {\n minHeight: number;\n paddingHorizontal: number;\n paddingVertical: number;\n gap: number;\n border: ColorReference;\n background: ColorReference;\n };\n tag: { minHeight: number; paddingHorizontal: number; gap: number; background: ColorReference; color: ColorReference };\n remove: { minTouchTarget: number; color: ColorReference };\n selectedTagOutline: ColorReference;\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const tagsInputBehavior = {\n controlled: [\"tags\", \"defaultTags\", \"onTagsChange\"],\n inputs: [\"policy\", \"commitKeys\", \"label\", \"removeLabel\"],\n /**\n * `onDraftChange`가 없으면 제품이 후보를 거를 수 없다 — 무엇을 입력 중인지 모르기\n * 때문이다. 후보 목록을 계약에 넣는 순간 이 이벤트도 함께 필요하다.\n */\n events: [\"onReject\", \"onDraftChange\"],\n defaults: { backspaceRemovesLastTag: tagsInputBehaviorDefaults.backspaceRemovesLastTag },\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"empty\", \"filled\"],\n validation: [\"valid\", \"invalid\"],\n },\n web: {\n roles: [\"combobox\", \"listbox\", \"option\", \"button\", \"list\", \"listitem\"],\n keyboard: [\"Enter\", \"Backspace\", \"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\", \"Escape\", \"Tab\"],\n focus: \"native\",\n },\n native: { roles: [\"button\"], states: [\"disabled\"], actions: [\"commit\", \"remove\"] },\n scenarios: [\n \"enter-commits-the-trimmed-value-and-clears-the-input\",\n \"an-empty-or-whitespace-only-value-is-never-committed\",\n \"duplicates-are-rejected-by-default-and-the-reason-is-reported-not-swallowed\",\n \"backspace-in-an-empty-input-selects-the-last-tag-before-a-second-press-removes-it\",\n \"every-tag-carries-its-own-remove-control-with-a-localized-name\",\n \"the-policy-judges-and-the-product-writes-the-sentence-the-user-reads\",\n \"a-candidate-list-lives-in-this-field-because-the-value-shape-is-this-fields-not-comboboxs\",\n \"enter-commits-the-active-candidate-when-one-is-highlighted-and-the-typed-text-otherwise\",\n \"candidate-filtering-belongs-to-the-product-the-contract-only-commits-the-choice\",\n ],\n} as const satisfies BehaviorContract;\n"]}
@@ -17,7 +17,7 @@ export declare const textFormatRecipe: {
17
17
  readonly radius: 8;
18
18
  readonly background: Readonly<{
19
19
  source: "theme";
20
- key: "surfaceAlt";
20
+ key: "bg";
21
21
  alpha?: number;
22
22
  }>;
23
23
  readonly color: Readonly<{
@@ -38,7 +38,7 @@ export declare const textFormatRecipe: {
38
38
  readonly radius: 8;
39
39
  readonly background: Readonly<{
40
40
  source: "theme";
41
- key: "surfaceAlt";
41
+ key: "bg";
42
42
  alpha?: number;
43
43
  }>;
44
44
  readonly color: Readonly<{
@@ -7,7 +7,7 @@ export const textFormatRecipe = {
7
7
  textVariant: "label",
8
8
  paddingHorizontal: spacing.xxs,
9
9
  radius: radius.sm,
10
- background: semanticColors.surface.sunken,
10
+ background: semanticColors.canvas,
11
11
  color: semanticColors.content.body,
12
12
  border: semanticColors.border.default,
13
13
  },
@@ -16,7 +16,7 @@ export const textFormatRecipe = {
16
16
  textVariant: "body",
17
17
  paddingHorizontal: spacing.xxs,
18
18
  radius: radius.sm,
19
- background: semanticColors.surface.sunken,
19
+ background: semanticColors.canvas,
20
20
  color: semanticColors.content.primary,
21
21
  },
22
22
  quote: {
@@ -1 +1 @@
1
- {"version":3,"file":"text-formats.js","sourceRoot":"","sources":["../src/text-formats.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAatD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,KAAK,EAAE,CAAC,MAAM,CAAU;IACxB,GAAG,EAAE;QACH,UAAU,EAAE,UAAU,CAAC,IAAI;QAC3B,WAAW,EAAE,OAAgB;QAC7B,iBAAiB,EAAE,OAAO,CAAC,GAAG;QAC9B,MAAM,EAAE,MAAM,CAAC,EAAE;QACjB,UAAU,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM;QACzC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI;QAClC,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,OAAO;KACtC;IACD,IAAI,EAAE;QACJ,UAAU,EAAE,UAAU,CAAC,IAAI;QAC3B,WAAW,EAAE,MAAe;QAC5B,iBAAiB,EAAE,OAAO,CAAC,GAAG;QAC9B,MAAM,EAAE,MAAM,CAAC,EAAE;QACjB,UAAU,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM;QACzC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,OAAO;KACtC;IACD,KAAK,EAAE;QACL,WAAW,EAAE,MAAe;QAC5B,kBAAkB,EAAE,OAAO,CAAC,EAAE;QAC9B,WAAW,EAAE,CAAC;QACd,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,OAAO;QACrC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI;KACnC;CA2BF,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,UAAU,EAAE,EAAE;IACd,MAAM,EAAE,CAAC,MAAM,CAAC;IAChB,aAAa,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IACjD,SAAS,EAAE,EAAE;IACb,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;IAC/C,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAC9C,SAAS,EAAE;QACT,qFAAqF;QACrF,4EAA4E;QAC5E,yFAAyF;KAC1F;CACkC,CAAC","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { fontFamily, radius, spacing, typography } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * `Text` 하나로는 만들 수 없는 **의미 있는 글자 조각들**이다 — 단축키, 코드, 인용.\n * 도움말·개발자 문서·약관 본문이 반복해서 필요로 하고, 지금까지는 제품이 `<kbd>`에\n * 직접 CSS를 붙여 만들었다.\n *\n * 왜 `Text`의 variant가 아닌가: 이들은 **크기가 아니라 요소**다. `<kbd>`, `<code>`,\n * `<blockquote>`는 각자 의미를 가진 HTML 요소이고 보조기기가 다르게 읽는다. variant로\n * 두면 `<span>`에 코드처럼 보이는 스타일만 입히게 되고, 그 차이가 사라진다.\n */\nexport type TextFormatKind = \"kbd\" | \"code\" | \"quote\";\n\nexport const textFormatRecipe = {\n slots: [\"root\"] as const,\n kbd: {\n fontFamily: fontFamily.code,\n textVariant: \"label\" as const,\n paddingHorizontal: spacing.xxs,\n radius: radius.sm,\n background: semanticColors.surface.sunken,\n color: semanticColors.content.body,\n border: semanticColors.border.default,\n },\n code: {\n fontFamily: fontFamily.code,\n textVariant: \"body\" as const,\n paddingHorizontal: spacing.xxs,\n radius: radius.sm,\n background: semanticColors.surface.sunken,\n color: semanticColors.content.primary,\n },\n quote: {\n textVariant: \"body\" as const,\n paddingInlineStart: spacing.md,\n borderWidth: 2,\n border: semanticColors.border.default,\n color: semanticColors.content.body,\n },\n} as const satisfies {\n slots: readonly [\"root\"];\n kbd: {\n fontFamily: typeof fontFamily.code;\n textVariant: keyof typeof typography;\n paddingHorizontal: number;\n radius: number;\n background: ColorReference;\n color: ColorReference;\n border: ColorReference;\n };\n code: {\n fontFamily: typeof fontFamily.code;\n textVariant: keyof typeof typography;\n paddingHorizontal: number;\n radius: number;\n background: ColorReference;\n color: ColorReference;\n };\n quote: {\n textVariant: keyof typeof typography;\n paddingInlineStart: number;\n borderWidth: number;\n border: ColorReference;\n color: ColorReference;\n };\n};\n\nexport const textFormatBehavior = {\n controlled: [],\n inputs: [\"kind\"],\n configuration: { kind: [\"kbd\", \"code\", \"quote\"] },\n stateAxes: {},\n web: { roles: [], keyboard: [], focus: \"none\" },\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"each-kind-emits-its-own-html-element-so-assistive-technology-reads-it-as-what-it-is\",\n \"these-are-elements-not-text-sizes-which-is-why-they-are-not-a-text-variant\",\n \"a-key-name-is-product-copy-because-the-same-key-is-called-different-things-per-platform\",\n ],\n} as const satisfies BehaviorContract;\n"]}
1
+ {"version":3,"file":"text-formats.js","sourceRoot":"","sources":["../src/text-formats.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAatD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,KAAK,EAAE,CAAC,MAAM,CAAU;IACxB,GAAG,EAAE;QACH,UAAU,EAAE,UAAU,CAAC,IAAI;QAC3B,WAAW,EAAE,OAAgB;QAC7B,iBAAiB,EAAE,OAAO,CAAC,GAAG;QAC9B,MAAM,EAAE,MAAM,CAAC,EAAE;QACjB,UAAU,EAAE,cAAc,CAAC,MAAM;QACjC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI;QAClC,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,OAAO;KACtC;IACD,IAAI,EAAE;QACJ,UAAU,EAAE,UAAU,CAAC,IAAI;QAC3B,WAAW,EAAE,MAAe;QAC5B,iBAAiB,EAAE,OAAO,CAAC,GAAG;QAC9B,MAAM,EAAE,MAAM,CAAC,EAAE;QACjB,UAAU,EAAE,cAAc,CAAC,MAAM;QACjC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,OAAO;KACtC;IACD,KAAK,EAAE;QACL,WAAW,EAAE,MAAe;QAC5B,kBAAkB,EAAE,OAAO,CAAC,EAAE;QAC9B,WAAW,EAAE,CAAC;QACd,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,OAAO;QACrC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI;KACnC;CA2BF,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,UAAU,EAAE,EAAE;IACd,MAAM,EAAE,CAAC,MAAM,CAAC;IAChB,aAAa,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IACjD,SAAS,EAAE,EAAE;IACb,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;IAC/C,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAC9C,SAAS,EAAE;QACT,qFAAqF;QACrF,4EAA4E;QAC5E,yFAAyF;KAC1F;CACkC,CAAC","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { fontFamily, radius, spacing, typography } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * `Text` 하나로는 만들 수 없는 **의미 있는 글자 조각들**이다 — 단축키, 코드, 인용.\n * 도움말·개발자 문서·약관 본문이 반복해서 필요로 하고, 지금까지는 제품이 `<kbd>`에\n * 직접 CSS를 붙여 만들었다.\n *\n * 왜 `Text`의 variant가 아닌가: 이들은 **크기가 아니라 요소**다. `<kbd>`, `<code>`,\n * `<blockquote>`는 각자 의미를 가진 HTML 요소이고 보조기기가 다르게 읽는다. variant로\n * 두면 `<span>`에 코드처럼 보이는 스타일만 입히게 되고, 그 차이가 사라진다.\n */\nexport type TextFormatKind = \"kbd\" | \"code\" | \"quote\";\n\nexport const textFormatRecipe = {\n slots: [\"root\"] as const,\n kbd: {\n fontFamily: fontFamily.code,\n textVariant: \"label\" as const,\n paddingHorizontal: spacing.xxs,\n radius: radius.sm,\n background: semanticColors.canvas,\n color: semanticColors.content.body,\n border: semanticColors.border.default,\n },\n code: {\n fontFamily: fontFamily.code,\n textVariant: \"body\" as const,\n paddingHorizontal: spacing.xxs,\n radius: radius.sm,\n background: semanticColors.canvas,\n color: semanticColors.content.primary,\n },\n quote: {\n textVariant: \"body\" as const,\n paddingInlineStart: spacing.md,\n borderWidth: 2,\n border: semanticColors.border.default,\n color: semanticColors.content.body,\n },\n} as const satisfies {\n slots: readonly [\"root\"];\n kbd: {\n fontFamily: typeof fontFamily.code;\n textVariant: keyof typeof typography;\n paddingHorizontal: number;\n radius: number;\n background: ColorReference;\n color: ColorReference;\n border: ColorReference;\n };\n code: {\n fontFamily: typeof fontFamily.code;\n textVariant: keyof typeof typography;\n paddingHorizontal: number;\n radius: number;\n background: ColorReference;\n color: ColorReference;\n };\n quote: {\n textVariant: keyof typeof typography;\n paddingInlineStart: number;\n borderWidth: number;\n border: ColorReference;\n color: ColorReference;\n };\n};\n\nexport const textFormatBehavior = {\n controlled: [],\n inputs: [\"kind\"],\n configuration: { kind: [\"kbd\", \"code\", \"quote\"] },\n stateAxes: {},\n web: { roles: [], keyboard: [], focus: \"none\" },\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"each-kind-emits-its-own-html-element-so-assistive-technology-reads-it-as-what-it-is\",\n \"these-are-elements-not-text-sizes-which-is-why-they-are-not-a-text-variant\",\n \"a-key-name-is-product-copy-because-the-same-key-is-called-different-things-per-platform\",\n ],\n} as const satisfies BehaviorContract;\n"]}
@@ -0,0 +1,15 @@
1
+ export declare const thinkingOrbRecipe: {
2
+ readonly defaults: {
3
+ readonly state: "working";
4
+ readonly size: 64;
5
+ readonly speed: 1;
6
+ };
7
+ readonly sizes: readonly [20, 64];
8
+ readonly motion: {
9
+ readonly reducedMotion: "static";
10
+ readonly staticTime: 0.6;
11
+ readonly maxDeltaMs: 64;
12
+ };
13
+ readonly maxPixelRatio: 2;
14
+ };
15
+ //# sourceMappingURL=thinking-orb-recipe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking-orb-recipe.d.ts","sourceRoot":"","sources":["../src/thinking-orb-recipe.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;CAMpB,CAAC"}
@@ -0,0 +1,9 @@
1
+ // Recipe stays separate from geometry so catalog consumers do not load animation code.
2
+ export const thinkingOrbRecipe = {
3
+ defaults: { state: "working", size: 64, speed: 1 },
4
+ sizes: [20, 64],
5
+ // Use a deterministic representative frame and retain the upstream DPR cap.
6
+ motion: { reducedMotion: "static", staticTime: 0.6, maxDeltaMs: 64 },
7
+ maxPixelRatio: 2,
8
+ };
9
+ //# sourceMappingURL=thinking-orb-recipe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking-orb-recipe.js","sourceRoot":"","sources":["../src/thinking-orb-recipe.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,QAAQ,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;IAClD,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IACf,4EAA4E;IAC5E,MAAM,EAAE,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE;IACpE,aAAa,EAAE,CAAC;CACR,CAAC","sourcesContent":["// Recipe stays separate from geometry so catalog consumers do not load animation code.\nexport const thinkingOrbRecipe = {\n defaults: { state: \"working\", size: 64, speed: 1 },\n sizes: [20, 64],\n // Use a deterministic representative frame and retain the upstream DPR cap.\n motion: { reducedMotion: \"static\", staticTime: 0.6, maxDeltaMs: 64 },\n maxPixelRatio: 2,\n} as const;\n"]}
@@ -0,0 +1,25 @@
1
+ import type { OrbState, OrbSize, OrbFrame } from "./internal/thinking-orb/types.js";
2
+ export type { OrbState, OrbSize, OrbFrame };
3
+ export declare const thinkingOrbStates: readonly ["working", "searching", "solving", "listening", "connecting", "weaving", "composing", "breathing", "shaping"];
4
+ export type ThinkingOrbOptions = Readonly<{
5
+ /** Actual operation phase, supplied by the host; the orb never simulates progress. */
6
+ state?: OrbState;
7
+ size?: OrbSize;
8
+ /** Localized description of the current operation. */
9
+ label: string;
10
+ speed?: number;
11
+ paused?: boolean;
12
+ /** Host navigation/list visibility; required for mounted but hidden native screens. */
13
+ active?: boolean;
14
+ }>;
15
+ export { thinkingOrbRecipe } from "./thinking-orb-recipe.js";
16
+ export declare function validateThinkingOrb({ state, size, speed, label }: ThinkingOrbOptions): void;
17
+ /** Theme-free, deterministic geometry; no DOM/React/native imports. */
18
+ export declare function buildThinkingOrbFrame(state: OrbState, size: OrbSize, time: number): OrbFrame;
19
+ /** A local elapsed clock freezes exactly, instead of jumping to wall time after resume. */
20
+ export declare function createThinkingOrbClock(): {
21
+ resetDelta(): void;
22
+ sample(now: number, speed: number): number;
23
+ readonly time: number;
24
+ };
25
+ //# sourceMappingURL=thinking-orb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking-orb.d.ts","sourceRoot":"","sources":["../src/thinking-orb.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACpF,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAE5C,eAAO,MAAM,iBAAiB,yHAA0H,CAAC;AACzJ,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,sFAAsF;IACtF,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC,CAAC;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG7D,wBAAgB,mBAAmB,CAAC,EAAE,KAAiB,EAAE,IAAS,EAAE,KAAS,EAAE,KAAK,EAAE,EAAE,kBAAkB,GAAG,IAAI,CAKhH;AAED,uEAAuE;AACvE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAK5F;AAED,2FAA2F;AAC3F,wBAAgB,sBAAsB;;gBAKtB,MAAM,SAAS,MAAM;;EAQpC"}
@@ -0,0 +1,40 @@
1
+ import { MODE_FRAMES } from "./internal/thinking-orb/registry.js";
2
+ import { resolvePreset } from "./internal/thinking-orb/presets.js";
3
+ export const thinkingOrbStates = ["working", "searching", "solving", "listening", "connecting", "weaving", "composing", "breathing", "shaping"];
4
+ export { thinkingOrbRecipe } from "./thinking-orb-recipe.js";
5
+ import { thinkingOrbRecipe } from "./thinking-orb-recipe.js";
6
+ export function validateThinkingOrb({ state = "working", size = 64, speed = 1, label }) {
7
+ if (!thinkingOrbStates.includes(state))
8
+ throw new TypeError("Unknown ThinkingOrb state");
9
+ if (size !== 20 && size !== 64)
10
+ throw new RangeError("ThinkingOrb size must be 20 or 64");
11
+ if (!Number.isFinite(speed) || speed <= 0 || speed > 4)
12
+ throw new RangeError("ThinkingOrb speed must be > 0 and <= 4");
13
+ if (typeof label !== "string" || !label.trim())
14
+ throw new TypeError("ThinkingOrb requires a localized label");
15
+ }
16
+ /** Theme-free, deterministic geometry; no DOM/React/native imports. */
17
+ export function buildThinkingOrbFrame(state, size, time) {
18
+ validateThinkingOrb({ state, size, label: state });
19
+ if (!Number.isFinite(time) || time < 0)
20
+ throw new RangeError("ThinkingOrb time must be finite and non-negative");
21
+ const preset = resolvePreset(state, size);
22
+ return MODE_FRAMES[preset.mode](size, time * preset.speed, preset.opts);
23
+ }
24
+ /** A local elapsed clock freezes exactly, instead of jumping to wall time after resume. */
25
+ export function createThinkingOrbClock() {
26
+ let elapsed = 0;
27
+ let previous;
28
+ return {
29
+ resetDelta() { previous = undefined; },
30
+ sample(now, speed) {
31
+ // Long scheduling gaps are capped so a stalled JS thread cannot jump the animation.
32
+ if (previous !== undefined)
33
+ elapsed += Math.min(thinkingOrbRecipe.motion.maxDeltaMs, Math.max(0, now - previous)) * speed / 1000;
34
+ previous = now;
35
+ return elapsed;
36
+ },
37
+ get time() { return elapsed; },
38
+ };
39
+ }
40
+ //# sourceMappingURL=thinking-orb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking-orb.js","sourceRoot":"","sources":["../src/thinking-orb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAInE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,CAAU,CAAC;AAYzJ,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,UAAU,mBAAmB,CAAC,EAAE,KAAK,GAAG,SAAS,EAAE,IAAI,GAAG,EAAE,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,EAAsB;IACxG,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,2BAA2B,CAAC,CAAC;IACzF,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,EAAE;QAAE,MAAM,IAAI,UAAU,CAAC,mCAAmC,CAAC,CAAC;IAC1F,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,wCAAwC,CAAC,CAAC;IACvH,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,SAAS,CAAC,wCAAwC,CAAC,CAAC;AAChH,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,qBAAqB,CAAC,KAAe,EAAE,IAAa,EAAE,IAAY;IAChF,mBAAmB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,kDAAkD,CAAC,CAAC;IACjH,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1C,OAAO,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;AAC1E,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,sBAAsB;IACpC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,QAA4B,CAAC;IACjC,OAAO;QACL,UAAU,KAAK,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,GAAW,EAAE,KAAa;YAC/B,oFAAoF;YACpF,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,QAAQ,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC;YACjI,QAAQ,GAAG,GAAG,CAAC;YACf,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,IAAI,IAAI,KAAK,OAAO,OAAO,CAAC,CAAC,CAAC;KAC/B,CAAC;AACJ,CAAC","sourcesContent":["import { MODE_FRAMES } from \"./internal/thinking-orb/registry.js\";\nimport { resolvePreset } from \"./internal/thinking-orb/presets.js\";\nimport type { OrbState, OrbSize, OrbFrame } from \"./internal/thinking-orb/types.js\";\nexport type { OrbState, OrbSize, OrbFrame };\n\nexport const thinkingOrbStates = [\"working\", \"searching\", \"solving\", \"listening\", \"connecting\", \"weaving\", \"composing\", \"breathing\", \"shaping\"] as const;\nexport type ThinkingOrbOptions = Readonly<{\n /** Actual operation phase, supplied by the host; the orb never simulates progress. */\n state?: OrbState;\n size?: OrbSize;\n /** Localized description of the current operation. */\n label: string;\n speed?: number;\n paused?: boolean;\n /** Host navigation/list visibility; required for mounted but hidden native screens. */\n active?: boolean;\n}>;\nexport { thinkingOrbRecipe } from \"./thinking-orb-recipe.js\";\nimport { thinkingOrbRecipe } from \"./thinking-orb-recipe.js\";\n\nexport function validateThinkingOrb({ state = \"working\", size = 64, speed = 1, label }: ThinkingOrbOptions): void {\n if (!thinkingOrbStates.includes(state)) throw new TypeError(\"Unknown ThinkingOrb state\");\n if (size !== 20 && size !== 64) throw new RangeError(\"ThinkingOrb size must be 20 or 64\");\n if (!Number.isFinite(speed) || speed <= 0 || speed > 4) throw new RangeError(\"ThinkingOrb speed must be > 0 and <= 4\");\n if (typeof label !== \"string\" || !label.trim()) throw new TypeError(\"ThinkingOrb requires a localized label\");\n}\n\n/** Theme-free, deterministic geometry; no DOM/React/native imports. */\nexport function buildThinkingOrbFrame(state: OrbState, size: OrbSize, time: number): OrbFrame {\n validateThinkingOrb({ state, size, label: state });\n if (!Number.isFinite(time) || time < 0) throw new RangeError(\"ThinkingOrb time must be finite and non-negative\");\n const preset = resolvePreset(state, size);\n return MODE_FRAMES[preset.mode](size, time * preset.speed, preset.opts);\n}\n\n/** A local elapsed clock freezes exactly, instead of jumping to wall time after resume. */\nexport function createThinkingOrbClock() {\n let elapsed = 0;\n let previous: number | undefined;\n return {\n resetDelta() { previous = undefined; },\n sample(now: number, speed: number) {\n // Long scheduling gaps are capped so a stalled JS thread cannot jump the animation.\n if (previous !== undefined) elapsed += Math.min(thinkingOrbRecipe.motion.maxDeltaMs, Math.max(0, now - previous)) * speed / 1000;\n previous = now;\n return elapsed;\n },\n get time() { return elapsed; },\n };\n}\n"]}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Geometry adapted from rit3zh/expo-dynamic-notifications, commit 5de059a5cbef.
3
+ * The MIT notice is shipped in THIRD_PARTY_NOTICES.md. See docs/plans/liquid-toast.md.
4
+ * Kept separate from the lifecycle: animation must never own a second queue.
5
+ */
6
+ export declare const liquidToastRecipe: {
7
+ readonly capsule: {
8
+ readonly width: 88;
9
+ readonly height: 24;
10
+ };
11
+ readonly gap: 34;
12
+ readonly minHeight: 74;
13
+ readonly maxWidth: 396;
14
+ readonly radius: 32;
15
+ readonly dropSize: 52;
16
+ readonly neckWidth: 60;
17
+ readonly blur: 14.3;
18
+ readonly gain: 22;
19
+ readonly threshold: 0.43;
20
+ readonly spring: {
21
+ readonly drop: {
22
+ readonly duration: 1150;
23
+ readonly dampingRatio: 0.82;
24
+ };
25
+ readonly expand: {
26
+ readonly duration: 1000;
27
+ readonly dampingRatio: 0.8;
28
+ };
29
+ readonly reveal: {
30
+ readonly duration: 700;
31
+ readonly dampingRatio: 1;
32
+ };
33
+ readonly tint: {
34
+ readonly duration: 700;
35
+ readonly dampingRatio: 1;
36
+ };
37
+ readonly collapse: {
38
+ readonly duration: 660;
39
+ readonly dampingRatio: 0.92;
40
+ readonly velocity: 2;
41
+ };
42
+ readonly return: {
43
+ readonly duration: 1150;
44
+ readonly dampingRatio: 0.9;
45
+ };
46
+ readonly fade: {
47
+ readonly duration: 360;
48
+ readonly dampingRatio: 1;
49
+ };
50
+ readonly drag: {
51
+ readonly duration: 560;
52
+ readonly dampingRatio: 0.7;
53
+ };
54
+ };
55
+ readonly delay: {
56
+ readonly tint: 420;
57
+ readonly expand: 340;
58
+ readonly reveal: 560;
59
+ readonly collapse: 100;
60
+ readonly return: 280;
61
+ };
62
+ readonly swipe: {
63
+ readonly distance: -18;
64
+ readonly velocity: -420;
65
+ };
66
+ };
67
+ /** Island frames are explicitly verified window coordinates, never inferred from insets. */
68
+ export type LiquidToastAnchor = Readonly<{
69
+ kind: "capsule";
70
+ }> | Readonly<{
71
+ kind: "island";
72
+ frame: Readonly<{
73
+ x: number;
74
+ y: number;
75
+ width: number;
76
+ height: number;
77
+ }>;
78
+ }>;
79
+ export declare function validateLiquidToastAnchor(anchor: LiquidToastAnchor): void;
80
+ export type LiquidToastLayout = Readonly<{
81
+ width: number;
82
+ height: number;
83
+ cardWidth: number;
84
+ cardTop: number;
85
+ cardLeft: number;
86
+ anchorX: number;
87
+ anchorY: number;
88
+ anchorWidth: number;
89
+ anchorHeight: number;
90
+ canvasTop: number;
91
+ canvasHeight: number;
92
+ fits: boolean;
93
+ }>;
94
+ export declare function resolveLiquidToastLayout(input: Readonly<{
95
+ width: number;
96
+ height: number;
97
+ availableHeight: number;
98
+ anchor: LiquidToastAnchor;
99
+ windowOrigin?: Readonly<{
100
+ x: number;
101
+ y: number;
102
+ }>;
103
+ }>): LiquidToastLayout;
104
+ export declare function buildLiquidToastGeometry(drop: number, expand: number, layout: LiquidToastLayout): {
105
+ x: number;
106
+ y: number;
107
+ width: number;
108
+ height: number;
109
+ radius: number;
110
+ neckX: number;
111
+ neckY: number;
112
+ neckWidth: number;
113
+ neckHeight: number;
114
+ offsetY: number;
115
+ };
116
+ //# sourceMappingURL=toast-liquid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toast-liquid.d.ts","sourceRoot":"","sources":["../src/toast-liquid.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BpB,CAAC;AAEX,4FAA4F;AAC5F,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IACvE,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,QAAQ,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1E,CAAC,CAAC;AAEH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAOzE;AAED,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IACpF,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAC5E,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;CACxD,CAAC,CAAC;AAEH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,CAAC;IACvD,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAClF,YAAY,CAAC,EAAE,QAAQ,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnD,CAAC,GAAG,iBAAiB,CA2BrB;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB;;;;;;;;;;;EA0B/F"}