@fairgarden/design 0.1.0-alpha.1 → 0.1.0-alpha.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 (517) hide show
  1. package/LICENSE +21 -0
  2. package/Readme.md +51 -27
  3. package/actions/button/Button.d.ts.map +1 -1
  4. package/actions/button/Button.js +13 -1
  5. package/actions/button/Button.js.map +1 -1
  6. package/actions/button/button.module.css +85 -25
  7. package/actions/chip/Chip.d.ts +2 -2
  8. package/actions/chip/Chip.js +2 -2
  9. package/actions/chip/chip.module.css +12 -18
  10. package/actions/link/Link.d.ts +1 -1
  11. package/actions/link/link.module.css +22 -19
  12. package/actions/toggle/Toggle.js +1 -1
  13. package/actions/toggle/toggle.module.css +27 -23
  14. package/content/card/card.module.css +27 -27
  15. package/content/card-grid/CardGrid.js +1 -1
  16. package/content/card-grid/card-grid.module.css +3 -3
  17. package/content/carousel/Carousel.d.ts.map +1 -1
  18. package/content/carousel/Carousel.js +1 -4
  19. package/content/carousel/Carousel.js.map +1 -1
  20. package/content/carousel/carousel.module.css +19 -9
  21. package/content/code-block/CodeActionsMenu.d.ts +34 -0
  22. package/content/code-block/CodeActionsMenu.d.ts.map +1 -0
  23. package/content/code-block/CodeActionsMenu.js +102 -0
  24. package/content/code-block/CodeActionsMenu.js.map +1 -0
  25. package/content/code-block/CodeBlock.d.ts +37 -0
  26. package/content/code-block/CodeBlock.d.ts.map +1 -0
  27. package/content/code-block/CodeBlock.js +30 -0
  28. package/content/code-block/CodeBlock.js.map +1 -0
  29. package/content/code-block/CodeBlockFrame.d.ts +54 -0
  30. package/content/code-block/CodeBlockFrame.d.ts.map +1 -0
  31. package/content/code-block/CodeBlockFrame.js +80 -0
  32. package/content/code-block/CodeBlockFrame.js.map +1 -0
  33. package/content/code-block/CodeBlockHeader.d.ts +26 -0
  34. package/content/code-block/CodeBlockHeader.d.ts.map +1 -0
  35. package/content/code-block/CodeBlockHeader.js +16 -0
  36. package/content/code-block/CodeBlockHeader.js.map +1 -0
  37. package/content/code-block/CodeBlockLazy.d.ts +11 -0
  38. package/content/code-block/CodeBlockLazy.d.ts.map +1 -0
  39. package/content/code-block/CodeBlockLazy.js +14 -0
  40. package/content/code-block/CodeBlockLazy.js.map +1 -0
  41. package/content/code-block/CodeBlockLoading.d.ts +31 -0
  42. package/content/code-block/CodeBlockLoading.d.ts.map +1 -0
  43. package/content/code-block/CodeBlockLoading.js +90 -0
  44. package/content/code-block/CodeBlockLoading.js.map +1 -0
  45. package/content/code-block/CodeBlockSection.d.ts +40 -0
  46. package/content/code-block/CodeBlockSection.d.ts.map +1 -0
  47. package/content/code-block/CodeBlockSection.js +105 -0
  48. package/content/code-block/CodeBlockSection.js.map +1 -0
  49. package/content/code-block/CodeBlockToasts.d.ts +26 -0
  50. package/content/code-block/CodeBlockToasts.d.ts.map +1 -0
  51. package/content/code-block/CodeBlockToasts.js +88 -0
  52. package/content/code-block/CodeBlockToasts.js.map +1 -0
  53. package/content/code-block/code-block.module.css +949 -0
  54. package/content/code-block/index.d.ts +4 -0
  55. package/content/code-block/index.d.ts.map +1 -0
  56. package/content/code-block/index.js +4 -0
  57. package/content/code-block/index.js.map +1 -0
  58. package/content/demo/DemoContent.d.ts +32 -0
  59. package/content/demo/DemoContent.d.ts.map +1 -0
  60. package/content/demo/DemoContent.js +87 -0
  61. package/content/demo/DemoContent.js.map +1 -0
  62. package/content/demo/DemoFrame.d.ts +6 -0
  63. package/content/demo/DemoFrame.d.ts.map +1 -0
  64. package/content/demo/DemoFrame.js +17 -0
  65. package/content/demo/DemoFrame.js.map +1 -0
  66. package/content/demo/DemoLazy.d.ts +11 -0
  67. package/content/demo/DemoLazy.d.ts.map +1 -0
  68. package/content/demo/DemoLazy.js +14 -0
  69. package/content/demo/DemoLazy.js.map +1 -0
  70. package/content/demo/DemoLoading.d.ts +23 -0
  71. package/content/demo/DemoLoading.d.ts.map +1 -0
  72. package/content/demo/DemoLoading.js +26 -0
  73. package/content/demo/DemoLoading.js.map +1 -0
  74. package/content/demo/demo.module.css +163 -0
  75. package/content/demo/index.d.ts +4 -0
  76. package/content/demo/index.d.ts.map +1 -0
  77. package/content/demo/index.js +4 -0
  78. package/content/demo/index.js.map +1 -0
  79. package/content/empty-state/EmptyState.d.ts +1 -1
  80. package/content/empty-state/EmptyState.js +1 -1
  81. package/content/empty-state/empty-state.module.css +11 -11
  82. package/content/feature-grid/feature-grid.module.css +22 -22
  83. package/content/index-rows/IndexRows.d.ts +2 -2
  84. package/content/index-rows/IndexRows.d.ts.map +1 -1
  85. package/content/index-rows/IndexRows.js +1 -1
  86. package/content/index-rows/index-rows.module.css +29 -29
  87. package/content/pricing/Pricing.d.ts +4 -4
  88. package/content/pricing/Pricing.d.ts.map +1 -1
  89. package/content/pricing/Pricing.js +4 -4
  90. package/content/pricing/Pricing.js.map +1 -1
  91. package/content/pricing/pricing.module.css +22 -22
  92. package/content/profile/Profile.d.ts +1 -1
  93. package/content/profile/Profile.js +2 -2
  94. package/content/profile/profile.module.css +13 -13
  95. package/content/quote/Quote.d.ts +1 -1
  96. package/content/quote/quote.module.css +9 -9
  97. package/content/timeline/Timeline.d.ts +1 -1
  98. package/content/timeline/Timeline.d.ts.map +1 -1
  99. package/content/timeline/Timeline.js +5 -5
  100. package/content/timeline/Timeline.js.map +1 -1
  101. package/content/timeline/timeline.module.css +12 -12
  102. package/content/types-table/TypePre.d.ts +11 -0
  103. package/content/types-table/TypePre.d.ts.map +1 -0
  104. package/content/types-table/TypePre.js +13 -0
  105. package/content/types-table/TypePre.js.map +1 -0
  106. package/content/types-table/TypesTable.d.ts +22 -0
  107. package/content/types-table/TypesTable.d.ts.map +1 -0
  108. package/content/types-table/TypesTable.js +124 -0
  109. package/content/types-table/TypesTable.js.map +1 -0
  110. package/content/types-table/index.d.ts +3 -0
  111. package/content/types-table/index.d.ts.map +1 -0
  112. package/content/types-table/index.js +3 -0
  113. package/content/types-table/index.js.map +1 -0
  114. package/content/types-table/types-table.module.css +160 -0
  115. package/data/chart/Chart.d.ts +8 -1
  116. package/data/chart/Chart.d.ts.map +1 -1
  117. package/data/chart/Chart.js +28 -15
  118. package/data/chart/Chart.js.map +1 -1
  119. package/data/chart/chart.module.css +20 -6
  120. package/data/figure/Figure.d.ts +1 -1
  121. package/data/figure/Figure.js +1 -1
  122. package/data/figure/figure.module.css +7 -7
  123. package/data/map/Map.d.ts +3 -3
  124. package/data/map/Map.d.ts.map +1 -1
  125. package/data/map/Map.js +10 -8
  126. package/data/map/Map.js.map +1 -1
  127. package/data/map/MapCanvas.d.ts.map +1 -1
  128. package/data/map/MapCanvas.js +5 -9
  129. package/data/map/MapCanvas.js.map +1 -1
  130. package/data/map/map.module.css +39 -14
  131. package/data/map/mapStyle.d.ts +2 -2
  132. package/data/map/mapStyle.d.ts.map +1 -1
  133. package/data/map/mapStyle.js +5 -5
  134. package/data/map/mapStyle.js.map +1 -1
  135. package/data/scroll-area/ScrollArea.d.ts.map +1 -1
  136. package/data/scroll-area/ScrollArea.js +8 -4
  137. package/data/scroll-area/ScrollArea.js.map +1 -1
  138. package/data/scroll-area/scroll-area.module.css +28 -9
  139. package/data/spec-grid/spec-grid.module.css +8 -8
  140. package/data/spec-list/spec-list.module.css +3 -3
  141. package/data/spec-sheet/SpecSheet.d.ts +2 -2
  142. package/data/spec-sheet/SpecSheet.js +1 -1
  143. package/data/spec-sheet/spec-sheet.module.css +9 -9
  144. package/data/stat/Stat.d.ts +1 -1
  145. package/data/stat/Stat.d.ts.map +1 -1
  146. package/data/stat/stat.module.css +4 -4
  147. package/data/table/Table.d.ts +6 -5
  148. package/data/table/Table.d.ts.map +1 -1
  149. package/data/table/Table.js.map +1 -1
  150. package/data/table/table.module.css +43 -46
  151. package/disclosure/accordion/Accordion.d.ts +1 -1
  152. package/disclosure/accordion/Accordion.js +1 -1
  153. package/disclosure/accordion/accordion.module.css +12 -12
  154. package/disclosure/collapsible/Collapsible.d.ts +4 -4
  155. package/disclosure/collapsible/Collapsible.js +4 -4
  156. package/disclosure/collapsible/collapsible.module.css +10 -10
  157. package/disclosure/faq/FAQ.d.ts +3 -3
  158. package/disclosure/faq/FAQ.d.ts.map +1 -1
  159. package/disclosure/faq/FAQ.js +3 -3
  160. package/disclosure/faq/FAQ.js.map +1 -1
  161. package/disclosure/faq/faq.module.css +20 -20
  162. package/feedback/alert/alert.module.css +4 -4
  163. package/feedback/avatar/avatar-group.module.css +2 -2
  164. package/feedback/avatar/avatar.module.css +9 -9
  165. package/feedback/badge/Badge.d.ts +1 -1
  166. package/feedback/badge/badge.module.css +7 -7
  167. package/feedback/meter/Meter.d.ts +2 -2
  168. package/feedback/meter/meter.module.css +14 -14
  169. package/feedback/progress/Progress.d.ts +1 -1
  170. package/feedback/progress/progress.module.css +7 -7
  171. package/feedback/tag/tag.module.css +7 -7
  172. package/feedback/toast/Toast.d.ts +3 -3
  173. package/feedback/toast/Toast.d.ts.map +1 -1
  174. package/feedback/toast/Toast.js +17 -7
  175. package/feedback/toast/Toast.js.map +1 -1
  176. package/feedback/toast/toast.module.css +31 -20
  177. package/forms/autocomplete/Autocomplete.d.ts +11 -0
  178. package/forms/autocomplete/Autocomplete.d.ts.map +1 -1
  179. package/forms/autocomplete/Autocomplete.js +27 -6
  180. package/forms/autocomplete/Autocomplete.js.map +1 -1
  181. package/forms/autocomplete/autocomplete.module.css +35 -41
  182. package/forms/checkbox/Checkbox.js +1 -1
  183. package/forms/checkbox/checkbox.module.css +29 -25
  184. package/forms/checkbox-group/checkbox-group.module.css +4 -4
  185. package/forms/combobox/Combobox.d.ts +10 -0
  186. package/forms/combobox/Combobox.d.ts.map +1 -1
  187. package/forms/combobox/Combobox.js +28 -7
  188. package/forms/combobox/Combobox.js.map +1 -1
  189. package/forms/combobox/combobox.module.css +34 -41
  190. package/forms/field/field.module.css +1 -1
  191. package/forms/fieldset/Fieldset.d.ts +1 -1
  192. package/forms/fieldset/Fieldset.js +2 -2
  193. package/forms/fieldset/fieldset.module.css +14 -14
  194. package/forms/form/form.module.css +2 -2
  195. package/forms/input/Input.d.ts +10 -3
  196. package/forms/input/Input.d.ts.map +1 -1
  197. package/forms/input/Input.js +10 -4
  198. package/forms/input/Input.js.map +1 -1
  199. package/forms/input/input.module.css +38 -21
  200. package/forms/number-field/number-field.module.css +24 -23
  201. package/forms/radio/Radio.d.ts +1 -1
  202. package/forms/radio/Radio.d.ts.map +1 -1
  203. package/forms/radio/Radio.js +5 -3
  204. package/forms/radio/Radio.js.map +1 -1
  205. package/forms/radio/radio.module.css +38 -30
  206. package/forms/radio-group/RadioGroup.d.ts +1 -1
  207. package/forms/radio-group/radio-group.module.css +3 -3
  208. package/forms/search/Search.d.ts +10 -0
  209. package/forms/search/Search.d.ts.map +1 -1
  210. package/forms/search/Search.js +25 -6
  211. package/forms/search/Search.js.map +1 -1
  212. package/forms/search/search.module.css +25 -33
  213. package/forms/select/Select.d.ts +10 -2
  214. package/forms/select/Select.d.ts.map +1 -1
  215. package/forms/select/Select.js +27 -12
  216. package/forms/select/Select.js.map +1 -1
  217. package/forms/select/select.module.css +42 -48
  218. package/forms/slider/Slider.js +2 -2
  219. package/forms/slider/slider.module.css +23 -23
  220. package/forms/switch/Switch.js +1 -1
  221. package/forms/switch/switch.module.css +23 -19
  222. package/foundations/expanding-box/ExpandingBox.d.ts +86 -0
  223. package/foundations/expanding-box/ExpandingBox.d.ts.map +1 -0
  224. package/foundations/expanding-box/ExpandingBox.js +179 -0
  225. package/foundations/expanding-box/ExpandingBox.js.map +1 -0
  226. package/foundations/expanding-box/expanding-box.module.css +415 -0
  227. package/foundations/expanding-box/index.d.ts +3 -0
  228. package/foundations/expanding-box/index.d.ts.map +1 -0
  229. package/foundations/expanding-box/index.js +3 -0
  230. package/foundations/expanding-box/index.js.map +1 -0
  231. package/foundations/expanding-box/startExpandingTransition.d.ts +33 -0
  232. package/foundations/expanding-box/startExpandingTransition.d.ts.map +1 -0
  233. package/foundations/expanding-box/startExpandingTransition.js +82 -0
  234. package/foundations/expanding-box/startExpandingTransition.js.map +1 -0
  235. package/foundations/ground/Ground.d.ts +1 -1
  236. package/foundations/ground/ground.module.css +4 -4
  237. package/foundations/icon/Icon.d.ts +1 -1
  238. package/foundations/icon/Icon.js +1 -1
  239. package/foundations/icon/icon.module.css +1 -1
  240. package/foundations/outline-morph/OutlineMorph.d.ts +112 -0
  241. package/foundations/outline-morph/OutlineMorph.d.ts.map +1 -0
  242. package/foundations/outline-morph/OutlineMorph.js +83 -0
  243. package/foundations/outline-morph/OutlineMorph.js.map +1 -0
  244. package/foundations/outline-morph/index.d.ts +3 -0
  245. package/foundations/outline-morph/index.d.ts.map +1 -0
  246. package/foundations/outline-morph/index.js +3 -0
  247. package/foundations/outline-morph/index.js.map +1 -0
  248. package/foundations/outline-morph/morphEngine.d.ts +219 -0
  249. package/foundations/outline-morph/morphEngine.d.ts.map +1 -0
  250. package/foundations/outline-morph/morphEngine.js +1331 -0
  251. package/foundations/outline-morph/morphEngine.js.map +1 -0
  252. package/foundations/outline-morph/outline-morph.module.css +113 -0
  253. package/foundations/outline-morph/useOutlineMorphRoot.d.ts +50 -0
  254. package/foundations/outline-morph/useOutlineMorphRoot.d.ts.map +1 -0
  255. package/foundations/outline-morph/useOutlineMorphRoot.js +55 -0
  256. package/foundations/outline-morph/useOutlineMorphRoot.js.map +1 -0
  257. package/foundations/separator/separator.module.css +10 -10
  258. package/foundations/sticker/Sticker.d.ts +4 -4
  259. package/foundations/sticker/Sticker.d.ts.map +1 -1
  260. package/foundations/sticker/Sticker.js +3 -3
  261. package/foundations/sticker/sticker.module.css +10 -10
  262. package/icons/paths.d.ts +1 -1
  263. package/icons/paths.d.ts.map +1 -1
  264. package/icons/paths.js +50 -0
  265. package/icons/paths.js.map +1 -1
  266. package/navigation/breadcrumb/Breadcrumb.d.ts +7 -1
  267. package/navigation/breadcrumb/Breadcrumb.d.ts.map +1 -1
  268. package/navigation/breadcrumb/Breadcrumb.js +8 -8
  269. package/navigation/breadcrumb/Breadcrumb.js.map +1 -1
  270. package/navigation/breadcrumb/breadcrumb.module.css +14 -14
  271. package/navigation/file-tabs/FileTabs.d.ts +169 -0
  272. package/navigation/file-tabs/FileTabs.d.ts.map +1 -0
  273. package/navigation/file-tabs/FileTabs.js +253 -0
  274. package/navigation/file-tabs/FileTabs.js.map +1 -0
  275. package/navigation/file-tabs/file-tabs.module.css +868 -0
  276. package/navigation/file-tabs/index.d.ts +2 -0
  277. package/navigation/file-tabs/index.d.ts.map +1 -0
  278. package/navigation/file-tabs/index.js +2 -0
  279. package/navigation/file-tabs/index.js.map +1 -0
  280. package/navigation/nav-drawer/NavDrawer.d.ts +32 -6
  281. package/navigation/nav-drawer/NavDrawer.d.ts.map +1 -1
  282. package/navigation/nav-drawer/NavDrawer.js +94 -28
  283. package/navigation/nav-drawer/NavDrawer.js.map +1 -1
  284. package/navigation/nav-drawer/nav-drawer.module.css +78 -55
  285. package/navigation/navigation-bar/NavigationBar.d.ts +31 -11
  286. package/navigation/navigation-bar/NavigationBar.d.ts.map +1 -1
  287. package/navigation/navigation-bar/NavigationBar.js +40 -17
  288. package/navigation/navigation-bar/NavigationBar.js.map +1 -1
  289. package/navigation/navigation-bar/navigation-bar.module.css +58 -27
  290. package/navigation/navigation-menu/NavigationMenu.d.ts +9 -9
  291. package/navigation/navigation-menu/NavigationMenu.d.ts.map +1 -1
  292. package/navigation/navigation-menu/NavigationMenu.js +15 -15
  293. package/navigation/navigation-menu/navigation-menu.module.css +39 -53
  294. package/navigation/pagination/Pagination.js +1 -1
  295. package/navigation/pagination/pagination.module.css +24 -24
  296. package/navigation/sidebar-nav/SidebarNav.d.ts +71 -0
  297. package/navigation/sidebar-nav/SidebarNav.d.ts.map +1 -0
  298. package/navigation/sidebar-nav/SidebarNav.js +169 -0
  299. package/navigation/sidebar-nav/SidebarNav.js.map +1 -0
  300. package/navigation/sidebar-nav/index.d.ts +2 -0
  301. package/navigation/sidebar-nav/index.d.ts.map +1 -0
  302. package/navigation/sidebar-nav/index.js +2 -0
  303. package/navigation/sidebar-nav/index.js.map +1 -0
  304. package/navigation/sidebar-nav/sidebar-nav.module.css +270 -0
  305. package/navigation/table-of-contents/TableOfContents.d.ts +92 -0
  306. package/navigation/table-of-contents/TableOfContents.d.ts.map +1 -0
  307. package/navigation/table-of-contents/TableOfContents.js +211 -0
  308. package/navigation/table-of-contents/TableOfContents.js.map +1 -0
  309. package/navigation/table-of-contents/index.d.ts +3 -0
  310. package/navigation/table-of-contents/index.d.ts.map +1 -0
  311. package/navigation/table-of-contents/index.js +3 -0
  312. package/navigation/table-of-contents/index.js.map +1 -0
  313. package/navigation/table-of-contents/table-of-contents.module.css +322 -0
  314. package/navigation/table-of-contents/useActiveHeading.d.ts +37 -0
  315. package/navigation/table-of-contents/useActiveHeading.d.ts.map +1 -0
  316. package/navigation/table-of-contents/useActiveHeading.js +111 -0
  317. package/navigation/table-of-contents/useActiveHeading.js.map +1 -0
  318. package/navigation/tabs/Tabs.d.ts +2 -2
  319. package/navigation/tabs/Tabs.js +3 -3
  320. package/navigation/tabs/tabs.module.css +22 -19
  321. package/navigation/toolbar/Toolbar.d.ts +7 -1
  322. package/navigation/toolbar/Toolbar.d.ts.map +1 -1
  323. package/navigation/toolbar/Toolbar.js +3 -3
  324. package/navigation/toolbar/Toolbar.js.map +1 -1
  325. package/navigation/toolbar/toolbar.module.css +6 -6
  326. package/overlays/alert-dialog/AlertDialog.d.ts +14 -4
  327. package/overlays/alert-dialog/AlertDialog.d.ts.map +1 -1
  328. package/overlays/alert-dialog/AlertDialog.js +15 -5
  329. package/overlays/alert-dialog/AlertDialog.js.map +1 -1
  330. package/overlays/alert-dialog/alert-dialog.module.css +4 -4
  331. package/overlays/context-menu/ContextMenu.d.ts +1 -1
  332. package/overlays/context-menu/ContextMenu.js +1 -1
  333. package/overlays/dialog/Dialog.d.ts +26 -11
  334. package/overlays/dialog/Dialog.d.ts.map +1 -1
  335. package/overlays/dialog/Dialog.js +36 -17
  336. package/overlays/dialog/Dialog.js.map +1 -1
  337. package/overlays/dialog/dialog.module.css +32 -31
  338. package/overlays/dialog/morph.d.ts +17 -0
  339. package/overlays/dialog/morph.d.ts.map +1 -0
  340. package/overlays/dialog/morph.js +44 -0
  341. package/overlays/dialog/morph.js.map +1 -0
  342. package/overlays/menu/Menu.d.ts +18 -7
  343. package/overlays/menu/Menu.d.ts.map +1 -1
  344. package/overlays/menu/Menu.js +35 -12
  345. package/overlays/menu/Menu.js.map +1 -1
  346. package/overlays/menu/menu.module.css +26 -50
  347. package/overlays/menu/morph.d.ts +16 -0
  348. package/overlays/menu/morph.d.ts.map +1 -0
  349. package/overlays/menu/morph.js +36 -0
  350. package/overlays/menu/morph.js.map +1 -0
  351. package/overlays/menu/popup.d.ts +2 -0
  352. package/overlays/menu/popup.d.ts.map +1 -1
  353. package/overlays/menu/popup.js +11 -3
  354. package/overlays/menu/popup.js.map +1 -1
  355. package/overlays/menubar/Menubar.d.ts +10 -1
  356. package/overlays/menubar/Menubar.d.ts.map +1 -1
  357. package/overlays/menubar/Menubar.js +22 -12
  358. package/overlays/menubar/Menubar.js.map +1 -1
  359. package/overlays/menubar/menubar.module.css +13 -13
  360. package/overlays/popover/Popover.d.ts +22 -8
  361. package/overlays/popover/Popover.d.ts.map +1 -1
  362. package/overlays/popover/Popover.js +51 -16
  363. package/overlays/popover/Popover.js.map +1 -1
  364. package/overlays/popover/popover.module.css +73 -47
  365. package/overlays/preview-card/PreviewCard.d.ts +3 -3
  366. package/overlays/preview-card/PreviewCard.d.ts.map +1 -1
  367. package/overlays/preview-card/PreviewCard.js +5 -6
  368. package/overlays/preview-card/PreviewCard.js.map +1 -1
  369. package/overlays/preview-card/preview-card.module.css +19 -54
  370. package/overlays/search-dialog/SearchDialog.d.ts +89 -0
  371. package/overlays/search-dialog/SearchDialog.d.ts.map +1 -0
  372. package/overlays/search-dialog/SearchDialog.js +220 -0
  373. package/overlays/search-dialog/SearchDialog.js.map +1 -0
  374. package/overlays/search-dialog/SearchDialogPopup.d.ts +39 -0
  375. package/overlays/search-dialog/SearchDialogPopup.d.ts.map +1 -0
  376. package/overlays/search-dialog/SearchDialogPopup.js +80 -0
  377. package/overlays/search-dialog/SearchDialogPopup.js.map +1 -0
  378. package/overlays/search-dialog/SearchDialogTrigger.d.ts +24 -0
  379. package/overlays/search-dialog/SearchDialogTrigger.d.ts.map +1 -0
  380. package/overlays/search-dialog/SearchDialogTrigger.js +32 -0
  381. package/overlays/search-dialog/SearchDialogTrigger.js.map +1 -0
  382. package/overlays/search-dialog/index.d.ts +3 -0
  383. package/overlays/search-dialog/index.d.ts.map +1 -0
  384. package/overlays/search-dialog/index.js +3 -0
  385. package/overlays/search-dialog/index.js.map +1 -0
  386. package/overlays/search-dialog/search-dialog.module.css +492 -0
  387. package/overlays/search-dialog/useShortcutLabel.d.ts +13 -0
  388. package/overlays/search-dialog/useShortcutLabel.d.ts.map +1 -0
  389. package/overlays/search-dialog/useShortcutLabel.js +19 -0
  390. package/overlays/search-dialog/useShortcutLabel.js.map +1 -0
  391. package/overlays/search-dialog/variants.d.ts +25 -0
  392. package/overlays/search-dialog/variants.d.ts.map +1 -0
  393. package/overlays/search-dialog/variants.js +31 -0
  394. package/overlays/search-dialog/variants.js.map +1 -0
  395. package/overlays/tooltip/Tooltip.d.ts +3 -3
  396. package/overlays/tooltip/Tooltip.d.ts.map +1 -1
  397. package/overlays/tooltip/Tooltip.js +3 -3
  398. package/overlays/tooltip/Tooltip.js.map +1 -1
  399. package/overlays/tooltip/tooltip.module.css +2 -2
  400. package/package.json +27 -19
  401. package/page/announcement-bar/AnnouncementBar.d.ts +1 -1
  402. package/page/announcement-bar/AnnouncementBar.js +4 -4
  403. package/page/announcement-bar/AnnouncementBar.js.map +1 -1
  404. package/page/announcement-bar/announcement-bar.module.css +11 -17
  405. package/page/cta-block/CTABlock.d.ts +5 -5
  406. package/page/cta-block/CTABlock.d.ts.map +1 -1
  407. package/page/cta-block/CTABlock.js +2 -2
  408. package/page/cta-block/cta-block.module.css +26 -26
  409. package/page/docs-layout/DocsLayout.d.ts +83 -0
  410. package/page/docs-layout/DocsLayout.d.ts.map +1 -0
  411. package/page/docs-layout/DocsLayout.js +108 -0
  412. package/page/docs-layout/DocsLayout.js.map +1 -0
  413. package/page/docs-layout/docs-layout.module.css +266 -0
  414. package/page/docs-layout/index.d.ts +2 -0
  415. package/page/docs-layout/index.d.ts.map +1 -0
  416. package/page/docs-layout/index.js +2 -0
  417. package/page/docs-layout/index.js.map +1 -0
  418. package/page/footer/Footer.d.ts +1 -1
  419. package/page/footer/Footer.js +1 -1
  420. package/page/footer/footer.module.css +31 -31
  421. package/page/hero/Hero.d.ts +3 -3
  422. package/page/hero/Hero.d.ts.map +1 -1
  423. package/page/hero/Hero.js +1 -1
  424. package/page/hero/hero.module.css +36 -36
  425. package/page/marquee/Marquee.d.ts +2 -2
  426. package/page/marquee/Marquee.js +1 -1
  427. package/page/marquee/marquee.module.css +11 -11
  428. package/page/newsletter/Newsletter.d.ts.map +1 -1
  429. package/page/newsletter/Newsletter.js +4 -59
  430. package/page/newsletter/Newsletter.js.map +1 -1
  431. package/page/newsletter/newsletter.module.css +44 -22
  432. package/page/section-bar/SectionBar.d.ts +10 -3
  433. package/page/section-bar/SectionBar.d.ts.map +1 -1
  434. package/page/section-bar/SectionBar.js +21 -11
  435. package/page/section-bar/SectionBar.js.map +1 -1
  436. package/page/section-bar/section-bar.module.css +43 -46
  437. package/page/section-divider/SectionDivider.d.ts +2 -2
  438. package/page/section-divider/SectionDivider.js +4 -4
  439. package/page/section-divider/SectionDivider.js.map +1 -1
  440. package/page/section-divider/section-divider.module.css +18 -18
  441. package/page/section-header/SectionHeader.d.ts +7 -7
  442. package/page/section-header/SectionHeader.d.ts.map +1 -1
  443. package/page/section-header/SectionHeader.js +3 -3
  444. package/page/section-header/SectionHeader.js.map +1 -1
  445. package/page/section-header/section-header.module.css +16 -16
  446. package/utils/Ornament.d.ts +1 -1
  447. package/utils/Ornament.js +2 -2
  448. package/utils/assignRef.d.ts +7 -1
  449. package/utils/assignRef.d.ts.map +1 -1
  450. package/utils/assignRef.js +18 -0
  451. package/utils/assignRef.js.map +1 -1
  452. package/utils/docs/DemoTitle.d.ts +12 -0
  453. package/utils/docs/DemoTitle.d.ts.map +1 -0
  454. package/utils/docs/DemoTitle.js +11 -0
  455. package/utils/docs/DemoTitle.js.map +1 -0
  456. package/utils/docs/Pre.d.ts +39 -0
  457. package/utils/docs/Pre.d.ts.map +1 -0
  458. package/utils/docs/Pre.js +26 -0
  459. package/utils/docs/Pre.js.map +1 -0
  460. package/utils/docs/createDemo.d.ts +89 -0
  461. package/utils/docs/createDemo.d.ts.map +1 -0
  462. package/utils/docs/createDemo.js +51 -0
  463. package/utils/docs/createDemo.js.map +1 -0
  464. package/utils/docs/createMdxComponents.d.ts +52 -0
  465. package/utils/docs/createMdxComponents.d.ts.map +1 -0
  466. package/utils/docs/createMdxComponents.js +59 -0
  467. package/utils/docs/createMdxComponents.js.map +1 -0
  468. package/utils/docs/createTypes.d.ts +23 -0
  469. package/utils/docs/createTypes.d.ts.map +1 -0
  470. package/utils/docs/createTypes.js +27 -0
  471. package/utils/docs/createTypes.js.map +1 -0
  472. package/utils/docs/index.d.ts +8 -0
  473. package/utils/docs/index.d.ts.map +1 -0
  474. package/utils/docs/index.js +16 -0
  475. package/utils/docs/index.js.map +1 -0
  476. package/utils/docs/mdx.module.css +201 -0
  477. package/utils/docs/serverSourceEnhancers.d.ts +26 -0
  478. package/utils/docs/serverSourceEnhancers.d.ts.map +1 -0
  479. package/utils/docs/serverSourceEnhancers.js +32 -0
  480. package/utils/docs/serverSourceEnhancers.js.map +1 -0
  481. package/utils/docs/toSidebarItems.d.ts +23 -0
  482. package/utils/docs/toSidebarItems.d.ts.map +1 -0
  483. package/utils/docs/toSidebarItems.js +36 -0
  484. package/utils/docs/toSidebarItems.js.map +1 -0
  485. package/utils/font-fallbacks.css +176 -0
  486. package/utils/fonts.d.ts +1 -0
  487. package/utils/fonts.d.ts.map +1 -1
  488. package/utils/fonts.js +12 -0
  489. package/utils/fonts.js.map +1 -1
  490. package/utils/frame.d.ts +4 -0
  491. package/utils/frame.d.ts.map +1 -0
  492. package/utils/frame.js +11 -0
  493. package/utils/frame.js.map +1 -0
  494. package/utils/global.css +480 -266
  495. package/utils/line.module.css +17 -17
  496. package/utils/media.css +1 -1
  497. package/utils/ornament.module.css +19 -19
  498. package/utils/overlay.d.ts +4 -4
  499. package/utils/overlay.js +4 -4
  500. package/utils/pattern.module.css +45 -45
  501. package/utils/print.css +48 -34
  502. package/utils/reset.css +9 -2
  503. package/utils/roles.css +29 -29
  504. package/utils/seriesPatterns.js +3 -3
  505. package/utils/syntax.module.css +92 -0
  506. package/utils/theme.css +6 -6
  507. package/utils/tokens.css +262 -192
  508. package/utils/tokens.d.ts +51 -8
  509. package/utils/tokens.d.ts.map +1 -1
  510. package/utils/tokens.js +46 -9
  511. package/utils/tokens.js.map +1 -1
  512. package/utils/type.module.css +87 -87
  513. package/utils/useReleaseMark.d.ts +13 -0
  514. package/utils/useReleaseMark.d.ts.map +1 -0
  515. package/utils/useReleaseMark.js +44 -0
  516. package/utils/useReleaseMark.js.map +1 -0
  517. package/utils/view-transitions.css +122 -0
