@pithyjs/ui-kit 0.1.0-beta.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 (417) hide show
  1. package/LICENSE +21 -0
  2. package/dist/animation/animate-in.d.ts +35 -0
  3. package/dist/animation/animate-in.d.ts.map +1 -0
  4. package/dist/animation/animate-in.js +71 -0
  5. package/dist/animation/animate-in.js.map +1 -0
  6. package/dist/atoms/actions/_icon-control.scss +53 -0
  7. package/dist/atoms/actions/button/Button.d.ts +93 -0
  8. package/dist/atoms/actions/button/Button.d.ts.map +1 -0
  9. package/dist/atoms/actions/button/Button.js +332 -0
  10. package/dist/atoms/actions/button/Button.js.map +1 -0
  11. package/dist/atoms/actions/button/button.scss +342 -0
  12. package/dist/atoms/actions/icon-button/IconButton.d.ts +47 -0
  13. package/dist/atoms/actions/icon-button/IconButton.d.ts.map +1 -0
  14. package/dist/atoms/actions/icon-button/IconButton.js +81 -0
  15. package/dist/atoms/actions/icon-button/IconButton.js.map +1 -0
  16. package/dist/atoms/actions/icon-button/icon-button.scss +20 -0
  17. package/dist/atoms/actions/icon-group/IconGroup.d.ts +60 -0
  18. package/dist/atoms/actions/icon-group/IconGroup.d.ts.map +1 -0
  19. package/dist/atoms/actions/icon-group/IconGroup.js +191 -0
  20. package/dist/atoms/actions/icon-group/IconGroup.js.map +1 -0
  21. package/dist/atoms/actions/icon-group/icon-group.scss +36 -0
  22. package/dist/atoms/actions/segmented-control/SegmentedControl.d.ts +49 -0
  23. package/dist/atoms/actions/segmented-control/SegmentedControl.d.ts.map +1 -0
  24. package/dist/atoms/actions/segmented-control/SegmentedControl.js +107 -0
  25. package/dist/atoms/actions/segmented-control/SegmentedControl.js.map +1 -0
  26. package/dist/atoms/actions/segmented-control/segmented-control.scss +90 -0
  27. package/dist/atoms/content/_mixins.scss +79 -0
  28. package/dist/atoms/content/action-tile/ActionTile.d.ts +27 -0
  29. package/dist/atoms/content/action-tile/ActionTile.d.ts.map +1 -0
  30. package/dist/atoms/content/action-tile/ActionTile.js +98 -0
  31. package/dist/atoms/content/action-tile/ActionTile.js.map +1 -0
  32. package/dist/atoms/content/action-tile/action-tile.scss +150 -0
  33. package/dist/atoms/content/badge/Badge.d.ts +57 -0
  34. package/dist/atoms/content/badge/Badge.d.ts.map +1 -0
  35. package/dist/atoms/content/badge/Badge.js +177 -0
  36. package/dist/atoms/content/badge/Badge.js.map +1 -0
  37. package/dist/atoms/content/badge/badge.scss +198 -0
  38. package/dist/atoms/content/heading/Heading.d.ts +53 -0
  39. package/dist/atoms/content/heading/Heading.d.ts.map +1 -0
  40. package/dist/atoms/content/heading/Heading.js +142 -0
  41. package/dist/atoms/content/heading/Heading.js.map +1 -0
  42. package/dist/atoms/content/heading/heading.scss +43 -0
  43. package/dist/atoms/content/icon/Icon.d.ts +68 -0
  44. package/dist/atoms/content/icon/Icon.d.ts.map +1 -0
  45. package/dist/atoms/content/icon/Icon.js +137 -0
  46. package/dist/atoms/content/icon/Icon.js.map +1 -0
  47. package/dist/atoms/content/icon/icon.scss +66 -0
  48. package/dist/atoms/content/icon/resolveIconSvg.d.ts +21 -0
  49. package/dist/atoms/content/icon/resolveIconSvg.d.ts.map +1 -0
  50. package/dist/atoms/content/icon/resolveIconSvg.js +73 -0
  51. package/dist/atoms/content/icon/resolveIconSvg.js.map +1 -0
  52. package/dist/atoms/content/image/Image.d.ts +81 -0
  53. package/dist/atoms/content/image/Image.d.ts.map +1 -0
  54. package/dist/atoms/content/image/Image.js +293 -0
  55. package/dist/atoms/content/image/Image.js.map +1 -0
  56. package/dist/atoms/content/image/image.scss +77 -0
  57. package/dist/atoms/content/text/Text.d.ts +58 -0
  58. package/dist/atoms/content/text/Text.d.ts.map +1 -0
  59. package/dist/atoms/content/text/Text.js +158 -0
  60. package/dist/atoms/content/text/Text.js.map +1 -0
  61. package/dist/atoms/content/text/text.scss +98 -0
  62. package/dist/atoms/content/tile/Tile.d.ts +66 -0
  63. package/dist/atoms/content/tile/Tile.d.ts.map +1 -0
  64. package/dist/atoms/content/tile/Tile.js +125 -0
  65. package/dist/atoms/content/tile/Tile.js.map +1 -0
  66. package/dist/atoms/content/tile/tile.scss +122 -0
  67. package/dist/atoms/feedback/tooltip/Tooltip.d.ts +44 -0
  68. package/dist/atoms/feedback/tooltip/Tooltip.d.ts.map +1 -0
  69. package/dist/atoms/feedback/tooltip/Tooltip.js +121 -0
  70. package/dist/atoms/feedback/tooltip/Tooltip.js.map +1 -0
  71. package/dist/atoms/feedback/tooltip/tooltip.scss +96 -0
  72. package/dist/atoms/forms/checkbox/Checkbox.d.ts +49 -0
  73. package/dist/atoms/forms/checkbox/Checkbox.d.ts.map +1 -0
  74. package/dist/atoms/forms/checkbox/Checkbox.js +269 -0
  75. package/dist/atoms/forms/checkbox/Checkbox.js.map +1 -0
  76. package/dist/atoms/forms/checkbox/CheckboxGroup.d.ts +50 -0
  77. package/dist/atoms/forms/checkbox/CheckboxGroup.d.ts.map +1 -0
  78. package/dist/atoms/forms/checkbox/CheckboxGroup.js +132 -0
  79. package/dist/atoms/forms/checkbox/CheckboxGroup.js.map +1 -0
  80. package/dist/atoms/forms/checkbox/checkbox.scss +506 -0
  81. package/dist/atoms/forms/file-picker/FilePicker.d.ts +23 -0
  82. package/dist/atoms/forms/file-picker/FilePicker.d.ts.map +1 -0
  83. package/dist/atoms/forms/file-picker/FilePicker.js +103 -0
  84. package/dist/atoms/forms/file-picker/FilePicker.js.map +1 -0
  85. package/dist/atoms/forms/file-picker/file-picker.scss +84 -0
  86. package/dist/atoms/forms/form-field/FormField.d.ts +39 -0
  87. package/dist/atoms/forms/form-field/FormField.d.ts.map +1 -0
  88. package/dist/atoms/forms/form-field/FormField.js +139 -0
  89. package/dist/atoms/forms/form-field/FormField.js.map +1 -0
  90. package/dist/atoms/forms/form-field/form-field.scss +46 -0
  91. package/dist/atoms/forms/input/Input.d.ts +81 -0
  92. package/dist/atoms/forms/input/Input.d.ts.map +1 -0
  93. package/dist/atoms/forms/input/Input.js +417 -0
  94. package/dist/atoms/forms/input/Input.js.map +1 -0
  95. package/dist/atoms/forms/input/input.scss +547 -0
  96. package/dist/atoms/forms/radio/Radio.d.ts +54 -0
  97. package/dist/atoms/forms/radio/Radio.d.ts.map +1 -0
  98. package/dist/atoms/forms/radio/Radio.js +278 -0
  99. package/dist/atoms/forms/radio/Radio.js.map +1 -0
  100. package/dist/atoms/forms/radio/RadioGroup.d.ts +54 -0
  101. package/dist/atoms/forms/radio/RadioGroup.d.ts.map +1 -0
  102. package/dist/atoms/forms/radio/RadioGroup.js +137 -0
  103. package/dist/atoms/forms/radio/RadioGroup.js.map +1 -0
  104. package/dist/atoms/forms/radio/radio.scss +459 -0
  105. package/dist/atoms/forms/select/Select.d.ts +57 -0
  106. package/dist/atoms/forms/select/Select.d.ts.map +1 -0
  107. package/dist/atoms/forms/select/Select.js +457 -0
  108. package/dist/atoms/forms/select/Select.js.map +1 -0
  109. package/dist/atoms/forms/select/select-dropdown.d.ts +49 -0
  110. package/dist/atoms/forms/select/select-dropdown.d.ts.map +1 -0
  111. package/dist/atoms/forms/select/select-dropdown.js +305 -0
  112. package/dist/atoms/forms/select/select-dropdown.js.map +1 -0
  113. package/dist/atoms/forms/select/select-keyboard.d.ts +14 -0
  114. package/dist/atoms/forms/select/select-keyboard.d.ts.map +1 -0
  115. package/dist/atoms/forms/select/select-keyboard.js +182 -0
  116. package/dist/atoms/forms/select/select-keyboard.js.map +1 -0
  117. package/dist/atoms/forms/select/select.scss +630 -0
  118. package/dist/atoms/forms/textarea/Textarea.d.ts +69 -0
  119. package/dist/atoms/forms/textarea/Textarea.d.ts.map +1 -0
  120. package/dist/atoms/forms/textarea/Textarea.js +394 -0
  121. package/dist/atoms/forms/textarea/Textarea.js.map +1 -0
  122. package/dist/atoms/forms/textarea/textarea.scss +486 -0
  123. package/dist/atoms/forms/toggle/Toggle.d.ts +47 -0
  124. package/dist/atoms/forms/toggle/Toggle.d.ts.map +1 -0
  125. package/dist/atoms/forms/toggle/Toggle.js +243 -0
  126. package/dist/atoms/forms/toggle/Toggle.js.map +1 -0
  127. package/dist/atoms/forms/toggle/toggle.scss +561 -0
  128. package/dist/compiler.d.ts +7 -0
  129. package/dist/compiler.d.ts.map +1 -0
  130. package/dist/compiler.js +18 -0
  131. package/dist/compiler.js.map +1 -0
  132. package/dist/icons/index.d.ts +14 -0
  133. package/dist/icons/index.d.ts.map +1 -0
  134. package/dist/icons/index.js +14 -0
  135. package/dist/icons/index.js.map +1 -0
  136. package/dist/icons/registry.d.ts +65 -0
  137. package/dist/icons/registry.d.ts.map +1 -0
  138. package/dist/icons/registry.js +126 -0
  139. package/dist/icons/registry.js.map +1 -0
  140. package/dist/index.d.ts +55 -0
  141. package/dist/index.d.ts.map +1 -0
  142. package/dist/index.js +64 -0
  143. package/dist/index.js.map +1 -0
  144. package/dist/layout/Grid.d.ts +78 -0
  145. package/dist/layout/Grid.d.ts.map +1 -0
  146. package/dist/layout/Grid.js +272 -0
  147. package/dist/layout/Grid.js.map +1 -0
  148. package/dist/layout/GridItem.d.ts +52 -0
  149. package/dist/layout/GridItem.d.ts.map +1 -0
  150. package/dist/layout/GridItem.js +99 -0
  151. package/dist/layout/GridItem.js.map +1 -0
  152. package/dist/layout/PageSection.d.ts +43 -0
  153. package/dist/layout/PageSection.d.ts.map +1 -0
  154. package/dist/layout/PageSection.js +75 -0
  155. package/dist/layout/PageSection.js.map +1 -0
  156. package/dist/layout/SmartContainer.d.ts +66 -0
  157. package/dist/layout/SmartContainer.d.ts.map +1 -0
  158. package/dist/layout/SmartContainer.js +194 -0
  159. package/dist/layout/SmartContainer.js.map +1 -0
  160. package/dist/layout/Stack.d.ts +65 -0
  161. package/dist/layout/Stack.d.ts.map +1 -0
  162. package/dist/layout/Stack.js +114 -0
  163. package/dist/layout/Stack.js.map +1 -0
  164. package/dist/organisms/content/Card.d.ts +93 -0
  165. package/dist/organisms/content/Card.d.ts.map +1 -0
  166. package/dist/organisms/content/Card.js +353 -0
  167. package/dist/organisms/content/Card.js.map +1 -0
  168. package/dist/organisms/content/_card-horizontal.scss +88 -0
  169. package/dist/organisms/content/_card-link.scss +49 -0
  170. package/dist/organisms/content/_card-overlay.scss +91 -0
  171. package/dist/organisms/content/_card-root.scss +99 -0
  172. package/dist/organisms/content/_card-slots.scss +143 -0
  173. package/dist/organisms/content/_card-states.scss +166 -0
  174. package/dist/organisms/content/_card-tokens.scss +22 -0
  175. package/dist/organisms/content/_card-variants.scss +31 -0
  176. package/dist/organisms/content/card.scss +15 -0
  177. package/dist/organisms/feedback/Modal.d.ts +39 -0
  178. package/dist/organisms/feedback/Modal.d.ts.map +1 -0
  179. package/dist/organisms/feedback/Modal.js +120 -0
  180. package/dist/organisms/feedback/Modal.js.map +1 -0
  181. package/dist/organisms/feedback/Popover.d.ts +30 -0
  182. package/dist/organisms/feedback/Popover.d.ts.map +1 -0
  183. package/dist/organisms/feedback/Popover.js +151 -0
  184. package/dist/organisms/feedback/Popover.js.map +1 -0
  185. package/dist/organisms/feedback/modal.scss +66 -0
  186. package/dist/organisms/feedback/popover.scss +27 -0
  187. package/dist/organisms/forms/DynamicForm.d.ts +118 -0
  188. package/dist/organisms/forms/DynamicForm.d.ts.map +1 -0
  189. package/dist/organisms/forms/DynamicForm.js +546 -0
  190. package/dist/organisms/forms/DynamicForm.js.map +1 -0
  191. package/dist/organisms/forms/Form.d.ts +62 -0
  192. package/dist/organisms/forms/Form.d.ts.map +1 -0
  193. package/dist/organisms/forms/Form.js +131 -0
  194. package/dist/organisms/forms/Form.js.map +1 -0
  195. package/dist/organisms/forms/dynamic-form.scss +91 -0
  196. package/dist/organisms/forms/form-helpers.d.ts +21 -0
  197. package/dist/organisms/forms/form-helpers.d.ts.map +1 -0
  198. package/dist/organisms/forms/form-helpers.js +57 -0
  199. package/dist/organisms/forms/form-helpers.js.map +1 -0
  200. package/dist/organisms/forms/form-state.d.ts +66 -0
  201. package/dist/organisms/forms/form-state.d.ts.map +1 -0
  202. package/dist/organisms/forms/form-state.js +156 -0
  203. package/dist/organisms/forms/form-state.js.map +1 -0
  204. package/dist/organisms/forms/form.scss +23 -0
  205. package/dist/organisms/forms/validation.d.ts +32 -0
  206. package/dist/organisms/forms/validation.d.ts.map +1 -0
  207. package/dist/organisms/forms/validation.js +118 -0
  208. package/dist/organisms/forms/validation.js.map +1 -0
  209. package/dist/organisms/navigation/Navbar.d.ts +66 -0
  210. package/dist/organisms/navigation/Navbar.d.ts.map +1 -0
  211. package/dist/organisms/navigation/Navbar.js +881 -0
  212. package/dist/organisms/navigation/Navbar.js.map +1 -0
  213. package/dist/organisms/navigation/_navbar-announcement.scss +62 -0
  214. package/dist/organisms/navigation/_navbar-brand.scss +69 -0
  215. package/dist/organisms/navigation/_navbar-container.scss +37 -0
  216. package/dist/organisms/navigation/_navbar-desktop.scss +116 -0
  217. package/dist/organisms/navigation/_navbar-drawer.scss +97 -0
  218. package/dist/organisms/navigation/_navbar-links.scss +143 -0
  219. package/dist/organisms/navigation/_navbar-menu.scss +193 -0
  220. package/dist/organisms/navigation/_navbar-root.scss +107 -0
  221. package/dist/organisms/navigation/_navbar-search.scss +83 -0
  222. package/dist/organisms/navigation/_navbar-toggle.scss +91 -0
  223. package/dist/organisms/navigation/_navbar-tokens.scss +15 -0
  224. package/dist/organisms/navigation/navbar.scss +17 -0
  225. package/dist/registry/categories.d.ts +6 -0
  226. package/dist/registry/categories.d.ts.map +1 -0
  227. package/dist/registry/categories.js +13 -0
  228. package/dist/registry/categories.js.map +1 -0
  229. package/dist/registry/components.d.ts +5 -0
  230. package/dist/registry/components.d.ts.map +1 -0
  231. package/dist/registry/components.js +95 -0
  232. package/dist/registry/components.js.map +1 -0
  233. package/dist/registry/index.d.ts +59 -0
  234. package/dist/registry/index.d.ts.map +1 -0
  235. package/dist/registry/index.js +79 -0
  236. package/dist/registry/index.js.map +1 -0
  237. package/dist/registry/levels.d.ts +6 -0
  238. package/dist/registry/levels.d.ts.map +1 -0
  239. package/dist/registry/levels.js +9 -0
  240. package/dist/registry/levels.js.map +1 -0
  241. package/dist/registry/registry-meta-types.d.ts +67 -0
  242. package/dist/registry/registry-meta-types.d.ts.map +1 -0
  243. package/dist/registry/registry-meta-types.js +6 -0
  244. package/dist/registry/registry-meta-types.js.map +1 -0
  245. package/dist/registry/registry-meta.d.ts +5 -0
  246. package/dist/registry/registry-meta.d.ts.map +1 -0
  247. package/dist/registry/registry-meta.js +731 -0
  248. package/dist/registry/registry-meta.js.map +1 -0
  249. package/dist/registry/renderer.d.ts +69 -0
  250. package/dist/registry/renderer.d.ts.map +1 -0
  251. package/dist/registry/renderer.js +311 -0
  252. package/dist/registry/renderer.js.map +1 -0
  253. package/dist/sections/_section-base.scss +204 -0
  254. package/dist/sections/contact/ContactSection.d.ts +55 -0
  255. package/dist/sections/contact/ContactSection.d.ts.map +1 -0
  256. package/dist/sections/contact/ContactSection.js +142 -0
  257. package/dist/sections/contact/ContactSection.js.map +1 -0
  258. package/dist/sections/contact/contact-section.scss +142 -0
  259. package/dist/sections/cta/CTASection.d.ts +52 -0
  260. package/dist/sections/cta/CTASection.d.ts.map +1 -0
  261. package/dist/sections/cta/CTASection.js +130 -0
  262. package/dist/sections/cta/CTASection.js.map +1 -0
  263. package/dist/sections/cta/cta-section.scss +131 -0
  264. package/dist/sections/entrance.d.ts +29 -0
  265. package/dist/sections/entrance.d.ts.map +1 -0
  266. package/dist/sections/entrance.js +69 -0
  267. package/dist/sections/entrance.js.map +1 -0
  268. package/dist/sections/features/FeaturesSection.d.ts +54 -0
  269. package/dist/sections/features/FeaturesSection.d.ts.map +1 -0
  270. package/dist/sections/features/FeaturesSection.js +139 -0
  271. package/dist/sections/features/FeaturesSection.js.map +1 -0
  272. package/dist/sections/features/features-section.scss +140 -0
  273. package/dist/sections/footer/FooterSection.d.ts +53 -0
  274. package/dist/sections/footer/FooterSection.d.ts.map +1 -0
  275. package/dist/sections/footer/FooterSection.js +147 -0
  276. package/dist/sections/footer/FooterSection.js.map +1 -0
  277. package/dist/sections/footer/footer-section.scss +131 -0
  278. package/dist/sections/hero/HeroSection.d.ts +66 -0
  279. package/dist/sections/hero/HeroSection.d.ts.map +1 -0
  280. package/dist/sections/hero/HeroSection.js +156 -0
  281. package/dist/sections/hero/HeroSection.js.map +1 -0
  282. package/dist/sections/hero/hero-section.scss +211 -0
  283. package/dist/shared/_form-animations.scss +283 -0
  284. package/dist/shared/_media-fit.scss +63 -0
  285. package/dist/shared/_mixins.scss +69 -0
  286. package/dist/shared/_tokens.scss +116 -0
  287. package/dist/shared/badge.d.ts +15 -0
  288. package/dist/shared/badge.d.ts.map +1 -0
  289. package/dist/shared/badge.js +15 -0
  290. package/dist/shared/badge.js.map +1 -0
  291. package/dist/shared/button.d.ts +22 -0
  292. package/dist/shared/button.d.ts.map +1 -0
  293. package/dist/shared/button.js +22 -0
  294. package/dist/shared/button.js.map +1 -0
  295. package/dist/shared/card.d.ts +21 -0
  296. package/dist/shared/card.d.ts.map +1 -0
  297. package/dist/shared/card.js +38 -0
  298. package/dist/shared/card.js.map +1 -0
  299. package/dist/shared/checkbox.d.ts +10 -0
  300. package/dist/shared/checkbox.d.ts.map +1 -0
  301. package/dist/shared/checkbox.js +28 -0
  302. package/dist/shared/checkbox.js.map +1 -0
  303. package/dist/shared/clickable.d.ts +34 -0
  304. package/dist/shared/clickable.d.ts.map +1 -0
  305. package/dist/shared/clickable.js +54 -0
  306. package/dist/shared/clickable.js.map +1 -0
  307. package/dist/shared/common.d.ts +31 -0
  308. package/dist/shared/common.d.ts.map +1 -0
  309. package/dist/shared/common.js +92 -0
  310. package/dist/shared/common.js.map +1 -0
  311. package/dist/shared/entrance-config.d.ts +46 -0
  312. package/dist/shared/entrance-config.d.ts.map +1 -0
  313. package/dist/shared/entrance-config.js +54 -0
  314. package/dist/shared/entrance-config.js.map +1 -0
  315. package/dist/shared/focusable.d.ts +8 -0
  316. package/dist/shared/focusable.d.ts.map +1 -0
  317. package/dist/shared/focusable.js +17 -0
  318. package/dist/shared/focusable.js.map +1 -0
  319. package/dist/shared/form-elements.d.ts +57 -0
  320. package/dist/shared/form-elements.d.ts.map +1 -0
  321. package/dist/shared/form-elements.js +95 -0
  322. package/dist/shared/form-elements.js.map +1 -0
  323. package/dist/shared/form-state.d.ts +52 -0
  324. package/dist/shared/form-state.d.ts.map +1 -0
  325. package/dist/shared/form-state.js +151 -0
  326. package/dist/shared/form-state.js.map +1 -0
  327. package/dist/shared/grid.d.ts +21 -0
  328. package/dist/shared/grid.d.ts.map +1 -0
  329. package/dist/shared/grid.js +187 -0
  330. package/dist/shared/grid.js.map +1 -0
  331. package/dist/shared/icon-slot.d.ts +22 -0
  332. package/dist/shared/icon-slot.d.ts.map +1 -0
  333. package/dist/shared/icon-slot.js +38 -0
  334. package/dist/shared/icon-slot.js.map +1 -0
  335. package/dist/shared/icon.d.ts +17 -0
  336. package/dist/shared/icon.d.ts.map +1 -0
  337. package/dist/shared/icon.js +18 -0
  338. package/dist/shared/icon.js.map +1 -0
  339. package/dist/shared/icons.d.ts +19 -0
  340. package/dist/shared/icons.d.ts.map +1 -0
  341. package/dist/shared/icons.js +22 -0
  342. package/dist/shared/icons.js.map +1 -0
  343. package/dist/shared/image.d.ts +35 -0
  344. package/dist/shared/image.d.ts.map +1 -0
  345. package/dist/shared/image.js +46 -0
  346. package/dist/shared/image.js.map +1 -0
  347. package/dist/shared/input.d.ts +12 -0
  348. package/dist/shared/input.d.ts.map +1 -0
  349. package/dist/shared/input.js +29 -0
  350. package/dist/shared/input.js.map +1 -0
  351. package/dist/shared/radio.d.ts +10 -0
  352. package/dist/shared/radio.d.ts.map +1 -0
  353. package/dist/shared/radio.js +29 -0
  354. package/dist/shared/radio.js.map +1 -0
  355. package/dist/shared/reconcile-list.d.ts +37 -0
  356. package/dist/shared/reconcile-list.d.ts.map +1 -0
  357. package/dist/shared/reconcile-list.js +95 -0
  358. package/dist/shared/reconcile-list.js.map +1 -0
  359. package/dist/shared/section.d.ts +23 -0
  360. package/dist/shared/section.d.ts.map +1 -0
  361. package/dist/shared/section.js +65 -0
  362. package/dist/shared/section.js.map +1 -0
  363. package/dist/shared/segmented.d.ts +17 -0
  364. package/dist/shared/segmented.d.ts.map +1 -0
  365. package/dist/shared/segmented.js +53 -0
  366. package/dist/shared/segmented.js.map +1 -0
  367. package/dist/shared/select.d.ts +18 -0
  368. package/dist/shared/select.d.ts.map +1 -0
  369. package/dist/shared/select.js +14 -0
  370. package/dist/shared/select.js.map +1 -0
  371. package/dist/shared/slots.d.ts +11 -0
  372. package/dist/shared/slots.d.ts.map +1 -0
  373. package/dist/shared/slots.js +33 -0
  374. package/dist/shared/slots.js.map +1 -0
  375. package/dist/shared/smart-container.d.ts +19 -0
  376. package/dist/shared/smart-container.d.ts.map +1 -0
  377. package/dist/shared/smart-container.js +29 -0
  378. package/dist/shared/smart-container.js.map +1 -0
  379. package/dist/shared/text.d.ts +29 -0
  380. package/dist/shared/text.d.ts.map +1 -0
  381. package/dist/shared/text.js +72 -0
  382. package/dist/shared/text.js.map +1 -0
  383. package/dist/shared/textarea.d.ts +6 -0
  384. package/dist/shared/textarea.d.ts.map +1 -0
  385. package/dist/shared/textarea.js +18 -0
  386. package/dist/shared/textarea.js.map +1 -0
  387. package/dist/shared/toggle.d.ts +6 -0
  388. package/dist/shared/toggle.d.ts.map +1 -0
  389. package/dist/shared/toggle.js +19 -0
  390. package/dist/shared/toggle.js.map +1 -0
  391. package/dist/shared/types.d.ts +41 -0
  392. package/dist/shared/types.d.ts.map +1 -0
  393. package/dist/shared/types.js +20 -0
  394. package/dist/shared/types.js.map +1 -0
  395. package/dist/structural/ErrorBoundary.d.ts +18 -0
  396. package/dist/structural/ErrorBoundary.d.ts.map +1 -0
  397. package/dist/structural/ErrorBoundary.js +119 -0
  398. package/dist/structural/ErrorBoundary.js.map +1 -0
  399. package/dist/structural/For.d.ts +37 -0
  400. package/dist/structural/For.d.ts.map +1 -0
  401. package/dist/structural/For.js +129 -0
  402. package/dist/structural/For.js.map +1 -0
  403. package/dist/structural/Lazy.d.ts +43 -0
  404. package/dist/structural/Lazy.d.ts.map +1 -0
  405. package/dist/structural/Lazy.js +47 -0
  406. package/dist/structural/Lazy.js.map +1 -0
  407. package/dist/structural/NavLink.d.ts +45 -0
  408. package/dist/structural/NavLink.d.ts.map +1 -0
  409. package/dist/structural/NavLink.js +37 -0
  410. package/dist/structural/NavLink.js.map +1 -0
  411. package/dist/structural/Show.d.ts +35 -0
  412. package/dist/structural/Show.d.ts.map +1 -0
  413. package/dist/structural/Show.js +34 -0
  414. package/dist/structural/Show.js.map +1 -0
  415. package/dist/structural/nav-link.scss +54 -0
  416. package/dist/styles/index.scss +58 -0
  417. package/package.json +64 -0
