@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,271 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import getId from "../utils/getId";
|
|
3
|
+
import { PopoutContextName, type PopoutContext, type HandleButtonPress } from "../utils/PopoutTypes";
|
|
4
|
+
import { ListBoxContextName, type ListBoxContext, type ItemState } from "./ListBoxTypes";
|
|
5
|
+
|
|
6
|
+
// TODO: orientation
|
|
7
|
+
|
|
8
|
+
interface ListBoxProps {
|
|
9
|
+
/** An ID for the root element */
|
|
10
|
+
id?: string;
|
|
11
|
+
/** Classes for the root element */
|
|
12
|
+
class?: ClassValue,
|
|
13
|
+
/** Styles for the root element */
|
|
14
|
+
style?: StyleValue,
|
|
15
|
+
/** Whether a single ListBoxItem or multiple ListBoxItems can be selected at the same time */
|
|
16
|
+
type?: "single" | "multiple",
|
|
17
|
+
/** The value of the selected ListBoxItem, or an array of values if the type is "multiple" */
|
|
18
|
+
value?: any | any[],
|
|
19
|
+
/** If set to true, the user cannot interact with any items in the ListBox */
|
|
20
|
+
disabled?: boolean,
|
|
21
|
+
/** An ARIA label for describing the ListBox to screen readers */
|
|
22
|
+
ariaLabel?: string | undefined,
|
|
23
|
+
/** Raised when the selected item(s) have changed */
|
|
24
|
+
onchange?: (value: any | any[]) => void,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A list of options that a user can select.
|
|
29
|
+
*
|
|
30
|
+
* See [the WAI ARIA guidelines for ListBoxes](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/).
|
|
31
|
+
*
|
|
32
|
+
* The structure of an ListBox is:
|
|
33
|
+
*
|
|
34
|
+
* ```
|
|
35
|
+
* <ListBox>
|
|
36
|
+
* <ListBoxItem />
|
|
37
|
+
* <ListBoxSeparator /> (optional)
|
|
38
|
+
* </ListBox>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export default function ListBox($props: ListBoxProps | undefined) {
|
|
42
|
+
let defaultId = getId();
|
|
43
|
+
let type = $props?.type ?? "single";
|
|
44
|
+
|
|
45
|
+
let $state = $watch({
|
|
46
|
+
value: $props?.value ?? (type === "single" ? undefined : [])
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Update $state on $props change
|
|
50
|
+
$run(() => {
|
|
51
|
+
$state.value = $props?.value ?? (type === "single" ? undefined : [])
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Update $props on $state change
|
|
55
|
+
$run(() => {
|
|
56
|
+
if ($props !== undefined) {
|
|
57
|
+
$props.value = $state.value;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
let itemStates: ItemState[] = [];
|
|
62
|
+
|
|
63
|
+
// This is used to assign automatic indexes to items where needed
|
|
64
|
+
let nextIndex = 0;
|
|
65
|
+
|
|
66
|
+
// Maybe get stuff out of a popout context
|
|
67
|
+
let handleButton: HandleButtonPress | undefined = undefined;
|
|
68
|
+
const popoutContext = $context[PopoutContextName] as PopoutContext;
|
|
69
|
+
if (popoutContext) {
|
|
70
|
+
handleButton = popoutContext.handleButton;
|
|
71
|
+
|
|
72
|
+
//popoutContext.handleClickOutside = handleClickOutside;
|
|
73
|
+
// Set the focusFirstElement method in the PopoutContext so that it can be
|
|
74
|
+
// called after everything has been shown
|
|
75
|
+
popoutContext.focusFirstElement = focusFirstElement;
|
|
76
|
+
popoutContext.focusLastElement = focusLastElement;
|
|
77
|
+
|
|
78
|
+
// Set the content ID so that it can be used with aria-controls in AccordionTrigger
|
|
79
|
+
popoutContext.state.contentId = $props?.id ?? defaultId;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Set the context to pass down to items, with functions for each item to call
|
|
83
|
+
let context: ListBoxContext = {
|
|
84
|
+
registerItem,
|
|
85
|
+
removeItem,
|
|
86
|
+
toggleItem,
|
|
87
|
+
handleKey,
|
|
88
|
+
};
|
|
89
|
+
$context[ListBoxContextName] = context;
|
|
90
|
+
|
|
91
|
+
function focusFirstElement() {
|
|
92
|
+
if (itemStates.length > 0) {
|
|
93
|
+
itemStates[0].setFocused();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function focusLastElement() {
|
|
98
|
+
if (itemStates.length > 0) {
|
|
99
|
+
itemStates.at(-1)!.setFocused();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
$run(() => {
|
|
104
|
+
// Get the value to ensure that this effect will be subscribed
|
|
105
|
+
let value = $state.value;
|
|
106
|
+
|
|
107
|
+
// And set the expanded item(s)
|
|
108
|
+
switch (type) {
|
|
109
|
+
case "single": {
|
|
110
|
+
for (let item of itemStates) {
|
|
111
|
+
item.selected = item.value === value;
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case "multiple": {
|
|
116
|
+
if (!Array.isArray(value)) {
|
|
117
|
+
value = [value];
|
|
118
|
+
}
|
|
119
|
+
for (let item of itemStates) {
|
|
120
|
+
item.selected = value.indexOf(item.value) !== -1;
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
$run(() => {
|
|
128
|
+
let disabled = $props?.disabled;
|
|
129
|
+
itemStates.forEach((s) => s.parentDisabled = disabled === true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Dispatch the change event when the value changes
|
|
133
|
+
let firstChange = true;
|
|
134
|
+
$run(() => {
|
|
135
|
+
const onchange = $props?.onchange;
|
|
136
|
+
const value = $state.value;
|
|
137
|
+
if (firstChange) {
|
|
138
|
+
firstChange = false;
|
|
139
|
+
} else if (onchange) {
|
|
140
|
+
onchange(value);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
function registerItem(itemState: ItemState) {
|
|
145
|
+
// If an index wasn't set for the item, set it automatically
|
|
146
|
+
if (itemState.index === -1) {
|
|
147
|
+
itemState.index = nextIndex++;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// If a value wasn't set for the item, set it to the index
|
|
151
|
+
if (!itemState.value) {
|
|
152
|
+
itemState.value = itemState.index;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// If we have a value, set whether the item should be selected
|
|
156
|
+
if ($state.value !== undefined && $state.value !== null) {
|
|
157
|
+
switch (type) {
|
|
158
|
+
case "single": {
|
|
159
|
+
itemState.selected = $state.value === itemState.value;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "multiple": {
|
|
163
|
+
itemState.selected = $state.value.includes(itemState.value);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Set whether the ListBox itself is disabled
|
|
170
|
+
itemState.parentDisabled = $props?.disabled === true
|
|
171
|
+
|
|
172
|
+
// Add the item's state to the array
|
|
173
|
+
itemStates.splice(itemState.index, 0, itemState);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// TODO: I think this should be done by value
|
|
177
|
+
function removeItem(itemIndex: number) {
|
|
178
|
+
// Remove the item and re-index
|
|
179
|
+
let index = itemStates.findIndex((item) => item.index === itemIndex);
|
|
180
|
+
if (index !== -1) {
|
|
181
|
+
itemStates.splice(itemIndex, 1);
|
|
182
|
+
}
|
|
183
|
+
itemStates.forEach((item, i) => (item.index = i));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function toggleItem(value: string) {
|
|
187
|
+
switch (type) {
|
|
188
|
+
case "single": {
|
|
189
|
+
for (let item of itemStates) {
|
|
190
|
+
if (item.value === value) {
|
|
191
|
+
$state.value = item.selected ? "" : item.value;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Pass this along to the containing popout if applicable, so
|
|
196
|
+
// that the popout can be closed
|
|
197
|
+
if (handleButton) {
|
|
198
|
+
handleButton("confirm", value)
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "multiple": {
|
|
203
|
+
let newValue = [];
|
|
204
|
+
for (let item of itemStates) {
|
|
205
|
+
if (
|
|
206
|
+
(item.value === value && !item.selected) ||
|
|
207
|
+
(item.value !== value && item.selected)
|
|
208
|
+
) {
|
|
209
|
+
newValue.push(item.value);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
$state.value = newValue.sort();
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function handleKey(index: number, e: KeyboardEvent) {
|
|
219
|
+
switch (e.key) {
|
|
220
|
+
case "Up":
|
|
221
|
+
case "ArrowUp": {
|
|
222
|
+
if (index > 0) {
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
itemStates[index - 1].setFocused();
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
case "Down":
|
|
229
|
+
case "ArrowDown": {
|
|
230
|
+
if (index < itemStates.length - 1) {
|
|
231
|
+
e.preventDefault();
|
|
232
|
+
itemStates[index + 1].setFocused();
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
case "Home": {
|
|
237
|
+
if (index > 0) {
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
itemStates[0].setFocused();
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case "End": {
|
|
244
|
+
if (index < itemStates.length - 1) {
|
|
245
|
+
e.preventDefault();
|
|
246
|
+
itemStates[itemStates.length - 1].setFocused();
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case "Escape": {
|
|
251
|
+
if (handleButton) {
|
|
252
|
+
handleButton("cancel")
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@render {
|
|
260
|
+
<div
|
|
261
|
+
id={$props?.id ?? defaultId}
|
|
262
|
+
class={["torp-list-box", $props?.class]}
|
|
263
|
+
{$props?.style}
|
|
264
|
+
role="listbox"
|
|
265
|
+
aria-label={$props?.ariaLabel}
|
|
266
|
+
aria-multiselectable={type === "multiple" ? "true" : undefined}
|
|
267
|
+
>
|
|
268
|
+
<slot />
|
|
269
|
+
</div>
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import { ListBoxContextName, type ItemState, type ListBoxContext } from "./ListBoxTypes";
|
|
3
|
+
|
|
4
|
+
interface ListBoxItemProps {
|
|
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
|
+
/** The index of this item in the ListBox. Set this if you will be inserting, removing or sorting items. If you don't set this, it will be set automatically */
|
|
12
|
+
index?: number,
|
|
13
|
+
/** A value for the item. Set this if you need more control over which item(s) are selected. If you don't set this, the value will be set to the item's initial index */
|
|
14
|
+
value?: string,
|
|
15
|
+
/** If set to true, the user cannot interact with this ListBoxItem */
|
|
16
|
+
disabled?: boolean,
|
|
17
|
+
/** Raised when this item is opened or closed */
|
|
18
|
+
ontoggle?: (open: boolean) => void,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An item in an ListBox.
|
|
23
|
+
*/
|
|
24
|
+
export default function ListBoxItem($props: ListBoxItemProps | undefined) {
|
|
25
|
+
let $state: ItemState = $watch({
|
|
26
|
+
index: -1,
|
|
27
|
+
value: "",
|
|
28
|
+
selected: false,
|
|
29
|
+
disabled: $props?.disabled ?? false,
|
|
30
|
+
parentDisabled: false,
|
|
31
|
+
setFocused
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
let button: HTMLButtonElement;
|
|
35
|
+
|
|
36
|
+
// Get stuff out of the ListBoxContext
|
|
37
|
+
const context = $context[ListBoxContextName] as ListBoxContext;
|
|
38
|
+
if (!context) {
|
|
39
|
+
throw new Error("ListBoxItem must be contained within a ListBox");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Register this item with the parent ListBox, which will handle toggling selected for all items
|
|
43
|
+
context.registerItem($state, setFocused);
|
|
44
|
+
|
|
45
|
+
// Remove the item from the parent ListBox when this item is destroyed
|
|
46
|
+
$run(() => {
|
|
47
|
+
return () => {
|
|
48
|
+
context.removeItem($state.index);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Dispatch the toggle event when the value of selected changes
|
|
53
|
+
let firstToggle = true;
|
|
54
|
+
$run(() => {
|
|
55
|
+
const ontoggle = $props?.ontoggle;
|
|
56
|
+
const selected = $state.selected;
|
|
57
|
+
if (firstToggle) {
|
|
58
|
+
firstToggle = false;
|
|
59
|
+
} else if (ontoggle) {
|
|
60
|
+
ontoggle(selected);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// This function is called by the parent ListBox to focus this item's button
|
|
65
|
+
function setFocused() {
|
|
66
|
+
button.focus();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handleClick() {
|
|
70
|
+
// Pass the value to the toggleItem method in the parent ListBox which will handle toggling
|
|
71
|
+
// the value of expanded for all items (e.g. to unselect other items if necessary)
|
|
72
|
+
context.toggleItem($state.value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function handleKey(e: KeyboardEvent) {
|
|
76
|
+
// For testing
|
|
77
|
+
switch (e.key) {
|
|
78
|
+
case "Enter":
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
context.toggleItem($state.value);
|
|
81
|
+
break;
|
|
82
|
+
default:
|
|
83
|
+
// Pass the key press up to the parent ListBox which will handle
|
|
84
|
+
// setting the focused button
|
|
85
|
+
context.handleKey($state.index, e);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@render {
|
|
91
|
+
<button
|
|
92
|
+
{$props?.id}
|
|
93
|
+
class={["torp-list-box-item", $props?.class]}
|
|
94
|
+
{$props?.style}
|
|
95
|
+
role="option"
|
|
96
|
+
aria-selected={$state.selected ? "true" : "false"}
|
|
97
|
+
aria-disabled={$state.disabled ? "true" : undefined}
|
|
98
|
+
data-state={$state.selected ? "active" : undefined}
|
|
99
|
+
data-disabled={$state.disabled ? "true" : undefined}
|
|
100
|
+
onclick={handleClick}
|
|
101
|
+
onkeydown={handleKey}
|
|
102
|
+
&ref={button}
|
|
103
|
+
>
|
|
104
|
+
<slot />
|
|
105
|
+
</button>
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
interface ListBoxSeparatorProps {
|
|
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 list box that separates logical sets of items.
|
|
14
|
+
*/
|
|
15
|
+
export default function ListBoxSeparator($props: ListBoxSeparatorProps | undefined) {
|
|
16
|
+
@render {
|
|
17
|
+
<div
|
|
18
|
+
{$props?.id}
|
|
19
|
+
class={["torp-list-box-separator", $props?.class]}
|
|
20
|
+
{$props?.style}
|
|
21
|
+
role="separator"
|
|
22
|
+
>
|
|
23
|
+
<slot />
|
|
24
|
+
</div>
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const ListBoxContextName: unique symbol = Symbol.for("torp.ListBox");
|
|
2
|
+
export const ListBoxItemContextName: unique symbol = Symbol.for("torp.ListBoxItem");
|
|
3
|
+
|
|
4
|
+
export interface ListBoxContext {
|
|
5
|
+
/** Called from an ListBoxItem when it is added */
|
|
6
|
+
registerItem: (state: ItemState, setFocused: () => void) => void;
|
|
7
|
+
/** Called from an ListBoxItem when it is removed */
|
|
8
|
+
removeItem: (index: number) => void;
|
|
9
|
+
/** Called from an ListBoxItem when it is toggled */
|
|
10
|
+
toggleItem: (value: string) => void;
|
|
11
|
+
/** Called from an ListBoxItem when it receives a keyboard event */
|
|
12
|
+
handleKey: (index: number, e: KeyboardEvent) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ItemState {
|
|
16
|
+
index: number;
|
|
17
|
+
value: any;
|
|
18
|
+
selected: boolean;
|
|
19
|
+
disabled: boolean;
|
|
20
|
+
parentDisabled: boolean;
|
|
21
|
+
/** Called in the ListBox when an item needs to be focused */
|
|
22
|
+
setFocused: () => void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type SlotRender } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface ListBoxProps {
|
|
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 a single ListBoxItem or multiple ListBoxItems can be selected at the same time */
|
|
12
|
+
type?: "single" | "multiple",
|
|
13
|
+
/** The value of the selected ListBoxItem, or an array of values if the type is "multiple" */
|
|
14
|
+
value?: any | any[],
|
|
15
|
+
/** If set to true, the user cannot interact with any items in the ListBox */
|
|
16
|
+
disabled?: boolean,
|
|
17
|
+
/** An ARIA label for describing the ListBox to screen readers */
|
|
18
|
+
ariaLabel?: string | undefined,
|
|
19
|
+
/** Raised when the selected item(s) have changed */
|
|
20
|
+
onchange?: (value: any | any[]) => void,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A list of options that a user can select.
|
|
25
|
+
*
|
|
26
|
+
* See [the WAI ARIA guidelines for ListBoxes](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/).
|
|
27
|
+
*
|
|
28
|
+
* The structure of an ListBox is:
|
|
29
|
+
*
|
|
30
|
+
* ```
|
|
31
|
+
* <ListBox>
|
|
32
|
+
* <ListBoxItem />
|
|
33
|
+
* <ListBoxSeparator /> (optional)
|
|
34
|
+
* </ListBox>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare function ListBox(
|
|
38
|
+
$parent: ParentNode,
|
|
39
|
+
$anchor: Node | null,
|
|
40
|
+
$props: ListBoxProps | undefined,
|
|
41
|
+
$context?: Record<PropertyKey, any>,
|
|
42
|
+
$slots?: Record<string, SlotRender>,
|
|
43
|
+
): void;
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
interface ListBoxItemProps {
|
|
48
|
+
/** An ID for the root element */
|
|
49
|
+
id?: string;
|
|
50
|
+
/** Classes for the root element */
|
|
51
|
+
class?: ClassValue,
|
|
52
|
+
/** Styles for the root element */
|
|
53
|
+
style?: StyleValue,
|
|
54
|
+
/** The index of this item in the ListBox. Set this if you will be inserting, removing or sorting items. If you don't set this, it will be set automatically */
|
|
55
|
+
index?: number,
|
|
56
|
+
/** A value for the item. Set this if you need more control over which item(s) are selected. If you don't set this, the value will be set to the item's initial index */
|
|
57
|
+
value?: string,
|
|
58
|
+
/** If set to true, the user cannot interact with this ListBoxItem */
|
|
59
|
+
disabled?: boolean,
|
|
60
|
+
/** Raised when this item is opened or closed */
|
|
61
|
+
ontoggle?: (open: boolean) => void,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* An item in an ListBox.
|
|
66
|
+
*/
|
|
67
|
+
declare function ListBoxItem(
|
|
68
|
+
$parent: ParentNode,
|
|
69
|
+
$anchor: Node | null,
|
|
70
|
+
$props: ListBoxItemProps | undefined,
|
|
71
|
+
$context?: Record<PropertyKey, any>,
|
|
72
|
+
$slots?: Record<string, SlotRender>,
|
|
73
|
+
): void;
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
interface ListBoxSeparatorProps {
|
|
78
|
+
/** An ID for the root element */
|
|
79
|
+
id?: string;
|
|
80
|
+
/** Classes for the root element */
|
|
81
|
+
class?: ClassValue,
|
|
82
|
+
/** Styles for the root element */
|
|
83
|
+
style?: StyleValue,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* An element in a list box that separates logical sets of items.
|
|
88
|
+
*/
|
|
89
|
+
declare function ListBoxSeparator(
|
|
90
|
+
$parent: ParentNode,
|
|
91
|
+
$anchor: Node | null,
|
|
92
|
+
$props: ListBoxSeparatorProps | undefined,
|
|
93
|
+
$context?: Record<PropertyKey, any>,
|
|
94
|
+
$slots?: Record<string, SlotRender>,
|
|
95
|
+
): void;
|
|
96
|
+
|
|
97
|
+
export { ListBox, ListBoxItem, ListBoxSeparator };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/ListBox/ListBox.torp
|
|
2
|
+
import ListBox_default from "./ListBox.torp";
|
|
3
|
+
|
|
4
|
+
// src/ListBox/ListBoxItem.torp
|
|
5
|
+
import ListBoxItem_default from "./ListBoxItem.torp";
|
|
6
|
+
|
|
7
|
+
// src/ListBox/ListBoxSeparator.torp
|
|
8
|
+
import ListBoxSeparator_default from "./ListBoxSeparator.torp";
|
|
9
|
+
export {
|
|
10
|
+
ListBox_default as ListBox,
|
|
11
|
+
ListBoxItem_default as ListBoxItem,
|
|
12
|
+
ListBoxSeparator_default as ListBoxSeparator
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/Menu/Menu.torp
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
-
import {
|
|
3
|
-
import { ModalContextName } from "../Modal/ModalTypes";
|
|
4
|
-
import { PopoverContextName, type PopoverContext } from "../Popover/PopoverTypes";
|
|
2
|
+
import { PopoutContextName, type PopoutContext } from "../utils/PopoutTypes";
|
|
5
3
|
import {
|
|
6
4
|
MenuContextName,
|
|
7
5
|
type MenuContext,
|
|
@@ -46,21 +44,22 @@ export default function Menu($props: MenuProps | undefined) {
|
|
|
46
44
|
|
|
47
45
|
// Get stuff out of the PopoverContext
|
|
48
46
|
// HACK: share state in PopupState and extend in context/modal/popover
|
|
49
|
-
const
|
|
50
|
-
if (!
|
|
51
|
-
throw new Error("Menu must be contained within a
|
|
47
|
+
const popoutContext = $context[PopoutContextName] as PopoutContext;
|
|
48
|
+
if (!popoutContext) {
|
|
49
|
+
throw new Error("Menu must be contained within a popout component");
|
|
52
50
|
}
|
|
53
|
-
const { setVisible, handleButton,
|
|
51
|
+
const { setVisible, handleButton, state } = popoutContext;
|
|
54
52
|
|
|
55
53
|
// Check whether this is a menu in a popout
|
|
56
|
-
const
|
|
57
|
-
const inPopout = !!
|
|
58
|
-
const setPopoutVisible =
|
|
54
|
+
const menuPopoutContext = $context[MenuPopoutContextName] as MenuPopoutContext;
|
|
55
|
+
const inPopout = !!menuPopoutContext;
|
|
56
|
+
const setPopoutVisible = menuPopoutContext?.setVisible;
|
|
59
57
|
|
|
60
58
|
// Set the focusFirstElement method in the PopoverContext(s) so that it can be called after everything has been shown
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
59
|
+
popoutContext.focusFirstElement = focusFirstElement;
|
|
60
|
+
if (menuPopoutContext) {
|
|
61
|
+
menuPopoutContext.focusFirstElement = focusFirstElement;
|
|
62
|
+
menuPopoutContext.focusLastElement = focusLastElement;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
// Set the context to pass down to items
|
|
@@ -69,7 +68,7 @@ export default function Menu($props: MenuProps | undefined) {
|
|
|
69
68
|
handleButton,
|
|
70
69
|
handleItemFocus,
|
|
71
70
|
handleItemKey,
|
|
72
|
-
|
|
71
|
+
state,
|
|
73
72
|
registerItem
|
|
74
73
|
};
|
|
75
74
|
$context[MenuContextName] = context;
|
|
@@ -80,6 +79,12 @@ export default function Menu($props: MenuProps | undefined) {
|
|
|
80
79
|
}
|
|
81
80
|
}
|
|
82
81
|
|
|
82
|
+
function focusLastElement() {
|
|
83
|
+
if (itemStates.length > 0) {
|
|
84
|
+
itemStates.at(-1)!.setFocused();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
83
88
|
function registerItem(setFocused: () => void) {
|
|
84
89
|
// Add the item's state to the array and return its index
|
|
85
90
|
const newItemState: ItemState = {
|
package/dist/Menu/MenuTypes.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export const MenuContextName = "Menu";
|
|
2
|
-
export const MenuButtonContextName = "MenuButton";
|
|
3
|
-
export const MenuRadioGroupContextName = "MenuRadioGroup";
|
|
4
|
-
export const MenuPopoutContextName = "MenuPopout";
|
|
1
|
+
export const MenuContextName: unique symbol = Symbol.for("torp.Menu");
|
|
2
|
+
export const MenuButtonContextName: unique symbol = Symbol.for("torp.MenuButton");
|
|
3
|
+
export const MenuRadioGroupContextName: unique symbol = Symbol.for("torp.MenuRadioGroup");
|
|
4
|
+
export const MenuPopoutContextName: unique symbol = Symbol.for("torp.MenuPopout");
|
|
5
5
|
|
|
6
6
|
export interface MenuContext {
|
|
7
7
|
setVisible: (value: boolean, result?: any) => void;
|
|
@@ -47,6 +47,7 @@ export interface MenuPopoutContext {
|
|
|
47
47
|
};
|
|
48
48
|
anchorElement?: HTMLElement;
|
|
49
49
|
focusFirstElement?: () => void;
|
|
50
|
+
focusLastElement?: () => void;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export interface ItemState {
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type MenuBarContext,
|
|
6
6
|
type MenuBarItemContext
|
|
7
7
|
} from "./MenuBarTypes";
|
|
8
|
-
import {
|
|
8
|
+
import { PopoutContextName, type PopoutContext } from "../utils/PopoutTypes";
|
|
9
9
|
|
|
10
10
|
interface MenuBarItemProps {
|
|
11
11
|
/** An ID for the root element */
|
|
@@ -59,17 +59,14 @@ export default function MenuBarItem($props: MenuBarItemProps | undefined) {
|
|
|
59
59
|
$context[MenuBarItemContextName] = context;
|
|
60
60
|
|
|
61
61
|
// Set the context to pass down to items
|
|
62
|
-
let
|
|
63
|
-
setVisible,
|
|
62
|
+
let popoutContext: PopoutContext = {
|
|
64
63
|
handleButton,
|
|
65
|
-
modal: false,
|
|
66
64
|
state: $state,
|
|
67
|
-
//anchorElement: anchor,
|
|
68
65
|
};
|
|
69
|
-
$context[
|
|
66
|
+
$context[PopoutContextName] = popoutContext;
|
|
70
67
|
|
|
71
68
|
$mount(() => {
|
|
72
|
-
setVisible();
|
|
69
|
+
setVisible(false);
|
|
73
70
|
});
|
|
74
71
|
|
|
75
72
|
function setVisible(value: boolean) {
|
|
@@ -99,7 +96,7 @@ export default function MenuBarItem($props: MenuBarItemProps | undefined) {
|
|
|
99
96
|
}
|
|
100
97
|
});
|
|
101
98
|
|
|
102
|
-
function handleButton(
|
|
99
|
+
function handleButton(_type: "confirm" | "cancel" | undefined, _value?: any) {
|
|
103
100
|
menubarContext.state.active = false;
|
|
104
101
|
setVisible(false);
|
|
105
102
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export const MenuBarContextName = "MenuBar";
|
|
2
|
-
export const MenuBarItemContextName = "MenuBarItem";
|
|
1
|
+
export const MenuBarContextName: unique symbol = Symbol.for("torp.MenuBar");
|
|
2
|
+
export const MenuBarItemContextName: unique symbol = Symbol.for("torp.MenuBarItem");
|
|
3
3
|
|
|
4
4
|
export interface MenuBarContext {
|
|
5
5
|
/** Called from a MenuBarItem when it is shown, to hide others */
|