@torpor/ui 0.1.7 → 0.2.1

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 (313) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/Accordion/Accordion.torp +25 -13
  3. package/dist/Accordion/AccordionContent.torp +15 -8
  4. package/dist/Accordion/AccordionHeader.torp +16 -7
  5. package/dist/Accordion/AccordionItem.torp +24 -10
  6. package/dist/Accordion/AccordionTrigger.torp +16 -8
  7. package/dist/Accordion/AccordionTypes.ts +2 -0
  8. package/dist/Accordion/index.d.ts +157 -0
  9. package/dist/Breadcrumb/Breadcrumb.torp +42 -0
  10. package/dist/Breadcrumb/BreadcrumbItem.torp +28 -0
  11. package/dist/Breadcrumb/BreadcrumbSeparator.torp +25 -0
  12. package/dist/Breadcrumb/index.d.ts +84 -0
  13. package/dist/Breadcrumb/index.js +14 -0
  14. package/dist/Calendar/Calendar.torp +273 -0
  15. package/dist/Calendar/CalendarDay.torp +75 -0
  16. package/dist/Calendar/CalendarTypes.ts +22 -0
  17. package/dist/Calendar/index.d.ts +36 -0
  18. package/dist/Calendar/index.js +10 -0
  19. package/dist/Calendar/index.js.map +1 -0
  20. package/dist/Dialog/Dialog.torp +16 -6
  21. package/dist/Dialog/DialogBody.torp +13 -5
  22. package/dist/Dialog/DialogButton.torp +18 -6
  23. package/dist/Dialog/DialogFooter.torp +13 -3
  24. package/dist/Dialog/DialogHeader.torp +13 -4
  25. package/dist/Dialog/index.d.ts +135 -0
  26. package/dist/Disclosure/Disclosure.torp +79 -0
  27. package/dist/Disclosure/DisclosureContent.torp +43 -0
  28. package/dist/Disclosure/DisclosureHeader.torp +43 -0
  29. package/dist/Disclosure/DisclosureTrigger.torp +44 -0
  30. package/dist/Disclosure/DisclosureTypes.ts +14 -0
  31. package/dist/Disclosure/index.d.ts +113 -0
  32. package/dist/Disclosure/index.js +18 -0
  33. package/dist/Disclosure/index.js.map +1 -0
  34. package/{src/Offscreen/Offscreen.torp → dist/Drawer/Drawer.torp} +20 -19
  35. package/dist/Drawer/index.d.ts +21 -0
  36. package/dist/Drawer/index.js +6 -0
  37. package/dist/Drawer/index.js.map +1 -0
  38. package/dist/Form/Field.torp +93 -0
  39. package/dist/Form/Form.torp +131 -0
  40. package/dist/Form/FormTypes.ts +30 -0
  41. package/dist/Form/Input.torp +94 -0
  42. package/dist/Form/Label.torp +31 -0
  43. package/dist/Form/Message.torp +35 -0
  44. package/dist/Form/StandardSchemaV1.ts +70 -0
  45. package/dist/Form/index.d.ts +117 -0
  46. package/dist/Form/index.js +22 -0
  47. package/dist/Form/index.js.map +1 -0
  48. package/dist/Menu/Menu.torp +169 -0
  49. package/dist/Menu/MenuButton.torp +64 -0
  50. package/dist/Menu/MenuCheck.torp +81 -0
  51. package/dist/Menu/MenuGroup.torp +29 -0
  52. package/dist/Menu/MenuIndicator.torp +38 -0
  53. package/dist/Menu/MenuPopout.torp +82 -0
  54. package/dist/Menu/MenuPopoutContent.torp +83 -0
  55. package/dist/Menu/MenuPopoutTrigger.torp +126 -0
  56. package/dist/Menu/MenuRadio.torp +99 -0
  57. package/dist/Menu/MenuRadioGroup.torp +97 -0
  58. package/dist/Menu/MenuSeparator.torp +24 -0
  59. package/dist/Menu/MenuTypes.ts +58 -0
  60. package/dist/Menu/index.d.ts +293 -0
  61. package/dist/Menu/index.js +46 -0
  62. package/dist/Menu/index.js.map +1 -0
  63. package/dist/MenuBar/MenuBar.torp +159 -0
  64. package/dist/MenuBar/MenuBarItem.torp +115 -0
  65. package/dist/MenuBar/MenuBarItemContent.torp +117 -0
  66. package/dist/MenuBar/MenuBarItemTrigger.torp +98 -0
  67. package/dist/MenuBar/MenuBarTypes.ts +36 -0
  68. package/dist/MenuBar/index.d.ts +125 -0
  69. package/dist/MenuBar/index.js +18 -0
  70. package/dist/MenuBar/index.js.map +1 -0
  71. package/dist/NavMenu/NavMenu.torp +15 -6
  72. package/dist/NavMenu/NavMenuButton.torp +14 -7
  73. package/dist/NavMenu/NavMenuElement.torp +16 -8
  74. package/dist/NavMenu/NavMenuGroup.torp +14 -5
  75. package/dist/NavMenu/NavMenuGroupHeader.torp +14 -6
  76. package/dist/NavMenu/NavMenuLink.torp +18 -10
  77. package/dist/NavMenu/NavMenuPopout.torp +13 -5
  78. package/dist/NavMenu/NavMenuPopoutButton.torp +17 -11
  79. package/dist/NavMenu/NavMenuPopoutContent.torp +42 -30
  80. package/dist/NavMenu/NavMenuSeparator.torp +12 -3
  81. package/dist/NavMenu/index.d.ts +259 -0
  82. package/dist/Notification/Notification.torp +58 -0
  83. package/dist/Notification/NotificationClose.torp +37 -0
  84. package/dist/Notification/NotificationContainer.torp +24 -0
  85. package/dist/Notification/NotificationContent.torp +23 -0
  86. package/dist/Notification/NotificationTypes.ts +5 -0
  87. package/dist/Notification/index.d.ts +92 -0
  88. package/dist/Notification/index.js +36 -0
  89. package/dist/Notification/index.js.map +1 -0
  90. package/dist/Notification/showNotification.ts +30 -0
  91. package/dist/Popover/Popover.torp +22 -8
  92. package/dist/Popover/PopoverContent.torp +41 -29
  93. package/dist/Popover/PopoverContentAnchored.torp +52 -44
  94. package/dist/Popover/PopoverContentContextual.torp +49 -37
  95. package/dist/Popover/PopoverOverlay.torp +13 -4
  96. package/dist/Popover/PopoverTrigger.torp +12 -3
  97. package/dist/Popover/PopoverTriggerContextual.torp +12 -3
  98. package/dist/Popover/PopoverTriggerHover.torp +14 -5
  99. package/dist/Popover/index.d.ts +191 -0
  100. package/dist/TabGroup/TabContent.torp +13 -7
  101. package/dist/TabGroup/TabGroup.torp +23 -11
  102. package/dist/TabGroup/TabHeader.torp +16 -7
  103. package/dist/TabGroup/TabList.torp +15 -6
  104. package/dist/TabGroup/TabTrigger.torp +14 -6
  105. package/dist/TabGroup/index.d.ts +150 -0
  106. package/dist/icons/ArrowDown.torp +15 -0
  107. package/dist/icons/ArrowLeft.torp +15 -0
  108. package/dist/icons/ArrowRight.torp +15 -0
  109. package/dist/icons/ArrowUp.torp +15 -0
  110. package/dist/icons/Bars.torp +17 -0
  111. package/dist/icons/Check.torp +15 -0
  112. package/dist/icons/ChevronDown.torp +15 -0
  113. package/dist/icons/ChevronLeft.torp +15 -0
  114. package/dist/icons/ChevronRight.torp +15 -0
  115. package/dist/icons/ChevronUp.torp +15 -0
  116. package/dist/icons/Cross.torp +16 -0
  117. package/dist/icons/Loading.torp +16 -0
  118. package/dist/icons/Minus.torp +15 -0
  119. package/dist/icons/Plus.torp +16 -0
  120. package/dist/icons/index.d.ts +142 -0
  121. package/dist/icons/index.js +58 -0
  122. package/dist/icons/index.js.map +1 -0
  123. package/dist/{motion.d.cts → index.d.ts} +2 -2
  124. package/dist/metafile-esm.json +1 -1
  125. package/dist/motion/fade.ts +7 -0
  126. package/dist/motion/index.d.ts +2 -0
  127. package/dist/{motion.js → motion/index.js} +1 -1
  128. package/dist/motion/index.js.map +1 -0
  129. package/dist/mount/index.d.ts +2 -0
  130. package/dist/utils/dateUtils.ts +244 -0
  131. package/dist/utils/index.d.ts +2 -0
  132. package/dist/utils/setPopoverPosition.ts +4 -0
  133. package/package.json +60 -35
  134. package/scripts/postbuild.ts +42 -5
  135. package/src/Accordion/Accordion.torp +25 -13
  136. package/src/Accordion/AccordionContent.torp +15 -8
  137. package/src/Accordion/AccordionHeader.torp +16 -7
  138. package/src/Accordion/AccordionItem.torp +24 -10
  139. package/src/Accordion/AccordionTrigger.torp +16 -8
  140. package/src/Accordion/AccordionTypes.ts +2 -0
  141. package/src/Breadcrumb/Breadcrumb.torp +42 -0
  142. package/src/Breadcrumb/BreadcrumbItem.torp +28 -0
  143. package/src/Breadcrumb/BreadcrumbSeparator.torp +25 -0
  144. package/src/Breadcrumb/index.ts +5 -0
  145. package/src/Calendar/Calendar.torp +273 -0
  146. package/src/Calendar/CalendarDay.torp +75 -0
  147. package/src/Calendar/CalendarTypes.ts +22 -0
  148. package/src/Calendar/index.ts +4 -0
  149. package/src/Dialog/Dialog.torp +16 -6
  150. package/src/Dialog/DialogBody.torp +13 -5
  151. package/src/Dialog/DialogButton.torp +18 -6
  152. package/src/Dialog/DialogFooter.torp +13 -3
  153. package/src/Dialog/DialogHeader.torp +13 -4
  154. package/src/Disclosure/Disclosure.torp +79 -0
  155. package/src/Disclosure/DisclosureContent.torp +43 -0
  156. package/src/Disclosure/DisclosureHeader.torp +43 -0
  157. package/src/Disclosure/DisclosureTrigger.torp +44 -0
  158. package/src/Disclosure/DisclosureTypes.ts +14 -0
  159. package/src/Disclosure/index.ts +6 -0
  160. package/{dist/Offscreen/Offscreen.torp → src/Drawer/Drawer.torp} +20 -19
  161. package/src/Drawer/index.ts +3 -0
  162. package/src/Form/Field.torp +93 -0
  163. package/src/Form/Form.torp +131 -0
  164. package/src/Form/FormTypes.ts +30 -0
  165. package/src/Form/Input.torp +94 -0
  166. package/src/Form/Label.torp +31 -0
  167. package/src/Form/Message.torp +35 -0
  168. package/src/Form/StandardSchemaV1.ts +70 -0
  169. package/src/Form/index.ts +7 -0
  170. package/src/Menu/Menu.torp +169 -0
  171. package/src/Menu/MenuButton.torp +64 -0
  172. package/src/Menu/MenuCheck.torp +81 -0
  173. package/src/Menu/MenuGroup.torp +29 -0
  174. package/src/Menu/MenuIndicator.torp +38 -0
  175. package/src/Menu/MenuPopout.torp +82 -0
  176. package/src/Menu/MenuPopoutContent.torp +83 -0
  177. package/src/Menu/MenuPopoutTrigger.torp +126 -0
  178. package/src/Menu/MenuRadio.torp +99 -0
  179. package/src/Menu/MenuRadioGroup.torp +97 -0
  180. package/src/Menu/MenuSeparator.torp +24 -0
  181. package/src/Menu/MenuTypes.ts +58 -0
  182. package/src/Menu/index.ts +25 -0
  183. package/src/MenuBar/MenuBar.torp +159 -0
  184. package/src/MenuBar/MenuBarItem.torp +115 -0
  185. package/src/MenuBar/MenuBarItemContent.torp +117 -0
  186. package/src/MenuBar/MenuBarItemTrigger.torp +98 -0
  187. package/src/MenuBar/MenuBarTypes.ts +36 -0
  188. package/src/MenuBar/index.ts +6 -0
  189. package/src/NavMenu/NavMenu.torp +15 -6
  190. package/src/NavMenu/NavMenuButton.torp +14 -7
  191. package/src/NavMenu/NavMenuElement.torp +16 -8
  192. package/src/NavMenu/NavMenuGroup.torp +14 -5
  193. package/src/NavMenu/NavMenuGroupHeader.torp +14 -6
  194. package/src/NavMenu/NavMenuLink.torp +18 -10
  195. package/src/NavMenu/NavMenuPopout.torp +13 -5
  196. package/src/NavMenu/NavMenuPopoutButton.torp +17 -11
  197. package/src/NavMenu/NavMenuPopoutContent.torp +42 -30
  198. package/src/NavMenu/NavMenuSeparator.torp +12 -3
  199. package/src/Notification/Notification.torp +58 -0
  200. package/src/Notification/NotificationClose.torp +37 -0
  201. package/src/Notification/NotificationContainer.torp +24 -0
  202. package/src/Notification/NotificationContent.torp +23 -0
  203. package/src/Notification/NotificationTypes.ts +5 -0
  204. package/src/Notification/index.ts +13 -0
  205. package/src/Notification/showNotification.ts +30 -0
  206. package/src/Popover/Popover.torp +22 -8
  207. package/src/Popover/PopoverContent.torp +41 -29
  208. package/src/Popover/PopoverContentAnchored.torp +52 -44
  209. package/src/Popover/PopoverContentContextual.torp +49 -37
  210. package/src/Popover/PopoverOverlay.torp +13 -4
  211. package/src/Popover/PopoverTrigger.torp +12 -3
  212. package/src/Popover/PopoverTriggerContextual.torp +12 -3
  213. package/src/Popover/PopoverTriggerHover.torp +14 -5
  214. package/src/Slider/Slider.torp +83 -0
  215. package/src/Slider/index.ts +3 -0
  216. package/src/TabGroup/TabContent.torp +13 -7
  217. package/src/TabGroup/TabGroup.torp +23 -11
  218. package/src/TabGroup/TabHeader.torp +16 -7
  219. package/src/TabGroup/TabList.torp +15 -6
  220. package/src/TabGroup/TabTrigger.torp +14 -6
  221. package/src/icons/ArrowDown.torp +15 -0
  222. package/src/icons/ArrowLeft.torp +15 -0
  223. package/src/icons/ArrowRight.torp +15 -0
  224. package/src/icons/ArrowUp.torp +15 -0
  225. package/src/icons/Bars.torp +17 -0
  226. package/src/icons/Check.torp +15 -0
  227. package/src/icons/ChevronDown.torp +15 -0
  228. package/src/icons/ChevronLeft.torp +15 -0
  229. package/src/icons/ChevronRight.torp +15 -0
  230. package/src/icons/ChevronUp.torp +15 -0
  231. package/src/icons/Cross.torp +16 -0
  232. package/src/icons/Loading.torp +16 -0
  233. package/src/icons/Minus.torp +15 -0
  234. package/src/icons/Plus.torp +16 -0
  235. package/src/icons/index.ts +31 -0
  236. package/src/motion/fade.ts +2 -2
  237. package/src/motion/index.ts +3 -0
  238. package/src/utils/dateUtils.ts +244 -0
  239. package/src/utils/setPopoverPosition.ts +4 -0
  240. package/test/Accordion/components/AccordionAccessibility.torp +2 -0
  241. package/test/Accordion/components/AccordionKeyboard.torp +2 -0
  242. package/test/Accordion/components/AccordionList.torp +2 -0
  243. package/test/Accordion/components/AccordionMultiple.torp +2 -0
  244. package/test/Accordion/components/AccordionSingle.torp +2 -0
  245. package/test/Breadcrumb/accessibility.test.ts +29 -0
  246. package/test/Breadcrumb/basic.test.ts +17 -0
  247. package/test/Breadcrumb/components/BreadcrumbTest.torp +15 -0
  248. package/test/Calendar/components/CalendarTest.torp +9 -0
  249. package/test/Calendar/default.test.ts +18 -0
  250. package/test/Dialog/components/DialogAccessibility.torp +5 -5
  251. package/test/Disclosure/accessibility.test.ts +31 -0
  252. package/test/Disclosure/components/DisclosureTest.torp +19 -0
  253. package/test/Disclosure/default.test.ts +15 -0
  254. package/test/Disclosure/toggle.test.ts +20 -0
  255. package/test/Form/components/FieldTest.torp +25 -0
  256. package/test/Form/components/FormTest.torp +31 -0
  257. package/test/Form/components/InputTest.torp +9 -0
  258. package/test/Form/field.test.ts +34 -0
  259. package/test/Form/form.test.ts +34 -0
  260. package/test/Form/input.test.ts +19 -0
  261. package/test/Menu/accessibility.test.ts +83 -0
  262. package/test/Menu/basic.test.ts +58 -0
  263. package/test/Menu/components/MenuAccessibility.torp +59 -0
  264. package/test/Menu/components/MenuBasic.torp +58 -0
  265. package/test/MenuBar/accessibility.test.ts +87 -0
  266. package/test/MenuBar/basic.test.ts +22 -0
  267. package/test/MenuBar/components/MenuBarAccessibility.torp +34 -0
  268. package/test/MenuBar/components/MenuBarBasic.torp +25 -0
  269. package/test/NavMenu/basic.test.ts +1 -1
  270. package/test/NavMenu/components/{NavMenu.torp → NavMenuBasic.torp} +1 -1
  271. package/test/Notification/accessibility.test.ts +21 -0
  272. package/test/Notification/components/CustomNotification.torp +14 -0
  273. package/test/Notification/components/NotificationAccessibility.torp +14 -0
  274. package/test/Notification/components/NotificationFunction.torp +20 -0
  275. package/test/Notification/components/NotificationVariable.torp +22 -0
  276. package/test/Notification/function.test.ts +25 -0
  277. package/test/Notification/variable.test.ts +25 -0
  278. package/test/Popover/components/CustomDialog.torp +25 -0
  279. package/test/Popover/components/PromptFunction.torp +19 -0
  280. package/test/Popover/components/PromptVariable.torp +38 -0
  281. package/test/Popover/function.test.ts +26 -0
  282. package/test/Popover/variable.test.ts +27 -0
  283. package/test/TabGroup/components/TabGroupAccessibility.torp +2 -0
  284. package/test/TabGroup/components/TabGroupKeyboard.torp +2 -0
  285. package/test/TabGroup/components/TabGroupList.torp +2 -0
  286. package/test/TabGroup/components/TabGroupTrigger.torp +2 -0
  287. package/tsup.config.ts +12 -4
  288. package/dist/Accordion/index.cjs +0 -53
  289. package/dist/Accordion/index.cjs.map +0 -1
  290. package/dist/Dialog/index.cjs +0 -53
  291. package/dist/Dialog/index.cjs.map +0 -1
  292. package/dist/NavMenu/index.cjs +0 -78
  293. package/dist/NavMenu/index.cjs.map +0 -1
  294. package/dist/Offscreen/index.cjs +0 -33
  295. package/dist/Offscreen/index.cjs.map +0 -1
  296. package/dist/Offscreen/index.js +0 -6
  297. package/dist/Popover/index.cjs +0 -222
  298. package/dist/Popover/index.cjs.map +0 -1
  299. package/dist/TabGroup/index.cjs +0 -53
  300. package/dist/TabGroup/index.cjs.map +0 -1
  301. package/dist/metafile-cjs.json +0 -1
  302. package/dist/motion.cjs +0 -34
  303. package/dist/motion.cjs.map +0 -1
  304. package/dist/motion.d.ts +0 -6
  305. package/dist/motion.js.map +0 -1
  306. package/src/Accordion.ts +0 -7
  307. package/src/Dialog.ts +0 -7
  308. package/src/NavMenu.ts +0 -23
  309. package/src/Offscreen/index.ts +0 -3
  310. package/src/Popover.ts +0 -29
  311. package/src/TabGroup.ts +0 -7
  312. package/src/motion.ts +0 -3
  313. /package/dist/{Offscreen → Breadcrumb}/index.js.map +0 -0
