@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,169 @@
1
+ import { PopoverContextName, type PopoverContext } from "../Popover/PopoverTypes";
2
+ import {
3
+ MenuContextName,
4
+ type MenuContext,
5
+ type ItemState,
6
+ MenuPopoutContextName,
7
+ type MenuPopoutContext
8
+ } from "./MenuTypes";
9
+
10
+ interface MenuProps {
11
+ /** An ID for the root element */
12
+ id?: string;
13
+ /** Class name(s) for the root element */
14
+ class?: string,
15
+ /** An ARIA label for describing the menu to screen readers */
16
+ ariaLabel?: string,
17
+ }
18
+
19
+ /**
20
+ * A list of choices for the user, such as a set of actions or functions. A
21
+ * menu is usually hidden until made visible by the user.
22
+ *
23
+ * See [the WAI ARIA guidelines for Menus](https://www.w3.org/WAI/ARIA/apg/patterns/menu/).
24
+ *
25
+ * The structure of a Menu is:
26
+ *
27
+ * ```
28
+ * <Menu>
29
+ * <MenuButton />
30
+ * <MenuSeparator /> (optional)
31
+ * <MenuGroup> (optional)
32
+ * <MenuButton />
33
+ * </MenuGroup>
34
+ * </Menu>
35
+ * ```
36
+ */
37
+ export default function Menu($props: MenuProps) {
38
+ $props ??= $watch({});
39
+
40
+ // A collection containing the state and some functions for each item
41
+ let itemStates: ItemState[] = [];
42
+ let focusIndex = -1;
43
+
44
+ // Get stuff out of the PopoverContext
45
+ const popoverContext = $context[PopoverContextName] as PopoverContext;
46
+ if (!popoverContext) {
47
+ throw new Error("Menu must be contained within a Contextual, Prompt or Popover");
48
+ }
49
+ const { setVisible, handleButton, modal, visible } = popoverContext;
50
+
51
+ // Check whether this is a menu in a popout
52
+ const popoutContext = $context[MenuPopoutContextName] as MenuPopoutContext;
53
+ const inPopout = !!popoutContext;
54
+ const setPopoutVisible = popoutContext?.setVisible;
55
+
56
+ // Set the focusFirstElement method in the PopoverContext(s) so that it can be called after everything has been shown
57
+ popoverContext.focusFirstElement = focusFirstElement;
58
+ if (popoutContext) {
59
+ popoutContext.focusFirstElement = focusFirstElement;
60
+ }
61
+
62
+ // Set the context to pass down to items
63
+ let context: MenuContext = {
64
+ setVisible,
65
+ handleButton,
66
+ handleItemFocus,
67
+ handleItemKey,
68
+ visible,
69
+ registerItem
70
+ };
71
+ $context[MenuContextName] = context;
72
+
73
+ function focusFirstElement() {
74
+ if (itemStates.length) {
75
+ itemStates[0].setFocused();
76
+ }
77
+ }
78
+
79
+ function registerItem(setFocused: () => void) {
80
+ // Add the item"s state to the array and return its index
81
+ const newItemState: ItemState = {
82
+ setFocused
83
+ };
84
+ itemStates.push(newItemState);
85
+ itemStates = itemStates;
86
+ return { index: itemStates.length - 1 };
87
+ }
88
+
89
+ function handleItemFocus(index: number) {
90
+ focusIndex = index;
91
+ }
92
+
93
+ function handleItemKey(e: KeyboardEvent) {
94
+ switch (e.key) {
95
+ case "Esc":
96
+ case "Escape": {
97
+ e.preventDefault();
98
+ setVisible(false);
99
+ break;
100
+ }
101
+ case "Down":
102
+ case "ArrowDown": {
103
+ e.preventDefault();
104
+ focusItem("next");
105
+ break;
106
+ }
107
+ case "Up":
108
+ case "ArrowUp": {
109
+ e.preventDefault();
110
+ focusItem("previous");
111
+ break;
112
+ }
113
+ case "Left":
114
+ case "ArrowLeft": {
115
+ // TODO: This should be based on what side the menu shows on
116
+ if (inPopout) {
117
+ e.preventDefault();
118
+ setPopoutVisible(false);
119
+ }
120
+ break;
121
+ }
122
+ case "Home": {
123
+ e.preventDefault();
124
+ focusItem("start");
125
+ break;
126
+ }
127
+ case "End": {
128
+ e.preventDefault();
129
+ focusItem("end");
130
+ break;
131
+ }
132
+ }
133
+ }
134
+
135
+ function focusItem(target: "start" | "previous" | "next" | "end") {
136
+ switch (target) {
137
+ case "start": {
138
+ focusIndex = 0;
139
+ break;
140
+ }
141
+ case "previous": {
142
+ focusIndex = Math.max(0, focusIndex - 1);
143
+ break;
144
+ }
145
+ case "next": {
146
+ focusIndex = Math.min(itemStates.length - 1, focusIndex + 1);
147
+ break;
148
+ }
149
+ case "end": {
150
+ focusIndex = itemStates.length - 1;
151
+ break;
152
+ }
153
+ }
154
+ itemStates[focusIndex]?.setFocused();
155
+ }
156
+
157
+ @render {
158
+ <div
159
+ {$props.id}
160
+ class={`torp-menu ${$props.class ?? ""}`.trim()}
161
+ tabindex="-1"
162
+ role="menu"
163
+ aria-label={$props.ariaLabel}
164
+ aria-orientation="vertical"
165
+ >
166
+ <slot />
167
+ </div>
168
+ }
169
+ }
@@ -0,0 +1,64 @@
1
+ import { MenuContextName, type MenuContext } from "./MenuTypes";
2
+
3
+ interface MenuButtonProps {
4
+ /** An ID for the root element */
5
+ id?: string;
6
+ /** Class name(s) for the root element */
7
+ class?: string,
8
+ /** The value that should be returned when this button is pressed and the Dialog was invoked by calling showContextual, showPrompt or showPopover */
9
+ value?: any,
10
+ /** Whether this button is disabled */
11
+ disabled?: boolean,
12
+ }
13
+
14
+ /**
15
+ * A button that is placed in a Menu or MenuGroup and signals a response by a user.
16
+ *
17
+ */
18
+ export default function MenuButton($props: MenuButtonProps) {
19
+ $props ??= $watch({});
20
+
21
+ let button: HTMLButtonElement;
22
+
23
+ // Get stuff out of the MenuContext
24
+ const menuContext = $context[MenuContextName] as MenuContext;
25
+ if (!menuContext) {
26
+ throw new Error("MenuButton must be contained within a Menu");
27
+ }
28
+ const { registerItem, handleButton, handleItemKey, handleItemFocus } = menuContext;
29
+
30
+ // Register this item with the parent Menu and store the index
31
+ const { index } = registerItem(setFocused);
32
+
33
+ function handleKeyDown(e) {
34
+ handleItemKey(e);
35
+ if ($props.onkeydown) $props.onkeydown(e);
36
+ }
37
+
38
+ function handleClick(e) {
39
+ handleButton($props.value);
40
+ if ($props.onclick) $props.onclick(e);
41
+ }
42
+
43
+ function setFocused() {
44
+ button.focus();
45
+ }
46
+
47
+ @render {
48
+ <button
49
+ {$props.id}
50
+ class={`torp-menu-button ${$props.class ?? ""}`.trim()}
51
+ type="button"
52
+ {$props.disabled}
53
+ role="menuitem"
54
+ aria-disabled={$props.disabled ? "true" : undefined}
55
+ data-disabled={$props.disabled ? "true" : undefined}
56
+ onkeydown={handleKeyDown}
57
+ onclick={handleClick}
58
+ onfocus={() => handleItemFocus(index)}
59
+ &ref={button}
60
+ >
61
+ <slot />
62
+ </button>
63
+ }
64
+ }
@@ -0,0 +1,81 @@
1
+ import {
2
+ MenuButtonContextName,
3
+ MenuContextName,
4
+ type MenuButtonContext,
5
+ type MenuContext
6
+ } from "./MenuTypes";
7
+
8
+ interface MenuCheckProps {
9
+ /** An ID for the root element */
10
+ id?: string;
11
+ /** Class name(s) for the root element */
12
+ class?: string,
13
+ /** Whether this item is checked */
14
+ checked?: boolean,
15
+ /** Whether this item is disabled */
16
+ disabled?: boolean,
17
+ }
18
+
19
+ /**
20
+ * A button that is placed in a Menu or MenuGroup to allow the user to turn an
21
+ * option on or off.
22
+ *
23
+ */
24
+ export default function MenuCheck($props: MenuCheckProps) {
25
+ $props ??= $watch({});
26
+
27
+ let $state = $watch({
28
+ checked: $props.checked
29
+ });
30
+
31
+ $run(() => {
32
+ $props.checked = $state.checked;
33
+ });
34
+
35
+ let button: HTMLButtonElement;
36
+
37
+ // Get stuff out of the MenuContext
38
+ const menuContext = $context[MenuContextName] as MenuContext;
39
+ if (!menuContext) {
40
+ throw new Error("MenuCheck must be contained within a Menu");
41
+ }
42
+ const { registerItem, handleItemKey, handleItemFocus } = menuContext;
43
+
44
+ // Register this item with the parent Menu
45
+ const { index } = registerItem(setFocused);
46
+
47
+ // Set the context to pass down to items
48
+ let context: MenuButtonContext = {
49
+ state: $state
50
+ };
51
+ $context[MenuButtonContextName] = context;
52
+
53
+ function setFocused() {
54
+ button.focus();
55
+ }
56
+
57
+ function handleClick() {
58
+ $state.checked = !$state.checked;
59
+ if ($props.onclick) $props.onclick();
60
+ }
61
+
62
+ @render {
63
+ <button
64
+ {$props.id}
65
+ class={`torp-menu-check ${$props.class ?? ""}`.trim()}
66
+ type="button"
67
+ {$props.disabled}
68
+ role="menuitemcheckbox"
69
+ aria-checked={$props.checked ? "true" : "false"}
70
+ aria-disabled={$props.disabled ? "true" : undefined}
71
+ data-state={$props.checked ? "checked" : "unchecked"}
72
+ data-disabled={$props.disabled ? "true" : undefined}
73
+ onkeydown={handleItemKey}
74
+ onclick={handleClick}
75
+ onfocus={() => handleItemFocus(index)}
76
+ &ref={button}
77
+ >
78
+ <slot />
79
+ </button>
80
+ }
81
+ }
@@ -0,0 +1,29 @@
1
+
2
+ interface MenuGroupProps {
3
+ /** An ID for the root element */
4
+ id?: string;
5
+ /** Class name(s) for the root element */
6
+ class?: string,
7
+ /** An ARIA label for describing the group to screen readers */
8
+ ariaLabel?: string,
9
+ }
10
+
11
+ /**
12
+ * A container for a logical group of menu items.
13
+ */
14
+ export default function MenuGroup($props: MenuGroupProps) {
15
+ $props ??= $watch({});
16
+
17
+ // TODO: Intercept the context to steal toggleItem??
18
+
19
+ @render {
20
+ <div
21
+ {$props.id}
22
+ class={`torp-menu-group ${$props.class ?? ""}`.trim()}
23
+ role="group"
24
+ aria-label={$props.ariaLabel}
25
+ >
26
+ <slot />
27
+ </div>
28
+ }
29
+ }
@@ -0,0 +1,38 @@
1
+ import { MenuButtonContextName, type MenuButtonContext } from "./MenuTypes";
2
+
3
+ interface MenuIndicatorProps {
4
+ /** An ID for the root element */
5
+ id?: string;
6
+ /** Class name(s) for the root element */
7
+ class?: string,
8
+ /** Whether this item is disabled */
9
+ disabled?: boolean,
10
+ }
11
+
12
+ /**
13
+ * An indicator for a MenuCheck or MenuRadio component, which is displayed
14
+ * when the value of checked is true.
15
+ */
16
+ export default function MenuIndicator($props: MenuIndicatorProps) {
17
+ $props ??= $watch({});
18
+
19
+ // Get stuff out of the MenuButtonContext
20
+ const context = $context[MenuButtonContextName] as MenuButtonContext;
21
+ if (!context) {
22
+ throw new Error("MenuIndicator must be contained within a MenuCheck or MenuRadio");
23
+ }
24
+ let $state = context.state;
25
+
26
+ @render {
27
+ @if ($state.checked) {
28
+ <div
29
+ {$props.id}
30
+ class={`torp-menu-indicator ${$props.class ?? ""}`.trim()}
31
+ aria-disabled={$props.disabled ? "true" : undefined}
32
+ data-disabled={$props.disabled ? "true" : undefined}
33
+ >
34
+ <slot />
35
+ </div>
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,82 @@
1
+ import { MenuPopoutContextName, type MenuPopoutContext } from "./MenuTypes";
2
+
3
+ interface MenuPopoutProps {
4
+ /** An ID for the root element */
5
+ id?: string;
6
+ /** Class name(s) for the root element */
7
+ class?: string,
8
+ /** Whether the popout is visible */
9
+ visible?: boolean,
10
+ /** Called when the popout is opened or closed */
11
+ ontoggle?: (open: boolean) => void,
12
+ /** Called when the popout has opened */
13
+ onopen?: () => void,
14
+ /** Called when the popout has closed */
15
+ onclose?: () => void,
16
+ }
17
+
18
+ /**
19
+ * A popout that is attached to a menu button and can be used to show a submenu.
20
+ *
21
+ * The structure of a MenuPopout is:
22
+ *
23
+ * ```
24
+ * <MenuPopout>
25
+ * <MenuPopoutTrigger /> (optional)
26
+ * <MenuPopoutContent />
27
+ * </MenuPopout>
28
+ * ```
29
+ */
30
+ export default function MenuPopout($props: MenuPopoutProps) {
31
+ $props ??= $watch({});
32
+ $props.visible ??= false;
33
+
34
+ let $state = $watch({
35
+ visible: $props.visible
36
+ });
37
+
38
+ let result: any;
39
+
40
+ // Set the context to pass down to items
41
+ let context: MenuPopoutContext = {
42
+ setVisible,
43
+ state: $state
44
+ };
45
+ $context[MenuPopoutContextName] = context;
46
+
47
+ function setVisible(value: boolean) {
48
+ $state.visible = value;
49
+ }
50
+
51
+ let firstToggle = true;
52
+ $run(() => {
53
+ const ontoggle = $props.ontoggle;
54
+ const onopen = $props.onopen;
55
+ const onclose = $props.onclose;
56
+ const visible = $state.visible;
57
+ if (firstToggle) {
58
+ firstToggle = false;
59
+ } else {
60
+ if (ontoggle) ontoggle(visible);
61
+ if (visible) {
62
+ if (onopen) onopen()
63
+ } else {
64
+ if (onclose) onclose(result);
65
+ }
66
+ }
67
+ });
68
+
69
+ function handleMouseLeave() {
70
+ setVisible(false);
71
+ }
72
+
73
+ @render {
74
+ <div
75
+ {$props.id}
76
+ class={`torp-menu-popout ${$props.class ?? ""}`.trim()}
77
+ onmouseleave={handleMouseLeave}
78
+ >
79
+ <slot />
80
+ </div>
81
+ }
82
+ }
@@ -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
+ }