@torpor/ui 0.3.6 → 0.4.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 (89) hide show
  1. package/dist/Accordion/Accordion.torp +26 -25
  2. package/dist/Accordion/AccordionItem.torp +3 -3
  3. package/dist/Accordion/AccordionTypes.ts +5 -5
  4. package/dist/Accordion/index.d.ts +2 -2
  5. package/dist/Calendar/CalendarTypes.ts +1 -1
  6. package/dist/Charts/BarChart.torp +159 -0
  7. package/dist/Charts/DataSeries.ts +5 -0
  8. package/dist/Charts/GridLines.torp +65 -0
  9. package/dist/Charts/LineChart.torp +196 -0
  10. package/dist/Charts/PieChart.torp +145 -0
  11. package/dist/Charts/ScatterChart.torp +215 -0
  12. package/dist/Charts/SparkLine.torp +71 -0
  13. package/dist/Charts/XAxis.torp +66 -0
  14. package/dist/Charts/YAxis.torp +47 -0
  15. package/dist/Charts/index.d.ts +220 -0
  16. package/dist/Charts/index.js +34 -0
  17. package/dist/Charts/index.js.map +1 -0
  18. package/dist/Contextual/Contextual.torp +8 -3
  19. package/dist/Contextual/ContextualTypes.ts +1 -1
  20. package/dist/Dialog/Dialog.torp +12 -16
  21. package/dist/Dialog/DialogButton.torp +3 -0
  22. package/dist/Dialog/DialogTypes.ts +2 -2
  23. package/dist/Dialog/index.d.ts +2 -0
  24. package/dist/Disclosure/Disclosure.torp +3 -3
  25. package/dist/Disclosure/DisclosureTypes.ts +1 -1
  26. package/dist/Drawer/Drawer.torp +2 -2
  27. package/dist/Drawer/DrawerTypes.ts +1 -1
  28. package/dist/Form/CheckBox.torp +1 -2
  29. package/dist/Form/Input.torp +1 -1
  30. package/dist/Form/MessageSummary.torp +38 -0
  31. package/dist/Form/TextArea.torp +3 -0
  32. package/dist/Form/index.d.ts +25 -1
  33. package/dist/Form/index.js +4 -0
  34. package/dist/ListBox/ListBox.torp +271 -0
  35. package/dist/ListBox/ListBoxItem.torp +107 -0
  36. package/dist/ListBox/ListBoxSeparator.torp +26 -0
  37. package/dist/ListBox/ListBoxTypes.ts +23 -0
  38. package/dist/ListBox/index.d.ts +97 -0
  39. package/dist/ListBox/index.js +14 -0
  40. package/dist/ListBox/index.js.map +1 -0
  41. package/dist/Menu/Menu.torp +19 -14
  42. package/dist/Menu/MenuTypes.ts +5 -4
  43. package/dist/MenuBar/MenuBarItem.torp +5 -8
  44. package/dist/MenuBar/MenuBarTypes.ts +2 -2
  45. package/dist/Modal/Modal.torp +7 -2
  46. package/dist/Modal/ModalTypes.ts +1 -1
  47. package/dist/NavMenu/NavMenu.torp +7 -0
  48. package/dist/NavMenu/NavMenuButton.torp +5 -2
  49. package/dist/NavMenu/NavMenuPopout.torp +1 -1
  50. package/dist/NavMenu/{NavMenuPopoutButton.torp → NavMenuPopoutTrigger.torp} +4 -4
  51. package/dist/NavMenu/NavMenuSeparator.torp +2 -1
  52. package/dist/NavMenu/NavMenuTypes.ts +4 -3
  53. package/dist/NavMenu/index.d.ts +22 -18
  54. package/dist/NavMenu/index.js +4 -4
  55. package/dist/Pagination/Pagination.torp +145 -0
  56. package/dist/Pagination/PaginationItem.torp +100 -0
  57. package/dist/Pagination/PaginationTypes.ts +25 -0
  58. package/dist/Pagination/buildPages.ts +57 -0
  59. package/dist/Pagination/index.d.ts +53 -0
  60. package/dist/Pagination/index.js +40 -0
  61. package/dist/Pagination/index.js.map +1 -0
  62. package/dist/Popover/Popover.torp +8 -3
  63. package/dist/Popover/PopoverTypes.ts +1 -1
  64. package/dist/SelectBox/SelectBox.torp +136 -0
  65. package/dist/SelectBox/SelectBoxContent.torp +81 -0
  66. package/dist/SelectBox/SelectBoxInput.torp +95 -0
  67. package/dist/SelectBox/SelectBoxTrigger.torp +41 -0
  68. package/dist/SelectBox/SelectBoxTypes.ts +18 -0
  69. package/dist/SelectBox/index.d.ts +127 -0
  70. package/dist/SelectBox/index.js +18 -0
  71. package/dist/SelectBox/index.js.map +1 -0
  72. package/dist/TabGroup/TabGroup.torp +3 -3
  73. package/dist/TabGroup/TabGroupTypes.ts +2 -2
  74. package/dist/ToolBar/ToolBar.torp +198 -0
  75. package/dist/ToolBar/ToolBarButton.torp +57 -0
  76. package/dist/ToolBar/ToolBarElement.torp +55 -0
  77. package/dist/ToolBar/ToolBarPopout.torp +97 -0
  78. package/dist/ToolBar/ToolBarPopoutContent.torp +92 -0
  79. package/dist/ToolBar/ToolBarPopoutTrigger.torp +138 -0
  80. package/dist/ToolBar/ToolBarSeparator.torp +25 -0
  81. package/dist/ToolBar/ToolBarTypes.ts +35 -0
  82. package/dist/ToolBar/index.d.ts +212 -0
  83. package/dist/ToolBar/index.js +30 -0
  84. package/dist/ToolBar/index.js.map +1 -0
  85. package/dist/metafile-esm.json +1 -1
  86. package/dist/utils/PopoutTypes.ts +35 -0
  87. package/dist/utils/chartColors.ts +17 -0
  88. package/dist/utils/chartUtils.ts +71 -0
  89. package/package.json +22 -2