@@ -0,0 +1,83 @@
1
+
2
+ import setPopoverPosition from "../utils/setPopoverPosition";
3
+ import { MenuPopoutContextName, type MenuPopoutContext } from "./MenuTypes";
4
+
5
+ interface MenuPopoutContentProps {
6
+ /** An ID for the root element */
7
+ id?: string;
8
+ /** Class name(s) for the root element */
9
+ class?: string,
10
+ /** Where the popout will be shown, relative to the popout button */
11
+ side?: "top" | "right" | "bottom" | "left",
12
+ /** How the popout will be aligned, relative to the popout button */
13
+ alignment?: "start" | "center" | "end",
14
+ }
15
+
16
+ /**
17
+ * The content that is displayed for a MenuPopout when it is open.
18
+ */
19
+ export default function MenuPopoutContent($props: MenuPopoutContentProps) {
20
+ $props ??= $watch({});
21
+ $props.side ??= "right";
22
+ $props.alignment ??= "start";
23
+
24
+ let div: HTMLDivElement;
25
+ let shown = false;
26
+
27
+ // Get stuff out of the MenuPopoutContext
28
+ const popoutContext = $context[MenuPopoutContextName] as MenuPopoutContext;
29
+ if (!popoutContext) {
30
+ throw new Error("MenuPopoutContent must be contained within a MenuPopout");
31
+ }
32
+ let $state = popoutContext.state;
33
+
34
+ $run(() => {
35
+ if (!$state.visible) {
36
+ hide();
37
+ }
38
+ });
39
+
40
+ function show() {
41
+ shown = true;
42
+
43
+ // Set the position and listen for window resize and scroll to reset the position
44
+ setPosition();
45
+ addEventListener("resize", setPosition);
46
+ addEventListener("scroll", setPosition);
47
+ if (popoutContext.focusFirstElement) {
48
+ popoutContext.focusFirstElement();
49
+ }
50
+ }
51
+
52
+ function hide() {
53
+ if (!shown) return;
54
+
55
+ // Stop listening for window resize and focus the anchor element per WAI guidelines
56
+ removeEventListener("resize", setPosition);
57
+ removeEventListener("scroll", setPosition);
58
+ if (popoutContext.anchorElement?.focus) {
59
+ popoutContext.anchorElement.focus();
60
+ }
61
+ }
62
+
63
+ function setPosition() {
64
+ if (!popoutContext.anchorElement) {
65
+ throw new Error("MenuPopoutContent must have an anchor element");
66
+ }
67
+
68
+ setPopoverPosition(popoutContext.anchorElement, div, $props.side, $props.alignment);
69
+ }
70
+
71
+ @render {
72
+ @if ($state.visible) {
73
+ <div
74
+ {$props.id}
75
+ class={`torp-menu-popout-content ${$props.class ?? ""}`.trim()}
76
+ &ref={div}
77
+ onmount={show}
78
+ >
79
+ <slot />
80
+ </div>
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,126 @@
1
+ import {
2
+ MenuContextName,
3
+ MenuPopoutContextName,
4
+ type MenuContext,
5
+ type MenuPopoutContext
6
+ } from "./MenuTypes";
7
+
8
+ interface MenuPopoutTriggerProps {
9
+ /** An ID for the root element */
10
+ id?: string;
11
+ /** Class name(s) for the root element */
12
+ class?: string,
13
+ /** The delay before showing the popout, in milliseconds */
14
+ hoverDelay?: number,
15
+ /** Whether this button is disabled */
16
+ disabled?: boolean,
17
+ }
18
+
19
+ /**
20
+ * A button that is placed in a Menu or MenuGroup and signals a response by a user.
21
+ */
22
+ export default function MenuPopoutTrigger($props: MenuPopoutTriggerProps) {
23
+ $props ??= $watch({});
24
+ $props.hoverDelay ??= 500;
25
+ $props.disabled ??= false;
26
+
27
+ let button: HTMLButtonElement;
28
+ let interval: NodeJS.Timeout | undefined;
29
+
30
+ // Get stuff out of the MenuContext
31
+ const menuContext = $context[MenuContextName] as MenuContext;
32
+ if (!menuContext) {
33
+ throw new Error("MenuPopoutTrigger must be contained within a Menu");
34
+ }
35
+ const { registerItem, handleItemKey, handleItemFocus } = menuContext;
36
+
37
+ // Get stuff out of the MenuPopoutContext
38
+ const popoutContext = $context[MenuPopoutContextName] as MenuPopoutContext;
39
+ if (!popoutContext) {
40
+ throw new Error("MenuPopoutTrigger must be contained within a MenuPopout");
41
+ }
42
+ const { state: $state, setVisible } = popoutContext;
43
+
44
+ // Register this item with the parent Menu and store the index
45
+ const { index } = registerItem(setFocused);
46
+
47
+ // TODO: This is getting called before @render
48
+ //$mount(() => {
49
+ // // Set the anchor element to the button when it exists
50
+ // popoutContext.anchorElement = button;
51
+ //});
52
+
53
+ function setFocused() {
54
+ button.focus();
55
+ }
56
+
57
+ function handleClick() {
58
+ setVisible(true);
59
+ }
60
+
61
+ function handlePopoutKey(e: KeyboardEvent) {
62
+ switch (e.key) {
63
+ case "Right":
64
+ case "ArrowRight":
65
+ // TODO: This should be based on what side the menu shows on
66
+ e.preventDefault();
67
+ setVisible(true);
68
+ break;
69
+ default:
70
+ handleItemKey(e);
71
+ }
72
+ }
73
+
74
+ function handleMouseEnter() {
75
+ if ($props.disabled) return;
76
+
77
+ // Handle the hover after a delay
78
+ if (!interval) {
79
+ interval = setTimeout(() => setVisible(true), $props.hoverDelay);
80
+ }
81
+ }
82
+
83
+ function handleMouseLeave() {
84
+ if ($props.disabled) return;
85
+
86
+ // Prevent short hovers or touches from firing the event
87
+ if (interval) {
88
+ clearTimeout(interval);
89
+ interval = undefined;
90
+ }
91
+ }
92
+
93
+ function handleMouseMove() {
94
+ if ($props.disabled) return;
95
+
96
+ // Reset the interval on mouse move
97
+ if (interval) {
98
+ clearTimeout(interval);
99
+ interval = setTimeout(() => setVisible(true), $props.hoverDelay);
100
+ }
101
+ }
102
+
103
+ @render {
104
+ <button
105
+ {$props.id}
106
+ class={`torp-menu-popout-trigger ${$props.class ?? ""}`.trim()}
107
+ type="button"
108
+ {$props.disabled}
109
+ role="menuitem"
110
+ aria-haspopup="true"
111
+ aria-expanded={($state.visible ?? false).toString()}
112
+ aria-disabled={$props.disabled ? "true" : undefined}
113
+ data-disabled={$props.disabled ? "true" : undefined}
114
+ onkeydown={handlePopoutKey}
115
+ onclick={handleClick}
116
+ onfocus={() => handleItemFocus(index)}
117
+ onmouseenter={handleMouseEnter}
118
+ onmouseleave={handleMouseLeave}
119
+ onmousemove={handleMouseMove}
120
+ &ref={button}
121
+ onmount={(el) => popoutContext.anchorElement = el}
122
+ >
123
+ <slot />
124
+ </button>
125
+ }
126
+ }
@@ -0,0 +1,99 @@
1
+ import {
2
+ MenuButtonContextName,
3
+ MenuContextName,
4
+ MenuRadioGroupContextName,
5
+ type MenuButtonContext,
6
+ type MenuContext,
7
+ type MenuRadioGroupContext
8
+ } from "./MenuTypes";
9
+
10
+ interface MenuRadioProps {
11
+ /** An ID for the root element */
12
+ id?: string;
13
+ /** Class name(s) for the root element */
14
+ class?: string,
15
+ /** The value of the item */
16
+ value?: string,
17
+ /** Whether this item is checked */
18
+ checked?: boolean,
19
+ /** Whether this item is disabled */
20
+ disabled?: boolean,
21
+ }
22
+
23
+ /**
24
+ * A button that is placed in a Menu or MenuGroup to allow the user to select
25
+ * from a number of options.
26
+ */
27
+ export default function MenuRadio($props: MenuRadioProps) {
28
+ $props ??= $watch({});
29
+
30
+ let $state = $watch({
31
+ checked: $props.checked,
32
+ });
33
+
34
+ $run(() => {
35
+ $props.checked = $state.checked;
36
+ });
37
+
38
+ let button: HTMLButtonElement;
39
+
40
+ // Get stuff out of the MenuContext
41
+ const menuContext = $context[MenuContextName] as MenuContext;
42
+ if (!menuContext) {
43
+ throw new Error("MenuRadio must be contained within a Menu");
44
+ }
45
+ const { registerItem, handleItemKey, handleItemFocus } = menuContext;
46
+
47
+ // Get stuff out of the MenuRadioGroupContext
48
+ const groupContext = $context[MenuRadioGroupContextName] as MenuRadioGroupContext;
49
+ if (!groupContext) {
50
+ throw new Error("MenuRadio must be contained within a MenuRadioGroup");
51
+ }
52
+ const { registerItemInGroup, toggleItem } = groupContext;
53
+
54
+ // Register this item with the parent Menu
55
+ const { index } = registerItem(setFocused);
56
+
57
+ // Register this item with the parent MenuRadioGroup
58
+ registerItemInGroup($props.value, setChecked);
59
+
60
+ // Set the context to pass down to items
61
+ let context: MenuButtonContext = {
62
+ state: $state
63
+ };
64
+ $context[MenuButtonContextName] = context;
65
+
66
+ function setFocused() {
67
+ button.focus();
68
+ }
69
+
70
+ function setChecked(value: boolean) {
71
+ $state.checked = value;
72
+ }
73
+
74
+ function handleClick() {
75
+ if (!$state.checked) {
76
+ toggleItem($props.value);
77
+ }
78
+ }
79
+
80
+ @render {
81
+ <button
82
+ {$props.id}
83
+ class={`torp-menu-radio ${$props.class ?? ""}`.trim()}
84
+ type="button"
85
+ {$props.disabled}
86
+ role="menuitemradio"
87
+ aria-checked={$props.checked}
88
+ aria-disabled={$props.disabled ? "true" : undefined}
89
+ data-state={$props.checked ? "checked" : "unchecked"}
90
+ data-disabled={$props.disabled ? "true" : undefined}
91
+ onkeydown={handleItemKey}
92
+ onclick={handleClick}
93
+ onfocus={() => handleItemFocus(index)}
94
+ &ref={button}
95
+ >
96
+ <slot />
97
+ </button>
98
+ }
99
+ }
@@ -0,0 +1,97 @@
1
+ import {
2
+ type MenuRadioGroupContext,
3
+ MenuRadioGroupContextName,
4
+ type RadioGroupItemState
5
+ } from "./MenuTypes";
6
+
7
+ interface MenuRadioGroupProps {
8
+ /** An ID for the root element */
9
+ id?: string;
10
+ /** Class name(s) for the root element */
11
+ class?: string,
12
+ /** An ARIA label for describing the menu to screen readers */
13
+ ariaLabel?: string,
14
+ /** The value of the checked MenuRadio */
15
+ value?: string | undefined,
16
+ /** Called when the checked item has changed */
17
+ onchange?: (value: any) => void,
18
+ }
19
+
20
+ /**
21
+ * A container for a logical group of menu RadioGroup items.
22
+ */
23
+ export default function MenuRadioGroup($props: MenuRadioGroupProps) {
24
+ $props ??= $watch({});
25
+
26
+ // A collection containing the state and some functions for each item
27
+ let itemStates: RadioGroupItemState[] = [];
28
+
29
+ // Set the context to pass down to items
30
+ let context: MenuRadioGroupContext = {
31
+ registerItemInGroup,
32
+ toggleItem
33
+ };
34
+ $context[MenuRadioGroupContextName] = context;
35
+
36
+ $mount(() => {
37
+ // Set the initial checked item
38
+ setValue($props.value);
39
+ });
40
+
41
+ function registerItemInGroup(itemValue: string, setChecked: (value: boolean) => void) {
42
+ // Add the item"s state to the array
43
+ const newItemState: RadioGroupItemState = {
44
+ value: itemValue,
45
+ checked: false,
46
+ setChecked
47
+ };
48
+ itemStates.push(newItemState);
49
+ itemStates = itemStates;
50
+ }
51
+
52
+ function setValue(newValue?: string) {
53
+ // No point doing this if there aren't any items yet (e.g. from the initial setValue() call)
54
+ if (!itemStates.length) {
55
+ return;
56
+ }
57
+
58
+ // Ensure the value passed in was a string (in case e.g. the user has set values to ints)
59
+ newValue = newValue?.toString();
60
+ for (let item of itemStates) {
61
+ item.checked = item.value === newValue;
62
+ item.setChecked(item.checked);
63
+ }
64
+
65
+ // Set the value
66
+ if ($props.value !== newValue) {
67
+ $props.value = newValue;
68
+
69
+ if ($props.onchange) {
70
+ $props.onchange($props.value);
71
+ }
72
+ }
73
+ }
74
+
75
+ function toggleItem(toggleValue: string) {
76
+ let newValue = "";
77
+ for (let item of itemStates) {
78
+ const checked = item.value === toggleValue;
79
+ item.setChecked(checked);
80
+ if (checked) {
81
+ newValue = item.value;
82
+ }
83
+ }
84
+ $props.value = newValue;
85
+ }
86
+
87
+ @render {
88
+ <div
89
+ {$props.id}
90
+ class={`torp-menu-radio-group ${$props.class ?? ""}`.trim()}
91
+ role="group"
92
+ aria-label={$props.ariaLabel}
93
+ >
94
+ <slot />
95
+ </div>
96
+ }
97
+ }
@@ -0,0 +1,24 @@
1
+
2
+ interface MenuSeparatorProps {
3
+ /** An ID for the root element */
4
+ id?: string;
5
+ /** Class name(s) for the root element */
6
+ class?: string,
7
+ }
8
+
9
+ /**
10
+ * An element in a menu that separates logical sets of menu items.
11
+ */
12
+ export default function MenuSeparator($props: MenuSeparatorProps) {
13
+ $props ??= $watch({});
14
+
15
+ @render {
16
+ <div
17
+ {$props.id}
18
+ class={`torp-menu-separator ${$props.class ?? ""}`.trim()}
19
+ role="separator"
20
+ >
21
+ <slot />
22
+ </div>
23
+ }
24
+ }
@@ -0,0 +1,58 @@
1
+ export const MenuContextName = "Menu";
2
+ export const MenuButtonContextName = "MenuButton";
3
+ export const MenuRadioGroupContextName = "MenuRadioGroup";
4
+ export const MenuPopoutContextName = "MenuPopout";
5
+
6
+ export interface MenuContext {
7
+ setVisible: (value: boolean, result?: any) => void;
8
+
9
+ /** Called from a MenuButton to close the Menu and return a result */
10
+ handleTrigger: (type: "confirm" | "cancel" | undefined, value?: any) => void;
11
+
12
+ /** Called from a MenuButton when its button is focused */
13
+ handleItemFocus: (index: number) => void;
14
+
15
+ /** Called from a MenuButton when a key is pressed */
16
+ handleItemKey: (e: KeyboardEvent) => void;
17
+
18
+ state: {
19
+ /** Whether the Menu is visible. Writable so that it can be subscribed to by children */
20
+ visible: boolean;
21
+ };
22
+
23
+ /** The element that caused this Menu to be shown */
24
+ anchorElement?: HTMLElement;
25
+
26
+ /** Called from each MenuButton/Check/Radio to register itself with this Menu */
27
+ registerItem: (setFocused: () => void) => { index: number };
28
+ }
29
+
30
+ export interface MenuButtonContext {
31
+ state: {
32
+ checked: boolean;
33
+ };
34
+ }
35
+
36
+ export interface MenuRadioGroupContext {
37
+ registerItemInGroup: (value: string, setChecked: (value: boolean) => void) => void;
38
+ toggleItem: (value: string) => void;
39
+ }
40
+
41
+ export interface MenuPopoutContext {
42
+ setVisible: (value: boolean) => void;
43
+ state: {
44
+ visible: boolean;
45
+ };
46
+ anchorElement?: HTMLElement;
47
+ focusFirstElement?: () => void;
48
+ }
49
+
50
+ export interface ItemState {
51
+ setFocused: () => void;
52
+ }
53
+
54
+ export interface RadioGroupItemState {
55
+ value: string;
56
+ checked: boolean;
57
+ setChecked: (value: boolean) => void;
58
+ }
@@ -0,0 +1,25 @@
1
+ import Menu from "./Menu.torp";
2
+ import MenuButton from "./MenuButton.torp";
3
+ import MenuCheck from "./MenuCheck.torp";
4
+ import MenuGroup from "./MenuGroup.torp";
5
+ import MenuIndicator from "./MenuIndicator.torp";
6
+ import MenuPopout from "./MenuPopout.torp";
7
+ import MenuPopoutContent from "./MenuPopoutContent.torp";
8
+ import MenuPopoutTrigger from "./MenuPopoutTrigger.torp";
9
+ import MenuRadio from "./MenuRadio.torp";
10
+ import MenuRadioGroup from "./MenuRadioGroup.torp";
11
+ import MenuSeparator from "./MenuSeparator.torp";
12
+
13
+ export {
14
+ Menu,
15
+ MenuGroup,
16
+ MenuButton,
17
+ MenuCheck,
18
+ MenuRadio,
19
+ MenuRadioGroup,
20
+ MenuIndicator,
21
+ MenuPopout,
22
+ MenuPopoutTrigger,
23
+ MenuPopoutContent,
24
+ MenuSeparator,
25
+ };
@@ -0,0 +1,159 @@
1
+ import { MenuBarContextName, type MenuBarContext, type ItemState } from "./MenuBarTypes";
2
+
3
+ interface MenuBarProps {
4
+ /** An ID for the root element */
5
+ id?: string;
6
+ /** Class name(s) for the root element */
7
+ class?: string,
8
+ /** An ARIA label for describing the menu to screen readers */
9
+ ariaLabel?: string,
10
+ /** The orientation of the menu, which affects keyboard navigation */
11
+ orientation?: "horizontal" | "vertical",
12
+ }
13
+
14
+ /**
15
+ * A menu bar, similar to that shown at the top of Windows applications or at the top of the screen on Macs.
16
+ *
17
+ * See [the WAI ARIA guidelines for Menus](https://www.w3.org/WAI/ARIA/apg/patterns/menu/).
18
+ *
19
+ * The structure of a MenuBar is:
20
+ *
21
+ * ```
22
+ * <MenuBar>
23
+ * <MenuBarItem>
24
+ * <MenuBarItemTrigger />
25
+ * <MenuBarItemContent>
26
+ * <Menu />
27
+ * </MenuBarItemContent>
28
+ * </MenuBarItem>
29
+ * </MenuBar>
30
+ * ```
31
+ */
32
+ export default function MenuBar($props: MenuBarProps) {
33
+ $props ??= $watch({});
34
+ $props.orientation ??= "horizontal";
35
+
36
+ let $state = $watch({
37
+ active: false,
38
+ });
39
+
40
+ // A collection containing the state and some functions for each item
41
+ let itemStates: ItemState[] = [];
42
+ let focusIndex = -1;
43
+
44
+ // Set the context to pass down to items
45
+ let context: MenuBarContext = {
46
+ handleItemShow,
47
+ handleButtonFocus,
48
+ handleButtonKey,
49
+ registerItem,
50
+ state: $state
51
+ };
52
+ $context[MenuBarContextName] = context;
53
+
54
+ function registerItem(setVisible: (value: boolean) => void, setFocused: () => void) {
55
+ // Add the item"s state to the array and return its index
56
+ const newItemState: ItemState = {
57
+ setVisible,
58
+ setFocused
59
+ };
60
+ itemStates.push(newItemState);
61
+ itemStates = itemStates;
62
+ return { index: itemStates.length - 1 };
63
+ }
64
+
65
+ function handleItemShow(index: number) {
66
+ // Make sure other items are hidden
67
+ itemStates.forEach((item, i) => {
68
+ if (i !== index) {
69
+ item.setVisible(false);
70
+ }
71
+ });
72
+ }
73
+
74
+ function handleButtonFocus(index: number) {
75
+ focusIndex = index;
76
+ }
77
+
78
+ function handleButtonKey(e: KeyboardEvent) {
79
+ switch (e.key) {
80
+ case "Right":
81
+ case "ArrowRight": {
82
+ if (orientation === "horizontal") {
83
+ e.preventDefault();
84
+ focusItem("next");
85
+ }
86
+ break;
87
+ }
88
+ case "Left":
89
+ case "ArrowLeft": {
90
+ if (orientation === "horizontal") {
91
+ e.preventDefault();
92
+ focusItem("previous");
93
+ }
94
+ break;
95
+ }
96
+ case "Down":
97
+ case "ArrowDown": {
98
+ if (orientation === "vertical") {
99
+ e.preventDefault();
100
+ focusItem("next");
101
+ }
102
+ break;
103
+ }
104
+ case "Up":
105
+ case "ArrowUp": {
106
+ if (orientation === "vertical") {
107
+ e.preventDefault();
108
+ focusItem("previous");
109
+ }
110
+ break;
111
+ }
112
+ case "Home": {
113
+ e.preventDefault();
114
+ focusItem("start");
115
+ break;
116
+ }
117
+ case "End": {
118
+ e.preventDefault();
119
+ focusItem("end");
120
+ break;
121
+ }
122
+ }
123
+ }
124
+
125
+ function focusItem(target: "start" | "previous" | "next" | "end") {
126
+ switch (target) {
127
+ case "start": {
128
+ focusIndex = 0;
129
+ break;
130
+ }
131
+ case "previous": {
132
+ focusIndex = Math.max(0, focusIndex - 1);
133
+ break;
134
+ }
135
+ case "next": {
136
+ focusIndex = Math.min(itemStates.length - 1, focusIndex + 1);
137
+ break;
138
+ }
139
+ case "end": {
140
+ focusIndex = itemStates.length - 1;
141
+ break;
142
+ }
143
+ }
144
+ itemStates[focusIndex]?.setFocused();
145
+ }
146
+
147
+ @render {
148
+ <div
149
+ {$props.id}
150
+ class={`torp-menu-bar ${$props.class ?? ""}`.trim()}
151
+ tabindex="-1"
152
+ role="menubar"
153
+ aria-label={$props.ariaLabel}
154
+ aria-orientation={$props.orientation}
155
+ >
156
+ <slot />
157
+ </div>
158
+ }
159
+ }