@torpor/ui 0.0.17 → 0.1.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/CHANGELOG.md +28 -0
- package/dist/Accordion/Accordion.torp +53 -54
- package/dist/Accordion/AccordionContent.torp +5 -5
- package/dist/Accordion/AccordionHeader.torp +5 -4
- package/dist/Accordion/AccordionItem.torp +13 -8
- package/dist/Accordion/AccordionTrigger.torp +5 -5
- package/dist/Accordion/AccordionTypes.ts +1 -0
- package/dist/Dialog/Dialog.torp +156 -0
- package/dist/Dialog/DialogBody.torp +34 -0
- package/dist/Dialog/DialogButton.torp +48 -0
- package/dist/Dialog/DialogFooter.torp +13 -0
- package/dist/Dialog/DialogHeader.torp +34 -0
- package/dist/Dialog/DialogTypes.ts +16 -0
- package/dist/Dialog/index.cjs +53 -0
- package/dist/Dialog/index.cjs.map +1 -0
- package/dist/Dialog/index.js +22 -0
- package/dist/Dialog/index.js.map +1 -0
- package/dist/NavMenu/NavMenu.torp +178 -0
- package/dist/NavMenu/NavMenuButton.torp +46 -0
- package/dist/NavMenu/NavMenuElement.torp +43 -0
- package/dist/NavMenu/NavMenuGroup.torp +30 -0
- package/dist/NavMenu/NavMenuGroupHeader.torp +30 -0
- package/dist/NavMenu/NavMenuLink.torp +52 -0
- package/dist/NavMenu/NavMenuPopout.torp +69 -0
- package/dist/NavMenu/NavMenuPopoutButton.torp +120 -0
- package/dist/NavMenu/NavMenuPopoutContent.torp +79 -0
- package/dist/NavMenu/NavMenuSeparator.torp +14 -0
- package/dist/NavMenu/NavMenuTypes.ts +32 -0
- package/dist/NavMenu/index.cjs +78 -0
- package/dist/NavMenu/index.cjs.map +1 -0
- package/dist/NavMenu/index.js +42 -0
- package/dist/NavMenu/index.js.map +1 -0
- package/dist/Popover/Popover.torp +105 -0
- package/dist/Popover/PopoverContentAnchored.torp +107 -0
- package/dist/Popover/PopoverContentContextual.torp +99 -0
- package/dist/Popover/PopoverContentStatic.torp +78 -0
- package/dist/Popover/PopoverOverlay.torp +32 -0
- package/dist/Popover/PopoverTrigger.torp +29 -0
- package/dist/Popover/PopoverTriggerContextual.torp +37 -0
- package/dist/Popover/PopoverTriggerHover.torp +30 -0
- package/dist/Popover/PopoverTypes.ts +30 -0
- package/dist/Popover/index.cjs +222 -0
- package/dist/Popover/index.cjs.map +1 -0
- package/dist/Popover/index.js +183 -0
- package/dist/Popover/index.js.map +1 -0
- package/dist/Popover/mountClickPopover.ts +50 -0
- package/dist/Popover/mountContextPopover.ts +50 -0
- package/dist/Popover/mountHoverPopover.ts +57 -0
- package/dist/Popover/showPopover.ts +24 -0
- package/dist/Popover/showPopoverPrompt.ts +23 -0
- package/dist/TabGroup/TabContent.torp +4 -4
- package/dist/TabGroup/TabGroup.torp +58 -59
- package/dist/TabGroup/TabGroupTypes.ts +1 -0
- package/dist/TabGroup/TabHeader.torp +10 -8
- package/dist/TabGroup/TabList.torp +4 -3
- package/dist/TabGroup/TabTrigger.torp +6 -6
- package/dist/metafile-cjs.json +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/mount/disableScroll.ts +28 -0
- package/dist/mount/hoverTrigger.ts +59 -0
- package/dist/mount/trapFocus.ts +58 -0
- package/dist/utils/documentEvents.ts +17 -0
- package/dist/utils/getScrollParent.ts +22 -0
- package/dist/utils/setContextPosition.ts +31 -0
- package/dist/utils/setPopoverPosition.ts +138 -0
- package/package.json +21 -6
- package/scripts/postbuild.ts +17 -5
- package/src/Accordion/Accordion.torp +53 -54
- package/src/Accordion/AccordionContent.torp +5 -5
- package/src/Accordion/AccordionHeader.torp +5 -4
- package/src/Accordion/AccordionItem.torp +13 -8
- package/src/Accordion/AccordionTrigger.torp +5 -5
- package/src/Accordion/AccordionTypes.ts +1 -0
- package/src/Dialog/Dialog.torp +156 -0
- package/src/Dialog/DialogBody.torp +34 -0
- package/src/Dialog/DialogButton.torp +48 -0
- package/src/Dialog/DialogFooter.torp +13 -0
- package/src/Dialog/DialogHeader.torp +34 -0
- package/src/Dialog/DialogTypes.ts +16 -0
- package/src/Dialog/index.ts +7 -0
- package/src/Dialog.ts +7 -0
- package/src/NavMenu/NavMenu.torp +178 -0
- package/src/NavMenu/NavMenuButton.torp +46 -0
- package/src/NavMenu/NavMenuElement.torp +43 -0
- package/src/NavMenu/NavMenuGroup.torp +30 -0
- package/src/NavMenu/NavMenuGroupHeader.torp +30 -0
- package/src/NavMenu/NavMenuLink.torp +52 -0
- package/src/NavMenu/NavMenuPopout.torp +69 -0
- package/src/NavMenu/NavMenuPopoutButton.torp +120 -0
- package/src/NavMenu/NavMenuPopoutContent.torp +79 -0
- package/src/NavMenu/NavMenuSeparator.torp +14 -0
- package/src/NavMenu/NavMenuTypes.ts +32 -0
- package/src/NavMenu/index.ts +23 -0
- package/src/NavMenu.ts +23 -0
- package/src/Popover/Popover.torp +105 -0
- package/src/Popover/PopoverContentAnchored.torp +107 -0
- package/src/Popover/PopoverContentContextual.torp +99 -0
- package/src/Popover/PopoverContentStatic.torp +78 -0
- package/src/Popover/PopoverOverlay.torp +32 -0
- package/src/Popover/PopoverTrigger.torp +29 -0
- package/src/Popover/PopoverTriggerContextual.torp +37 -0
- package/src/Popover/PopoverTriggerHover.torp +30 -0
- package/src/Popover/PopoverTypes.ts +30 -0
- package/src/Popover/index.ts +29 -0
- package/src/Popover/mountClickPopover.ts +50 -0
- package/src/Popover/mountContextPopover.ts +50 -0
- package/src/Popover/mountHoverPopover.ts +57 -0
- package/src/Popover/showPopover.ts +24 -0
- package/src/Popover/showPopoverPrompt.ts +23 -0
- package/src/Popover.ts +29 -0
- package/src/TabGroup/TabContent.torp +4 -4
- package/src/TabGroup/TabGroup.torp +58 -59
- package/src/TabGroup/TabGroupTypes.ts +1 -0
- package/src/TabGroup/TabHeader.torp +10 -8
- package/src/TabGroup/TabList.torp +4 -3
- package/src/TabGroup/TabTrigger.torp +6 -6
- package/src/mount/disableScroll.ts +28 -0
- package/src/mount/hoverTrigger.ts +59 -0
- package/src/mount/trapFocus.ts +58 -0
- package/src/utils/documentEvents.ts +17 -0
- package/src/utils/getScrollParent.ts +22 -0
- package/src/utils/setContextPosition.ts +31 -0
- package/src/utils/setPopoverPosition.ts +138 -0
- package/test/Accordion/accessibility.test.ts +0 -3
- package/test/Accordion/for-items.test.ts +2 -4
- package/test/Accordion/keyboard.test.ts +0 -3
- package/test/Accordion/multiple-item-toggle.test.ts +3 -5
- package/test/Accordion/single-item-toggle.test.ts +3 -5
- package/test/Dialog/accessibility-alert.test.ts +40 -0
- package/test/Dialog/accessibility-modal.test.ts +52 -0
- package/test/Dialog/components/CustomDialog.torp +20 -0
- package/test/Dialog/components/DialogAccessibility.torp +31 -0
- package/test/Dialog/components/DialogFunction.torp +22 -0
- package/test/Dialog/components/DialogVariable.torp +35 -0
- package/test/Dialog/function.test.ts +26 -0
- package/test/Dialog/variable.test.ts +27 -0
- package/test/NavMenu/accessibility.test.ts +97 -0
- package/test/NavMenu/basic.test.ts +22 -0
- package/test/NavMenu/components/NavMenu.torp +28 -0
- package/test/NavMenu/components/NavMenuAccessibility.torp +35 -0
- package/test/TabGroup/for-items.test.ts +2 -4
- package/test/TabGroup/item-trigger.test.ts +3 -5
- package/tsup.config.ts +8 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { DialogContextName, type DialogContext } from "./DialogTypes";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The header for a Dialog. Content in the header will be announced by screen
|
|
6
|
+
* readers when the Dialog is opened, unless ariaAnnounce is false.
|
|
7
|
+
*
|
|
8
|
+
* @prop {boolean} ariaAnnounce -- Whether the header will be announced by screen readers when the Dialog is opened
|
|
9
|
+
*/
|
|
10
|
+
export default function DialogHeader($props: any) {
|
|
11
|
+
$props.id ??= getId();
|
|
12
|
+
$props.ariaAnnounce ??= true;
|
|
13
|
+
|
|
14
|
+
// Get stuff out of the DialogContext
|
|
15
|
+
const context = $context[DialogContextName] as DialogContext;
|
|
16
|
+
if (!context) {
|
|
17
|
+
throw new Error("DialogHeader must be contained within a Dialog");
|
|
18
|
+
}
|
|
19
|
+
let $state = context.state;
|
|
20
|
+
|
|
21
|
+
// Set the header id for using in the Dialog's ARIA attributes
|
|
22
|
+
if ($props.ariaAnnounce) {
|
|
23
|
+
$state.headerId = $props.id;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@render {
|
|
27
|
+
<div
|
|
28
|
+
id={$props.id}
|
|
29
|
+
class={`torp-dialog-footer ${$props.class ?? ""}`.trim()}
|
|
30
|
+
>
|
|
31
|
+
<:slot />
|
|
32
|
+
</div>
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const DialogContextName = "Dialog";
|
|
2
|
+
|
|
3
|
+
export interface DialogContext {
|
|
4
|
+
handleButton: (type: "confirm" | "cancel" | undefined, value?: any) => void;
|
|
5
|
+
state: DialogState;
|
|
6
|
+
anchorElement?: HTMLElement;
|
|
7
|
+
confirmButton?: HTMLButtonElement;
|
|
8
|
+
cancelButton?: HTMLButtonElement;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface DialogState {
|
|
12
|
+
dialogId: string;
|
|
13
|
+
headerId: string;
|
|
14
|
+
bodyId: string;
|
|
15
|
+
visible: boolean;
|
|
16
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/Dialog/index.ts
|
|
21
|
+
var Dialog_exports = {};
|
|
22
|
+
__export(Dialog_exports, {
|
|
23
|
+
Dialog: () => Dialog_default,
|
|
24
|
+
DialogBody: () => DialogBody_default,
|
|
25
|
+
DialogButton: () => DialogButton_default,
|
|
26
|
+
DialogFooter: () => DialogFooter_default,
|
|
27
|
+
DialogHeader: () => DialogHeader_default
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(Dialog_exports);
|
|
30
|
+
|
|
31
|
+
// src/Dialog/Dialog.torp
|
|
32
|
+
import Dialog_default from "./Dialog.torp";
|
|
33
|
+
|
|
34
|
+
// src/Dialog/DialogBody.torp
|
|
35
|
+
import DialogBody_default from "./DialogBody.torp";
|
|
36
|
+
|
|
37
|
+
// src/Dialog/DialogButton.torp
|
|
38
|
+
import DialogButton_default from "./DialogButton.torp";
|
|
39
|
+
|
|
40
|
+
// src/Dialog/DialogFooter.torp
|
|
41
|
+
import DialogFooter_default from "./DialogFooter.torp";
|
|
42
|
+
|
|
43
|
+
// src/Dialog/DialogHeader.torp
|
|
44
|
+
import DialogHeader_default from "./DialogHeader.torp";
|
|
45
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
46
|
+
0 && (module.exports = {
|
|
47
|
+
Dialog,
|
|
48
|
+
DialogBody,
|
|
49
|
+
DialogButton,
|
|
50
|
+
DialogFooter,
|
|
51
|
+
DialogHeader
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/Dialog/index.ts"],"sourcesContent":["import Dialog from \"./Dialog.torp\";\nimport DialogBody from \"./DialogBody.torp\";\nimport DialogButton from \"./DialogButton.torp\";\nimport DialogFooter from \"./DialogFooter.torp\";\nimport DialogHeader from \"./DialogHeader.torp\";\n\nexport { Dialog, DialogHeader, DialogBody, DialogFooter, DialogButton };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/Dialog/Dialog.torp
|
|
2
|
+
import Dialog_default from "./Dialog.torp";
|
|
3
|
+
|
|
4
|
+
// src/Dialog/DialogBody.torp
|
|
5
|
+
import DialogBody_default from "./DialogBody.torp";
|
|
6
|
+
|
|
7
|
+
// src/Dialog/DialogButton.torp
|
|
8
|
+
import DialogButton_default from "./DialogButton.torp";
|
|
9
|
+
|
|
10
|
+
// src/Dialog/DialogFooter.torp
|
|
11
|
+
import DialogFooter_default from "./DialogFooter.torp";
|
|
12
|
+
|
|
13
|
+
// src/Dialog/DialogHeader.torp
|
|
14
|
+
import DialogHeader_default from "./DialogHeader.torp";
|
|
15
|
+
export {
|
|
16
|
+
Dialog_default as Dialog,
|
|
17
|
+
DialogBody_default as DialogBody,
|
|
18
|
+
DialogButton_default as DialogButton,
|
|
19
|
+
DialogFooter_default as DialogFooter,
|
|
20
|
+
DialogHeader_default as DialogHeader
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NavMenuContextName,
|
|
3
|
+
type NavMenuContext,
|
|
4
|
+
type ItemState,
|
|
5
|
+
NavMenuPopoutContextName,
|
|
6
|
+
type NavMenuPopoutContext
|
|
7
|
+
} from "./NavMenuTypes";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A list of links for navigating a website.
|
|
11
|
+
*
|
|
12
|
+
* See [the WAI ARIA guidelines for Menus](https://www.w3.org/WAI/ARIA/apg/patterns/menu/).
|
|
13
|
+
*
|
|
14
|
+
* The structure of a NavMenu is:
|
|
15
|
+
*
|
|
16
|
+
* ```
|
|
17
|
+
* <NavMenu>
|
|
18
|
+
* <NavMenuLink />
|
|
19
|
+
* <NavMenuSeparator /> (optional)
|
|
20
|
+
* <NavMenuPopout /> (optional)
|
|
21
|
+
* ...
|
|
22
|
+
* </NavMenu>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @prop {string | undefined} ariaLabel -- An ARIA label for describing the menu to screen readers
|
|
26
|
+
* @prop {"horizontal" | "vertical"} orientation -- The orientation of the menu, which affects keyboard navigation
|
|
27
|
+
*/
|
|
28
|
+
export default function NavMenu($props: any) {
|
|
29
|
+
$props.orientation ||= "horizontal";
|
|
30
|
+
|
|
31
|
+
// A collection containing the state and some functions for each item
|
|
32
|
+
let itemStates: ItemState[] = [];
|
|
33
|
+
let focusIndex = -1;
|
|
34
|
+
|
|
35
|
+
// Check whether this is a menu in a popout
|
|
36
|
+
const popoutContext = $context[NavMenuPopoutContextName] as NavMenuPopoutContext;
|
|
37
|
+
const inPopout = !!popoutContext;
|
|
38
|
+
let popoutState = popoutContext?.state;
|
|
39
|
+
|
|
40
|
+
// Set the focusFirstElement method in the PopoverContext(s) so that it can be called after everything has been shown
|
|
41
|
+
if (popoutContext) {
|
|
42
|
+
popoutContext.focusFirstElement = focusFirstElement;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Set the context to pass down to items
|
|
46
|
+
let context: NavMenuContext = {
|
|
47
|
+
handleItemFocus,
|
|
48
|
+
handleItemKey,
|
|
49
|
+
registerItem
|
|
50
|
+
};
|
|
51
|
+
$context[NavMenuContextName] = context;
|
|
52
|
+
|
|
53
|
+
// If the NavMenu is not in a popout, focus its first item. Otherwise the popout will call focusFirstElement, below
|
|
54
|
+
function handleFocus(e: FocusEvent & { currentTarget: EventTarget & HTMLElement }) {
|
|
55
|
+
if (!popoutContext && itemStates.length) {
|
|
56
|
+
itemStates[0].setFocused();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function focusFirstElement() {
|
|
61
|
+
if (itemStates.length) {
|
|
62
|
+
itemStates[0].setFocused();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function registerItem(setFocused: () => void) {
|
|
67
|
+
// Add the item's state to the array and return its index
|
|
68
|
+
const newItemState: ItemState = {
|
|
69
|
+
setFocused
|
|
70
|
+
};
|
|
71
|
+
itemStates.push(newItemState);
|
|
72
|
+
itemStates = itemStates;
|
|
73
|
+
return { index: itemStates.length - 1 };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function handleItemFocus(index: number) {
|
|
77
|
+
focusIndex = index;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function handleItemKey(e: KeyboardEvent) {
|
|
81
|
+
switch (e.key) {
|
|
82
|
+
case "Esc":
|
|
83
|
+
case "Escape": {
|
|
84
|
+
if (inPopout) {
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
//setPopoutVisible(false);
|
|
87
|
+
popoutState.visible = false;
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "Right":
|
|
92
|
+
case "ArrowRight": {
|
|
93
|
+
if (orientation === "horizontal") {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
focusItem("next");
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "Left":
|
|
100
|
+
case "ArrowLeft": {
|
|
101
|
+
if (orientation === "horizontal") {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
focusItem("previous");
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "Down":
|
|
108
|
+
case "ArrowDown": {
|
|
109
|
+
if (orientation === "vertical") {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
focusItem("next");
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case "Up":
|
|
116
|
+
case "ArrowUp": {
|
|
117
|
+
if (orientation === "vertical") {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
focusItem("previous");
|
|
120
|
+
} else {
|
|
121
|
+
// TODO: This should be based on what side the menu shows on
|
|
122
|
+
// And on the orientation
|
|
123
|
+
if (inPopout) {
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
popoutState.visible = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "Home": {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
focusItem("start");
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case "End": {
|
|
136
|
+
e.preventDefault();
|
|
137
|
+
focusItem("end");
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function focusItem(target: "start" | "previous" | "next" | "end") {
|
|
144
|
+
switch (target) {
|
|
145
|
+
case "start": {
|
|
146
|
+
focusIndex = 0;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "previous": {
|
|
150
|
+
focusIndex = Math.max(0, focusIndex - 1);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case "next": {
|
|
154
|
+
focusIndex = Math.min(itemStates.length - 1, focusIndex + 1);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case "end": {
|
|
158
|
+
focusIndex = itemStates.length - 1;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
itemStates[focusIndex]?.setFocused();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@render {
|
|
166
|
+
<nav
|
|
167
|
+
id={$props.id}
|
|
168
|
+
class={`torp-nav-menu ${$props.class ?? ""}`.trim()}
|
|
169
|
+
tabindex="0"
|
|
170
|
+
role="menubar"
|
|
171
|
+
aria-label={$props.ariaLabel}
|
|
172
|
+
aria-orientation={$props.orientation}
|
|
173
|
+
onfocus={handleFocus}
|
|
174
|
+
>
|
|
175
|
+
<:slot />
|
|
176
|
+
</nav>
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { NavMenuContextName, type NavMenuContext } from "./NavMenuTypes";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A button that is placed in a NavMenu to carry out a non-navigation action such as logging out.
|
|
5
|
+
*
|
|
6
|
+
* @prop {boolean} disabled -- Whether this button is disabled
|
|
7
|
+
*/
|
|
8
|
+
export default function NavMenuButton($props: any) {
|
|
9
|
+
/** Set when the item is disabled */
|
|
10
|
+
let dataDisabled = $props.disabled || undefined;
|
|
11
|
+
|
|
12
|
+
let button: HTMLButtonElement;
|
|
13
|
+
|
|
14
|
+
// Get stuff out of the NavMenuContext
|
|
15
|
+
const menuContext = $context[NavMenuContextName] as NavMenuContext;
|
|
16
|
+
if (!menuContext) {
|
|
17
|
+
throw new Error("NavMenuButton must be contained within a NavMenu");
|
|
18
|
+
}
|
|
19
|
+
const { registerItem, handleItemKey, handleItemFocus } = menuContext;
|
|
20
|
+
|
|
21
|
+
// Register this item with the parent NavMenu and store the index
|
|
22
|
+
const { index } = registerItem(setFocused);
|
|
23
|
+
|
|
24
|
+
function setFocused() {
|
|
25
|
+
button.focus();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@render {
|
|
29
|
+
<button
|
|
30
|
+
id={$props.id}
|
|
31
|
+
class={`torp-nav-menu-button ${$props.class ?? ""}`.trim()}
|
|
32
|
+
type="button"
|
|
33
|
+
{disabled}
|
|
34
|
+
role="menuitem"
|
|
35
|
+
aria-disabled={disabled ? "true" : undefined}
|
|
36
|
+
data-disabled={dataDisabled}
|
|
37
|
+
onkeydown
|
|
38
|
+
onkeydown={handleItemKey}
|
|
39
|
+
onclick
|
|
40
|
+
onfocus={() => handleItemFocus(index)}
|
|
41
|
+
&ref={button}
|
|
42
|
+
>
|
|
43
|
+
<:slot />
|
|
44
|
+
</button>
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { NavMenuContextName, type NavMenuContext } from "./NavMenuTypes";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An element of any type that can be placed in a NavMenu.
|
|
5
|
+
*
|
|
6
|
+
* @prop {boolean} disabled -- Whether this button is disabled
|
|
7
|
+
* @prop {HTMLElement} element -- An element that can be focused via keyboard navigation
|
|
8
|
+
*/
|
|
9
|
+
export default function NavMenuElement($props: any) {
|
|
10
|
+
/** Set when the item is disabled */
|
|
11
|
+
let dataDisabled = $props.disabled || undefined;
|
|
12
|
+
|
|
13
|
+
// Get stuff out of the NavMenuContext
|
|
14
|
+
const menuContext = $context[NavMenuContextName] as NavMenuContext;
|
|
15
|
+
if (!menuContext) {
|
|
16
|
+
throw new Error("NavMenuElement must be contained within a NavMenu");
|
|
17
|
+
}
|
|
18
|
+
const { registerItem, handleItemKey, handleItemFocus } = menuContext;
|
|
19
|
+
|
|
20
|
+
// Register this item with the parent NavMenu and store the index
|
|
21
|
+
const { index } = registerItem(setFocused);
|
|
22
|
+
|
|
23
|
+
function setFocused() {
|
|
24
|
+
element.focus();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@render {
|
|
28
|
+
<div
|
|
29
|
+
id={$props.id}
|
|
30
|
+
class={`torp-nav-menu-div ${$props.class ?? ""}`.trim()}
|
|
31
|
+
type="div"
|
|
32
|
+
role="menuitem"
|
|
33
|
+
aria-disabled={$props.disabled ? "true" : undefined}
|
|
34
|
+
data-disabled={dataDisabled}
|
|
35
|
+
onkeydown
|
|
36
|
+
onkeydown={handleItemKey}
|
|
37
|
+
onclick
|
|
38
|
+
onfocus={() => handleItemFocus(index)}
|
|
39
|
+
>
|
|
40
|
+
<:slot />
|
|
41
|
+
</div>
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { NavMenuGroupContextName, type NavMenuGroupContext } from "./NavMenuTypes";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A container for a logical group of navigation menu items.
|
|
5
|
+
*
|
|
6
|
+
* @prop {string | undefined} ariaLabel -- An ARIA label for describing the group to screen readers
|
|
7
|
+
*/
|
|
8
|
+
export default function NavMenuGroup($props: any) {
|
|
9
|
+
let $state = $watch({
|
|
10
|
+
headerId: ""
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Set the context to pass down to items
|
|
14
|
+
let context: NavMenuGroupContext = {
|
|
15
|
+
state: $state,
|
|
16
|
+
};
|
|
17
|
+
$context[NavMenuGroupContextName] = context;
|
|
18
|
+
|
|
19
|
+
@render {
|
|
20
|
+
<div
|
|
21
|
+
id={$props.id}
|
|
22
|
+
class={`torp-nav-menu-group ${$props.class ?? ""}`.trim()}
|
|
23
|
+
role="group"
|
|
24
|
+
aria-label={$props.ariaLabel}
|
|
25
|
+
aria-labelledby={$state.headerId}
|
|
26
|
+
>
|
|
27
|
+
<:slot />
|
|
28
|
+
</div>
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { NavMenuGroupContextName, type NavMenuGroupContext } from "./NavMenuTypes";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The header for a NavMenuGroup.
|
|
6
|
+
*
|
|
7
|
+
* @prop {"h1" | "h2" | "h3" | "h4" | "h5" | "h6"} as -- The type of HTML header to use
|
|
8
|
+
*/
|
|
9
|
+
export default function NavMenuGroupHeader($props: any) {
|
|
10
|
+
$props.id ??= getId();
|
|
11
|
+
|
|
12
|
+
const context = $context[NavMenuGroupContextName] as NavMenuGroupContext;
|
|
13
|
+
if (!context) {
|
|
14
|
+
throw new Error("NavMenuGroupHeader must be contained within an NavMenuGroup");
|
|
15
|
+
}
|
|
16
|
+
let $state = context.state;
|
|
17
|
+
|
|
18
|
+
// Set the header ID now that we know what it is
|
|
19
|
+
$state.headerId = $props.id;
|
|
20
|
+
|
|
21
|
+
@render {
|
|
22
|
+
<h4
|
|
23
|
+
@// TODO: self={$props.as}
|
|
24
|
+
id={$props.id}
|
|
25
|
+
class={`torp-nav-menu-group-header ${$props.class ?? ""}`.trim()}
|
|
26
|
+
>
|
|
27
|
+
<:slot />
|
|
28
|
+
</h4>
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
|
|
2
|
+
import { NavMenuContextName, type NavMenuContext } from "./NavMenuTypes";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A link that is placed in a NavMenu and signals a response by a user.
|
|
6
|
+
*
|
|
7
|
+
* @prop {string} href -- The URL for the menu link
|
|
8
|
+
* @prop {newWindow} boolean -- Whether the menu link should open in a new tab/window
|
|
9
|
+
* @prop {boolean} disabled -- Whether this link is disabled
|
|
10
|
+
*/
|
|
11
|
+
export default function NavMenuLink($props: any) {
|
|
12
|
+
$props.newWindow ??= false;
|
|
13
|
+
|
|
14
|
+
/** Set when the item is disabled */
|
|
15
|
+
let dataDisabled = $props.disabled || undefined;
|
|
16
|
+
|
|
17
|
+
let link: HTMLAnchorElement;
|
|
18
|
+
|
|
19
|
+
// Get stuff out of the NavMenuContext
|
|
20
|
+
const menuContext = $context[NavMenuContextName] as NavMenuContext;
|
|
21
|
+
if (!menuContext) {
|
|
22
|
+
throw new Error("NavMenuLink must be contained within a NavMenu");
|
|
23
|
+
}
|
|
24
|
+
const { registerItem, handleItemKey, handleItemFocus } = menuContext;
|
|
25
|
+
|
|
26
|
+
// Register this item with the parent NavMenu and store the index
|
|
27
|
+
const { index } = registerItem(setFocused);
|
|
28
|
+
|
|
29
|
+
function setFocused() {
|
|
30
|
+
link.focus();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@render {
|
|
34
|
+
<a
|
|
35
|
+
id={$props.id}
|
|
36
|
+
class={`torp-nav-menu-link ${$props.class ?? ""}`.trim()}
|
|
37
|
+
href={$props.href}
|
|
38
|
+
disabled={$props.disabled}
|
|
39
|
+
target={$props.newWindow ? "_blank" : undefined}
|
|
40
|
+
role="menuitem"
|
|
41
|
+
aria-disabled={$props.disabled ? "true" : undefined}
|
|
42
|
+
data-disabled={dataDisabled}
|
|
43
|
+
onkeydown
|
|
44
|
+
onkeydown={handleItemKey}
|
|
45
|
+
onclick
|
|
46
|
+
onfocus={() => handleItemFocus(index)}
|
|
47
|
+
&ref={link}
|
|
48
|
+
>
|
|
49
|
+
<:slot />
|
|
50
|
+
</a>
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NavMenuPopoutContextName, type NavMenuPopoutContext } from "./NavMenuTypes";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A popout that is attached to a menu button and can be used to show a submenu.
|
|
5
|
+
*
|
|
6
|
+
* The structure of a NavMenuPopout is:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* <NavMenuPopout>
|
|
10
|
+
* <NavMenuPopoutButton />
|
|
11
|
+
* <NavMenuPopoutContent />
|
|
12
|
+
* </NavMenuPopout>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @prop {boolean} visible -- Whether the popout is visible
|
|
16
|
+
*/
|
|
17
|
+
export default function NavMenuPopout($props: any) {
|
|
18
|
+
$props.visible ??= false;
|
|
19
|
+
|
|
20
|
+
let $state = $watch({
|
|
21
|
+
visible: $props.visible
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
let result: any;
|
|
25
|
+
|
|
26
|
+
// Set the context to pass down to items
|
|
27
|
+
let context: NavMenuPopoutContext = {
|
|
28
|
+
state: $state,
|
|
29
|
+
};
|
|
30
|
+
$context[NavMenuPopoutContextName] = context;
|
|
31
|
+
|
|
32
|
+
// Copy props into state whenever they change
|
|
33
|
+
$run(() => {
|
|
34
|
+
$state.visible = $props.visible ?? false;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Dispatch the toggle event when the popout is opened or closed
|
|
38
|
+
let firstToggle = true;
|
|
39
|
+
$run(() => {
|
|
40
|
+
const ontoggle = $props.ontoggle;
|
|
41
|
+
const onopen = $props.onopen;
|
|
42
|
+
const onclose = $props.onclose;
|
|
43
|
+
const visible = $props.visible;
|
|
44
|
+
if (firstToggle) {
|
|
45
|
+
firstToggle = false;
|
|
46
|
+
} else {
|
|
47
|
+
if (ontoggle) ontoggle(visible);
|
|
48
|
+
if (visible) {
|
|
49
|
+
if (onopen) onopen()
|
|
50
|
+
} else {
|
|
51
|
+
if (onclose) onclose(result);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function handleMouseLeave() {
|
|
57
|
+
$state.visible = false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@render {
|
|
61
|
+
<div
|
|
62
|
+
id={$props.id}
|
|
63
|
+
class={`torp-nav-menu-popout ${$props.class ?? ""}`.trim()}
|
|
64
|
+
onmouseleave={handleMouseLeave}
|
|
65
|
+
>
|
|
66
|
+
<:slot />
|
|
67
|
+
</div>
|
|
68
|
+
}
|
|
69
|
+
}
|