@@ -0,0 +1,198 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import {
3
+ ToolBarContextName,
4
+ type ToolBarContext,
5
+ type ItemState,
6
+ ToolBarPopoutContextName,
7
+ type ToolBarPopoutContext
8
+ } from "./ToolBarTypes";
9
+
10
+ interface ToolBarProps {
11
+ /** An ID for the root element */
12
+ id?: string;
13
+ /** Classes for the root element */
14
+ class?: ClassValue,
15
+ /** Styles for the root element */
16
+ style?: StyleValue,
17
+ /** An ARIA label for describing the menu to screen readers */
18
+ ariaLabel?: string | undefined,
19
+ /** The orientation of the menu, which affects keyboard navigation */
20
+ orientation?: "horizontal" | "vertical",
21
+ }
22
+
23
+ /**
24
+ * A toolbar is a container for grouping a set of controls, such as buttons, menubuttons, or checkboxes.
25
+ *
26
+ * See [the WAI ARIA guidelines for ToolBars](https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/).
27
+ *
28
+ * The structure of a ToolBar is:
29
+ *
30
+ * ```
31
+ * <ToolBar>
32
+ * <ToolBarButton />
33
+ * <ToolBarSeparator /> (optional)
34
+ * <ToolBarPopout /> (optional)
35
+ * ...
36
+ * </ToolBar>
37
+ * ```
38
+ */
39
+ export default function ToolBar($props: ToolBarProps | undefined) {
40
+ let orientation = $props?.orientation ?? "horizontal";
41
+
42
+ // A collection containing the state and some functions for each item
43
+ let itemStates: ItemState[] = [];
44
+ let focusIndex = -1;
45
+
46
+ // Check whether this is a menu in a popout
47
+ const popoutContext = $context[ToolBarPopoutContextName] as ToolBarPopoutContext;
48
+ const inPopout = !!popoutContext;
49
+ let popoutState = popoutContext?.state;
50
+
51
+ // Set the focusFirstElement method in the PopoverContext(s) so that it can be called after everything has been shown
52
+ if (popoutContext) {
53
+ popoutContext.focusFirstElement = focusFirstElement;
54
+ popoutContext.focusLastElement = focusLastElement;
55
+ }
56
+
57
+ // Set the context to pass down to items
58
+ let context: ToolBarContext = {
59
+ handleItemFocus,
60
+ handleItemKey,
61
+ registerItem,
62
+ orientation
63
+ };
64
+ $context[ToolBarContextName] = context;
65
+
66
+ // If the ToolBar is not in a popout, focus its first item. Otherwise the popout will call focusFirstElement, below
67
+ function handleFocus() {
68
+ if (!popoutContext && itemStates.length) {
69
+ itemStates[0].setFocused();
70
+ }
71
+ }
72
+
73
+ function focusFirstElement() {
74
+ if (itemStates.length) {
75
+ itemStates[0].setFocused();
76
+ }
77
+ }
78
+
79
+ function focusLastElement() {
80
+ if (itemStates.length > 0) {
81
+ itemStates.at(-1)!.setFocused();
82
+ }
83
+ }
84
+
85
+ function registerItem(setFocused: () => void) {
86
+ // Add the item's state to the array and return its index
87
+ const newItemState: ItemState = {
88
+ setFocused
89
+ };
90
+ itemStates.push(newItemState);
91
+ itemStates = itemStates;
92
+ return { index: itemStates.length - 1 };
93
+ }
94
+
95
+ function handleItemFocus(index: number) {
96
+ focusIndex = index;
97
+ }
98
+
99
+ function handleItemKey(e: KeyboardEvent) {
100
+ switch (e.key) {
101
+ case "Esc":
102
+ case "Escape": {
103
+ if (inPopout) {
104
+ e.preventDefault();
105
+ //setPopoutVisible(false);
106
+ popoutState.visible = false;
107
+ }
108
+ break;
109
+ }
110
+ case "Right":
111
+ case "ArrowRight": {
112
+ if (orientation === "horizontal") {
113
+ e.preventDefault();
114
+ focusItem("next");
115
+ }
116
+ break;
117
+ }
118
+ case "Left":
119
+ case "ArrowLeft": {
120
+ if (orientation === "horizontal") {
121
+ e.preventDefault();
122
+ focusItem("previous");
123
+ }
124
+ break;
125
+ }
126
+ case "Down":
127
+ case "ArrowDown": {
128
+ if (orientation === "vertical") {
129
+ e.preventDefault();
130
+ focusItem("next");
131
+ }
132
+ break;
133
+ }
134
+ case "Up":
135
+ case "ArrowUp": {
136
+ if (orientation === "vertical") {
137
+ e.preventDefault();
138
+ focusItem("previous");
139
+ } else {
140
+ // TODO: This should be based on what side the menu shows on
141
+ // And on the orientation
142
+ if (inPopout) {
143
+ e.preventDefault();
144
+ popoutState.visible = false;
145
+ }
146
+ }
147
+ break;
148
+ }
149
+ case "Home": {
150
+ e.preventDefault();
151
+ focusItem("start");
152
+ break;
153
+ }
154
+ case "End": {
155
+ e.preventDefault();
156
+ focusItem("end");
157
+ break;
158
+ }
159
+ }
160
+ }
161
+
162
+ function focusItem(target: "start" | "previous" | "next" | "end") {
163
+ switch (target) {
164
+ case "start": {
165
+ focusIndex = 0;
166
+ break;
167
+ }
168
+ case "previous": {
169
+ focusIndex = Math.max(0, focusIndex - 1);
170
+ break;
171
+ }
172
+ case "next": {
173
+ focusIndex = Math.min(itemStates.length - 1, focusIndex + 1);
174
+ break;
175
+ }
176
+ case "end": {
177
+ focusIndex = itemStates.length - 1;
178
+ break;
179
+ }
180
+ }
181
+ itemStates[focusIndex]?.setFocused();
182
+ }
183
+
184
+ @render {
185
+ <nav
186
+ {$props?.id}
187
+ class={["torp-tool-bar", $props?.class]}
188
+ {$props?.style}
189
+ tabindex="0"
190
+ role="toolbar"
191
+ aria-label={$props?.ariaLabel}
192
+ aria-orientation={orientation}
193
+ onfocus={handleFocus}
194
+ >
195
+ <slot />
196
+ </nav>
197
+ }
198
+ }
@@ -0,0 +1,57 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import { ToolBarContextName, type ToolBarContext } from "./ToolBarTypes";
3
+
4
+ interface ToolBarButtonProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Classes for the root element */
8
+ class?: ClassValue,
9
+ /** Styles for the root element */
10
+ style?: StyleValue,
11
+ /** Whether this button is disabled */
12
+ disabled?: boolean,
13
+ /** Raised when the button is clicked */
14
+ onclick?: (e: MouseEvent) => void,
15
+ /** Raised when a key is pressed down */
16
+ onkeydown?: (e: KeyboardEvent) => void,
17
+ }
18
+
19
+ /**
20
+ * A button that is placed in a ToolBar.
21
+ *
22
+ */
23
+ export default function ToolBarButton($props: ToolBarButtonProps | undefined) {
24
+ let button: HTMLButtonElement;
25
+
26
+ // Get stuff out of the ToolBarContext
27
+ const menuContext = $context[ToolBarContextName] as ToolBarContext;
28
+ if (!menuContext) {
29
+ throw new Error("ToolBarButton must be contained within a ToolBar");
30
+ }
31
+ const { registerItem, handleItemKey, handleItemFocus } = menuContext;
32
+
33
+ // Register this item with the parent ToolBar and store the index
34
+ const { index } = registerItem(setFocused);
35
+
36
+ function setFocused() {
37
+ button.focus();
38
+ }
39
+
40
+ @render {
41
+ <button
42
+ {$props?.id}
43
+ class={["torp-tool-bar-button", $props?.class]}
44
+ {$props?.style}
45
+ type="button"
46
+ {$props?.disabled}
47
+ aria-disabled={$props?.disabled ? "true" : undefined}
48
+ data-disabled={$props?.disabled ? "true" : undefined}
49
+ {$props?.onclick}
50
+ onkeydown={handleItemKey}
51
+ onfocus={() => handleItemFocus(index)}
52
+ &ref={button}
53
+ >
54
+ <slot />
55
+ </button>
56
+ }
57
+ }
@@ -0,0 +1,55 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import { ToolBarContextName, type ToolBarContext } from "./ToolBarTypes";
3
+
4
+ interface ToolBarElementProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Classes for the root element */
8
+ class?: ClassValue,
9
+ /** Styles for the root element */
10
+ style?: StyleValue,
11
+ /** Whether this button is disabled */
12
+ disabled?: boolean,
13
+ /** An element that can be focused via keyboard navigation */
14
+ element?: HTMLElement,
15
+ }
16
+
17
+ /**
18
+ * An element of any type that can be placed in a ToolBar.
19
+ *
20
+ */
21
+ export default function ToolBarElement($props: ToolBarElementProps | undefined) {
22
+ // Get stuff out of the ToolBarContext
23
+ const menuContext = $context[ToolBarContextName] as ToolBarContext;
24
+ if (!menuContext) {
25
+ throw new Error("ToolBarElement must be contained within a ToolBar");
26
+ }
27
+ const { registerItem, handleItemKey, handleItemFocus } = menuContext;
28
+
29
+ // Register this item with the parent ToolBar and store the index
30
+ const { index } = registerItem(setFocused);
31
+
32
+ function setFocused() {
33
+ if ($props?.element) {
34
+ $props.element.focus();
35
+ }
36
+ }
37
+
38
+ @render {
39
+ <div
40
+ {$props?.id}
41
+ class={["torp-tool-bar-div", $props?.class]}
42
+ {$props?.style}
43
+ type="div"
44
+ role="menuitem"
45
+ aria-disabled={$props?.disabled ? "true" : undefined}
46
+ data-disabled={$props?.disabled ? "true" : undefined}
47
+ onkeydown
48
+ onkeydown={handleItemKey}
49
+ onclick
50
+ onfocus={() => handleItemFocus(index)}
51
+ >
52
+ <slot />
53
+ </div>
54
+ }
55
+ }
@@ -0,0 +1,97 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import { ToolBarPopoutContextName, type ToolBarPopoutContext } from "./ToolBarTypes";
3
+ import { PopoutContextName, type PopoutContext } from "../utils/PopoutTypes";
4
+
5
+ interface ToolBarPopoutProps {
6
+ /** An ID for the root element */
7
+ id?: string;
8
+ /** Classes for the root element */
9
+ class?: ClassValue,
10
+ /** Styles for the root element */
11
+ style?: StyleValue,
12
+ /** Whether the popout is visible */
13
+ visible?: boolean,
14
+ /** Raised when the popout is opened or closed */
15
+ ontoggle?: (open: boolean) => void,
16
+ /** Raised when the popout has opened */
17
+ onopen?: () => void,
18
+ /** Raised when the popout has closed */
19
+ onclose?: (result: any) => void,
20
+ }
21
+
22
+ /**
23
+ * A popout that is attached to a menu button and can be used to show a submenu.
24
+ *
25
+ * The structure of a ToolBarPopout is:
26
+ *
27
+ * ```
28
+ * <ToolBarPopout>
29
+ * <ToolBarPopoutTrigger />
30
+ * <ToolBarPopoutContent />
31
+ * </ToolBarPopout>
32
+ * ```
33
+ */
34
+ export default function ToolBarPopout($props: ToolBarPopoutProps | undefined) {
35
+ let $state = $watch({
36
+ visible: false
37
+ });
38
+
39
+ // Sync props and state
40
+ $run(() => {
41
+ $state.visible = $props?.visible ?? false;
42
+ });
43
+ $run(() => {
44
+ if ($props !== undefined) {
45
+ $props.visible = $state.visible;
46
+ }
47
+ });
48
+
49
+ let result: any;
50
+
51
+ // Set the context to pass down to items
52
+ let context: ToolBarPopoutContext = {
53
+ state: $state,
54
+ };
55
+ $context[ToolBarPopoutContextName] = context;
56
+
57
+ // Set the context to pass down to items
58
+ let popoutContext: PopoutContext = {
59
+ handleButton: () => {},
60
+ state: $state,
61
+ };
62
+ $context[PopoutContextName] = popoutContext;
63
+
64
+ // Dispatch the toggle event when the popout is opened or closed
65
+ let firstToggle = true;
66
+ $run(() => {
67
+ const ontoggle = $props?.ontoggle;
68
+ const onopen = $props?.onopen;
69
+ const onclose = $props?.onclose;
70
+ const visible = $state.visible;
71
+ if (firstToggle) {
72
+ firstToggle = false;
73
+ } else {
74
+ if (ontoggle) ontoggle(visible);
75
+ if (visible) {
76
+ if (onopen) onopen()
77
+ } else {
78
+ if (onclose) onclose(result);
79
+ }
80
+ }
81
+ });
82
+
83
+ function handleMouseLeave() {
84
+ $state.visible = false;
85
+ }
86
+
87
+ @render {
88
+ <div
89
+ {$props?.id}
90
+ class={["torp-tool-bar-popout", $props?.class]}
91
+ {$props?.style}
92
+ onmouseleave={handleMouseLeave}
93
+ >
94
+ <slot />
95
+ </div>
96
+ }
97
+ }
@@ -0,0 +1,92 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import getScrollParent from "../utils/getScrollParent";
3
+ import setPopoverPosition from "../utils/setPopoverPosition";
4
+ import { ToolBarPopoutContextName, type ToolBarPopoutContext } from "./ToolBarTypes";
5
+
6
+ interface ToolBarPopoutContentProps {
7
+ /** An ID for the root element */
8
+ id?: string;
9
+ /** Classes for the root element */
10
+ class?: ClassValue,
11
+ /** Styles for the root element */
12
+ style?: StyleValue,
13
+ /** Where the popout will be shown, relative to the popout button */
14
+ side?: "top" | "right" | "bottom" | "left",
15
+ /** How the popout will be aligned, relative to the popout button */
16
+ alignment?: "start" | "center" | "end",
17
+ }
18
+
19
+ /**
20
+ * The content that is displayed for a ToolBarPopout when it is open.
21
+ *
22
+ */
23
+ export default function ToolBarPopoutContent($props: ToolBarPopoutContentProps | undefined) {
24
+ let div: HTMLDivElement;
25
+ let shown = false;
26
+
27
+ // Get stuff out of the ToolBarPopoutContext
28
+ const context = $context[ToolBarPopoutContextName] as ToolBarPopoutContext;
29
+ if (!context) {
30
+ throw new Error("ToolBarPopoutContent must be contained within a ToolBarPopout");
31
+ }
32
+ let $state = context.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
+ const anchorElement = context.anchorElement!
46
+ const scrollParent = getScrollParent(anchorElement)
47
+ scrollParent.addEventListener("resize", setPosition);
48
+ scrollParent.addEventListener("scroll", setPosition);
49
+ if (context.focusFirstElement) {
50
+ context.focusFirstElement();
51
+ }
52
+ }
53
+
54
+ function hide() {
55
+ if (!shown) return;
56
+
57
+ // Stop listening for window resize and focus the anchor element per WAI guidelines
58
+ const anchorElement = context.anchorElement!
59
+ const scrollParent = getScrollParent(anchorElement)
60
+ scrollParent.removeEventListener("resize", setPosition);
61
+ scrollParent.removeEventListener("scroll", setPosition);
62
+ if (anchorElement.focus) {
63
+ anchorElement.focus();
64
+ }
65
+ }
66
+
67
+ function setPosition() {
68
+ if (!context.anchorElement) {
69
+ throw new Error("ToolBarPopoutContent must have an anchor element");
70
+ }
71
+
72
+ if (div) {
73
+ let side = $props?.side ?? "bottom";
74
+ let alignment = $props?.alignment ?? "start";
75
+ setPopoverPosition(context.anchorElement, div, side, alignment);
76
+ }
77
+ }
78
+
79
+ @render {
80
+ @if ($state.visible) {
81
+ <div
82
+ {$props?.id}
83
+ class={["torp-tool-bar-popout-content", $props?.class]}
84
+ {$props?.style}
85
+ &ref={div}
86
+ onmount={show}
87
+ >
88
+ <slot />
89
+ </div>
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,138 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import {
3
+ ToolBarContextName,
4
+ ToolBarPopoutContextName,
5
+ type ToolBarContext,
6
+ type ToolBarPopoutContext,
7
+ } from "./ToolBarTypes";
8
+
9
+ interface ToolBarPopoutTriggerProps {
10
+ /** An ID for the root element */
11
+ id?: string;
12
+ /** Classes for the root element */
13
+ class?: ClassValue,
14
+ /** Styles for the root element */
15
+ style?: StyleValue,
16
+ /** The delay before showing the popout, in milliseconds */
17
+ hoverDelay?: number,
18
+ /** Whether this button is disabled */
19
+ disabled?: boolean,
20
+ }
21
+
22
+ /**
23
+ * A button that is placed in a ToolBar and signals a response by a user.
24
+ *
25
+ */
26
+ export default function ToolBarPopoutTrigger($props: ToolBarPopoutTriggerProps | undefined) {
27
+ let defaultHoverDelay = 500;
28
+
29
+ let button: HTMLButtonElement;
30
+ let interval: NodeJS.Timeout | undefined;
31
+
32
+ // Get stuff out of the ToolBarContext
33
+ const toolBarContext = $context[ToolBarContextName] as ToolBarContext;
34
+ if (!toolBarContext) {
35
+ throw new Error("ToolBarPopoutTrigger must be contained within a ToolBar");
36
+ }
37
+ const { registerItem, handleItemKey, handleItemFocus } = toolBarContext;
38
+
39
+ // Get stuff out of the ToolBarPopoutContext
40
+ const popoutContext = $context[ToolBarPopoutContextName] as ToolBarPopoutContext;
41
+ if (!popoutContext) {
42
+ throw new Error("ToolBarPopoutTrigger must be contained within a ToolBarPopout");
43
+ }
44
+ let $state = popoutContext.state;
45
+
46
+ // Register this item with the parent ToolBar and store the index
47
+ const { index } = registerItem(setFocused);
48
+
49
+ // TODO: $mount should be run AFTER the button has been set
50
+ //$mount(() => {
51
+ // // Set the anchor element to the button when it exists
52
+ // popoutContext.anchorElement = button;
53
+ //});
54
+
55
+ function setFocused() {
56
+ button.focus();
57
+ }
58
+
59
+ function handleClick() {
60
+ $state.visible = true;
61
+ }
62
+
63
+ function handlePopoutKey(e: KeyboardEvent) {
64
+ switch (e.key) {
65
+ case "Right":
66
+ case "ArrowRight":
67
+ if (toolBarContext.orientation === "vertical") {
68
+ // TODO: This should be based on what side the menu shows on
69
+ e.preventDefault();
70
+ $state.visible = true;
71
+ }
72
+ break;
73
+ case "Down":
74
+ case "ArrowDown":
75
+ if (toolBarContext.orientation === "horizontal") {
76
+ e.preventDefault();
77
+ $state.visible = true;
78
+ }
79
+ break;
80
+ default:
81
+ handleItemKey(e);
82
+ }
83
+ }
84
+
85
+ function handleMouseEnter() {
86
+ if ($props?.disabled) return;
87
+
88
+ // Handle the hover after a delay
89
+ if (!interval) {
90
+ interval = setTimeout(() => $state.visible = true, $props?.hoverDelay ?? defaultHoverDelay);
91
+ }
92
+ }
93
+
94
+ function handleMouseLeave() {
95
+ if ($props?.disabled) return;
96
+
97
+ // Prevent short hovers or touches from firing the event
98
+ if (interval) {
99
+ clearTimeout(interval);
100
+ interval = undefined;
101
+ }
102
+ }
103
+
104
+ function handleMouseMove() {
105
+ if ($props?.disabled) return;
106
+
107
+ // Reset the interval on mouse move
108
+ if (interval) {
109
+ clearTimeout(interval);
110
+ interval = setTimeout(() => $state.visible = true, $props?.hoverDelay ?? defaultHoverDelay);
111
+ }
112
+ }
113
+
114
+ @render {
115
+ <button
116
+ {$props?.id}
117
+ class={["torp-tool-bar-popout-trigger", $props?.class]}
118
+ {$props?.style}
119
+ type="button"
120
+ {$props?.disabled}
121
+ role="menuitem"
122
+ aria-haspopup="true"
123
+ aria-expanded={$state.visible.toString()}
124
+ aria-disabled={$props?.disabled ? "true" : undefined}
125
+ data-disabled={$props?.disabled ? "true" : undefined}
126
+ onkeydown={handlePopoutKey}
127
+ onclick={handleClick}
128
+ onfocus={() => handleItemFocus(index)}
129
+ onmouseenter={handleMouseEnter}
130
+ onmouseleave={handleMouseLeave}
131
+ onmousemove={handleMouseMove}
132
+ &ref={button}
133
+ onmount={(el) => popoutContext.anchorElement = el }
134
+ >
135
+ <slot />
136
+ </button>
137
+ }
138
+ }
@@ -0,0 +1,25 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+
3
+ interface ToolBarSeparatorProps {
4
+ /** An ID for the root element */
5
+ id?: string;
6
+ /** Classes for the root element */
7
+ class?: ClassValue,
8
+ /** Styles for the root element */
9
+ style?: StyleValue,
10
+ }
11
+
12
+ /**
13
+ * An element in a menu that separates logical sets of menu items.
14
+ */
15
+ export default function ToolBarSeparator($props: ToolBarSeparatorProps | undefined) {
16
+ @render {
17
+ <div
18
+ {$props?.id}
19
+ class={["torp-tool-bar-separator", $props?.class]}
20
+ role="separator"
21
+ >
22
+ <slot />
23
+ </div>
24
+ }
25
+ }
@@ -0,0 +1,35 @@
1
+ export const ToolBarContextName: unique symbol = Symbol.for("torp.ToolBar");
2
+ export const ToolBarPopoutContextName: unique symbol = Symbol.for("torp.ToolBarPopout");
3
+ export const ToolBarGroupContextName: unique symbol = Symbol.for("torp.ToolBarGroup");
4
+
5
+ export interface ToolBarContext {
6
+ /** Called from a ToolBarLink or ToolBarButton when its button is focused */
7
+ handleItemFocus: (index: number) => void;
8
+
9
+ /** Called from a ToolBarLink or ToolBarButton when a key is pressed */
10
+ handleItemKey: (e: KeyboardEvent) => void;
11
+
12
+ /** Called from each ToolBarLink or ToolBarButton to register itself with this ToolBar */
13
+ registerItem: (setFocused: () => void) => { index: number };
14
+
15
+ orientation: "horizontal" | "vertical";
16
+ }
17
+
18
+ export interface ToolBarPopoutContext {
19
+ state: {
20
+ visible: boolean;
21
+ };
22
+ anchorElement?: HTMLElement;
23
+ focusFirstElement?: () => void;
24
+ focusLastElement?: () => void;
25
+ }
26
+
27
+ export interface ToolBarGroupContext {
28
+ state: {
29
+ headerId: string;
30
+ };
31
+ }
32
+
33
+ export interface ItemState {
34
+ setFocused: () => void;
35
+ }