@@ -0,0 +1,1331 @@
1
+ import { EXPAND_DURATION_MS, EXPAND_EASE, SURFACE_DURATION_MS } from '../../utils/tokens';
2
+ /*
3
+ * The outline morph's engine: plain DOM and the Web Animations API, no
4
+ * React. `useOutlineMorph` owns one controller per trigger and popup pair.
5
+ *
6
+ * Geometry. Every box is the outer edge of a frame: the focus ring (the
7
+ * trigger's border box grown by the outline's offset and width, each outer
8
+ * radius r + offset + width, as the browser draws a rounded outline), the
9
+ * trigger's edge (its border box and radii), or the popup's frame (its
10
+ * border box and radii). Radii are per corner and clamped as CSS clamps
11
+ * them, so a pill trigger starts as the pill it is and an ear with two
12
+ * square corners keeps them. The overlay frame is absolutely positioned
13
+ * in the popup's positioned container, so boxes are kept in that
14
+ * container's coordinates: the frame moves with the popup and never takes
15
+ * part in layout (a modal surface, which has no positioner, puts it in a
16
+ * fixed full-viewport layer). Its box, radii and edge color animate; it
17
+ * moves by a pure translation and resizes by width and height, never a
18
+ * scale, so the edge stays crisp. Its edge weight never tweens: where the
19
+ * start's weight and the popup's differ (a 2 px ring into a 3 px panel), it
20
+ * steps once, at WEIGHT_STEP of the eased travel, while the frame moves
21
+ * fastest. A popup side with no edge of its own that lies on the viewport's
22
+ * edge (a sheet) is landed on one weight past it, so that edge leaves the
23
+ * screen rather than popping at the hand-off. The popup is
24
+ * clipped to the same box (an `inset() round` clip on the same timing), so
25
+ * its contents appear inside the growing frame; nothing animates opacity.
26
+ * While a morph runs, the end it travels to is re-measured every frame, so
27
+ * a scroll, resize or late popup size change bends the path instead of
28
+ * leaving the frame behind; a close also re-reads which ring it lands on,
29
+ * so focus leaving the field mid-close lands on the field's edge.
30
+ */
31
+ /** The attribute the popup carries while the overlay draws its frame. */
32
+ export const OUTLINE_MORPH_ATTRIBUTE = 'data-outline-morph';
33
+ /**
34
+ * The app-wide (and per-subtree) switch: `none` on the trigger or the popup
35
+ * (inherited, so `:root` turns every morph off) leaves every popup its own
36
+ * open and close. Read at each open and close.
37
+ */
38
+ export const OUTLINE_MORPH_PROPERTY = '--fgd-outline-morph';
39
+ /**
40
+ * The popup's attribute while an open waits for its placement (a frame or
41
+ * two, at most PLACEMENT_FRAMES): the foundation's CSS holds the popup
42
+ * clipped away meanwhile (still focusable, as Base UI moves focus in during
43
+ * the wait), a discrete hold, so it never paints unclipped before the morph
44
+ * starts [D196]. A timeout removes it and the popup shows at once.
45
+ */
46
+ export const OUTLINE_MORPH_PENDING_ATTRIBUTE = 'data-outline-morph-pending';
47
+ /** The overlay frame's attribute while it shows. */
48
+ const RUNNING_ATTRIBUTE = 'data-running';
49
+ /** The tail cap's placement and face, custom properties the engine sets on it (never real properties). */
50
+ const TAIL_X = '--fgd-outline-morph-tail-x';
51
+ const TAIL_Y = '--fgd-outline-morph-tail-y';
52
+ const TAIL_FILL = '--fgd-outline-morph-tail-fill';
53
+ /**
54
+ * The frame's offset in its container and its size, as registered
55
+ * `<length>` properties so the Web Animations API interpolates them; the
56
+ * frame's CSS rounds them to whole pixels for its `translate`, `width` and
57
+ * `height`, so the edge is never painted at a fractional position (which
58
+ * would soften it across three pixels) and the last frame paints the
59
+ * popup's own pixel-snapped edges, whatever its fractional size.
60
+ */
61
+ const OFFSET_X = '--fgd-outline-morph-x';
62
+ const OFFSET_Y = '--fgd-outline-morph-y';
63
+ const SIZE_W = '--fgd-outline-morph-w';
64
+ const SIZE_H = '--fgd-outline-morph-h';
65
+ /**
66
+ * The frame's paint, also as registered properties (radii, edge color and
67
+ * weight), which the frame's CSS maps to `border-radius`, `border-color` and
68
+ * `border-width`, so the animation writes custom properties only.
69
+ */
70
+ const RADII = ['--fgd-outline-morph-r1', '--fgd-outline-morph-r2', '--fgd-outline-morph-r3', '--fgd-outline-morph-r4'];
71
+ const EDGE_COLOR = '--fgd-outline-morph-color';
72
+ const EDGE_WEIGHT = '--fgd-outline-morph-weight';
73
+ let offsetMode;
74
+ /**
75
+ * Registers the offset and size properties once. Where registration or CSS
76
+ * `round()` is missing, the frame animates `translate`, `width` and
77
+ * `height` itself (fractional).
78
+ */
79
+ function frameOffsetMode() {
80
+ if (offsetMode)
81
+ return offsetMode;
82
+ offsetMode = 'plain';
83
+ if (typeof CSS === 'undefined' || typeof CSS.registerProperty !== 'function')
84
+ return offsetMode;
85
+ if (!CSS.supports('translate', 'round(1.5px, 1px) round(1.5px, 1px)'))
86
+ return offsetMode;
87
+ const properties = [
88
+ ...[OFFSET_X, OFFSET_Y, SIZE_W, SIZE_H, ...RADII, EDGE_WEIGHT].map((name) => [name, '<length>', '0px']),
89
+ [EDGE_COLOR, '<color>', 'transparent'],
90
+ ];
91
+ for (const [name, syntax, initialValue] of properties) {
92
+ try {
93
+ CSS.registerProperty({ name, syntax, inherits: false, initialValue });
94
+ }
95
+ catch {
96
+ // Already registered (another copy of the module, or a hot reload).
97
+ }
98
+ }
99
+ offsetMode = 'rounded';
100
+ return offsetMode;
101
+ }
102
+ /** A morph resumed from its current state never runs shorter than this share of its duration. */
103
+ const MIN_RESUME_SHARE = 0.35;
104
+ /** Frames to wait for the popup to be placed before letting it open its own way. */
105
+ const PLACEMENT_FRAMES = 8;
106
+ /** How long a landed close waits for the popup to hide before clearing the frame itself. */
107
+ const SETTLE_MS = 120;
108
+ /** Re-measured ends closer than this (px) leave the keyframes alone. */
109
+ const DRIFT_PX = 0.5;
110
+ /** How far inside the frame's outer edge the popup's clip runs (px); less than the edge weight. */
111
+ const CLIP_INSET_PX = 1;
112
+ /** An open's start may still move onto a ring drawn late while its progress is under this share. */
113
+ const EARLY_PROGRESS = 0.25;
114
+ /** Where on the eased travel (0–1) the frame's edge weight steps, when its two ends differ. */
115
+ const WEIGHT_STEP = 0.15;
116
+ function px(value) {
117
+ const number = parseFloat(value);
118
+ return Number.isFinite(number) ? number : 0;
119
+ }
120
+ function camel(property) {
121
+ return property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
122
+ }
123
+ function toRect(rect) {
124
+ return { x: rect.left, y: rect.top, w: rect.width, h: rect.height };
125
+ }
126
+ function drifted(a, b) {
127
+ return (Math.abs(a.x - b.x) > DRIFT_PX ||
128
+ Math.abs(a.y - b.y) > DRIFT_PX ||
129
+ Math.abs(a.w - b.w) > DRIFT_PX ||
130
+ Math.abs(a.h - b.h) > DRIFT_PX);
131
+ }
132
+ /** A computed color's alpha: 0 for `transparent`, else its `/ a` or `rgba()` alpha, else 1. */
133
+ function alphaOf(color) {
134
+ const value = color.trim();
135
+ if (value === '' || value === 'transparent')
136
+ return 0;
137
+ const slash = value.match(/\/\s*([\d.]+)(%?)\s*\)$/);
138
+ if (slash)
139
+ return slash[2] ? parseFloat(slash[1]) / 100 : parseFloat(slash[1]);
140
+ const rgba = value.match(/^rgba\((?:[^,]+,){3}\s*([\d.]+)\s*\)$/);
141
+ if (rgba)
142
+ return parseFloat(rgba[1]);
143
+ return 1;
144
+ }
145
+ /** One corner's horizontal radius from its computed value ("12px", "12px 6px" or "50%"). */
146
+ function cornerRadius(value, basis) {
147
+ const first = value.trim().split(/\s+/)[0] ?? '0';
148
+ return first.endsWith('%') ? (parseFloat(first) / 100) * basis : px(first);
149
+ }
150
+ /**
151
+ * Scales radii down as CSS does when adjacent corners overflow a side
152
+ * (CSS Backgrounds 3, corner overlap), so `--radius-round` reads as the
153
+ * pill's half-height, never 100000 px.
154
+ */
155
+ function clampRadii(r, w, h) {
156
+ let factor = 1;
157
+ const sides = [
158
+ [r[0] + r[1], w],
159
+ [r[1] + r[2], h],
160
+ [r[2] + r[3], w],
161
+ [r[3] + r[0], h],
162
+ ];
163
+ for (const [sum, length] of sides) {
164
+ if (sum > length && sum > 0)
165
+ factor = Math.min(factor, Math.max(0, length) / sum);
166
+ }
167
+ return factor < 1 ? r.map((value) => value * factor) : r;
168
+ }
169
+ /** An element's used corner radii for a box of w × h. */
170
+ function usedRadii(style, w, h) {
171
+ return clampRadii([
172
+ cornerRadius(style.borderTopLeftRadius, w),
173
+ cornerRadius(style.borderTopRightRadius, w),
174
+ cornerRadius(style.borderBottomRightRadius, w),
175
+ cornerRadius(style.borderBottomLeftRadius, w),
176
+ ], w, h);
177
+ }
178
+ /**
179
+ * A property's value once its running CSS transition lands: the transition's
180
+ * end keyframe, else the computed value. The trigger's edge steps color on
181
+ * open and close, and the frame must meet the step's end, not its start.
182
+ */
183
+ function settledValue(element, property) {
184
+ const current = getComputedStyle(element).getPropertyValue(property);
185
+ if (typeof CSSTransition === 'undefined')
186
+ return current;
187
+ for (const animation of element.getAnimations()) {
188
+ if (!(animation instanceof CSSTransition) || animation.transitionProperty !== property)
189
+ continue;
190
+ const frames = animation.effect?.getKeyframes() ?? [];
191
+ const end = frames[frames.length - 1]?.[camel(property)];
192
+ if (typeof end === 'string')
193
+ return end;
194
+ }
195
+ return current;
196
+ }
197
+ /**
198
+ * Whether the morph may run now: Web Animations, motion allowed, no forced
199
+ * colors, not printing. Read at each open, so a preference change applies
200
+ * to the next one.
201
+ */
202
+ export function canOutlineMorph() {
203
+ if (typeof window === 'undefined' || typeof Element.prototype.animate !== 'function') {
204
+ return false;
205
+ }
206
+ if (typeof window.matchMedia !== 'function')
207
+ return true;
208
+ return !(window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
209
+ window.matchMedia('(forced-colors: active)').matches ||
210
+ window.matchMedia('print').matches);
211
+ }
212
+ /** A `<time>` custom property's computed value in ms ("333ms", "0.2s", "0ms"), or null. */
213
+ function timeMs(value) {
214
+ const match = value.trim().match(/^(-?[\d.]+)(ms|s)$/);
215
+ if (!match)
216
+ return null;
217
+ const number = parseFloat(match[1]) * (match[2] === 's' ? 1000 : 1);
218
+ return Number.isFinite(number) ? Math.max(0, number) : null;
219
+ }
220
+ const easeChecked = new Map();
221
+ /** Whether the Web Animations API accepts the easing (a `linear()` token needs a recent engine). */
222
+ function easeValid(ease) {
223
+ let valid = easeChecked.get(ease);
224
+ if (valid === undefined) {
225
+ try {
226
+ document.createElement('div').animate([], { duration: 1, easing: ease }).cancel();
227
+ valid = true;
228
+ }
229
+ catch {
230
+ valid = false;
231
+ }
232
+ easeChecked.set(ease, valid);
233
+ }
234
+ return valid;
235
+ }
236
+ /**
237
+ * The morph's timing from the tokens as the trigger resolves them, read at
238
+ * each open and close: `--fgd-duration-expand`, `--fgd-duration-collapse`
239
+ * (else `--fgd-duration-disclosure`) and `--fgd-ease-expand`, so an app's
240
+ * override reaches the morph; a modal surface (`surface`) reads
241
+ * `--fgd-duration-expand-surface` and `--fgd-duration-collapse-surface`. The JS mirrors (utils/tokens.ts) are only the
242
+ * fallback for a value that can't be read; `override` (the hook's
243
+ * `duration` option) wins over the duration tokens.
244
+ */
245
+ function readTiming(source, override, surface) {
246
+ const style = getComputedStyle(source);
247
+ const token = (name) => style.getPropertyValue(name);
248
+ const open = override?.open ??
249
+ (surface
250
+ ? (timeMs(token('--fgd-duration-expand-surface')) ?? SURFACE_DURATION_MS.open)
251
+ : (timeMs(token('--fgd-duration-expand')) ?? EXPAND_DURATION_MS.open));
252
+ const close = override?.close ??
253
+ (surface
254
+ ? (timeMs(token('--fgd-duration-collapse-surface')) ?? SURFACE_DURATION_MS.close)
255
+ : (timeMs(token('--fgd-duration-collapse')) ??
256
+ timeMs(token('--fgd-duration-disclosure')) ??
257
+ EXPAND_DURATION_MS.close));
258
+ const ease = token('--fgd-ease-expand').trim().replace(/\s+/g, ' ');
259
+ return { open, close, ease: ease && easeValid(ease) ? ease : EXPAND_EASE };
260
+ }
261
+ /** Whether `--fgd-outline-morph: none` applies to the element (inherited from any ancestor). */
262
+ function switchedOff(element) {
263
+ if (!element)
264
+ return false;
265
+ return getComputedStyle(element).getPropertyValue(OUTLINE_MORPH_PROPERTY).trim() === 'none';
266
+ }
267
+ /** Whether the trigger is rendered (a trigger hidden by `display: none` has no box to morph from). */
268
+ function rendered(element) {
269
+ return element.isConnected && element.getClientRects().length > 0;
270
+ }
271
+ /** Whether the element paints an outline now. */
272
+ function outlineShown(style) {
273
+ return style.outlineStyle !== 'none' && px(style.outlineWidth) > 0;
274
+ }
275
+ /** Whether the trigger draws its focus ring now, as its ring mode draws it. */
276
+ function ringDrawn(source, mode) {
277
+ const focused = mode === 'focus-within' ? source.matches(':focus-within') : source.matches(':focus-visible');
278
+ return focused && outlineShown(getComputedStyle(source));
279
+ }
280
+ /** Whether focus sits in the trigger or the popup's container (so it stays with, or comes back to, the trigger). */
281
+ function focusHeld(source, popup) {
282
+ const active = document.activeElement;
283
+ if (!active || active === document.body)
284
+ return false;
285
+ return source.contains(active) || popup.some((element) => element?.contains(active) ?? false);
286
+ }
287
+ /** A registered `<length>` the engine sets on a trigger for a moment to resolve its ring tokens there. */
288
+ const PROBE = '--fgd-outline-morph-probe';
289
+ let probeRegistered;
290
+ function probeReady() {
291
+ if (probeRegistered !== undefined)
292
+ return probeRegistered;
293
+ probeRegistered = false;
294
+ if (typeof CSS === 'undefined' || typeof CSS.registerProperty !== 'function')
295
+ return false;
296
+ try {
297
+ CSS.registerProperty({ name: PROBE, syntax: '<length>', inherits: false, initialValue: '0px' });
298
+ }
299
+ catch {
300
+ // Already registered (another copy of the module, or a hot reload).
301
+ }
302
+ probeRegistered = true;
303
+ return true;
304
+ }
305
+ /**
306
+ * A length token as the element itself resolves it (so `1lh` and `em` are
307
+ * its own): set for a moment as a registered `<length>` on the element,
308
+ * whose computed value is absolute, then removed again (never a rewrite of
309
+ * the element's style attribute). Null where registration is missing.
310
+ */
311
+ function resolveOn(element, value) {
312
+ if (!probeReady())
313
+ return null;
314
+ const hadStyle = element.hasAttribute('style');
315
+ element.style.setProperty(PROBE, value);
316
+ const resolved = getComputedStyle(element).getPropertyValue(PROBE);
317
+ // Only the engine's own property goes; an attribute the probe created goes with it.
318
+ element.style.removeProperty(PROBE);
319
+ if (!hadStyle && element.getAttribute('style') === '')
320
+ element.removeAttribute('style');
321
+ return resolved ? px(resolved) : null;
322
+ }
323
+ /**
324
+ * The focus ring's outer box, in viewport coordinates. Drawn: the
325
+ * trigger's computed outline (its color as its transition lands).
326
+ * Predicted (focus is elsewhere and returns with a ring): the system's ring
327
+ * tokens as the trigger resolves them, `--fgd-focus-width` solid
328
+ * `--primary12` at `--fgd-focus-offset` [D45]; a trigger whose ring sits
329
+ * elsewhere (inset) sets those tokens on itself.
330
+ */
331
+ function ringBox(source, drawn, resolve) {
332
+ const rect = source.getBoundingClientRect();
333
+ const style = getComputedStyle(source);
334
+ const width = drawn
335
+ ? px(style.outlineWidth)
336
+ : resolve(style.getPropertyValue('--fgd-focus-width'), source);
337
+ const offset = drawn
338
+ ? px(style.outlineOffset)
339
+ : resolve(style.getPropertyValue('--fgd-focus-offset'), source);
340
+ const color = drawn
341
+ ? settledValue(source, 'outline-color')
342
+ : style.getPropertyValue('--primary12').trim() || style.color;
343
+ const out = width + offset;
344
+ const w = rect.width + 2 * out;
345
+ const h = rect.height + 2 * out;
346
+ // A rounded corner's outline is rounded by offset + width more; a square one stays square.
347
+ const inner = usedRadii(style, rect.width, rect.height);
348
+ const r = inner.map((radius) => (radius > 0 ? Math.max(0, radius + out) : 0));
349
+ return { x: rect.left - out, y: rect.top - out, w, h, r: clampRadii(r, w, h), color, k: width };
350
+ }
351
+ /** Whether the trigger has a visible boxed edge (all four sides), rather than an underline, a fill or none. */
352
+ function hasBoxEdge(style) {
353
+ return (style.borderTopStyle !== 'none' &&
354
+ px(style.borderTopWidth) > 0 &&
355
+ px(style.borderRightWidth) > 0 &&
356
+ px(style.borderBottomWidth) > 0 &&
357
+ px(style.borderLeftWidth) > 0 &&
358
+ alphaOf(style.borderTopColor) > 0);
359
+ }
360
+ /**
361
+ * The trigger's own edge, in viewport coordinates, where the morph starts
362
+ * and lands without a ring: its border box and radii in the color of its
363
+ * boxed edge (as that edge's transition lands), else its border box in its
364
+ * opaque fill (a solid button); a trigger with neither (a text button, an
365
+ * underline, a bare glyph, a menubar item) has no edge of its own, so its
366
+ * ring's box stands in, in its ink. The color is always opaque, so the
367
+ * frame never fades in.
368
+ */
369
+ function edgeBox(source, resolve) {
370
+ const rect = source.getBoundingClientRect();
371
+ const style = getComputedStyle(source);
372
+ const box = (color, k) => ({
373
+ ...toRect(rect),
374
+ r: usedRadii(style, rect.width, rect.height),
375
+ color,
376
+ k,
377
+ });
378
+ if (hasBoxEdge(style))
379
+ return box(settledValue(source, 'border-top-color'), px(style.borderTopWidth));
380
+ const fill = settledValue(source, 'background-color');
381
+ // A fill has no edge weight of its own: the frame keeps the popup's, drawn inside it in its color.
382
+ if (alphaOf(fill) >= 1)
383
+ return box(fill, null);
384
+ return { ...ringBox(source, false, resolve), color: style.color };
385
+ }
386
+ /** Where the morph starts or lands on the trigger: the ring while one is (or will be) drawn, else its edge. */
387
+ function sourceBox(source, ring, resolve) {
388
+ if (ring === 'drawn')
389
+ return ringBox(source, true, resolve);
390
+ if (ring === 'predicted')
391
+ return ringBox(source, false, resolve);
392
+ return edgeBox(source, resolve);
393
+ }
394
+ /**
395
+ * The controller behind `useOutlineMorph`. Elements arrive through the ref
396
+ * callbacks; `update` receives the open state in a layout effect, so both
397
+ * directions start in the commit that changes the popup, before it paints.
398
+ */
399
+ export class OutlineMorphController {
400
+ /** Explicit durations (the hook's `duration` option); otherwise the tokens are read at each open and close. */
401
+ duration = undefined;
402
+ ring = 'focus-visible';
403
+ /** A modal surface (a dialog, a sheet): the `-surface` durations. */
404
+ surface = false;
405
+ /** The timing of the running morph, read from the trigger when it started. */
406
+ timing = { ...EXPAND_DURATION_MS, ease: EXPAND_EASE };
407
+ /** Every trigger attached (a popup may have several); `source` is the one this open came from. */
408
+ sources = new Set();
409
+ source = null;
410
+ target = null;
411
+ frame = null;
412
+ /** The popup's own tail (an adopter's arrow) and the cap that stands in for it while the morph runs. */
413
+ tailSource = null;
414
+ tail = null;
415
+ tailAnimation = null;
416
+ tailFlight = null;
417
+ phase = 'idle';
418
+ open = false;
419
+ /** Whether the morph was on at the last update (a popup mounting before its open reaches us is held if so). */
420
+ enabled = true;
421
+ anticipateFrame = 0;
422
+ /**
423
+ * Where a pending open starts (viewport coordinates), the share of the
424
+ * open duration it runs, and, for a fresh open, the ring it started from.
425
+ */
426
+ pending = null;
427
+ flight = null;
428
+ frameAnimation = null;
429
+ targetAnimation = null;
430
+ /** The popup's own frame, read while it shows (its border is the face while the morph runs). */
431
+ targetFrame = null;
432
+ placementObserver = null;
433
+ placementFrame = 0;
434
+ placementTries = 0;
435
+ trackFrame = 0;
436
+ settleObserver = null;
437
+ settleTimer;
438
+ /** A trigger: its focus ring (or edge) is where the morph starts and lands. */
439
+ sourceRef = (element) => {
440
+ if (element)
441
+ this.sources.add(element);
442
+ };
443
+ /** The popup: its border box and radius are the frame the morph grows into. */
444
+ targetRef = (element) => {
445
+ if (element === this.target)
446
+ return;
447
+ // Another popup element: the old one's morph ends with it.
448
+ if (element && this.target && this.phase !== 'pending')
449
+ this.stop();
450
+ this.target = element;
451
+ // A popup (re)attached while a fresh open waits is held too.
452
+ if (element && this.phase === 'pending' && this.pending?.ring !== undefined) {
453
+ element.setAttribute(OUTLINE_MORPH_PENDING_ATTRIBUTE, '');
454
+ }
455
+ else if (element && this.phase === 'idle' && !this.open && this.enabled) {
456
+ this.anticipate(element);
457
+ }
458
+ if (element)
459
+ this.watchPlacement();
460
+ else
461
+ this.detachSoon();
462
+ };
463
+ /** The popup's own tail, if it has one: the cap draws its copy while the morph runs [§9.17]. */
464
+ tailSourceRef = (element) => {
465
+ this.tailSource = element;
466
+ };
467
+ /** The tail cap, the overlay's tail, rendered after the frame in the same container. */
468
+ tailRef = (element) => {
469
+ if (element === this.tail)
470
+ return;
471
+ this.tail?.removeAttribute(RUNNING_ATTRIBUTE);
472
+ this.tail = element;
473
+ };
474
+ /** The overlay frame, a sibling of the popup inside its positioned container. */
475
+ frameRef = (element) => {
476
+ if (element === this.frame)
477
+ return;
478
+ if (element && this.frame && this.phase !== 'pending')
479
+ this.stop();
480
+ this.frame = element;
481
+ if (element)
482
+ this.watchPlacement();
483
+ else
484
+ this.detachSoon();
485
+ };
486
+ /**
487
+ * A popup mounted before its open reaches the controller (its owner's
488
+ * open state can land a commit after Base UI's): held as a pending open
489
+ * would be, if the morph would run, so it never paints unclipped first.
490
+ * The open claims the hold; if none comes within two frames, it goes.
491
+ */
492
+ anticipate(element) {
493
+ const source = this.pickSource();
494
+ if (!source || !canOutlineMorph() || switchedOff(source))
495
+ return;
496
+ if (!rendered(source) || readTiming(source, this.duration, this.surface).open <= 0)
497
+ return;
498
+ element.setAttribute(OUTLINE_MORPH_PENDING_ATTRIBUTE, '');
499
+ if (this.anticipateFrame)
500
+ cancelAnimationFrame(this.anticipateFrame);
501
+ this.anticipateFrame = requestAnimationFrame(() => {
502
+ this.anticipateFrame = requestAnimationFrame(() => {
503
+ this.anticipateFrame = 0;
504
+ if (this.phase !== 'pending')
505
+ element.removeAttribute(OUTLINE_MORPH_PENDING_ATTRIBUTE);
506
+ });
507
+ });
508
+ }
509
+ /**
510
+ * The trigger this open belongs to: the only one attached, else the one
511
+ * marked open (Base UI's `aria-expanded` / `data-popup-open`), else the
512
+ * one holding focus, else the last attached. Detached ones are dropped.
513
+ */
514
+ pickSource() {
515
+ for (const element of this.sources) {
516
+ if (!element.isConnected)
517
+ this.sources.delete(element);
518
+ }
519
+ const list = [...this.sources];
520
+ if (list.length <= 1)
521
+ return list[0] ?? null;
522
+ const active = document.activeElement;
523
+ return (list.find((element) => element.getAttribute('aria-expanded') === 'true' || element.hasAttribute('data-popup-open')) ??
524
+ list.find((element) => active != null && element.contains(active)) ??
525
+ list[list.length - 1]);
526
+ }
527
+ /**
528
+ * The popup or the frame detached: once the commit is over, a running
529
+ * morph stops if it is still gone. A ref that is only reattached (a
530
+ * merged ref's new identity, React's development double mount) comes
531
+ * back within the same commit, and a pending open keeps waiting for it.
532
+ */
533
+ detachSoon() {
534
+ queueMicrotask(() => {
535
+ if ((this.target && this.frame) || this.phase === 'pending')
536
+ return;
537
+ this.stop();
538
+ });
539
+ }
540
+ /** The popup's positioned container, which also holds the frame. */
541
+ get container() {
542
+ return this.frame?.parentElement ?? null;
543
+ }
544
+ /**
545
+ * A length token's px value: a plain length as is, else resolved on the
546
+ * trigger itself (`resolveOn`), else through the frame's own
547
+ * `outline-offset` (the frame is ours, hidden while idle, and its CSS
548
+ * never sets that property), since a custom property's computed value
549
+ * keeps `calc()` and font-relative units unresolved.
550
+ */
551
+ resolveLength = (value, context) => {
552
+ const raw = value.trim();
553
+ if (raw === '' || /^-?[\d.]+px$/.test(raw))
554
+ return px(raw);
555
+ const own = resolveOn(context, raw);
556
+ if (own !== null)
557
+ return own;
558
+ const probe = this.frame;
559
+ if (!probe)
560
+ return px(raw);
561
+ probe.style.setProperty('outline-offset', raw);
562
+ const resolved = px(getComputedStyle(probe).outlineOffset);
563
+ probe.style.removeProperty('outline-offset');
564
+ return resolved;
565
+ };
566
+ /**
567
+ * Receives the open state, and whether the morph is on, after each commit.
568
+ * Off (`enabled` false) stops a running morph, so the change the commit
569
+ * carries happens the popup's own way.
570
+ */
571
+ update(open, enabled) {
572
+ const wasOpen = this.open;
573
+ this.open = open;
574
+ this.enabled = enabled;
575
+ if (!enabled) {
576
+ // Also records a popup opened this way as open, so a later close can morph from it.
577
+ this.stop();
578
+ return;
579
+ }
580
+ if (open === wasOpen)
581
+ return;
582
+ if (open)
583
+ this.startOpen();
584
+ else
585
+ this.startClose();
586
+ }
587
+ /** Clears everything: animations, observers, the frame and the popup's attribute. */
588
+ stop() {
589
+ this.cancelPlacement();
590
+ this.cancelSettle();
591
+ this.cancelAnimations();
592
+ this.pending = null;
593
+ this.frame?.removeAttribute(RUNNING_ATTRIBUTE);
594
+ this.tail?.removeAttribute(RUNNING_ATTRIBUTE);
595
+ this.target?.removeAttribute(OUTLINE_MORPH_ATTRIBUTE);
596
+ this.target?.removeAttribute(OUTLINE_MORPH_PENDING_ATTRIBUTE);
597
+ this.phase = this.open ? 'open' : 'idle';
598
+ }
599
+ startOpen() {
600
+ const reversing = (this.phase === 'closing' || this.phase === 'closed') &&
601
+ (this.frame?.hasAttribute(RUNNING_ATTRIBUTE) ?? false);
602
+ const source = reversing ? (this.source ?? this.pickSource()) : this.pickSource();
603
+ const timing = source ? readTiming(source, this.duration, this.surface) : null;
604
+ // A zero open duration (the token set to 0ms, as under reduced motion) is the morph off; so is
605
+ // no trigger to start from (a programmatic open with none attached, or one not rendered).
606
+ if (!source ||
607
+ !rendered(source) ||
608
+ !timing ||
609
+ timing.open <= 0 ||
610
+ !canOutlineMorph() ||
611
+ switchedOff(source)) {
612
+ this.stop();
613
+ return;
614
+ }
615
+ this.source = source;
616
+ this.timing = timing;
617
+ const current = reversing ? this.currentBox() : null;
618
+ if (current) {
619
+ // Reopened while closing: grow again from where the frame is, for the share left.
620
+ const share = Math.max(this.progress(), MIN_RESUME_SHARE);
621
+ this.cancelSettle();
622
+ this.cancelAnimations();
623
+ this.pending = { from: current, share };
624
+ }
625
+ else {
626
+ this.stop();
627
+ // The trigger still holds focus here; a popup that takes it does so on a later frame.
628
+ const ring = ringDrawn(source, this.ring) ? 'drawn' : 'none';
629
+ this.pending = { from: sourceBox(source, ring, this.resolveLength), share: 1, ring };
630
+ // Held unpainted until it is placed and the morph starts (a reversal is already on screen).
631
+ this.target?.setAttribute(OUTLINE_MORPH_PENDING_ATTRIBUTE, '');
632
+ }
633
+ this.phase = 'pending';
634
+ this.placementTries = 0;
635
+ this.watchPlacement();
636
+ }
637
+ startClose() {
638
+ const source = this.source ?? this.pickSource();
639
+ const target = this.target;
640
+ const container = this.container;
641
+ const running = this.phase === 'opening' || this.phase === 'open';
642
+ const timing = source ? readTiming(source, this.duration, this.surface) : null;
643
+ if (!running ||
644
+ !source ||
645
+ !rendered(source) ||
646
+ !target ||
647
+ !container ||
648
+ !timing ||
649
+ timing.close <= 0 ||
650
+ !canOutlineMorph() ||
651
+ switchedOff(source) ||
652
+ switchedOff(target)) {
653
+ // Never shown by the morph (or motion is now off): the popup closes its own way.
654
+ this.stop();
655
+ return;
656
+ }
657
+ this.source = source;
658
+ this.timing = timing;
659
+ const ring = this.closingRing(source, container);
660
+ const to = sourceBox(source, ring, this.resolveLength);
661
+ let from;
662
+ let share = 1;
663
+ if (this.phase === 'opening') {
664
+ share = Math.max(this.progress(), MIN_RESUME_SHARE);
665
+ from = this.currentBox();
666
+ }
667
+ else {
668
+ const frame = this.readTargetFrame(target);
669
+ this.targetFrame = frame;
670
+ from = landing(toRect(target.getBoundingClientRect()), frame, frame.width ?? to.k ?? this.defaultWeight(target), frame.color ?? to.color, this.viewport());
671
+ }
672
+ if (!from) {
673
+ // Gone, or a side with no edge that isn't on the viewport's edge: it closes at once.
674
+ this.stop();
675
+ return;
676
+ }
677
+ this.phase = 'closing';
678
+ this.run(from, to, { kind: 'source', ring }, this.timing.close * share);
679
+ }
680
+ /**
681
+ * Which ring a close lands on. A field that draws its ring on any focus
682
+ * (`focus-within`) keeps it while focus stays in it. Otherwise focus comes
683
+ * back to the trigger when the popup hides, with a ring if the focus inside
684
+ * the popup shows one (the browser carries it over) or if the popup was
685
+ * closed from the keyboard.
686
+ */
687
+ closingRing(source, container) {
688
+ if (ringDrawn(source, this.ring))
689
+ return 'drawn';
690
+ const active = document.activeElement;
691
+ // The popup's container holds it (a positioner), or it is the popup's sibling (a modal layer).
692
+ const inPopup = active instanceof HTMLElement && (container.contains(active) || (this.target?.contains(active) ?? false));
693
+ if (this.ring === 'focus-within')
694
+ return inPopup ? 'predicted' : 'none';
695
+ const ringReturns = source.matches(':focus-visible') || (inPopup && active.matches(':focus-visible'));
696
+ return ringReturns ? 'predicted' : 'none';
697
+ }
698
+ /**
699
+ * The ring a running close lands on now: drawn once the trigger draws it;
700
+ * the planned one while focus stays in the trigger or the popup (on its
701
+ * way back); none once focus has gone elsewhere (an outside press).
702
+ */
703
+ liveRing(planned, source) {
704
+ if (ringDrawn(source, this.ring))
705
+ return 'drawn';
706
+ if (planned === 'none')
707
+ return 'none';
708
+ if (focusHeld(source, [this.container, this.target]))
709
+ return 'predicted';
710
+ // Focus has left. A field's ring went with it; a trigger whose popup
711
+ // hands focus back may pass through the body on the way, so only focus
712
+ // landing somewhere else cancels its ring.
713
+ const active = document.activeElement;
714
+ if (this.ring === 'focus-within' || (active != null && active !== document.body))
715
+ return 'none';
716
+ return planned;
717
+ }
718
+ /** Starts a pending open once the popup is placed and laid out. Returns whether it started. */
719
+ tryStart() {
720
+ const pending = this.pending;
721
+ const target = this.target;
722
+ const container = this.container;
723
+ if (this.phase !== 'pending' || !pending || !target || !container)
724
+ return false;
725
+ if (container.hidden)
726
+ return false;
727
+ const rect = target.getBoundingClientRect();
728
+ if (rect.width === 0 || rect.height === 0)
729
+ return false;
730
+ // A popup not yet placed is held invisible (Base UI's positioner) at the viewport origin.
731
+ if (getComputedStyle(container).opacity === '0')
732
+ return false;
733
+ this.cancelPlacement();
734
+ target.removeAttribute(OUTLINE_MORPH_PENDING_ATTRIBUTE);
735
+ if (switchedOff(target)) {
736
+ // The popup opts out (its own form, such as a tail, isn't a plain frame): it opens its own way.
737
+ this.stop();
738
+ return true;
739
+ }
740
+ // A reversed close keeps the frame read before the popup's border took its face.
741
+ const frame = this.targetFrame && target.hasAttribute(OUTLINE_MORPH_ATTRIBUTE)
742
+ ? this.targetFrame
743
+ : this.readTargetFrame(target);
744
+ this.targetFrame = frame;
745
+ this.pending = null;
746
+ this.phase = 'opening';
747
+ // A press into a field focuses it after the open began: start on the ring it now draws.
748
+ const upgrade = pending.ring === 'none' && this.source != null && ringDrawn(this.source, this.ring);
749
+ const from = upgrade && this.source ? sourceBox(this.source, 'drawn', this.resolveLength) : pending.from;
750
+ const to = landing(toRect(rect), frame, frame.width ?? from.k ?? this.defaultWeight(target), frame.color ?? from.color, this.viewport());
751
+ if (!to) {
752
+ // A side with no edge of its own, not on the viewport's edge: no frame to land on, so it opens at once.
753
+ this.stop();
754
+ return true;
755
+ }
756
+ this.run(from, to, { kind: 'target' }, this.timing.open * pending.share, upgrade ? 'drawn' : pending.ring);
757
+ return true;
758
+ }
759
+ /**
760
+ * The box fixed elements are laid out in: a modal layer's own box (fixed,
761
+ * inset 0: the viewport less a reserved scrollbar gutter), else the
762
+ * root's client box.
763
+ */
764
+ viewport() {
765
+ const container = this.container;
766
+ if (container && getComputedStyle(container).position === 'fixed') {
767
+ return toRect(container.getBoundingClientRect());
768
+ }
769
+ const root = document.documentElement;
770
+ return { x: 0, y: 0, w: root.clientWidth, h: root.clientHeight };
771
+ }
772
+ /** The system ring's weight, `--fgd-focus-width` (2 px): a frame's weight where neither end has one. */
773
+ defaultWeight(context) {
774
+ return this.resolveLength(getComputedStyle(context).getPropertyValue('--fgd-focus-width'), context) || 2;
775
+ }
776
+ readTargetFrame(target) {
777
+ const style = getComputedStyle(target);
778
+ const rect = target.getBoundingClientRect();
779
+ // A side counts as an edge only if it paints: a border in the popup's
780
+ // own face (a band's seam) is invisible [D12], so it is no edge.
781
+ const face = style.backgroundColor;
782
+ const shown = (side) => {
783
+ const color = style[`border${side}Color`];
784
+ if (style[`border${side}Style`] === 'none' || alphaOf(color) === 0 || color === face)
785
+ return 0;
786
+ return px(style[`border${side}Width`]);
787
+ };
788
+ const sides = [shown('Top'), shown('Right'), shown('Bottom'), shown('Left')];
789
+ const widest = sides.indexOf(Math.max(...sides));
790
+ const colors = [style.borderTopColor, style.borderRightColor, style.borderBottomColor, style.borderLeftColor];
791
+ const edged = sides[widest] > 0;
792
+ return {
793
+ r: usedRadii(style, rect.width, rect.height),
794
+ color: edged ? colors[widest] : null,
795
+ width: edged ? sides[widest] : null,
796
+ sides,
797
+ };
798
+ }
799
+ /** The frame's box now, mid-animation, in viewport coordinates. */
800
+ currentBox() {
801
+ const frame = this.frame;
802
+ const container = this.container;
803
+ if (!frame || !container || !frame.hasAttribute(RUNNING_ATTRIBUTE))
804
+ return null;
805
+ const origin = container.getBoundingClientRect();
806
+ const style = getComputedStyle(frame);
807
+ let tx;
808
+ let ty;
809
+ if (frameOffsetMode() === 'rounded') {
810
+ tx = style.getPropertyValue(OFFSET_X);
811
+ ty = style.getPropertyValue(OFFSET_Y);
812
+ }
813
+ else {
814
+ // `translate` serializes as "x y", or "x" when y is 0.
815
+ ;
816
+ [tx = '0', ty = '0'] = style.translate === 'none' ? [] : style.translate.split(' ');
817
+ }
818
+ const w = px(style.width);
819
+ const h = px(style.height);
820
+ return {
821
+ x: origin.left + px(tx),
822
+ y: origin.top + px(ty),
823
+ w,
824
+ h,
825
+ r: usedRadii(style, w, h),
826
+ color: style.borderTopColor,
827
+ k: px(style.borderTopWidth),
828
+ };
829
+ }
830
+ /** How far the running animation has come (eased, 0–1); 1 when none runs. */
831
+ progress() {
832
+ const progress = this.frameAnimation?.effect?.getComputedTiming().progress;
833
+ return typeof progress === 'number' ? progress : 1;
834
+ }
835
+ /**
836
+ * Animates the frame and the popup's clip from one box to the other
837
+ * (viewport coordinates, converted to the container's) on one timing.
838
+ * The popup's border takes its face (its attribute) while the frame draws
839
+ * the edge.
840
+ */
841
+ run(from, to, toward, duration, fromRing) {
842
+ const frame = this.frame;
843
+ const target = this.target;
844
+ const container = this.container;
845
+ if (!frame || !target || !container) {
846
+ this.stop();
847
+ return;
848
+ }
849
+ this.cancelAnimations();
850
+ const origin = container.getBoundingClientRect();
851
+ const local = (box) => ({
852
+ ...box,
853
+ x: box.x - origin.left,
854
+ y: box.y - origin.top,
855
+ });
856
+ // Each end's weight; an end with none of its own (a fill, a sheet with no edge) takes the other's.
857
+ const fallback = this.targetFrame?.width ?? this.defaultWeight(target);
858
+ const fromK = from.k ?? to.k ?? fallback;
859
+ const toK = to.k ?? from.k ?? fallback;
860
+ const flight = {
861
+ from: local(from),
862
+ to: local(to),
863
+ toward,
864
+ target: local(toRect(target.getBoundingClientRect())),
865
+ fromK,
866
+ toK,
867
+ fromRing,
868
+ sourceAt: fromRing !== undefined && this.source ? local(toRect(this.source.getBoundingClientRect())) : undefined,
869
+ };
870
+ this.flight = flight;
871
+ const timing = {
872
+ duration: Math.max(0, Math.round(duration)),
873
+ easing: this.timing.ease,
874
+ fill: 'both',
875
+ };
876
+ target.setAttribute(OUTLINE_MORPH_ATTRIBUTE, '');
877
+ frame.setAttribute(RUNNING_ATTRIBUTE, '');
878
+ const frameAnimation = frame.animate(frameKeyframes(flight), timing);
879
+ const targetAnimation = target.animate(clipKeyframes(flight), timing);
880
+ // One start time for both: a clip the compositor runs can start a frame
881
+ // after the frame's box and trail it by that frame. Both paint their
882
+ // first keyframe first (the ring, the popup's frame, or where a reversed
883
+ // morph turns, which is what is already on screen), then the clip takes
884
+ // the box's start time.
885
+ frameAnimation.ready.then(() => {
886
+ if (this.targetAnimation === targetAnimation && frameAnimation.startTime != null) {
887
+ targetAnimation.startTime = frameAnimation.startTime;
888
+ }
889
+ }, () => { });
890
+ this.frameAnimation = frameAnimation;
891
+ this.targetAnimation = targetAnimation;
892
+ this.startTail(flight, timing, frameAnimation);
893
+ this.track();
894
+ frameAnimation.finished.then(() => {
895
+ if (this.frameAnimation !== frameAnimation)
896
+ return;
897
+ this.cancelTrack();
898
+ if (this.phase === 'opening')
899
+ this.handOff();
900
+ else if (this.phase === 'closing')
901
+ this.landClose();
902
+ }, () => { });
903
+ }
904
+ /**
905
+ * Re-measures the end the morph travels to every frame while it runs; if
906
+ * it moved (a scroll, a resize, the popup settling its size) or a close's
907
+ * ring changed (focus came back, or left), both animations take new
908
+ * keyframes at their current time.
909
+ */
910
+ track() {
911
+ this.cancelTrack();
912
+ const step = () => {
913
+ this.trackFrame = 0;
914
+ const flight = this.flight;
915
+ const source = this.source;
916
+ const target = this.target;
917
+ const container = this.container;
918
+ if (!flight || !source || !target || !container || !this.frameAnimation)
919
+ return;
920
+ const origin = container.getBoundingClientRect();
921
+ const targetRect = toRect(target.getBoundingClientRect());
922
+ const localTarget = {
923
+ ...targetRect,
924
+ x: targetRect.x - origin.left,
925
+ y: targetRect.y - origin.top,
926
+ };
927
+ let changed = drifted(localTarget, flight.target);
928
+ if (flight.toward.kind === 'target') {
929
+ if (changed && this.targetFrame) {
930
+ // The popup moved or resized (a sheet's bleed follows its box).
931
+ const end = landing(targetRect, this.targetFrame, flight.toK, flight.to.color, this.viewport());
932
+ if (end)
933
+ flight.to = { ...flight.to, ...end, x: end.x - origin.left, y: end.y - origin.top };
934
+ }
935
+ // Early re-measure: a trigger that moved against the container in
936
+ // the first frames (the page shifting under a scroll lock) moves the
937
+ // start with it, before much of the path is painted.
938
+ if (flight.sourceAt && this.progress() < EARLY_PROGRESS) {
939
+ const now = toRect(source.getBoundingClientRect());
940
+ const at = { ...now, x: now.x - origin.left, y: now.y - origin.top };
941
+ if (drifted(at, flight.sourceAt)) {
942
+ const was = flight.sourceAt;
943
+ flight.from = {
944
+ ...flight.from,
945
+ x: flight.from.x + at.x - was.x,
946
+ y: flight.from.y + at.y - was.y,
947
+ w: flight.from.w + at.w - was.w,
948
+ h: flight.from.h + at.h - was.h,
949
+ };
950
+ flight.sourceAt = at;
951
+ changed = true;
952
+ }
953
+ }
954
+ // A field pressed open takes focus just after the open began (the
955
+ // press's default action): in the first frames, move the start
956
+ // onto the ring it now draws, before much of the path is painted.
957
+ if (flight.fromRing === 'none' && this.progress() < EARLY_PROGRESS && ringDrawn(source, this.ring)) {
958
+ const start = sourceBox(source, 'drawn', this.resolveLength);
959
+ flight.from = { ...start, x: start.x - origin.left, y: start.y - origin.top };
960
+ flight.fromK = start.k ?? flight.fromK;
961
+ flight.fromRing = 'drawn';
962
+ changed = true;
963
+ }
964
+ }
965
+ else {
966
+ const ring = this.liveRing(flight.toward.ring, source);
967
+ const end = sourceBox(source, ring, this.resolveLength);
968
+ const localEnd = { ...end, x: end.x - origin.left, y: end.y - origin.top };
969
+ if (ring !== flight.toward.ring) {
970
+ // Focus came back with its ring, or left: land on what the trigger shows now.
971
+ flight.toward = { kind: 'source', ring };
972
+ flight.to = localEnd;
973
+ flight.toK = localEnd.k ?? flight.toK;
974
+ changed = true;
975
+ }
976
+ else if (drifted(localEnd, flight.to)) {
977
+ flight.to = { ...flight.to, x: localEnd.x, y: localEnd.y, w: localEnd.w, h: localEnd.h };
978
+ changed = true;
979
+ }
980
+ }
981
+ // The tail moves with its popup (a flip, a shift): re-read it too.
982
+ const tailFlight = this.tailAnimation ? this.readTail(origin) : null;
983
+ const tailMoved = tailFlight != null &&
984
+ this.tailFlight != null &&
985
+ (Math.abs(tailFlight.cx - this.tailFlight.cx) > DRIFT_PX ||
986
+ Math.abs(tailFlight.cy - this.tailFlight.cy) > DRIFT_PX ||
987
+ tailFlight.side !== this.tailFlight.side);
988
+ if (changed) {
989
+ flight.target = localTarget;
990
+ this.frameAnimation.effect?.setKeyframes(frameKeyframes(flight));
991
+ this.targetAnimation?.effect?.setKeyframes(clipKeyframes(flight));
992
+ }
993
+ if (tailFlight && (changed || tailMoved)) {
994
+ this.placeTail(tailFlight);
995
+ this.tailAnimation?.effect?.setKeyframes(tailKeyframes(tailFlight, flight));
996
+ }
997
+ this.trackFrame = requestAnimationFrame(step);
998
+ };
999
+ this.trackFrame = requestAnimationFrame(step);
1000
+ }
1001
+ /**
1002
+ * The open lands: in one task the popup's clip and attribute go (its own
1003
+ * border shows) and the frame hides, so nothing changes on screen.
1004
+ */
1005
+ handOff() {
1006
+ this.cancelAnimations();
1007
+ this.target?.removeAttribute(OUTLINE_MORPH_ATTRIBUTE);
1008
+ this.frame?.removeAttribute(RUNNING_ATTRIBUTE);
1009
+ // The popup's own tail shows again on the pixels the cap held.
1010
+ this.tail?.removeAttribute(RUNNING_ATTRIBUTE);
1011
+ this.phase = 'open';
1012
+ }
1013
+ /**
1014
+ * The popup's own tail as the cap copies it, in the container's
1015
+ * coordinates, or null (no tail, or not laid out). Its box is exact for
1016
+ * the quarter turns a tail takes. The edge it sits on is read from where
1017
+ * it lies against the popup (so logical sides need no direction rule).
1018
+ */
1019
+ readTail(origin) {
1020
+ const source = this.tailSource;
1021
+ const target = this.target;
1022
+ if (!source || !target || !source.isConnected || !target.contains(source))
1023
+ return null;
1024
+ const w = source.offsetWidth;
1025
+ const h = source.offsetHeight;
1026
+ if (w === 0 || h === 0)
1027
+ return null;
1028
+ const rect = source.getBoundingClientRect();
1029
+ const popup = target.getBoundingClientRect();
1030
+ const side = source.getAttribute('data-side') ?? 'bottom';
1031
+ const cx = rect.left + rect.width / 2;
1032
+ const cy = rect.top + rect.height / 2;
1033
+ const normal = cy <= popup.top ? 'up' : cy >= popup.bottom ? 'down' : cx <= popup.left ? 'left' : 'right';
1034
+ return {
1035
+ cx: cx - origin.left,
1036
+ cy: cy - origin.top,
1037
+ w,
1038
+ h,
1039
+ d: h,
1040
+ normal,
1041
+ side,
1042
+ };
1043
+ }
1044
+ /**
1045
+ * Places the cap on the tail (custom properties only), in whole pixels
1046
+ * from the container's corner: the tail's box is laid out, so it paints
1047
+ * snapped to the pixel grid, and a fractional translation would paint the
1048
+ * cap's stroke softer than the tail it hands off to.
1049
+ */
1050
+ placeTail(tail) {
1051
+ const cap = this.tail;
1052
+ if (!cap)
1053
+ return;
1054
+ cap.style.setProperty(TAIL_X, `${Math.round(tail.cx - tail.w / 2)}px`);
1055
+ cap.style.setProperty(TAIL_Y, `${Math.round(tail.cy - tail.h / 2)}px`);
1056
+ cap.setAttribute('data-side', tail.side);
1057
+ this.tailFlight = tail;
1058
+ }
1059
+ /**
1060
+ * The tail cap: a copy of the popup's tail above the frame, shown only on
1061
+ * the trigger side of the frame's trigger-facing inner edge, so it grows
1062
+ * out of that edge as the frame arrives, tip first and always attached,
1063
+ * and is whole exactly as the frame lands; a close retracts it into the
1064
+ * edge. Its stroke takes the frame's color on the same timing, its face
1065
+ * the popup's. Nothing fades.
1066
+ */
1067
+ startTail(flight, timing, frameAnimation) {
1068
+ const cap = this.tail;
1069
+ const target = this.target;
1070
+ const container = this.container;
1071
+ if (!cap || !target || !container)
1072
+ return;
1073
+ const tail = this.readTail(container.getBoundingClientRect());
1074
+ if (!tail) {
1075
+ cap.removeAttribute(RUNNING_ATTRIBUTE);
1076
+ return;
1077
+ }
1078
+ this.placeTail(tail);
1079
+ cap.style.setProperty(TAIL_FILL, getComputedStyle(target).backgroundColor);
1080
+ cap.setAttribute(RUNNING_ATTRIBUTE, '');
1081
+ const tailAnimation = cap.animate(tailKeyframes(tail, flight), timing);
1082
+ frameAnimation.ready.then(() => {
1083
+ if (this.tailAnimation === tailAnimation && frameAnimation.startTime != null) {
1084
+ tailAnimation.startTime = frameAnimation.startTime;
1085
+ }
1086
+ }, () => { });
1087
+ this.tailAnimation = tailAnimation;
1088
+ }
1089
+ /**
1090
+ * The close lands on the trigger. The frame and the popup's clip hold
1091
+ * their end state until the popup's container hides, which takes the
1092
+ * frame with it in the frame focus returns (so the ring shows at once);
1093
+ * then everything clears. A container that never hides clears after
1094
+ * SETTLE_MS.
1095
+ */
1096
+ landClose() {
1097
+ this.phase = 'closed';
1098
+ const container = this.container;
1099
+ const settle = () => {
1100
+ if (this.phase === 'closed')
1101
+ this.stop();
1102
+ };
1103
+ if (!container || container.hidden || !container.isConnected) {
1104
+ settle();
1105
+ return;
1106
+ }
1107
+ this.cancelSettle();
1108
+ this.settleObserver = new MutationObserver(() => {
1109
+ if (container.hidden || !container.isConnected)
1110
+ settle();
1111
+ });
1112
+ this.settleObserver.observe(container, { attributes: true, attributeFilter: ['hidden', 'style'] });
1113
+ this.settleTimer = setTimeout(settle, SETTLE_MS);
1114
+ }
1115
+ /** While an open waits for the popup's placement: try now, then watch its container and the popup, and poll a few frames. */
1116
+ watchPlacement() {
1117
+ if (this.phase !== 'pending' || this.tryStart())
1118
+ return;
1119
+ const container = this.container;
1120
+ const target = this.target;
1121
+ this.placementObserver?.disconnect();
1122
+ this.placementObserver = null;
1123
+ if (container && target) {
1124
+ this.placementObserver = new MutationObserver(() => {
1125
+ this.tryStart();
1126
+ });
1127
+ this.placementObserver.observe(container, { attributes: true, attributeFilter: ['style', 'hidden'] });
1128
+ this.placementObserver.observe(target, { attributes: true });
1129
+ }
1130
+ if (this.placementFrame)
1131
+ return;
1132
+ const poll = () => {
1133
+ this.placementFrame = 0;
1134
+ if (this.phase !== 'pending' || this.tryStart())
1135
+ return;
1136
+ this.placementTries += 1;
1137
+ if (this.placementTries >= PLACEMENT_FRAMES) {
1138
+ // Never placed in time: the popup opens its own way.
1139
+ this.stop();
1140
+ return;
1141
+ }
1142
+ this.placementFrame = requestAnimationFrame(poll);
1143
+ };
1144
+ this.placementFrame = requestAnimationFrame(poll);
1145
+ }
1146
+ cancelPlacement() {
1147
+ this.placementObserver?.disconnect();
1148
+ this.placementObserver = null;
1149
+ if (this.placementFrame)
1150
+ cancelAnimationFrame(this.placementFrame);
1151
+ this.placementFrame = 0;
1152
+ }
1153
+ cancelTrack() {
1154
+ if (this.trackFrame)
1155
+ cancelAnimationFrame(this.trackFrame);
1156
+ this.trackFrame = 0;
1157
+ }
1158
+ cancelSettle() {
1159
+ this.settleObserver?.disconnect();
1160
+ this.settleObserver = null;
1161
+ if (this.settleTimer !== undefined)
1162
+ clearTimeout(this.settleTimer);
1163
+ this.settleTimer = undefined;
1164
+ }
1165
+ cancelAnimations() {
1166
+ this.cancelTrack();
1167
+ this.frameAnimation?.cancel();
1168
+ this.targetAnimation?.cancel();
1169
+ this.tailAnimation?.cancel();
1170
+ this.frameAnimation = null;
1171
+ this.targetAnimation = null;
1172
+ this.tailAnimation = null;
1173
+ this.tailFlight = null;
1174
+ this.flight = null;
1175
+ }
1176
+ }
1177
+ /** Radii as a `border-radius` (or `round`) value, drawn in by `inset`. */
1178
+ function radiiValue(r, inset = 0) {
1179
+ return r.map((radius) => `${Math.max(0, radius - inset)}px`).join(' ');
1180
+ }
1181
+ /**
1182
+ * The frame's two keyframes: box, radii, color, one edge weight. The box
1183
+ * moves by a translation (never a scale, so the edge keeps its weight),
1184
+ * through the rounded offset properties where supported, and resizes by
1185
+ * width and height from a fixed start corner, so the frame's travel is
1186
+ * never reported as a layout shift.
1187
+ */
1188
+ function frameKeyframes(flight) {
1189
+ const rounded = frameOffsetMode() === 'rounded';
1190
+ const weight = (k) => (rounded ? { [EDGE_WEIGHT]: `${k}px` } : { borderWidth: `${k}px` });
1191
+ const place = (box, k) => {
1192
+ // Each end at the edges the browser paints it with: snapped to whole
1193
+ // pixels, the size from the snapped far edges (so a popup 297.5 px tall
1194
+ // ends on its own painted frame, not a pixel off).
1195
+ const w = Math.round(box.x + box.w) - Math.round(box.x);
1196
+ const h = Math.round(box.y + box.h) - Math.round(box.y);
1197
+ if (!rounded) {
1198
+ return {
1199
+ translate: `${box.x}px ${box.y}px`,
1200
+ width: `${w}px`,
1201
+ height: `${h}px`,
1202
+ borderRadius: radiiValue(box.r),
1203
+ borderColor: box.color,
1204
+ ...weight(k),
1205
+ };
1206
+ }
1207
+ return {
1208
+ [OFFSET_X]: `${box.x}px`,
1209
+ [OFFSET_Y]: `${box.y}px`,
1210
+ [SIZE_W]: `${w}px`,
1211
+ [SIZE_H]: `${h}px`,
1212
+ [RADII[0]]: `${Math.max(0, box.r[0])}px`,
1213
+ [RADII[1]]: `${Math.max(0, box.r[1])}px`,
1214
+ [RADII[2]]: `${Math.max(0, box.r[2])}px`,
1215
+ [RADII[3]]: `${Math.max(0, box.r[3])}px`,
1216
+ [EDGE_COLOR]: box.color,
1217
+ ...weight(k),
1218
+ };
1219
+ };
1220
+ const from = place(flight.from, flight.fromK);
1221
+ const to = place(flight.to, flight.toK);
1222
+ if (flight.fromK === flight.toK)
1223
+ return [from, to];
1224
+ // The weight steps once, never tweens (a fractional edge paints soft):
1225
+ // two keyframes for the weight alone, at the same offset, on the eased
1226
+ // progress the effect's easing produces.
1227
+ return [
1228
+ { ...from, offset: 0 },
1229
+ { ...weight(flight.fromK), offset: WEIGHT_STEP },
1230
+ { ...weight(flight.toK), offset: WEIGHT_STEP },
1231
+ { ...to, offset: 1 },
1232
+ ];
1233
+ }
1234
+ /**
1235
+ * The box the frame lands on (or leaves from) on the popup: its border box
1236
+ * and radii, at edge weight `k` in `color` (the popup's own edge, or, for
1237
+ * a popup with no edge at all, the other end's). A side with no edge of its
1238
+ * own (a sheet's) that lies on the viewport's edge is met one weight past
1239
+ * it, so the frame's edge there is off screen at the hand-off; such a side
1240
+ * anywhere else leaves no frame to land on (null: the popup opens and
1241
+ * closes at once). `viewport` is the box fixed elements are laid out in.
1242
+ */
1243
+ function landing(rect, frame, k, color, viewport) {
1244
+ const box = { ...rect, r: frame.r, color, k };
1245
+ if (frame.sides.every((side) => side >= k - 0.01))
1246
+ return box;
1247
+ const flush = [
1248
+ rect.y <= viewport.y + 0.5,
1249
+ rect.x + rect.w >= viewport.x + viewport.w - 0.5,
1250
+ rect.y + rect.h >= viewport.y + viewport.h - 0.5,
1251
+ rect.x <= viewport.x + 0.5,
1252
+ ];
1253
+ const out = [0, 0, 0, 0];
1254
+ for (let side = 0; side < 4; side += 1) {
1255
+ if (frame.sides[side] >= k - 0.01)
1256
+ continue;
1257
+ if (!flush[side])
1258
+ return null;
1259
+ out[side] = k;
1260
+ }
1261
+ return {
1262
+ ...box,
1263
+ x: rect.x - out[3],
1264
+ y: rect.y - out[0],
1265
+ w: rect.w + out[1] + out[3],
1266
+ h: rect.h + out[0] + out[2],
1267
+ };
1268
+ }
1269
+ /**
1270
+ * The popup's two clip keyframes: the frame's box relative to the popup,
1271
+ * drawn in by CLIP_INSET_PX (still under the frame's edge), so the frame's
1272
+ * whole-pixel rounding never lets the popup show past its outer edge.
1273
+ */
1274
+ function clipKeyframes(flight) {
1275
+ const t = flight.target;
1276
+ const d = CLIP_INSET_PX;
1277
+ const clip = (box) => ({
1278
+ clipPath: `inset(${box.y - t.y + d}px ${t.x + t.w - (box.x + box.w) + d}px ${t.y + t.h - (box.y + box.h) + d}px ${box.x - t.x + d}px round ${radiiValue(box.r, d)})`,
1279
+ });
1280
+ return [clip(flight.from), clip(flight.to)];
1281
+ }
1282
+ /**
1283
+ * How much of the tail shows (px, from its tip): the distance from its
1284
+ * tip-side edge to the frame's trigger-facing inner edge, for the frame box
1285
+ * `box` at edge weight `weight`. At or under 0 nothing shows; at `d` (the
1286
+ * frame on the popup's edge) all of it.
1287
+ */
1288
+ function tailDepth(tail, box, weight) {
1289
+ switch (tail.normal) {
1290
+ case 'up':
1291
+ return box.y + weight - (tail.cy - tail.d / 2);
1292
+ case 'down':
1293
+ return tail.cy + tail.d / 2 - (box.y + box.h - weight);
1294
+ case 'left':
1295
+ return box.x + weight - (tail.cx - tail.d / 2);
1296
+ case 'right':
1297
+ return tail.cx + tail.d / 2 - (box.x + box.w - weight);
1298
+ }
1299
+ }
1300
+ /**
1301
+ * The cap's clip, in its own unrotated box (the tip at the local top, the
1302
+ * base at the local bottom whatever the side, since the rotation applies
1303
+ * after the clip): the moving local bottom at the visible depth; the three
1304
+ * fixed sides one stroke width outside the box, so the tip's miter is never
1305
+ * cut.
1306
+ */
1307
+ function tailClip(tail, depth, stroke) {
1308
+ // Unclamped above: an inset past the box is an empty shape, and keeping
1309
+ // the true value keeps the clip linear with the edge, so it opens exactly
1310
+ // when the edge reaches the tip.
1311
+ const bottom = Math.max(-stroke, tail.d - depth);
1312
+ return `inset(${-stroke}px ${-stroke}px ${bottom}px ${-stroke}px)`;
1313
+ }
1314
+ /**
1315
+ * The cap's two keyframes, on the frame's timing: its clip from the depth
1316
+ * at the frame's start box to the depth at its end box (both linear in the
1317
+ * eased progress, as the frame's box is, so the clip tracks the edge), and
1318
+ * its stroke color (`currentColor`) from the frame's start color to its end
1319
+ * color.
1320
+ */
1321
+ function tailKeyframes(tail, flight) {
1322
+ const stroke = Math.max(flight.fromK, flight.toK);
1323
+ return [
1324
+ {
1325
+ clipPath: tailClip(tail, tailDepth(tail, flight.from, flight.fromK), stroke),
1326
+ color: flight.from.color,
1327
+ },
1328
+ { clipPath: tailClip(tail, tailDepth(tail, flight.to, flight.toK), stroke), color: flight.to.color },
1329
+ ];
1330
+ }
1331
+ //# sourceMappingURL=morphEngine.js.map