@torpor/ui 0.3.7 → 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.
- package/dist/Accordion/Accordion.torp +26 -25
- package/dist/Accordion/AccordionItem.torp +3 -3
- package/dist/Accordion/AccordionTypes.ts +5 -5
- package/dist/Accordion/index.d.ts +2 -2
- package/dist/Calendar/CalendarTypes.ts +1 -1
- package/dist/{Chart → Charts}/GridLines.torp +3 -0
- package/dist/{Chart → Charts}/XAxis.torp +7 -1
- package/dist/{Chart → Charts}/YAxis.torp +7 -1
- package/dist/{Chart → Charts}/index.js +8 -8
- package/dist/Contextual/Contextual.torp +8 -3
- package/dist/Contextual/ContextualTypes.ts +1 -1
- package/dist/Dialog/Dialog.torp +12 -16
- package/dist/Dialog/DialogButton.torp +3 -0
- package/dist/Dialog/DialogTypes.ts +2 -2
- package/dist/Dialog/index.d.ts +2 -0
- package/dist/Disclosure/Disclosure.torp +3 -3
- package/dist/Disclosure/DisclosureTypes.ts +1 -1
- package/dist/Drawer/Drawer.torp +2 -2
- package/dist/Drawer/DrawerTypes.ts +1 -1
- package/dist/Form/CheckBox.torp +1 -2
- package/dist/Form/Input.torp +1 -1
- package/dist/Form/MessageSummary.torp +38 -0
- package/dist/Form/index.d.ts +23 -1
- package/dist/Form/index.js +4 -0
- package/dist/ListBox/ListBox.torp +271 -0
- package/dist/ListBox/ListBoxItem.torp +107 -0
- package/dist/ListBox/ListBoxSeparator.torp +26 -0
- package/dist/ListBox/ListBoxTypes.ts +23 -0
- package/dist/ListBox/index.d.ts +97 -0
- package/dist/ListBox/index.js +14 -0
- package/dist/ListBox/index.js.map +1 -0
- package/dist/Menu/Menu.torp +19 -14
- package/dist/Menu/MenuTypes.ts +5 -4
- package/dist/MenuBar/MenuBarItem.torp +5 -8
- package/dist/MenuBar/MenuBarTypes.ts +2 -2
- package/dist/Modal/Modal.torp +7 -2
- package/dist/Modal/ModalTypes.ts +1 -1
- package/dist/NavMenu/NavMenu.torp +7 -0
- package/dist/NavMenu/NavMenuButton.torp +5 -2
- package/dist/NavMenu/NavMenuPopout.torp +1 -1
- package/dist/NavMenu/{NavMenuPopoutButton.torp → NavMenuPopoutTrigger.torp} +4 -4
- package/dist/NavMenu/NavMenuSeparator.torp +2 -1
- package/dist/NavMenu/NavMenuTypes.ts +4 -3
- package/dist/NavMenu/index.d.ts +22 -18
- package/dist/NavMenu/index.js +4 -4
- package/dist/Pagination/Pagination.torp +145 -0
- package/dist/Pagination/PaginationItem.torp +100 -0
- package/dist/Pagination/PaginationTypes.ts +25 -0
- package/dist/Pagination/buildPages.ts +57 -0
- package/dist/Pagination/index.d.ts +53 -0
- package/dist/Pagination/index.js +40 -0
- package/dist/Pagination/index.js.map +1 -0
- package/dist/Popover/Popover.torp +8 -3
- package/dist/Popover/PopoverTypes.ts +1 -1
- package/dist/SelectBox/SelectBox.torp +136 -0
- package/dist/SelectBox/SelectBoxContent.torp +81 -0
- package/dist/SelectBox/SelectBoxInput.torp +95 -0
- package/dist/SelectBox/SelectBoxTrigger.torp +41 -0
- package/dist/SelectBox/SelectBoxTypes.ts +18 -0
- package/dist/SelectBox/index.d.ts +127 -0
- package/dist/SelectBox/index.js +18 -0
- package/dist/SelectBox/index.js.map +1 -0
- package/dist/TabGroup/TabGroup.torp +3 -3
- package/dist/TabGroup/TabGroupTypes.ts +2 -2
- package/dist/ToolBar/ToolBar.torp +198 -0
- package/dist/ToolBar/ToolBarButton.torp +57 -0
- package/dist/ToolBar/ToolBarElement.torp +55 -0
- package/dist/ToolBar/ToolBarPopout.torp +97 -0
- package/dist/ToolBar/ToolBarPopoutContent.torp +92 -0
- package/dist/ToolBar/ToolBarPopoutTrigger.torp +138 -0
- package/dist/ToolBar/ToolBarSeparator.torp +25 -0
- package/dist/ToolBar/ToolBarTypes.ts +35 -0
- package/dist/ToolBar/index.d.ts +212 -0
- package/dist/ToolBar/index.js +30 -0
- package/dist/ToolBar/index.js.map +1 -0
- package/dist/metafile-esm.json +1 -1
- package/dist/utils/PopoutTypes.ts +35 -0
- package/dist/utils/chartUtils.ts +1 -1
- package/package.json +22 -6
- /package/dist/{Chart → Charts}/BarChart.torp +0 -0
- /package/dist/{Chart → Charts}/DataSeries.ts +0 -0
- /package/dist/{Chart → Charts}/LineChart.torp +0 -0
- /package/dist/{Chart → Charts}/PieChart.torp +0 -0
- /package/dist/{Chart → Charts}/ScatterChart.torp +0 -0
- /package/dist/{Chart → Charts}/SparkLine.torp +0 -0
- /package/dist/{Chart → Charts}/index.d.ts +0 -0
- /package/dist/{Chart → Charts}/index.js.map +0 -0
|
@@ -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
|
+
}
|