@torpor/ui 0.1.7 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/dist/Accordion/Accordion.torp +25 -13
- package/dist/Accordion/AccordionContent.torp +15 -8
- package/dist/Accordion/AccordionHeader.torp +16 -7
- package/dist/Accordion/AccordionItem.torp +24 -10
- package/dist/Accordion/AccordionTrigger.torp +16 -8
- package/dist/Accordion/AccordionTypes.ts +2 -0
- package/dist/Accordion/index.d.ts +157 -0
- package/dist/Breadcrumb/Breadcrumb.torp +42 -0
- package/dist/Breadcrumb/BreadcrumbItem.torp +28 -0
- package/dist/Breadcrumb/BreadcrumbSeparator.torp +25 -0
- package/dist/Breadcrumb/index.d.ts +84 -0
- package/dist/Breadcrumb/index.js +14 -0
- package/dist/Calendar/Calendar.torp +273 -0
- package/dist/Calendar/CalendarDay.torp +75 -0
- package/dist/Calendar/CalendarTypes.ts +22 -0
- package/dist/Calendar/index.d.ts +36 -0
- package/dist/Calendar/index.js +10 -0
- package/dist/Calendar/index.js.map +1 -0
- package/dist/Dialog/Dialog.torp +16 -6
- package/dist/Dialog/DialogBody.torp +13 -5
- package/dist/Dialog/DialogButton.torp +18 -6
- package/dist/Dialog/DialogFooter.torp +13 -3
- package/dist/Dialog/DialogHeader.torp +13 -4
- package/dist/Dialog/index.d.ts +135 -0
- package/dist/Disclosure/Disclosure.torp +79 -0
- package/dist/Disclosure/DisclosureContent.torp +43 -0
- package/dist/Disclosure/DisclosureHeader.torp +43 -0
- package/dist/Disclosure/DisclosureTrigger.torp +44 -0
- package/dist/Disclosure/DisclosureTypes.ts +14 -0
- package/dist/Disclosure/index.d.ts +113 -0
- package/dist/Disclosure/index.js +18 -0
- package/dist/Disclosure/index.js.map +1 -0
- package/{src/Offscreen/Offscreen.torp → dist/Drawer/Drawer.torp} +20 -19
- package/dist/Drawer/index.d.ts +21 -0
- package/dist/Drawer/index.js +6 -0
- package/dist/Drawer/index.js.map +1 -0
- package/dist/Form/Field.torp +93 -0
- package/dist/Form/Form.torp +131 -0
- package/dist/Form/FormTypes.ts +30 -0
- package/dist/Form/Input.torp +94 -0
- package/dist/Form/Label.torp +31 -0
- package/dist/Form/Message.torp +35 -0
- package/dist/Form/StandardSchemaV1.ts +70 -0
- package/dist/Form/index.d.ts +117 -0
- package/dist/Form/index.js +22 -0
- package/dist/Form/index.js.map +1 -0
- package/dist/Menu/Menu.torp +169 -0
- package/dist/Menu/MenuButton.torp +64 -0
- package/dist/Menu/MenuCheck.torp +81 -0
- package/dist/Menu/MenuGroup.torp +29 -0
- package/dist/Menu/MenuIndicator.torp +38 -0
- package/dist/Menu/MenuPopout.torp +82 -0
- package/dist/Menu/MenuPopoutContent.torp +83 -0
- package/dist/Menu/MenuPopoutTrigger.torp +126 -0
- package/dist/Menu/MenuRadio.torp +99 -0
- package/dist/Menu/MenuRadioGroup.torp +97 -0
- package/dist/Menu/MenuSeparator.torp +24 -0
- package/dist/Menu/MenuTypes.ts +58 -0
- package/dist/Menu/index.d.ts +293 -0
- package/dist/Menu/index.js +46 -0
- package/dist/Menu/index.js.map +1 -0
- package/dist/MenuBar/MenuBar.torp +159 -0
- package/dist/MenuBar/MenuBarItem.torp +115 -0
- package/dist/MenuBar/MenuBarItemContent.torp +117 -0
- package/dist/MenuBar/MenuBarItemTrigger.torp +98 -0
- package/dist/MenuBar/MenuBarTypes.ts +36 -0
- package/dist/MenuBar/index.d.ts +125 -0
- package/dist/MenuBar/index.js +18 -0
- package/dist/MenuBar/index.js.map +1 -0
- package/dist/NavMenu/NavMenu.torp +15 -6
- package/dist/NavMenu/NavMenuButton.torp +14 -7
- package/dist/NavMenu/NavMenuElement.torp +16 -8
- package/dist/NavMenu/NavMenuGroup.torp +14 -5
- package/dist/NavMenu/NavMenuGroupHeader.torp +14 -6
- package/dist/NavMenu/NavMenuLink.torp +18 -10
- package/dist/NavMenu/NavMenuPopout.torp +13 -5
- package/dist/NavMenu/NavMenuPopoutButton.torp +17 -11
- package/dist/NavMenu/NavMenuPopoutContent.torp +42 -30
- package/dist/NavMenu/NavMenuSeparator.torp +12 -3
- package/dist/NavMenu/index.d.ts +259 -0
- package/dist/Notification/Notification.torp +58 -0
- package/dist/Notification/NotificationClose.torp +37 -0
- package/dist/Notification/NotificationContainer.torp +24 -0
- package/dist/Notification/NotificationContent.torp +23 -0
- package/dist/Notification/NotificationTypes.ts +5 -0
- package/dist/Notification/index.d.ts +92 -0
- package/dist/Notification/index.js +36 -0
- package/dist/Notification/index.js.map +1 -0
- package/dist/Notification/showNotification.ts +30 -0
- package/dist/Popover/Popover.torp +22 -8
- package/dist/Popover/PopoverContent.torp +41 -29
- package/dist/Popover/PopoverContentAnchored.torp +52 -44
- package/dist/Popover/PopoverContentContextual.torp +49 -37
- package/dist/Popover/PopoverOverlay.torp +13 -4
- package/dist/Popover/PopoverTrigger.torp +12 -3
- package/dist/Popover/PopoverTriggerContextual.torp +12 -3
- package/dist/Popover/PopoverTriggerHover.torp +14 -5
- package/dist/Popover/index.d.ts +191 -0
- package/dist/TabGroup/TabContent.torp +13 -7
- package/dist/TabGroup/TabGroup.torp +23 -11
- package/dist/TabGroup/TabHeader.torp +16 -7
- package/dist/TabGroup/TabList.torp +15 -6
- package/dist/TabGroup/TabTrigger.torp +14 -6
- package/dist/TabGroup/index.d.ts +150 -0
- package/dist/icons/ArrowDown.torp +15 -0
- package/dist/icons/ArrowLeft.torp +15 -0
- package/dist/icons/ArrowRight.torp +15 -0
- package/dist/icons/ArrowUp.torp +15 -0
- package/dist/icons/Bars.torp +17 -0
- package/dist/icons/Check.torp +15 -0
- package/dist/icons/ChevronDown.torp +15 -0
- package/dist/icons/ChevronLeft.torp +15 -0
- package/dist/icons/ChevronRight.torp +15 -0
- package/dist/icons/ChevronUp.torp +15 -0
- package/dist/icons/Cross.torp +16 -0
- package/dist/icons/Loading.torp +16 -0
- package/dist/icons/Minus.torp +15 -0
- package/dist/icons/Plus.torp +16 -0
- package/dist/icons/index.d.ts +142 -0
- package/dist/icons/index.js +58 -0
- package/dist/icons/index.js.map +1 -0
- package/dist/{motion.d.cts → index.d.ts} +2 -2
- package/dist/metafile-esm.json +1 -1
- package/dist/motion/fade.ts +7 -0
- package/dist/motion/index.d.ts +2 -0
- package/dist/{motion.js → motion/index.js} +1 -1
- package/dist/motion/index.js.map +1 -0
- package/dist/mount/index.d.ts +2 -0
- package/dist/utils/dateUtils.ts +244 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/setPopoverPosition.ts +4 -0
- package/package.json +60 -35
- package/scripts/postbuild.ts +42 -5
- package/src/Accordion/Accordion.torp +25 -13
- package/src/Accordion/AccordionContent.torp +15 -8
- package/src/Accordion/AccordionHeader.torp +16 -7
- package/src/Accordion/AccordionItem.torp +24 -10
- package/src/Accordion/AccordionTrigger.torp +16 -8
- package/src/Accordion/AccordionTypes.ts +2 -0
- package/src/Breadcrumb/Breadcrumb.torp +42 -0
- package/src/Breadcrumb/BreadcrumbItem.torp +28 -0
- package/src/Breadcrumb/BreadcrumbSeparator.torp +25 -0
- package/src/Breadcrumb/index.ts +5 -0
- package/src/Calendar/Calendar.torp +273 -0
- package/src/Calendar/CalendarDay.torp +75 -0
- package/src/Calendar/CalendarTypes.ts +22 -0
- package/src/Calendar/index.ts +4 -0
- package/src/Dialog/Dialog.torp +16 -6
- package/src/Dialog/DialogBody.torp +13 -5
- package/src/Dialog/DialogButton.torp +18 -6
- package/src/Dialog/DialogFooter.torp +13 -3
- package/src/Dialog/DialogHeader.torp +13 -4
- package/src/Disclosure/Disclosure.torp +79 -0
- package/src/Disclosure/DisclosureContent.torp +43 -0
- package/src/Disclosure/DisclosureHeader.torp +43 -0
- package/src/Disclosure/DisclosureTrigger.torp +44 -0
- package/src/Disclosure/DisclosureTypes.ts +14 -0
- package/src/Disclosure/index.ts +6 -0
- package/{dist/Offscreen/Offscreen.torp → src/Drawer/Drawer.torp} +20 -19
- package/src/Drawer/index.ts +3 -0
- package/src/Form/Field.torp +93 -0
- package/src/Form/Form.torp +131 -0
- package/src/Form/FormTypes.ts +30 -0
- package/src/Form/Input.torp +94 -0
- package/src/Form/Label.torp +31 -0
- package/src/Form/Message.torp +35 -0
- package/src/Form/StandardSchemaV1.ts +70 -0
- package/src/Form/index.ts +7 -0
- package/src/Menu/Menu.torp +169 -0
- package/src/Menu/MenuButton.torp +64 -0
- package/src/Menu/MenuCheck.torp +81 -0
- package/src/Menu/MenuGroup.torp +29 -0
- package/src/Menu/MenuIndicator.torp +38 -0
- package/src/Menu/MenuPopout.torp +82 -0
- package/src/Menu/MenuPopoutContent.torp +83 -0
- package/src/Menu/MenuPopoutTrigger.torp +126 -0
- package/src/Menu/MenuRadio.torp +99 -0
- package/src/Menu/MenuRadioGroup.torp +97 -0
- package/src/Menu/MenuSeparator.torp +24 -0
- package/src/Menu/MenuTypes.ts +58 -0
- package/src/Menu/index.ts +25 -0
- package/src/MenuBar/MenuBar.torp +159 -0
- package/src/MenuBar/MenuBarItem.torp +115 -0
- package/src/MenuBar/MenuBarItemContent.torp +117 -0
- package/src/MenuBar/MenuBarItemTrigger.torp +98 -0
- package/src/MenuBar/MenuBarTypes.ts +36 -0
- package/src/MenuBar/index.ts +6 -0
- package/src/NavMenu/NavMenu.torp +15 -6
- package/src/NavMenu/NavMenuButton.torp +14 -7
- package/src/NavMenu/NavMenuElement.torp +16 -8
- package/src/NavMenu/NavMenuGroup.torp +14 -5
- package/src/NavMenu/NavMenuGroupHeader.torp +14 -6
- package/src/NavMenu/NavMenuLink.torp +18 -10
- package/src/NavMenu/NavMenuPopout.torp +13 -5
- package/src/NavMenu/NavMenuPopoutButton.torp +17 -11
- package/src/NavMenu/NavMenuPopoutContent.torp +42 -30
- package/src/NavMenu/NavMenuSeparator.torp +12 -3
- package/src/Notification/Notification.torp +58 -0
- package/src/Notification/NotificationClose.torp +37 -0
- package/src/Notification/NotificationContainer.torp +24 -0
- package/src/Notification/NotificationContent.torp +23 -0
- package/src/Notification/NotificationTypes.ts +5 -0
- package/src/Notification/index.ts +13 -0
- package/src/Notification/showNotification.ts +30 -0
- package/src/Popover/Popover.torp +22 -8
- package/src/Popover/PopoverContent.torp +41 -29
- package/src/Popover/PopoverContentAnchored.torp +52 -44
- package/src/Popover/PopoverContentContextual.torp +49 -37
- package/src/Popover/PopoverOverlay.torp +13 -4
- package/src/Popover/PopoverTrigger.torp +12 -3
- package/src/Popover/PopoverTriggerContextual.torp +12 -3
- package/src/Popover/PopoverTriggerHover.torp +14 -5
- package/src/Slider/Slider.torp +83 -0
- package/src/Slider/index.ts +3 -0
- package/src/TabGroup/TabContent.torp +13 -7
- package/src/TabGroup/TabGroup.torp +23 -11
- package/src/TabGroup/TabHeader.torp +16 -7
- package/src/TabGroup/TabList.torp +15 -6
- package/src/TabGroup/TabTrigger.torp +14 -6
- package/src/icons/ArrowDown.torp +15 -0
- package/src/icons/ArrowLeft.torp +15 -0
- package/src/icons/ArrowRight.torp +15 -0
- package/src/icons/ArrowUp.torp +15 -0
- package/src/icons/Bars.torp +17 -0
- package/src/icons/Check.torp +15 -0
- package/src/icons/ChevronDown.torp +15 -0
- package/src/icons/ChevronLeft.torp +15 -0
- package/src/icons/ChevronRight.torp +15 -0
- package/src/icons/ChevronUp.torp +15 -0
- package/src/icons/Cross.torp +16 -0
- package/src/icons/Loading.torp +16 -0
- package/src/icons/Minus.torp +15 -0
- package/src/icons/Plus.torp +16 -0
- package/src/icons/index.ts +31 -0
- package/src/motion/fade.ts +2 -2
- package/src/motion/index.ts +3 -0
- package/src/utils/dateUtils.ts +244 -0
- package/src/utils/setPopoverPosition.ts +4 -0
- package/test/Accordion/components/AccordionAccessibility.torp +2 -0
- package/test/Accordion/components/AccordionKeyboard.torp +2 -0
- package/test/Accordion/components/AccordionList.torp +2 -0
- package/test/Accordion/components/AccordionMultiple.torp +2 -0
- package/test/Accordion/components/AccordionSingle.torp +2 -0
- package/test/Breadcrumb/accessibility.test.ts +29 -0
- package/test/Breadcrumb/basic.test.ts +17 -0
- package/test/Breadcrumb/components/BreadcrumbTest.torp +15 -0
- package/test/Calendar/components/CalendarTest.torp +9 -0
- package/test/Calendar/default.test.ts +18 -0
- package/test/Dialog/components/DialogAccessibility.torp +5 -5
- package/test/Disclosure/accessibility.test.ts +31 -0
- package/test/Disclosure/components/DisclosureTest.torp +19 -0
- package/test/Disclosure/default.test.ts +15 -0
- package/test/Disclosure/toggle.test.ts +20 -0
- package/test/Form/components/FieldTest.torp +25 -0
- package/test/Form/components/FormTest.torp +31 -0
- package/test/Form/components/InputTest.torp +9 -0
- package/test/Form/field.test.ts +34 -0
- package/test/Form/form.test.ts +34 -0
- package/test/Form/input.test.ts +19 -0
- package/test/Menu/accessibility.test.ts +83 -0
- package/test/Menu/basic.test.ts +58 -0
- package/test/Menu/components/MenuAccessibility.torp +59 -0
- package/test/Menu/components/MenuBasic.torp +58 -0
- package/test/MenuBar/accessibility.test.ts +87 -0
- package/test/MenuBar/basic.test.ts +22 -0
- package/test/MenuBar/components/MenuBarAccessibility.torp +34 -0
- package/test/MenuBar/components/MenuBarBasic.torp +25 -0
- package/test/NavMenu/basic.test.ts +1 -1
- package/test/NavMenu/components/{NavMenu.torp → NavMenuBasic.torp} +1 -1
- package/test/Notification/accessibility.test.ts +21 -0
- package/test/Notification/components/CustomNotification.torp +14 -0
- package/test/Notification/components/NotificationAccessibility.torp +14 -0
- package/test/Notification/components/NotificationFunction.torp +20 -0
- package/test/Notification/components/NotificationVariable.torp +22 -0
- package/test/Notification/function.test.ts +25 -0
- package/test/Notification/variable.test.ts +25 -0
- package/test/Popover/components/CustomDialog.torp +25 -0
- package/test/Popover/components/PromptFunction.torp +19 -0
- package/test/Popover/components/PromptVariable.torp +38 -0
- package/test/Popover/function.test.ts +26 -0
- package/test/Popover/variable.test.ts +27 -0
- package/test/TabGroup/components/TabGroupAccessibility.torp +2 -0
- package/test/TabGroup/components/TabGroupKeyboard.torp +2 -0
- package/test/TabGroup/components/TabGroupList.torp +2 -0
- package/test/TabGroup/components/TabGroupTrigger.torp +2 -0
- package/tsup.config.ts +12 -4
- package/dist/Accordion/index.cjs +0 -53
- package/dist/Accordion/index.cjs.map +0 -1
- package/dist/Dialog/index.cjs +0 -53
- package/dist/Dialog/index.cjs.map +0 -1
- package/dist/NavMenu/index.cjs +0 -78
- package/dist/NavMenu/index.cjs.map +0 -1
- package/dist/Offscreen/index.cjs +0 -33
- package/dist/Offscreen/index.cjs.map +0 -1
- package/dist/Offscreen/index.js +0 -6
- package/dist/Popover/index.cjs +0 -222
- package/dist/Popover/index.cjs.map +0 -1
- package/dist/TabGroup/index.cjs +0 -53
- package/dist/TabGroup/index.cjs.map +0 -1
- package/dist/metafile-cjs.json +0 -1
- package/dist/motion.cjs +0 -34
- package/dist/motion.cjs.map +0 -1
- package/dist/motion.d.ts +0 -6
- package/dist/motion.js.map +0 -1
- package/src/Accordion.ts +0 -7
- package/src/Dialog.ts +0 -7
- package/src/NavMenu.ts +0 -23
- package/src/Offscreen/index.ts +0 -3
- package/src/Popover.ts +0 -29
- package/src/TabGroup.ts +0 -7
- package/src/motion.ts +0 -3
- /package/dist/{Offscreen → Breadcrumb}/index.js.map +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
|
|
3
|
+
|
|
4
|
+
interface InputProps {
|
|
5
|
+
/** An ID for the root element */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** Class name(s) for the root element */
|
|
8
|
+
class?: string,
|
|
9
|
+
/** The input type */
|
|
10
|
+
type?: "text" | "number" | "password" | "email" | "url" | "tel",
|
|
11
|
+
/** The name of the input's form data field */
|
|
12
|
+
name?: string,
|
|
13
|
+
/** The data value that this field is bound to */
|
|
14
|
+
value?: any,
|
|
15
|
+
/** Text to display when the input value is empty */
|
|
16
|
+
placeholder?: string,
|
|
17
|
+
/** Whether the input should be readonly */
|
|
18
|
+
readonly?: boolean,
|
|
19
|
+
/** The maximum number of characters allowed for the input value */
|
|
20
|
+
maxlength?: number,
|
|
21
|
+
/** An ARIA label for describing the input to screen readers when there is no associated label element */
|
|
22
|
+
ariaLabel?: string,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A standard input in a form.
|
|
27
|
+
*/
|
|
28
|
+
export default function Input($props: InputProps) {
|
|
29
|
+
$props ??= $watch({});
|
|
30
|
+
$props.id ??= getId();
|
|
31
|
+
$props.type ??= "text";
|
|
32
|
+
|
|
33
|
+
let blurred = false;
|
|
34
|
+
|
|
35
|
+
// Get stuff out of the FormContext and FieldContext
|
|
36
|
+
const formContext = $context[FormContextName] as FormContext;
|
|
37
|
+
const fieldContext = $context[FieldContextName] as FieldContext;
|
|
38
|
+
if (fieldContext) {
|
|
39
|
+
$props.name ??= fieldContext.name;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Use the FieldContext's state, or create a new one
|
|
43
|
+
let $state = fieldContext?.state ?? $watch({
|
|
44
|
+
inputId: $props.id,
|
|
45
|
+
value: $props.value,
|
|
46
|
+
errors: {} as Record<string, string>,
|
|
47
|
+
valid: false,
|
|
48
|
+
message: "",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Binding
|
|
52
|
+
$run(() => {
|
|
53
|
+
$props.value = $state.value;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Set the input ID now that we know it and it will get linked with the associated label
|
|
57
|
+
$state.inputId = $props.id;
|
|
58
|
+
|
|
59
|
+
// NOTE: Adapted this from https://stackoverflow.com/a/57393751,
|
|
60
|
+
// not sure if it's still a good way to do things
|
|
61
|
+
function handleInput(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
|
|
62
|
+
$state.value = $props!.type?.match(/^(number|range)$/) ? +e.currentTarget.value : e.currentTarget.value;
|
|
63
|
+
if (formContext) {
|
|
64
|
+
formContext.validate();
|
|
65
|
+
}
|
|
66
|
+
if (fieldContext && blurred) {
|
|
67
|
+
fieldContext.validate();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleBlur() {
|
|
72
|
+
if (fieldContext) {
|
|
73
|
+
fieldContext.validate();
|
|
74
|
+
}
|
|
75
|
+
blurred = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@render {
|
|
79
|
+
<input
|
|
80
|
+
{$props.id}
|
|
81
|
+
class={`torp-input ${$props.class ?? ""}`.trim()}
|
|
82
|
+
{$props.name}
|
|
83
|
+
&value={$state.value}
|
|
84
|
+
{$props.type}
|
|
85
|
+
{$props.placeholder}
|
|
86
|
+
{$props.readonly}
|
|
87
|
+
{$props.maxlength}
|
|
88
|
+
aria-label={$props.ariaLabel}
|
|
89
|
+
data-valid={$state.valid ? "valid" : "invalid"}
|
|
90
|
+
oninput={handleInput}
|
|
91
|
+
onblur={handleBlur}
|
|
92
|
+
/>
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { FieldContextName, type FieldContext } from "./FormTypes";
|
|
2
|
+
|
|
3
|
+
interface LabelProps {
|
|
4
|
+
/** An ID for the root element */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** Class name(s) for the root element */
|
|
7
|
+
class?: string,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A label to describe the contents of an input in a form.
|
|
12
|
+
*/
|
|
13
|
+
export default function Label($props?: LabelProps) {
|
|
14
|
+
// Get stuff out of the FieldContext
|
|
15
|
+
const context = $context[FieldContextName] as FieldContext;
|
|
16
|
+
if (!context) {
|
|
17
|
+
throw new Error("Label must be contained within a Field");
|
|
18
|
+
}
|
|
19
|
+
let { state: $state } = context;
|
|
20
|
+
|
|
21
|
+
@render {
|
|
22
|
+
<label
|
|
23
|
+
{$props?.id}
|
|
24
|
+
class={`torp-label ${$props?.class ?? ""}`.trim()}
|
|
25
|
+
for={$state.inputId}
|
|
26
|
+
data-valid={$state.valid ? "valid" : "invalid"}
|
|
27
|
+
>
|
|
28
|
+
<slot />
|
|
29
|
+
</label>
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FieldContextName, type FieldContext } from "./FormTypes";
|
|
2
|
+
|
|
3
|
+
interface MessageProps {
|
|
4
|
+
/** An ID for the root element */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** Class name(s) for the root element */
|
|
7
|
+
class?: string,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A validation message to show when the contents of an input component have
|
|
12
|
+
* problems that need to be fixed.
|
|
13
|
+
*/
|
|
14
|
+
export default function Message($props?: MessageProps) {
|
|
15
|
+
// Get stuff out of the FieldContext
|
|
16
|
+
const context = $context[FieldContextName] as FieldContext;
|
|
17
|
+
if (!context) {
|
|
18
|
+
throw new Error("Message must be contained within a Field");
|
|
19
|
+
}
|
|
20
|
+
const { state: $state } = context;
|
|
21
|
+
|
|
22
|
+
@render {
|
|
23
|
+
@if ($state.message) {
|
|
24
|
+
<div
|
|
25
|
+
{$props?.id}
|
|
26
|
+
class={`torp-message ${$props?.class ?? ""}`.trim()}
|
|
27
|
+
data-valid={$state.valid ? "valid" : "invalid"}
|
|
28
|
+
>
|
|
29
|
+
<slot name="message">
|
|
30
|
+
{$state.message}
|
|
31
|
+
</slot>
|
|
32
|
+
</div>
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// From https://github.com/standard-schema/standard-schema
|
|
2
|
+
|
|
3
|
+
/** The Standard Schema interface. */
|
|
4
|
+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
5
|
+
/** The Standard Schema properties. */
|
|
6
|
+
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export declare namespace StandardSchemaV1 {
|
|
10
|
+
/** The Standard Schema properties interface. */
|
|
11
|
+
export interface Props<Input = unknown, Output = Input> {
|
|
12
|
+
/** The version number of the standard. */
|
|
13
|
+
readonly version: 1;
|
|
14
|
+
/** The vendor name of the schema library. */
|
|
15
|
+
readonly vendor: string;
|
|
16
|
+
/** Validates unknown input values. */
|
|
17
|
+
readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
|
|
18
|
+
/** Inferred types associated with the schema. */
|
|
19
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The result interface of the validate function. */
|
|
23
|
+
export type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
24
|
+
|
|
25
|
+
/** The result interface if validation succeeds. */
|
|
26
|
+
export interface SuccessResult<Output> {
|
|
27
|
+
/** The typed output value. */
|
|
28
|
+
readonly value: Output;
|
|
29
|
+
/** The non-existent issues. */
|
|
30
|
+
readonly issues?: undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The result interface if validation fails. */
|
|
34
|
+
export interface FailureResult {
|
|
35
|
+
/** The issues of failed validation. */
|
|
36
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The issue interface of the failure output. */
|
|
40
|
+
export interface Issue {
|
|
41
|
+
/** The error message of the issue. */
|
|
42
|
+
readonly message: string;
|
|
43
|
+
/** The path of the issue, if any. */
|
|
44
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The path segment interface of the issue. */
|
|
48
|
+
export interface PathSegment {
|
|
49
|
+
/** The key representing a path segment. */
|
|
50
|
+
readonly key: PropertyKey;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The Standard Schema types interface. */
|
|
54
|
+
export interface Types<Input = unknown, Output = Input> {
|
|
55
|
+
/** The input type of the schema. */
|
|
56
|
+
readonly input: Input;
|
|
57
|
+
/** The output type of the schema. */
|
|
58
|
+
readonly output: Output;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Infers the input type of a Standard Schema. */
|
|
62
|
+
export type InferInput<Schema extends StandardSchemaV1> = NonNullable<
|
|
63
|
+
Schema["~standard"]["types"]
|
|
64
|
+
>["input"];
|
|
65
|
+
|
|
66
|
+
/** Infers the output type of a Standard Schema. */
|
|
67
|
+
export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<
|
|
68
|
+
Schema["~standard"]["types"]
|
|
69
|
+
>["output"];
|
|
70
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { PopoverContextName, type PopoverContext } from "../Popover/PopoverTypes";
|
|
2
|
+
import {
|
|
3
|
+
MenuContextName,
|
|
4
|
+
type MenuContext,
|
|
5
|
+
type ItemState,
|
|
6
|
+
MenuPopoutContextName,
|
|
7
|
+
type MenuPopoutContext
|
|
8
|
+
} from "./MenuTypes";
|
|
9
|
+
|
|
10
|
+
interface MenuProps {
|
|
11
|
+
/** An ID for the root element */
|
|
12
|
+
id?: string;
|
|
13
|
+
/** Class name(s) for the root element */
|
|
14
|
+
class?: string,
|
|
15
|
+
/** An ARIA label for describing the menu to screen readers */
|
|
16
|
+
ariaLabel?: string,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A list of choices for the user, such as a set of actions or functions. A
|
|
21
|
+
* menu is usually hidden until made visible by the user.
|
|
22
|
+
*
|
|
23
|
+
* See [the WAI ARIA guidelines for Menus](https://www.w3.org/WAI/ARIA/apg/patterns/menu/).
|
|
24
|
+
*
|
|
25
|
+
* The structure of a Menu is:
|
|
26
|
+
*
|
|
27
|
+
* ```
|
|
28
|
+
* <Menu>
|
|
29
|
+
* <MenuButton />
|
|
30
|
+
* <MenuSeparator /> (optional)
|
|
31
|
+
* <MenuGroup> (optional)
|
|
32
|
+
* <MenuButton />
|
|
33
|
+
* </MenuGroup>
|
|
34
|
+
* </Menu>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export default function Menu($props: MenuProps) {
|
|
38
|
+
$props ??= $watch({});
|
|
39
|
+
|
|
40
|
+
// A collection containing the state and some functions for each item
|
|
41
|
+
let itemStates: ItemState[] = [];
|
|
42
|
+
let focusIndex = -1;
|
|
43
|
+
|
|
44
|
+
// Get stuff out of the PopoverContext
|
|
45
|
+
const popoverContext = $context[PopoverContextName] as PopoverContext;
|
|
46
|
+
if (!popoverContext) {
|
|
47
|
+
throw new Error("Menu must be contained within a Contextual, Prompt or Popover");
|
|
48
|
+
}
|
|
49
|
+
const { setVisible, handleButton, modal, visible } = popoverContext;
|
|
50
|
+
|
|
51
|
+
// Check whether this is a menu in a popout
|
|
52
|
+
const popoutContext = $context[MenuPopoutContextName] as MenuPopoutContext;
|
|
53
|
+
const inPopout = !!popoutContext;
|
|
54
|
+
const setPopoutVisible = popoutContext?.setVisible;
|
|
55
|
+
|
|
56
|
+
// Set the focusFirstElement method in the PopoverContext(s) so that it can be called after everything has been shown
|
|
57
|
+
popoverContext.focusFirstElement = focusFirstElement;
|
|
58
|
+
if (popoutContext) {
|
|
59
|
+
popoutContext.focusFirstElement = focusFirstElement;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Set the context to pass down to items
|
|
63
|
+
let context: MenuContext = {
|
|
64
|
+
setVisible,
|
|
65
|
+
handleButton,
|
|
66
|
+
handleItemFocus,
|
|
67
|
+
handleItemKey,
|
|
68
|
+
visible,
|
|
69
|
+
registerItem
|
|
70
|
+
};
|
|
71
|
+
$context[MenuContextName] = context;
|
|
72
|
+
|
|
73
|
+
function focusFirstElement() {
|
|
74
|
+
if (itemStates.length) {
|
|
75
|
+
itemStates[0].setFocused();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function registerItem(setFocused: () => void) {
|
|
80
|
+
// Add the item"s state to the array and return its index
|
|
81
|
+
const newItemState: ItemState = {
|
|
82
|
+
setFocused
|
|
83
|
+
};
|
|
84
|
+
itemStates.push(newItemState);
|
|
85
|
+
itemStates = itemStates;
|
|
86
|
+
return { index: itemStates.length - 1 };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function handleItemFocus(index: number) {
|
|
90
|
+
focusIndex = index;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function handleItemKey(e: KeyboardEvent) {
|
|
94
|
+
switch (e.key) {
|
|
95
|
+
case "Esc":
|
|
96
|
+
case "Escape": {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
setVisible(false);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "Down":
|
|
102
|
+
case "ArrowDown": {
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
focusItem("next");
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "Up":
|
|
108
|
+
case "ArrowUp": {
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
focusItem("previous");
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case "Left":
|
|
114
|
+
case "ArrowLeft": {
|
|
115
|
+
// TODO: This should be based on what side the menu shows on
|
|
116
|
+
if (inPopout) {
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
setPopoutVisible(false);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case "Home": {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
focusItem("start");
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case "End": {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
focusItem("end");
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function focusItem(target: "start" | "previous" | "next" | "end") {
|
|
136
|
+
switch (target) {
|
|
137
|
+
case "start": {
|
|
138
|
+
focusIndex = 0;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "previous": {
|
|
142
|
+
focusIndex = Math.max(0, focusIndex - 1);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "next": {
|
|
146
|
+
focusIndex = Math.min(itemStates.length - 1, focusIndex + 1);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "end": {
|
|
150
|
+
focusIndex = itemStates.length - 1;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
itemStates[focusIndex]?.setFocused();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@render {
|
|
158
|
+
<div
|
|
159
|
+
{$props.id}
|
|
160
|
+
class={`torp-menu ${$props.class ?? ""}`.trim()}
|
|
161
|
+
tabindex="-1"
|
|
162
|
+
role="menu"
|
|
163
|
+
aria-label={$props.ariaLabel}
|
|
164
|
+
aria-orientation="vertical"
|
|
165
|
+
>
|
|
166
|
+
<slot />
|
|
167
|
+
</div>
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { MenuContextName, type MenuContext } from "./MenuTypes";
|
|
2
|
+
|
|
3
|
+
interface MenuButtonProps {
|
|
4
|
+
/** An ID for the root element */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** Class name(s) for the root element */
|
|
7
|
+
class?: string,
|
|
8
|
+
/** The value that should be returned when this button is pressed and the Dialog was invoked by calling showContextual, showPrompt or showPopover */
|
|
9
|
+
value?: any,
|
|
10
|
+
/** Whether this button is disabled */
|
|
11
|
+
disabled?: boolean,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A button that is placed in a Menu or MenuGroup and signals a response by a user.
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
export default function MenuButton($props: MenuButtonProps) {
|
|
19
|
+
$props ??= $watch({});
|
|
20
|
+
|
|
21
|
+
let button: HTMLButtonElement;
|
|
22
|
+
|
|
23
|
+
// Get stuff out of the MenuContext
|
|
24
|
+
const menuContext = $context[MenuContextName] as MenuContext;
|
|
25
|
+
if (!menuContext) {
|
|
26
|
+
throw new Error("MenuButton must be contained within a Menu");
|
|
27
|
+
}
|
|
28
|
+
const { registerItem, handleButton, handleItemKey, handleItemFocus } = menuContext;
|
|
29
|
+
|
|
30
|
+
// Register this item with the parent Menu and store the index
|
|
31
|
+
const { index } = registerItem(setFocused);
|
|
32
|
+
|
|
33
|
+
function handleKeyDown(e) {
|
|
34
|
+
handleItemKey(e);
|
|
35
|
+
if ($props.onkeydown) $props.onkeydown(e);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handleClick(e) {
|
|
39
|
+
handleButton($props.value);
|
|
40
|
+
if ($props.onclick) $props.onclick(e);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function setFocused() {
|
|
44
|
+
button.focus();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@render {
|
|
48
|
+
<button
|
|
49
|
+
{$props.id}
|
|
50
|
+
class={`torp-menu-button ${$props.class ?? ""}`.trim()}
|
|
51
|
+
type="button"
|
|
52
|
+
{$props.disabled}
|
|
53
|
+
role="menuitem"
|
|
54
|
+
aria-disabled={$props.disabled ? "true" : undefined}
|
|
55
|
+
data-disabled={$props.disabled ? "true" : undefined}
|
|
56
|
+
onkeydown={handleKeyDown}
|
|
57
|
+
onclick={handleClick}
|
|
58
|
+
onfocus={() => handleItemFocus(index)}
|
|
59
|
+
&ref={button}
|
|
60
|
+
>
|
|
61
|
+
<slot />
|
|
62
|
+
</button>
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MenuButtonContextName,
|
|
3
|
+
MenuContextName,
|
|
4
|
+
type MenuButtonContext,
|
|
5
|
+
type MenuContext
|
|
6
|
+
} from "./MenuTypes";
|
|
7
|
+
|
|
8
|
+
interface MenuCheckProps {
|
|
9
|
+
/** An ID for the root element */
|
|
10
|
+
id?: string;
|
|
11
|
+
/** Class name(s) for the root element */
|
|
12
|
+
class?: string,
|
|
13
|
+
/** Whether this item is checked */
|
|
14
|
+
checked?: boolean,
|
|
15
|
+
/** Whether this item is disabled */
|
|
16
|
+
disabled?: boolean,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A button that is placed in a Menu or MenuGroup to allow the user to turn an
|
|
21
|
+
* option on or off.
|
|
22
|
+
*
|
|
23
|
+
*/
|
|
24
|
+
export default function MenuCheck($props: MenuCheckProps) {
|
|
25
|
+
$props ??= $watch({});
|
|
26
|
+
|
|
27
|
+
let $state = $watch({
|
|
28
|
+
checked: $props.checked
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
$run(() => {
|
|
32
|
+
$props.checked = $state.checked;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
let button: HTMLButtonElement;
|
|
36
|
+
|
|
37
|
+
// Get stuff out of the MenuContext
|
|
38
|
+
const menuContext = $context[MenuContextName] as MenuContext;
|
|
39
|
+
if (!menuContext) {
|
|
40
|
+
throw new Error("MenuCheck must be contained within a Menu");
|
|
41
|
+
}
|
|
42
|
+
const { registerItem, handleItemKey, handleItemFocus } = menuContext;
|
|
43
|
+
|
|
44
|
+
// Register this item with the parent Menu
|
|
45
|
+
const { index } = registerItem(setFocused);
|
|
46
|
+
|
|
47
|
+
// Set the context to pass down to items
|
|
48
|
+
let context: MenuButtonContext = {
|
|
49
|
+
state: $state
|
|
50
|
+
};
|
|
51
|
+
$context[MenuButtonContextName] = context;
|
|
52
|
+
|
|
53
|
+
function setFocused() {
|
|
54
|
+
button.focus();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function handleClick() {
|
|
58
|
+
$state.checked = !$state.checked;
|
|
59
|
+
if ($props.onclick) $props.onclick();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@render {
|
|
63
|
+
<button
|
|
64
|
+
{$props.id}
|
|
65
|
+
class={`torp-menu-check ${$props.class ?? ""}`.trim()}
|
|
66
|
+
type="button"
|
|
67
|
+
{$props.disabled}
|
|
68
|
+
role="menuitemcheckbox"
|
|
69
|
+
aria-checked={$props.checked ? "true" : "false"}
|
|
70
|
+
aria-disabled={$props.disabled ? "true" : undefined}
|
|
71
|
+
data-state={$props.checked ? "checked" : "unchecked"}
|
|
72
|
+
data-disabled={$props.disabled ? "true" : undefined}
|
|
73
|
+
onkeydown={handleItemKey}
|
|
74
|
+
onclick={handleClick}
|
|
75
|
+
onfocus={() => handleItemFocus(index)}
|
|
76
|
+
&ref={button}
|
|
77
|
+
>
|
|
78
|
+
<slot />
|
|
79
|
+
</button>
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
interface MenuGroupProps {
|
|
3
|
+
/** An ID for the root element */
|
|
4
|
+
id?: string;
|
|
5
|
+
/** Class name(s) for the root element */
|
|
6
|
+
class?: string,
|
|
7
|
+
/** An ARIA label for describing the group to screen readers */
|
|
8
|
+
ariaLabel?: string,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A container for a logical group of menu items.
|
|
13
|
+
*/
|
|
14
|
+
export default function MenuGroup($props: MenuGroupProps) {
|
|
15
|
+
$props ??= $watch({});
|
|
16
|
+
|
|
17
|
+
// TODO: Intercept the context to steal toggleItem??
|
|
18
|
+
|
|
19
|
+
@render {
|
|
20
|
+
<div
|
|
21
|
+
{$props.id}
|
|
22
|
+
class={`torp-menu-group ${$props.class ?? ""}`.trim()}
|
|
23
|
+
role="group"
|
|
24
|
+
aria-label={$props.ariaLabel}
|
|
25
|
+
>
|
|
26
|
+
<slot />
|
|
27
|
+
</div>
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { MenuButtonContextName, type MenuButtonContext } from "./MenuTypes";
|
|
2
|
+
|
|
3
|
+
interface MenuIndicatorProps {
|
|
4
|
+
/** An ID for the root element */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** Class name(s) for the root element */
|
|
7
|
+
class?: string,
|
|
8
|
+
/** Whether this item is disabled */
|
|
9
|
+
disabled?: boolean,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* An indicator for a MenuCheck or MenuRadio component, which is displayed
|
|
14
|
+
* when the value of checked is true.
|
|
15
|
+
*/
|
|
16
|
+
export default function MenuIndicator($props: MenuIndicatorProps) {
|
|
17
|
+
$props ??= $watch({});
|
|
18
|
+
|
|
19
|
+
// Get stuff out of the MenuButtonContext
|
|
20
|
+
const context = $context[MenuButtonContextName] as MenuButtonContext;
|
|
21
|
+
if (!context) {
|
|
22
|
+
throw new Error("MenuIndicator must be contained within a MenuCheck or MenuRadio");
|
|
23
|
+
}
|
|
24
|
+
let $state = context.state;
|
|
25
|
+
|
|
26
|
+
@render {
|
|
27
|
+
@if ($state.checked) {
|
|
28
|
+
<div
|
|
29
|
+
{$props.id}
|
|
30
|
+
class={`torp-menu-indicator ${$props.class ?? ""}`.trim()}
|
|
31
|
+
aria-disabled={$props.disabled ? "true" : undefined}
|
|
32
|
+
data-disabled={$props.disabled ? "true" : undefined}
|
|
33
|
+
>
|
|
34
|
+
<slot />
|
|
35
|
+
</div>
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { MenuPopoutContextName, type MenuPopoutContext } from "./MenuTypes";
|
|
2
|
+
|
|
3
|
+
interface MenuPopoutProps {
|
|
4
|
+
/** An ID for the root element */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** Class name(s) for the root element */
|
|
7
|
+
class?: string,
|
|
8
|
+
/** Whether the popout is visible */
|
|
9
|
+
visible?: boolean,
|
|
10
|
+
/** Called when the popout is opened or closed */
|
|
11
|
+
ontoggle?: (open: boolean) => void,
|
|
12
|
+
/** Called when the popout has opened */
|
|
13
|
+
onopen?: () => void,
|
|
14
|
+
/** Called when the popout has closed */
|
|
15
|
+
onclose?: () => void,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A popout that is attached to a menu button and can be used to show a submenu.
|
|
20
|
+
*
|
|
21
|
+
* The structure of a MenuPopout is:
|
|
22
|
+
*
|
|
23
|
+
* ```
|
|
24
|
+
* <MenuPopout>
|
|
25
|
+
* <MenuPopoutTrigger /> (optional)
|
|
26
|
+
* <MenuPopoutContent />
|
|
27
|
+
* </MenuPopout>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export default function MenuPopout($props: MenuPopoutProps) {
|
|
31
|
+
$props ??= $watch({});
|
|
32
|
+
$props.visible ??= false;
|
|
33
|
+
|
|
34
|
+
let $state = $watch({
|
|
35
|
+
visible: $props.visible
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
let result: any;
|
|
39
|
+
|
|
40
|
+
// Set the context to pass down to items
|
|
41
|
+
let context: MenuPopoutContext = {
|
|
42
|
+
setVisible,
|
|
43
|
+
state: $state
|
|
44
|
+
};
|
|
45
|
+
$context[MenuPopoutContextName] = context;
|
|
46
|
+
|
|
47
|
+
function setVisible(value: boolean) {
|
|
48
|
+
$state.visible = value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let firstToggle = true;
|
|
52
|
+
$run(() => {
|
|
53
|
+
const ontoggle = $props.ontoggle;
|
|
54
|
+
const onopen = $props.onopen;
|
|
55
|
+
const onclose = $props.onclose;
|
|
56
|
+
const visible = $state.visible;
|
|
57
|
+
if (firstToggle) {
|
|
58
|
+
firstToggle = false;
|
|
59
|
+
} else {
|
|
60
|
+
if (ontoggle) ontoggle(visible);
|
|
61
|
+
if (visible) {
|
|
62
|
+
if (onopen) onopen()
|
|
63
|
+
} else {
|
|
64
|
+
if (onclose) onclose(result);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function handleMouseLeave() {
|
|
70
|
+
setVisible(false);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@render {
|
|
74
|
+
<div
|
|
75
|
+
{$props.id}
|
|
76
|
+
class={`torp-menu-popout ${$props.class ?? ""}`.trim()}
|
|
77
|
+
onmouseleave={handleMouseLeave}
|
|
78
|
+
>
|
|
79
|
+
<slot />
|
|
80
|
+
</div>
|
|
81
|
+
}
|
|
82
|
+
}
|