@@ -0,0 +1,881 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.ui-kit.navbar",
4
+ "title": "<Navbar> Organism",
5
+ "category": "feature",
6
+ "risk": "high"
7
+ }
8
+ */
9
+ import { captureChildren } from '@pithyjs/core';
10
+ import { isSafeHref } from '@pithyjs/shared';
11
+ import { getGlobalRouter, routerLink, cleanupRouterDirectives, PREFETCH_STRATEGIES, } from '@pithyjs/router';
12
+ import { nextUid } from '../../shared/common';
13
+ import { captureSlot, extractSlot } from '../../shared/slots';
14
+ import { validateEnum } from '../../shared/section';
15
+ import { HAMBURGER_ICON, CLOSE_ICON } from '../../shared/icons';
16
+ function isItemActive(current, target, exact) {
17
+ if (!current || !target)
18
+ return false;
19
+ if (exact)
20
+ return current === target;
21
+ if (target === '/')
22
+ return current === '/';
23
+ return current === target || current.startsWith(target + '/');
24
+ }
25
+ // Ref-counted global state so concurrent drawers don't clobber each other's
26
+ // snapshots. Last releaser restores the body overflow; each element's inert
27
+ // count only drops to zero when every drawer that requested it has released.
28
+ let scrollLockRefs = 0;
29
+ let originalBodyOverflow = '';
30
+ const inertRefs = new WeakMap();
31
+ function acquireScrollLock() {
32
+ if (scrollLockRefs === 0) {
33
+ originalBodyOverflow = document.body.style.overflow;
34
+ document.body.style.overflow = 'hidden';
35
+ }
36
+ scrollLockRefs += 1;
37
+ }
38
+ function releaseScrollLock() {
39
+ if (scrollLockRefs === 0)
40
+ return;
41
+ scrollLockRefs -= 1;
42
+ if (scrollLockRefs === 0) {
43
+ document.body.style.overflow = originalBodyOverflow;
44
+ }
45
+ }
46
+ function acquireInert(el) {
47
+ const count = inertRefs.get(el) ?? 0;
48
+ if (count === 0)
49
+ el.setAttribute('inert', '');
50
+ inertRefs.set(el, count + 1);
51
+ }
52
+ function releaseInert(el) {
53
+ const count = inertRefs.get(el) ?? 0;
54
+ if (count <= 1) {
55
+ inertRefs.delete(el);
56
+ el.removeAttribute('inert');
57
+ }
58
+ else {
59
+ inertRefs.set(el, count - 1);
60
+ }
61
+ }
62
+ /**
63
+ * Test-only: reset the module-level ref counters so one test's leaked state
64
+ * (e.g. a nav that opened but never closed) can't skew the next test's
65
+ * acquire logic. Not exported from the public barrel.
66
+ */
67
+ export function __resetNavbarGlobalLocksForTest() {
68
+ scrollLockRefs = 0;
69
+ originalBodyOverflow = '';
70
+ }
71
+ /** @codexApi {"parent":"pithy.ui-kit.navbar","name":"Navbar","stability":"stable","signature":"(props: NavbarProps) => { el: HTMLElement; destroy: () => void }"} */
72
+ export function Navbar(props) {
73
+ const { label, brand, brandImageSrc, brandImageAlt, brandHref = '/', items, sticky = false, transparent = false, density = 'comfortable', skipToContentTarget = 'main-content', id, } = props;
74
+ if (import.meta.env?.DEV) {
75
+ if (typeof label !== 'string' || label.length === 0) {
76
+ console.warn('[<Navbar>] `label` is required — <nav> needs an accessible name. A page may contain multiple <nav> elements.');
77
+ }
78
+ if (brandImageSrc && !brandImageAlt) {
79
+ console.warn('[<Navbar>] `brandImageAlt` is required when `brandImageSrc` is set — screen readers need an accessible name for the logo image.');
80
+ }
81
+ }
82
+ const safeDensity = validateEnum('Navbar', 'density', density, ['comfortable', 'compact'], 'comfortable');
83
+ const menuId = nextUid('ui-navbar-menu');
84
+ const disposers = [];
85
+ // Capture projected children BEFORE calling any inner components, per the
86
+ // section-template pattern. We re-home nodes into the correct slot below
87
+ // based on their `data-slot` attribute.
88
+ const { el: childFragment, destroy: destroyChildren } = captureChildren();
89
+ disposers.push(destroyChildren);
90
+ const nav = document.createElement('nav');
91
+ nav.classList.add('ui-navbar');
92
+ const safeLabel = typeof label === 'string' && label.length > 0 ? label : 'Navigation';
93
+ nav.setAttribute('aria-label', safeLabel);
94
+ nav.style.viewTransitionName = `pithy-${menuId}`;
95
+ nav.dataset.density = safeDensity;
96
+ if (sticky)
97
+ nav.dataset.sticky = 'true';
98
+ if (transparent)
99
+ nav.dataset.transparent = 'true';
100
+ if (id)
101
+ nav.id = id;
102
+ // Must stay the first focusable element so Tab lands on it before anything.
103
+ const skip = document.createElement('a');
104
+ skip.classList.add('ui-navbar-skip');
105
+ skip.href = `#${skipToContentTarget}`;
106
+ skip.textContent = 'Skip to content';
107
+ nav.appendChild(skip);
108
+ // Mounted after the skip link so skip stays the first tab stop.
109
+ const projectedAnnouncement = extractSlot(childFragment, 'announcement');
110
+ if (projectedAnnouncement) {
111
+ const bar = document.createElement('div');
112
+ bar.classList.add('ui-navbar-announcement');
113
+ captureSlot(bar, 'announcement');
114
+ bar.appendChild(projectedAnnouncement);
115
+ nav.appendChild(bar);
116
+ }
117
+ const container = document.createElement('div');
118
+ container.classList.add('ui-navbar-container');
119
+ nav.appendChild(container);
120
+ const brandHasImage = typeof brandImageSrc === 'string' && brandImageSrc.length > 0;
121
+ const brandHasText = typeof brand === 'string' && brand.length > 0;
122
+ const safeBrandHref = typeof brandHref === 'string' && isSafeHref(brandHref) ? brandHref : '/';
123
+ // Shared creation logic for the brand mark. Called once for the main brand
124
+ // link and once for the drawer brand so both sites emit identical markup
125
+ // and attributes (the DOM does contain two <img> elements by design).
126
+ const appendBrandContent = (parent) => {
127
+ if (brandHasImage) {
128
+ const img = document.createElement('img');
129
+ img.classList.add('ui-navbar-brand-logo');
130
+ img.src = brandImageSrc;
131
+ // Alt is required when image is set; fall back to empty string so the
132
+ // image is treated as decorative rather than announced as the URL.
133
+ img.alt = typeof brandImageAlt === 'string' ? brandImageAlt : '';
134
+ img.setAttribute('loading', 'eager');
135
+ img.setAttribute('decoding', 'async');
136
+ parent.appendChild(img);
137
+ }
138
+ if (brandHasText) {
139
+ const wordmark = document.createElement('span');
140
+ wordmark.classList.add('ui-navbar-brand-text');
141
+ wordmark.textContent = brand;
142
+ parent.appendChild(wordmark);
143
+ }
144
+ };
145
+ const projectedBrand = extractSlot(childFragment, 'brand');
146
+ if (brandHasImage || brandHasText || projectedBrand) {
147
+ const brandLink = document.createElement('a');
148
+ brandLink.classList.add('ui-navbar-brand');
149
+ captureSlot(brandLink, 'brand');
150
+ brandLink.href = safeBrandHref;
151
+ if (projectedBrand) {
152
+ brandLink.appendChild(projectedBrand);
153
+ }
154
+ else {
155
+ appendBrandContent(brandLink);
156
+ }
157
+ container.appendChild(brandLink);
158
+ }
159
+ const toggle = document.createElement('button');
160
+ toggle.type = 'button';
161
+ toggle.classList.add('ui-navbar-toggle');
162
+ toggle.setAttribute('aria-label', 'Open menu');
163
+ // Native Invoker Commands: the button opens/closes the menu popover,
164
+ // and the browser syncs aria-expanded/aria-controls for us.
165
+ toggle.setAttribute('commandfor', menuId);
166
+ toggle.setAttribute('command', 'toggle-popover');
167
+ // aria-controls pairs with commandfor but also provides a fallback for
168
+ // AT that don't yet surface Invoker Command relationships.
169
+ toggle.setAttribute('aria-controls', menuId);
170
+ toggle.innerHTML = HAMBURGER_ICON;
171
+ container.appendChild(toggle);
172
+ const menu = document.createElement('div');
173
+ menu.id = menuId;
174
+ menu.classList.add('ui-navbar-menu');
175
+ menu.setAttribute('popover', 'auto');
176
+ container.appendChild(menu);
177
+ // Close affordance mirrored inside the drawer because on mobile the outer
178
+ // hamburger sits below the popover ::backdrop and can't be tapped.
179
+ const drawerHeader = document.createElement('div');
180
+ drawerHeader.classList.add('ui-navbar-drawer-header');
181
+ if (brandHasImage || brandHasText) {
182
+ const drawerBrand = document.createElement('a');
183
+ drawerBrand.classList.add('ui-navbar-drawer-brand');
184
+ drawerBrand.href = safeBrandHref;
185
+ appendBrandContent(drawerBrand);
186
+ drawerHeader.appendChild(drawerBrand);
187
+ }
188
+ const closeInDrawer = document.createElement('button');
189
+ closeInDrawer.type = 'button';
190
+ closeInDrawer.classList.add('ui-navbar-close');
191
+ closeInDrawer.setAttribute('aria-label', 'Close menu');
192
+ closeInDrawer.setAttribute('commandfor', menuId);
193
+ closeInDrawer.setAttribute('command', 'hide-popover');
194
+ closeInDrawer.innerHTML = CLOSE_ICON;
195
+ drawerHeader.appendChild(closeInDrawer);
196
+ menu.appendChild(drawerHeader);
197
+ // popover=auto doesn't auto-close on internal activation, and routerLink
198
+ // calls stopPropagation(), so a bubble-phase listener here never fires on
199
+ // link clicks. Capture-phase runs before the anchor's handler; we defer
200
+ // hidePopover() one microtask so navigation still wins.
201
+ const closeMenuOnClick = (event) => {
202
+ const target = event.target;
203
+ if (!target)
204
+ return;
205
+ if (!target.closest('a, button'))
206
+ return;
207
+ if (target.closest('.ui-navbar-close'))
208
+ return;
209
+ if (!isMenuOpen)
210
+ return;
211
+ queueMicrotask(() => {
212
+ try {
213
+ menu.hidePopover();
214
+ }
215
+ catch {
216
+ /* already closed */
217
+ }
218
+ });
219
+ };
220
+ menu.addEventListener('click', closeMenuOnClick, true);
221
+ disposers.push(() => menu.removeEventListener('click', closeMenuOnClick, true));
222
+ // Long-press on a drawer link in touch mode opens the browser's context
223
+ // menu, which can leave the popover/body-scroll state inconsistent when
224
+ // dismissed. Suppress it for link items only so desktop right-click on
225
+ // chrome (close button, brand) still works normally.
226
+ const suppressLinkContextMenu = (event) => {
227
+ const target = event.target;
228
+ if (target?.closest('.ui-navbar-link'))
229
+ event.preventDefault();
230
+ };
231
+ menu.addEventListener('contextmenu', suppressLinkContextMenu);
232
+ disposers.push(() => menu.removeEventListener('contextmenu', suppressLinkContextMenu));
233
+ const list = document.createElement('ul');
234
+ list.classList.add('ui-navbar-list', 'u-list-reset');
235
+ // Safari/VoiceOver strips list semantics when list-style:none is applied
236
+ // via CSS. Explicit role restores "list, N items" announcement.
237
+ list.setAttribute('role', 'list');
238
+ menu.appendChild(list);
239
+ // Wrap the decorative indicator in an <li> so the <ul> contains only valid
240
+ // children. role="presentation" prevents it from inflating the list count.
241
+ const indicatorLi = document.createElement('li');
242
+ indicatorLi.setAttribute('role', 'presentation');
243
+ indicatorLi.setAttribute('aria-hidden', 'true');
244
+ const indicator = document.createElement('span');
245
+ indicator.classList.add('ui-navbar-indicator');
246
+ indicatorLi.appendChild(indicator);
247
+ list.appendChild(indicatorLi);
248
+ const positionIndicator = () => {
249
+ const active = list.querySelector('a[aria-current="page"], a.router-link-active');
250
+ if (!active || active.offsetParent === null) {
251
+ indicator.style.setProperty('--_indicator-opacity', '0');
252
+ return;
253
+ }
254
+ const listRect = list.getBoundingClientRect();
255
+ const activeRect = active.getBoundingClientRect();
256
+ const isRtl = resolveDirection(nav) === 'rtl';
257
+ // In LTR inset-inline-start is physical left → positive translate moves right.
258
+ // In RTL inset-inline-start is physical right → negative translate moves left.
259
+ const offset = isRtl
260
+ ? activeRect.right - listRect.right
261
+ : activeRect.left - listRect.left;
262
+ indicator.style.setProperty('--_indicator-x', `${offset}px`);
263
+ indicator.style.setProperty('--_indicator-w', `${activeRect.width}px`);
264
+ indicator.style.setProperty('--_indicator-opacity', '1');
265
+ };
266
+ const indicatorObserver = typeof MutationObserver !== 'undefined'
267
+ ? new MutationObserver(() => positionIndicator())
268
+ : null;
269
+ indicatorObserver?.observe(list, {
270
+ subtree: true,
271
+ attributes: true,
272
+ attributeFilter: ['aria-current', 'class'],
273
+ });
274
+ if (indicatorObserver)
275
+ disposers.push(() => indicatorObserver.disconnect());
276
+ if (typeof window !== 'undefined') {
277
+ const onResize = () => positionIndicator();
278
+ window.addEventListener('resize', onResize);
279
+ disposers.push(() => window.removeEventListener('resize', onResize));
280
+ // Initial position after layout settles (fonts, container queries).
281
+ const indicatorRafId = requestAnimationFrame(positionIndicator);
282
+ disposers.push(() => cancelAnimationFrame(indicatorRafId));
283
+ }
284
+ const safeItems = Array.isArray(items) ? items : [];
285
+ // Router may not be mounted yet when Navbar constructs (App template renders
286
+ // Navbar before @routerOutlet runs). Re-resolve right before use — and always
287
+ // tag anchors with `data-router-link` so `setGlobalRouter → bindExistingRouterLinks`
288
+ // hooks them up on late router mount.
289
+ const resolveRouter = () => {
290
+ try {
291
+ return getGlobalRouter();
292
+ }
293
+ catch {
294
+ return null;
295
+ }
296
+ };
297
+ const currentPath = typeof window !== 'undefined' && window.location
298
+ ? window.location.pathname || '/'
299
+ : '/';
300
+ // Populated for the popstate fallback — used only if no router ever mounts.
301
+ const anchorHandles = [];
302
+ for (let i = 0; i < safeItems.length; i++) {
303
+ const item = safeItems[i];
304
+ if (!item ||
305
+ typeof item.label !== 'string' ||
306
+ typeof item.href !== 'string')
307
+ continue;
308
+ if (!isSafeHref(item.href))
309
+ continue;
310
+ const li = document.createElement('li');
311
+ li.classList.add('ui-navbar-item');
312
+ li.style.setProperty('--_i', String(i));
313
+ const anchor = document.createElement('a');
314
+ anchor.classList.add('ui-nav-link', 'ui-navbar-link');
315
+ anchor.href = item.href;
316
+ anchor.textContent = item.label;
317
+ const prefetchStrategy = item.prefetch === undefined
318
+ ? 'none'
319
+ : validateEnum('Navbar', `items[${i}].prefetch`, item.prefetch, PREFETCH_STRATEGIES, 'none');
320
+ anchor.setAttribute('data-router-link', '');
321
+ anchor.setAttribute('data-prefetch', prefetchStrategy);
322
+ if (item.exactActive === true)
323
+ anchor.setAttribute('exact-match', '');
324
+ const syncDataCurrent = () => {
325
+ if (anchor.getAttribute('aria-current') === 'page') {
326
+ anchor.dataset.current = 'page';
327
+ }
328
+ else {
329
+ delete anchor.dataset.current;
330
+ }
331
+ };
332
+ if (typeof MutationObserver !== 'undefined') {
333
+ const mo = new MutationObserver(syncDataCurrent);
334
+ mo.observe(anchor, {
335
+ attributes: true,
336
+ attributeFilter: ['aria-current'],
337
+ });
338
+ disposers.push(() => mo.disconnect());
339
+ }
340
+ disposers.push(() => cleanupRouterDirectives(anchor));
341
+ const router = resolveRouter();
342
+ if (router) {
343
+ anchor.setAttribute('data-router-bound', 'true');
344
+ routerLink(anchor, router, {
345
+ exactMatch: item.exactActive === true,
346
+ prefetch: prefetchStrategy,
347
+ });
348
+ syncDataCurrent();
349
+ }
350
+ else {
351
+ const active = isItemActive(currentPath, item.href, item.exactActive === true);
352
+ if (active) {
353
+ anchor.classList.add('router-link-active');
354
+ anchor.setAttribute('aria-current', 'page');
355
+ }
356
+ syncDataCurrent();
357
+ anchorHandles.push({ anchor, item });
358
+ }
359
+ li.appendChild(anchor);
360
+ list.appendChild(li);
361
+ }
362
+ // Search slot sits between items and actions so a ⌘K trigger reads as a
363
+ // peer of the nav, not a trailing action. Extracted before fall-through.
364
+ const projectedSearch = extractSlot(childFragment, 'search');
365
+ if (projectedSearch) {
366
+ const searchSlot = document.createElement('div');
367
+ searchSlot.classList.add('ui-navbar-search');
368
+ captureSlot(searchSlot, 'search');
369
+ searchSlot.appendChild(projectedSearch);
370
+ menu.appendChild(searchSlot);
371
+ }
372
+ const actionsSlot = document.createElement('div');
373
+ actionsSlot.classList.add('ui-navbar-actions');
374
+ captureSlot(actionsSlot, 'actions');
375
+ const projectedActions = extractSlot(childFragment, 'actions');
376
+ if (projectedActions)
377
+ actionsSlot.appendChild(projectedActions);
378
+ for (const node of Array.from(childFragment.childNodes)) {
379
+ if (node.nodeType === Node.ELEMENT_NODE)
380
+ actionsSlot.appendChild(node);
381
+ }
382
+ menu.appendChild(actionsSlot);
383
+ // A sticky nav keeps any in-nav sentinel pinned, so IntersectionObserver
384
+ // can't tell us we've scrolled — a passive scroll listener is the fallback.
385
+ /** Max time to wait for transitionend before clearing inline menu styles. */
386
+ const CLOSE_TRANSITION_TIMEOUT = 600;
387
+ const SCROLL_THRESHOLD = 8;
388
+ let scrollRafQueued = false;
389
+ let scrollRafId = 0;
390
+ const applyScrollFrame = () => {
391
+ scrollRafQueued = false;
392
+ const y = typeof window !== 'undefined'
393
+ ? window.scrollY || window.pageYOffset || 0
394
+ : 0;
395
+ if (y > SCROLL_THRESHOLD) {
396
+ nav.dataset.scrolled = 'true';
397
+ }
398
+ else {
399
+ delete nav.dataset.scrolled;
400
+ }
401
+ if (isMenuOpen)
402
+ positionMenu();
403
+ };
404
+ const onWindowScroll = () => {
405
+ if (scrollRafQueued)
406
+ return;
407
+ scrollRafQueued = true;
408
+ scrollRafId = requestAnimationFrame(applyScrollFrame);
409
+ };
410
+ // Tracked from the `toggle` event so we never call
411
+ // `menu.matches(":popover-open")`, which throws a SyntaxError in browsers
412
+ // that don't recognise the `:popover-open` pseudo-class (Firefox <125, Safari <17).
413
+ let isMenuOpen = false;
414
+ // Sync aria-expanded ourselves in case Invoker Commands haven't shipped yet.
415
+ // Initial focus lands inside the menu only when the keyboard opened it;
416
+ // pointer opens leave focus on the trigger so no unasked-for focus ring appears.
417
+ let toggleLastInput = 'pointer';
418
+ const onTogglePointerDown = () => {
419
+ toggleLastInput = 'pointer';
420
+ };
421
+ const onToggleKeyDown = (ev) => {
422
+ if (ev.key === 'Enter' || ev.key === ' ' || ev.key === 'Spacebar') {
423
+ toggleLastInput = 'keyboard';
424
+ }
425
+ };
426
+ toggle.addEventListener('pointerdown', onTogglePointerDown);
427
+ toggle.addEventListener('keydown', onToggleKeyDown);
428
+ disposers.push(() => {
429
+ toggle.removeEventListener('pointerdown', onTogglePointerDown);
430
+ toggle.removeEventListener('keydown', onToggleKeyDown);
431
+ });
432
+ // All focusables inside the drawer — drives the Tab focus trap so every
433
+ // control (links, buttons, form fields in the actions/search slots,
434
+ // explicit tabindex) is reachable via Tab.
435
+ const MENU_FOCUSABLE_SELECTOR = [
436
+ 'a[href]',
437
+ 'button:not([disabled])',
438
+ 'input:not([disabled])',
439
+ 'select:not([disabled])',
440
+ 'textarea:not([disabled])',
441
+ '[tabindex]:not([tabindex="-1"])',
442
+ ].join(',');
443
+ const getMenuFocusables = () => Array.from(menu.querySelectorAll(MENU_FOCUSABLE_SELECTOR)).filter(el => !el.hasAttribute('inert') && !el.closest('[inert]') && !el.hidden);
444
+ // Arrow/Home/End/initial-focus walk the primary nav links only — chrome
445
+ // (drawer brand, close button) and slot content (actions/search) stay
446
+ // reachable via Tab but shouldn't receive arrow navigation.
447
+ const getMenuNavItems = () => Array.from(menu.querySelectorAll('.ui-navbar-list .ui-navbar-link'));
448
+ const onMenuToggle = (e) => {
449
+ const ev = e;
450
+ const open = ev.newState === 'open';
451
+ isMenuOpen = open;
452
+ toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
453
+ toggle.setAttribute('aria-label', open ? 'Close menu' : 'Open menu');
454
+ if (open && toggleLastInput === 'keyboard') {
455
+ const items = getMenuNavItems();
456
+ if (items.length > 0)
457
+ items[0].focus();
458
+ }
459
+ // When the drawer closes via light-dismiss (backdrop click) or
460
+ // programmatic hidePopover(), focus falls to <body>. Restore it to the
461
+ // toggle so keyboard users aren't stranded at the top of the page.
462
+ // Skip if navigation already moved focus to a heading (activeElement
463
+ // won't be body in that case).
464
+ if (!open) {
465
+ toggleLastInput = 'pointer';
466
+ requestAnimationFrame(() => {
467
+ if (document.activeElement === document.body ||
468
+ document.activeElement === document.documentElement) {
469
+ toggle.focus({ preventScroll: true });
470
+ }
471
+ });
472
+ }
473
+ };
474
+ toggle.setAttribute('aria-expanded', 'false');
475
+ menu.addEventListener('toggle', onMenuToggle);
476
+ disposers.push(() => menu.removeEventListener('toggle', onMenuToggle));
477
+ // True only when the navbar is in mobile layout (hamburger mode). Driven by
478
+ // the ResizeObserver-based overflow detection below — `data-layout="mobile"`
479
+ // is set when nav items don't fit in a single row.
480
+ const isMobileLayout = () => nav.dataset.layout === 'mobile';
481
+ const onMenuKeyDown = (ev) => {
482
+ if (ev.key !== 'ArrowDown' &&
483
+ ev.key !== 'ArrowUp' &&
484
+ ev.key !== 'Home' &&
485
+ ev.key !== 'End' &&
486
+ ev.key !== 'Tab') {
487
+ return;
488
+ }
489
+ if (ev.key === 'Tab') {
490
+ // Mobile-only focus trap — cycle across every focusable in the drawer
491
+ // (including the in-drawer close button).
492
+ if (!isMobileLayout() || !isMenuOpen)
493
+ return;
494
+ const focusables = getMenuFocusables();
495
+ if (focusables.length === 0)
496
+ return;
497
+ const active = document.activeElement;
498
+ const idx = active ? focusables.indexOf(active) : -1;
499
+ if (ev.shiftKey && idx === 0) {
500
+ ev.preventDefault();
501
+ focusables[focusables.length - 1].focus();
502
+ }
503
+ else if (!ev.shiftKey && idx === focusables.length - 1) {
504
+ ev.preventDefault();
505
+ focusables[0].focus();
506
+ }
507
+ return;
508
+ }
509
+ // Arrow/Home/End operate on nav items only — close button is chrome.
510
+ const items = getMenuNavItems();
511
+ if (items.length === 0)
512
+ return;
513
+ const active = document.activeElement;
514
+ const idx = active ? items.indexOf(active) : -1;
515
+ let nextIdx = idx;
516
+ if (ev.key === 'ArrowDown')
517
+ nextIdx = idx < 0 ? 0 : (idx + 1) % items.length;
518
+ else if (ev.key === 'ArrowUp')
519
+ nextIdx =
520
+ idx < 0 ? items.length - 1 : (idx - 1 + items.length) % items.length;
521
+ else if (ev.key === 'Home')
522
+ nextIdx = 0;
523
+ else if (ev.key === 'End')
524
+ nextIdx = items.length - 1;
525
+ if (nextIdx !== idx) {
526
+ ev.preventDefault();
527
+ items[nextIdx].focus();
528
+ }
529
+ };
530
+ menu.addEventListener('keydown', onMenuKeyDown);
531
+ disposers.push(() => menu.removeEventListener('keydown', onMenuKeyDown));
532
+ // Satisfies WCAG 2.4.11 when the menu overflows. Mutates `scrollTop` directly
533
+ // rather than calling `scrollIntoView` so no ancestor can scroll by surprise.
534
+ const FOCUS_EDGE_MARGIN = 8;
535
+ const onMenuFocusIn = (ev) => {
536
+ const target = ev.target;
537
+ if (!target || target === menu || !menu.contains(target))
538
+ return;
539
+ if (menu.scrollHeight <= menu.clientHeight)
540
+ return;
541
+ const menuRect = menu.getBoundingClientRect();
542
+ const targetRect = target.getBoundingClientRect();
543
+ const topDelta = targetRect.top - menuRect.top - FOCUS_EDGE_MARGIN;
544
+ const bottomDelta = targetRect.bottom - menuRect.bottom + FOCUS_EDGE_MARGIN;
545
+ if (topDelta < 0) {
546
+ menu.scrollTop += topDelta;
547
+ }
548
+ else if (bottomDelta > 0) {
549
+ menu.scrollTop += bottomDelta;
550
+ }
551
+ };
552
+ menu.addEventListener('focusin', onMenuFocusIn);
553
+ disposers.push(() => menu.removeEventListener('focusin', onMenuFocusIn));
554
+ // Popovers in the top layer aren't implicitly anchored to their invoker, and
555
+ // CSS Anchor Positioning is Chromium-only — so we pin the menu with JS on
556
+ // open and clear the inline styles on close (so desktop's container query
557
+ // can retake the layout).
558
+ const POPOVER_GAP = 8;
559
+ // Attribute walk first so the resolver is deterministic across runtimes;
560
+ // jsdom/happy-dom return "ltr" from getComputedStyle regardless of inheritance.
561
+ const resolveDirection = (el) => {
562
+ let cur = el;
563
+ while (cur) {
564
+ const dir = cur.getAttribute?.('dir');
565
+ if (dir === 'rtl')
566
+ return 'rtl';
567
+ if (dir === 'ltr')
568
+ return 'ltr';
569
+ cur = cur.parentElement;
570
+ }
571
+ if (typeof window !== 'undefined' && window.getComputedStyle) {
572
+ const computed = window.getComputedStyle(el).direction;
573
+ if (computed === 'rtl')
574
+ return 'rtl';
575
+ }
576
+ return 'ltr';
577
+ };
578
+ const positionMenu = () => {
579
+ if (isMobileLayout())
580
+ return;
581
+ const r = toggle.getBoundingClientRect();
582
+ const topPx = r.bottom + POPOVER_GAP;
583
+ menu.style.insetBlockStart = `${topPx}px`;
584
+ const isRtl = resolveDirection(nav) === 'rtl';
585
+ const viewportInlineEnd = isRtl ? r.left : window.innerWidth - r.right;
586
+ menu.style.insetInlineEnd = `${Math.max(0, viewportInlineEnd)}px`;
587
+ menu.style.insetInlineStart = 'auto';
588
+ menu.style.insetBlockEnd = 'auto';
589
+ menu.style.setProperty('--_menu-top', `${topPx}px`);
590
+ };
591
+ let heldScrollLock = false;
592
+ // Elements this instance has incremented an inert ref on. Instance-local so
593
+ // destroy only decrements what it acquired; the global ref map ensures other
594
+ // concurrent drawers keep the `inert` attribute while they still need it.
595
+ let inertedSiblings = [];
596
+ // Walk from nav up to <body>, inerting siblings at every ancestor level.
597
+ // This ensures <main> inside a wrapper like <div id="app"> is also inerted,
598
+ // not just direct body children.
599
+ const applyInertSiblings = () => {
600
+ if (inertedSiblings.length > 0 || typeof document === 'undefined')
601
+ return;
602
+ let cur = nav;
603
+ while (cur && cur !== document.body) {
604
+ const parentEl = cur.parentElement;
605
+ if (!parentEl)
606
+ break;
607
+ for (const child of Array.from(parentEl.children)) {
608
+ if (!(child instanceof HTMLElement))
609
+ continue;
610
+ if (child === cur)
611
+ continue;
612
+ // Skip elements that were inert before any drawer touched them — those
613
+ // belong to someone else (a modal, a permanently disabled panel, etc.).
614
+ if (child.hasAttribute('inert') && !inertRefs.has(child))
615
+ continue;
616
+ acquireInert(child);
617
+ inertedSiblings.push(child);
618
+ }
619
+ cur = parentEl;
620
+ }
621
+ };
622
+ const releaseInertSiblings = () => {
623
+ for (const el of inertedSiblings)
624
+ releaseInert(el);
625
+ inertedSiblings = [];
626
+ };
627
+ // Clearing inline position before the close transition finishes snaps the
628
+ // still-visible popover to the viewport's top-left, so we defer the clear
629
+ // to the opacity transitionend (with a timeout fallback).
630
+ let pendingCloseCleanup = null;
631
+ const clearMenuPosition = () => {
632
+ menu.style.insetBlockStart = '';
633
+ menu.style.insetInlineEnd = '';
634
+ menu.style.insetInlineStart = '';
635
+ menu.style.insetBlockEnd = '';
636
+ menu.style.removeProperty('--_menu-top');
637
+ };
638
+ const cancelPendingClose = () => {
639
+ if (pendingCloseCleanup) {
640
+ pendingCloseCleanup();
641
+ pendingCloseCleanup = null;
642
+ }
643
+ };
644
+ const onBeforeToggle = (e) => {
645
+ const ev = e;
646
+ if (ev.newState === 'open') {
647
+ cancelPendingClose();
648
+ positionMenu();
649
+ // Only lock scroll when the layout is mobile (hamburger mode); desktop
650
+ // renders the menu inline so no overlay or scroll lock is needed.
651
+ if (isMobileLayout() && !heldScrollLock) {
652
+ acquireScrollLock();
653
+ heldScrollLock = true;
654
+ applyInertSiblings();
655
+ }
656
+ }
657
+ else {
658
+ // Defer scroll-lock and inert release until the close transition
659
+ // finishes — releasing immediately would re-enable background scroll
660
+ // and focus while the drawer is still visually present.
661
+ const finalizeClose = () => {
662
+ if (heldScrollLock) {
663
+ releaseScrollLock();
664
+ heldScrollLock = false;
665
+ }
666
+ releaseInertSiblings();
667
+ clearMenuPosition();
668
+ };
669
+ const prefersReducedMotion = typeof window !== 'undefined' &&
670
+ window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
671
+ if (prefersReducedMotion) {
672
+ finalizeClose();
673
+ }
674
+ else {
675
+ const onEnd = (te) => {
676
+ if (te.target !== menu)
677
+ return;
678
+ if (te.propertyName !== 'opacity')
679
+ return;
680
+ cleanup();
681
+ };
682
+ const timeoutId = window.setTimeout(() => cleanup(), CLOSE_TRANSITION_TIMEOUT);
683
+ const cleanup = () => {
684
+ menu.removeEventListener('transitionend', onEnd);
685
+ window.clearTimeout(timeoutId);
686
+ pendingCloseCleanup = null;
687
+ finalizeClose();
688
+ };
689
+ pendingCloseCleanup = cleanup;
690
+ menu.addEventListener('transitionend', onEnd);
691
+ }
692
+ }
693
+ };
694
+ disposers.push(() => {
695
+ if (heldScrollLock) {
696
+ releaseScrollLock();
697
+ heldScrollLock = false;
698
+ }
699
+ releaseInertSiblings();
700
+ cancelPendingClose();
701
+ });
702
+ menu.addEventListener('beforetoggle', onBeforeToggle);
703
+ disposers.push(() => menu.removeEventListener('beforetoggle', onBeforeToggle));
704
+ if (typeof window !== 'undefined') {
705
+ window.addEventListener('scroll', onWindowScroll, { passive: true });
706
+ window.addEventListener('resize', onWindowScroll, { passive: true });
707
+ // Catch hydration mid-page (user already scrolled).
708
+ applyScrollFrame();
709
+ disposers.push(() => {
710
+ window.removeEventListener('scroll', onWindowScroll);
711
+ window.removeEventListener('resize', onWindowScroll);
712
+ if (scrollRafId !== 0)
713
+ cancelAnimationFrame(scrollRafId);
714
+ });
715
+ }
716
+ // Content-aware responsive layout — switch to hamburger only when items
717
+ // don't fit in a single row. ResizeObserver fires before paint so there's
718
+ // no visible flash on initial render.
719
+ let layoutDestroyed = false;
720
+ /** Temporarily set overflow:hidden for accurate list width measurement,
721
+ * then restore the previous inline value. This avoids persistent clipping
722
+ * of focus rings and keeps measurement side-effect free. */
723
+ const measureListWidths = () => {
724
+ const prev = list.style.overflow;
725
+ list.style.overflow = 'hidden';
726
+ const scrollWidth = list.scrollWidth;
727
+ const clientWidth = list.clientWidth;
728
+ list.style.overflow = prev;
729
+ return { scrollWidth, clientWidth };
730
+ };
731
+ // Track the nav width at the moment we switched to mobile. Only attempt
732
+ // switching back to desktop when the nav has actually grown — this prevents
733
+ // oscillation where toggling data-layout changes the list dimensions, which
734
+ // re-triggers the ResizeObserver, which toggles again, ad infinitum.
735
+ let mobileAtNavWidth = 0;
736
+ const goMobile = () => {
737
+ mobileAtNavWidth = nav.clientWidth;
738
+ nav.dataset.layout = 'mobile';
739
+ };
740
+ // Layout flips reuse the popover's display/overlay transitions — without
741
+ // suppression, desktop→mobile plays the hide animation and flashes items.
742
+ let transitionUnlockId = 0;
743
+ const runWithoutTransitions = (fn) => {
744
+ nav.dataset.layoutTransitioning = 'true';
745
+ fn();
746
+ if (typeof requestAnimationFrame === 'undefined') {
747
+ delete nav.dataset.layoutTransitioning;
748
+ return;
749
+ }
750
+ if (transitionUnlockId !== 0)
751
+ cancelAnimationFrame(transitionUnlockId);
752
+ transitionUnlockId = requestAnimationFrame(() => {
753
+ transitionUnlockId = requestAnimationFrame(() => {
754
+ transitionUnlockId = 0;
755
+ delete nav.dataset.layoutTransitioning;
756
+ });
757
+ });
758
+ };
759
+ const checkLayout = () => {
760
+ if (layoutDestroyed)
761
+ return;
762
+ if (nav.dataset.layout !== 'mobile') {
763
+ // Desktop: measure the list directly for overflow
764
+ const { scrollWidth, clientWidth } = measureListWidths();
765
+ if (scrollWidth > clientWidth + 1)
766
+ runWithoutTransitions(goMobile); // +1 subpixel guard
767
+ }
768
+ else {
769
+ // Body scroll-lock (set synchronously in beforetoggle) removes the
770
+ // scrollbar, which widens nav.clientWidth past mobileAtNavWidth.
771
+ // Check the module-level ref count so another Navbar's open drawer
772
+ // also pauses this instance's probe.
773
+ if (scrollLockRefs > 0)
774
+ return;
775
+ // Mobile: only attempt desktop when the nav has grown wider than
776
+ // when we last switched. This gates out the self-triggered callbacks
777
+ // from the layout toggle below.
778
+ if (nav.clientWidth <= mobileAtNavWidth)
779
+ return;
780
+ runWithoutTransitions(() => {
781
+ // Temporarily flip to desktop to get real measurements, then decide.
782
+ delete nav.dataset.layout;
783
+ const { scrollWidth, clientWidth } = measureListWidths();
784
+ if (scrollWidth > clientWidth + 1) {
785
+ // Items still overflow — revert and update the cached nav width
786
+ // so we don't try again until the viewport grows further.
787
+ goMobile();
788
+ }
789
+ else if (isMenuOpen) {
790
+ // Genuinely wider viewport — close the popover so stale
791
+ // scroll-lock / inert doesn't linger on the desktop layout.
792
+ try {
793
+ menu.hidePopover();
794
+ }
795
+ catch {
796
+ /* already closed */
797
+ }
798
+ }
799
+ });
800
+ }
801
+ };
802
+ if (typeof ResizeObserver !== 'undefined') {
803
+ const layoutObserver = new ResizeObserver(checkLayout);
804
+ layoutObserver.observe(nav);
805
+ // Also observe the list — internal content changes (slot content, label
806
+ // text) can change scrollWidth without resizing the nav container itself.
807
+ layoutObserver.observe(list);
808
+ disposers.push(() => {
809
+ layoutDestroyed = true;
810
+ layoutObserver.disconnect();
811
+ });
812
+ }
813
+ else if (typeof window !== 'undefined') {
814
+ // Fallback for environments without ResizeObserver — listen for window
815
+ // resize events so items don't overflow/clip on small screens.
816
+ checkLayout();
817
+ const onResize = () => {
818
+ if (!layoutDestroyed)
819
+ checkLayout();
820
+ };
821
+ window.addEventListener('resize', onResize);
822
+ disposers.push(() => {
823
+ layoutDestroyed = true;
824
+ window.removeEventListener('resize', onResize);
825
+ });
826
+ }
827
+ // Always push a disposer for the destroyed flag so fonts.ready (and any
828
+ // future async paths) are gated even when neither branch above ran.
829
+ disposers.push(() => {
830
+ layoutDestroyed = true;
831
+ });
832
+ // Re-check after fonts finish loading — web fonts can change text metrics
833
+ // and cause scrollWidth to differ without triggering a resize on nav.
834
+ if (typeof document !== 'undefined' && document.fonts?.ready) {
835
+ document.fonts.ready.then(() => {
836
+ if (!layoutDestroyed)
837
+ checkLayout();
838
+ });
839
+ }
840
+ // No-router fallback: if no router is mounted, back/forward still fires
841
+ // popstate and we re-sync active-state. Safe if the router binds later via
842
+ // bindExistingRouterLinks — both paths agree on prefix-match semantics.
843
+ if (resolveRouter() === null &&
844
+ typeof window !== 'undefined' &&
845
+ anchorHandles.length > 0) {
846
+ const onPopState = () => {
847
+ const path = window.location.pathname || '/';
848
+ for (const { anchor, item } of anchorHandles) {
849
+ const active = isItemActive(path, item.href, item.exactActive === true);
850
+ anchor.classList.toggle('router-link-active', active);
851
+ if (active) {
852
+ anchor.setAttribute('aria-current', 'page');
853
+ anchor.dataset.current = 'page';
854
+ }
855
+ else {
856
+ anchor.removeAttribute('aria-current');
857
+ delete anchor.dataset.current;
858
+ }
859
+ }
860
+ };
861
+ window.addEventListener('popstate', onPopState);
862
+ disposers.push(() => window.removeEventListener('popstate', onPopState));
863
+ }
864
+ return {
865
+ el: nav,
866
+ destroy: () => {
867
+ for (const dispose of disposers) {
868
+ try {
869
+ dispose();
870
+ }
871
+ catch (err) {
872
+ if (import.meta.env?.DEV) {
873
+ console.error('[<Navbar>] disposer threw:', err);
874
+ }
875
+ }
876
+ }
877
+ nav.replaceChildren();
878
+ },
879
+ };
880
+ }
881
+ //# sourceMappingURL=Navbar.js.map