@torpor/ui 0.3.7 → 1.0.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/README.md +83 -7
- package/dist/Accordion/Accordion.torp +43 -165
- package/dist/Accordion/AccordionContent.torp +6 -6
- package/dist/Accordion/AccordionHeader.torp +7 -11
- package/dist/Accordion/AccordionItem.torp +24 -24
- package/dist/Accordion/AccordionTrigger.torp +17 -8
- package/dist/Accordion/AccordionTypes.ts +6 -6
- package/dist/Accordion/index.d.ts +23 -25
- package/dist/Accordion/index.js +6 -22
- package/dist/Breadcrumb/Breadcrumb.torp +12 -10
- package/dist/Breadcrumb/BreadcrumbItem.torp +6 -6
- package/dist/Breadcrumb/BreadcrumbList.torp +21 -0
- package/dist/Breadcrumb/BreadcrumbSeparator.torp +4 -4
- package/dist/Breadcrumb/index.d.ts +39 -15
- package/dist/Breadcrumb/index.js +5 -14
- package/dist/Calendar/Calendar.torp +163 -156
- package/dist/Calendar/CalendarDay.torp +91 -45
- package/dist/Calendar/CalendarDayHeader.torp +36 -0
- package/dist/Calendar/CalendarGrid.torp +104 -0
- package/dist/Calendar/CalendarGridHeader.torp +43 -0
- package/dist/Calendar/CalendarHeader.torp +48 -0
- package/dist/Calendar/CalendarHeaderTrigger.torp +54 -0
- package/dist/Calendar/CalendarTypes.ts +25 -4
- package/dist/Calendar/buildDays.ts +54 -0
- package/dist/Calendar/index.d.ts +191 -5
- package/dist/Calendar/index.js +8 -10
- package/dist/Carousel/Carousel.torp +180 -0
- package/dist/Carousel/CarouselIndicators.torp +65 -0
- package/dist/Carousel/CarouselNext.torp +43 -0
- package/dist/Carousel/CarouselPrevious.torp +44 -0
- package/dist/Carousel/CarouselSlide.torp +57 -0
- package/dist/Carousel/CarouselTypes.ts +28 -0
- package/dist/Carousel/index.d.ts +147 -0
- package/dist/Carousel/index.js +6 -0
- package/dist/Charts/BarChart.torp +70 -0
- package/dist/Charts/Chart.torp +120 -0
- package/dist/Charts/ChartBars.torp +78 -0
- package/dist/Charts/ChartColumns.torp +66 -0
- package/dist/Charts/ChartGrid.torp +54 -0
- package/dist/Charts/ChartLines.torp +60 -0
- package/dist/Charts/ChartTypes.ts +25 -0
- package/dist/Charts/ChartXAxis.torp +83 -0
- package/dist/Charts/ChartYAxis.torp +105 -0
- package/dist/Charts/ColumnChart.torp +70 -0
- package/dist/{Chart → Charts}/GridLines.torp +7 -4
- package/dist/Charts/LineChart.torp +73 -0
- package/dist/{Chart → Charts}/PieChart.torp +14 -20
- package/dist/{Chart → Charts}/ScatterChart.torp +45 -38
- package/dist/{Chart → Charts}/SparkLine.torp +16 -19
- package/dist/{Chart → Charts}/XAxis.torp +18 -12
- package/dist/{Chart → Charts}/YAxis.torp +15 -9
- package/dist/Charts/index.d.ts +469 -0
- package/dist/Charts/index.js +17 -0
- package/dist/ColorPicker/ColorPalette.torp +154 -0
- package/dist/ColorPicker/ColorPicker.torp +157 -0
- package/dist/ColorPicker/index.d.ts +85 -0
- package/dist/ColorPicker/index.js +3 -0
- package/dist/ComboBox/ComboBox.torp +205 -0
- package/dist/ComboBox/ComboBoxContent.torp +112 -0
- package/dist/ComboBox/ComboBoxInput.torp +145 -0
- package/dist/ComboBox/ComboBoxTrigger.torp +49 -0
- package/dist/ComboBox/ComboBoxTypes.ts +39 -0
- package/dist/ComboBox/index.d.ts +152 -0
- package/dist/ComboBox/index.js +6 -0
- package/dist/CommandPalette/CommandPalette.torp +220 -0
- package/dist/CommandPalette/CommandTypes.ts +14 -0
- package/dist/CommandPalette/index.d.ts +45 -0
- package/dist/CommandPalette/index.js +2 -0
- package/dist/Contextual/Contextual.torp +39 -52
- package/dist/Contextual/ContextualContent.torp +29 -73
- package/dist/Contextual/ContextualHover.torp +112 -0
- package/dist/Contextual/ContextualTrigger.torp +31 -42
- package/dist/Contextual/ContextualTypes.ts +5 -18
- package/dist/Contextual/index.d.ts +44 -27
- package/dist/Contextual/index.js +5 -14
- package/dist/DataGrid/DataGrid.torp +496 -0
- package/dist/DataGrid/DataGridTypes.ts +25 -0
- package/dist/DataGrid/index.d.ts +90 -0
- package/dist/DataGrid/index.js +3 -0
- package/dist/DataGrid/sortRows.ts +32 -0
- package/dist/DatePicker/DatePicker.torp +222 -0
- package/dist/DatePicker/DatePickerHeader.torp +20 -0
- package/dist/DatePicker/index.d.ts +55 -0
- package/dist/DatePicker/index.js +2 -0
- package/dist/DateRangePicker/DateRangePicker.torp +311 -0
- package/dist/DateRangePicker/DateRangePickerDay.torp +137 -0
- package/dist/DateRangePicker/DateRangePickerTypes.ts +21 -0
- package/dist/DateRangePicker/index.d.ts +76 -0
- package/dist/DateRangePicker/index.js +3 -0
- package/dist/Dialog/Dialog.torp +33 -30
- package/dist/Dialog/DialogBody.torp +5 -9
- package/dist/Dialog/DialogButton.torp +14 -11
- package/dist/Dialog/DialogFooter.torp +3 -7
- package/dist/Dialog/DialogHeader.torp +6 -10
- package/dist/Dialog/DialogTypes.ts +2 -2
- package/dist/Dialog/index.d.ts +28 -26
- package/dist/Dialog/index.js +6 -22
- package/dist/Disclosure/Disclosure.torp +18 -18
- package/dist/Disclosure/DisclosureContent.torp +5 -6
- package/dist/Disclosure/DisclosureHeader.torp +7 -11
- package/dist/Disclosure/DisclosureTrigger.torp +4 -5
- package/dist/Disclosure/DisclosureTypes.ts +1 -1
- package/dist/Disclosure/index.d.ts +14 -16
- package/dist/Disclosure/index.js +5 -18
- package/dist/Drawer/Drawer.torp +21 -51
- package/dist/Drawer/DrawerContent.torp +67 -13
- package/dist/Drawer/DrawerOverlay.torp +4 -4
- package/dist/Drawer/DrawerTrigger.torp +16 -4
- package/dist/Drawer/DrawerTypes.ts +4 -1
- package/dist/Drawer/index.d.ts +16 -20
- package/dist/Drawer/index.js +5 -18
- package/dist/FocusGroup/FocusGroup.torp +70 -0
- package/dist/FocusGroup/FocusGroupButton.torp +59 -0
- package/dist/FocusGroup/FocusGroupTypes.ts +14 -0
- package/dist/FocusGroup/index.d.ts +44 -0
- package/dist/FocusGroup/index.js +3 -0
- package/dist/Form/CheckBox.torp +28 -20
- package/dist/Form/Field.torp +17 -17
- package/dist/Form/File.torp +31 -22
- package/dist/Form/FileDrop.torp +187 -0
- package/dist/Form/Form.torp +24 -22
- package/dist/Form/FormTypes.ts +2 -0
- package/dist/Form/FormValues.torp +37 -0
- package/dist/Form/Hidden.torp +4 -12
- package/dist/Form/Input.torp +38 -32
- package/dist/Form/Label.torp +3 -3
- package/dist/Form/Message.torp +13 -5
- package/dist/Form/MessageSummary.torp +39 -0
- package/dist/Form/Option.torp +11 -7
- package/dist/Form/Select.torp +32 -28
- package/dist/Form/TextArea.torp +35 -31
- package/dist/Form/Toggle.torp +109 -0
- package/dist/Form/index.d.ts +198 -77
- package/dist/Form/index.js +16 -46
- package/dist/ListBox/ListBox.torp +374 -0
- package/dist/ListBox/ListBoxItem.torp +131 -0
- package/dist/ListBox/ListBoxSeparator.torp +29 -0
- package/dist/ListBox/ListBoxTypes.ts +41 -0
- package/dist/ListBox/LoadedListBoxItems.torp +41 -0
- package/dist/ListBox/index.d.ts +136 -0
- package/dist/ListBox/index.js +4 -0
- package/dist/MaskedInput/MaskedInput.torp +143 -0
- package/dist/MaskedInput/index.d.ts +52 -0
- package/dist/MaskedInput/index.js +2 -0
- package/dist/Menu/Menu.torp +90 -70
- package/dist/Menu/MenuButton.torp +17 -10
- package/dist/Menu/MenuCheck.torp +22 -18
- package/dist/Menu/MenuGroup.torp +4 -4
- package/dist/Menu/MenuIndicator.torp +5 -13
- package/dist/Menu/MenuPopout.torp +20 -50
- package/dist/Menu/MenuPopoutContent.torp +15 -66
- package/dist/Menu/MenuPopoutTrigger.torp +19 -100
- package/dist/Menu/MenuRadio.torp +25 -26
- package/dist/Menu/MenuRadioGroup.torp +25 -68
- package/dist/Menu/MenuSeparator.torp +3 -2
- package/dist/Menu/MenuTypes.ts +12 -21
- package/dist/Menu/index.d.ts +45 -51
- package/dist/Menu/index.js +12 -46
- package/dist/MenuBar/MenuBar.torp +31 -83
- package/dist/MenuBar/MenuBarItem.torp +34 -55
- package/dist/MenuBar/MenuBarItemContent.torp +15 -101
- package/dist/MenuBar/MenuBarItemTrigger.torp +20 -55
- package/dist/MenuBar/MenuBarTypes.ts +13 -12
- package/dist/MenuBar/index.d.ts +17 -19
- package/dist/MenuBar/index.js +5 -18
- package/dist/Modal/Modal.torp +28 -48
- package/dist/Modal/ModalContent.torp +21 -44
- package/dist/Modal/ModalOverlay.torp +4 -4
- package/dist/Modal/ModalTrigger.torp +16 -32
- package/dist/Modal/ModalTypes.ts +6 -17
- package/dist/Modal/index.d.ts +17 -29
- package/dist/Modal/index.js +29 -28
- package/dist/Modal/index.js.map +1 -1
- package/dist/Modal/showPrompt.ts +2 -2
- package/dist/NavMenu/NavMenu.torp +22 -104
- package/dist/NavMenu/NavMenuButton.torp +10 -8
- package/dist/NavMenu/NavMenuElement.torp +6 -7
- package/dist/NavMenu/NavMenuGroup.torp +5 -5
- package/dist/NavMenu/NavMenuGroupHeader.torp +5 -8
- package/dist/NavMenu/NavMenuLink.torp +7 -10
- package/dist/NavMenu/NavMenuPopout.torp +16 -61
- package/dist/NavMenu/NavMenuPopoutContent.torp +14 -73
- package/dist/NavMenu/NavMenuPopoutTrigger.torp +47 -0
- package/dist/NavMenu/NavMenuSeparator.torp +3 -6
- package/dist/NavMenu/NavMenuTypes.ts +6 -15
- package/dist/NavMenu/index.d.ts +55 -55
- package/dist/NavMenu/index.js +11 -42
- package/dist/Notification/Notification.torp +12 -24
- package/dist/Notification/NotificationClose.torp +6 -3
- package/dist/Notification/NotificationContainer.torp +3 -7
- package/dist/Notification/NotificationContent.torp +3 -7
- package/dist/Notification/NotificationTypes.ts +1 -1
- package/dist/Notification/index.d.ts +15 -12
- package/dist/Notification/index.js +22 -32
- package/dist/Notification/index.js.map +1 -1
- package/dist/NumberInput/NumberInput.torp +255 -0
- package/dist/NumberInput/index.d.ts +44 -0
- package/dist/NumberInput/index.js +2 -0
- package/dist/Pagination/Pagination.torp +152 -0
- package/dist/Pagination/PaginationItem.torp +137 -0
- package/dist/Pagination/PaginationTypes.ts +22 -0
- package/dist/Pagination/buildPages.ts +61 -0
- package/dist/Pagination/index.d.ts +63 -0
- package/dist/Pagination/index.js +3 -0
- package/dist/Popover/Popover.torp +39 -52
- package/dist/Popover/PopoverContent.torp +27 -76
- package/dist/Popover/PopoverHover.torp +90 -0
- package/dist/Popover/PopoverTrigger.torp +17 -40
- package/dist/Popover/PopoverTypes.ts +5 -18
- package/dist/Popover/index.d.ts +40 -25
- package/dist/Popover/index.js +18 -18
- package/dist/Popover/index.js.map +1 -1
- package/dist/Progress/Progress.torp +88 -0
- package/dist/Progress/index.d.ts +37 -0
- package/dist/Progress/index.js +2 -0
- package/dist/Rating/Rating.torp +225 -0
- package/dist/Rating/index.d.ts +44 -0
- package/dist/Rating/index.js +2 -0
- package/dist/SegmentedControl/SegmentedControl.torp +233 -0
- package/dist/SegmentedControl/SegmentedControlTypes.ts +26 -0
- package/dist/SegmentedControl/SegmentedItem.torp +78 -0
- package/dist/SegmentedControl/index.d.ts +74 -0
- package/dist/SegmentedControl/index.js +3 -0
- package/dist/SelectBox/SelectBox.torp +190 -0
- package/dist/SelectBox/SelectBoxContent.torp +107 -0
- package/dist/SelectBox/SelectBoxTrigger.torp +141 -0
- package/dist/SelectBox/SelectBoxTypes.ts +33 -0
- package/dist/SelectBox/index.d.ts +112 -0
- package/dist/SelectBox/index.js +5 -0
- package/dist/Slider/Slider.torp +248 -0
- package/dist/Slider/index.d.ts +45 -0
- package/dist/Slider/index.js +2 -0
- package/dist/Splitter/Splitter.torp +156 -0
- package/dist/Splitter/SplitterHandle.torp +161 -0
- package/dist/Splitter/SplitterPane.torp +63 -0
- package/dist/Splitter/SplitterTypes.ts +31 -0
- package/dist/Splitter/index.d.ts +103 -0
- package/dist/Splitter/index.js +4 -0
- package/dist/Stepper/Stepper.torp +146 -0
- package/dist/Stepper/StepperMarker.torp +24 -0
- package/dist/Stepper/StepperNext.torp +44 -0
- package/dist/Stepper/StepperPrevious.torp +44 -0
- package/dist/Stepper/StepperStep.torp +90 -0
- package/dist/Stepper/StepperTypes.ts +30 -0
- package/dist/Stepper/index.d.ts +149 -0
- package/dist/Stepper/index.js +6 -0
- package/dist/TabGroup/TabContent.torp +6 -6
- package/dist/TabGroup/TabGroup.torp +97 -113
- package/dist/TabGroup/TabGroupTypes.ts +3 -3
- package/dist/TabGroup/TabHeader.torp +23 -19
- package/dist/TabGroup/TabList.torp +8 -21
- package/dist/TabGroup/TabTrigger.torp +6 -7
- package/dist/TabGroup/index.d.ts +26 -26
- package/dist/TabGroup/index.js +6 -22
- package/dist/TagInput/TagInput.torp +378 -0
- package/dist/TagInput/index.d.ts +48 -0
- package/dist/TagInput/index.js +2 -0
- package/dist/TimePicker/TimePicker.torp +462 -0
- package/dist/TimePicker/index.d.ts +53 -0
- package/dist/TimePicker/index.js +2 -0
- package/dist/ToolBar/ToolBar.torp +117 -0
- package/dist/ToolBar/ToolBarButton.torp +56 -0
- package/dist/ToolBar/ToolBarElement.torp +51 -0
- package/dist/ToolBar/ToolBarPopout.torp +45 -0
- package/dist/ToolBar/ToolBarPopoutContent.torp +34 -0
- package/dist/ToolBar/ToolBarPopoutTrigger.torp +47 -0
- package/dist/ToolBar/ToolBarSeparator.torp +21 -0
- package/dist/ToolBar/ToolBarTypes.ts +28 -0
- package/dist/ToolBar/index.d.ts +210 -0
- package/dist/ToolBar/index.js +8 -0
- package/dist/Tree/Tree.torp +395 -0
- package/dist/Tree/TreeItem.torp +176 -0
- package/dist/Tree/TreeItemChildren.torp +60 -0
- package/dist/Tree/TreeItemContent.torp +46 -0
- package/dist/Tree/TreeItemLabel.torp +94 -0
- package/dist/Tree/TreeItemTrigger.torp +78 -0
- package/dist/Tree/TreeLoadedChildren.torp +122 -0
- package/dist/Tree/TreeTypes.ts +63 -0
- package/dist/Tree/index.d.ts +262 -0
- package/dist/Tree/index.js +7 -0
- package/dist/icons/ChevronDoubleLeft.torp +19 -0
- package/dist/icons/ChevronDoubleRight.torp +19 -0
- package/dist/icons/index.d.ts +61 -1
- package/dist/icons/index.js +17 -58
- package/dist/loader-DEeG5vFY.js +44 -0
- package/dist/loader-DEeG5vFY.js.map +1 -0
- package/dist/motion/index.d.ts +12 -8
- package/dist/motion/index.d.ts.map +1 -0
- package/dist/motion/index.js +72 -88
- package/dist/motion/index.js.map +1 -1
- package/dist/mount/draggable.ts +63 -0
- package/dist/mount/dropTarget.ts +108 -0
- package/dist/mount/hoverTrigger.ts +1 -1
- package/dist/utils/$handle.ts +23 -0
- package/dist/utils/LoadedItems.torp +47 -0
- package/dist/utils/PopoutClickTrigger.torp +99 -0
- package/dist/utils/PopoutTypes.ts +82 -0
- package/dist/utils/SubmenuPopout.torp +89 -0
- package/dist/utils/SubmenuPopoutContent.torp +87 -0
- package/dist/utils/SubmenuPopoutTrigger.torp +160 -0
- package/dist/utils/chartColors.ts +13 -13
- package/dist/utils/chartLayout.ts +143 -0
- package/dist/utils/chartUtils.ts +1 -1
- package/dist/utils/colorUtils.ts +103 -0
- package/dist/utils/createItemGroup.ts +152 -0
- package/dist/utils/dateUtils.ts +8 -7
- package/dist/utils/documentEvents.ts +4 -4
- package/dist/utils/dragState.ts +86 -0
- package/dist/utils/focusGroup.ts +204 -0
- package/dist/utils/focusItem.ts +44 -0
- package/dist/utils/formField.ts +70 -0
- package/dist/utils/hoverOpen.ts +50 -0
- package/dist/utils/hoverReveal.ts +194 -0
- package/dist/utils/loader.ts +113 -0
- package/dist/utils/maskUtils.ts +98 -0
- package/dist/utils/popoutContent.ts +137 -0
- package/dist/utils/reorderList.ts +52 -0
- package/package.json +116 -25
- package/dist/Accordion/index.js.map +0 -1
- package/dist/Breadcrumb/index.js.map +0 -1
- package/dist/Calendar/index.js.map +0 -1
- package/dist/Chart/BarChart.torp +0 -159
- package/dist/Chart/LineChart.torp +0 -196
- package/dist/Chart/index.d.ts +0 -220
- package/dist/Chart/index.js +0 -34
- package/dist/Chart/index.js.map +0 -1
- package/dist/Contextual/index.js.map +0 -1
- package/dist/Dialog/index.js.map +0 -1
- package/dist/Disclosure/index.js.map +0 -1
- package/dist/Drawer/index.js.map +0 -1
- package/dist/Form/index.js.map +0 -1
- package/dist/Menu/index.js.map +0 -1
- package/dist/MenuBar/index.js.map +0 -1
- package/dist/NavMenu/NavMenuPopoutButton.torp +0 -129
- package/dist/NavMenu/index.js.map +0 -1
- package/dist/TabGroup/index.js.map +0 -1
- package/dist/icons/index.js.map +0 -1
- package/dist/index.d.ts +0 -19
- package/dist/metafile-esm.json +0 -1
- package/dist/motion/fade.ts +0 -16
- package/dist/motion/grow.ts +0 -23
- package/dist/motion/measure.ts +0 -28
- package/dist/motion/slide.ts +0 -45
- package/dist/mount/index.d.ts +0 -2
- package/dist/utils/index.d.ts +0 -2
- /package/dist/{Chart → Charts}/DataSeries.ts +0 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import type { Bindable, ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import {
|
|
3
|
+
type FieldContext,
|
|
4
|
+
FieldContextName,
|
|
5
|
+
type FormContext,
|
|
6
|
+
FormContextName,
|
|
7
|
+
} from "../Form/FormTypes";
|
|
8
|
+
import FormValues from "../Form/FormValues.torp";
|
|
9
|
+
import createFormField from "../utils/formField";
|
|
10
|
+
|
|
11
|
+
interface TimePickerProps {
|
|
12
|
+
/** An ID for the root element */
|
|
13
|
+
id?: string;
|
|
14
|
+
/** Classes for the root element */
|
|
15
|
+
class?: ClassValue;
|
|
16
|
+
/** Styles for the root element */
|
|
17
|
+
style?: StyleValue;
|
|
18
|
+
/**
|
|
19
|
+
* The current time as "HH:mm" or "HH:mm:ss" (24-hour), or undefined if
|
|
20
|
+
* the field is empty
|
|
21
|
+
*/
|
|
22
|
+
value?: Bindable<string | undefined>;
|
|
23
|
+
/** If set to true, times are displayed and entered in 12-hour format with an AM/PM toggle */
|
|
24
|
+
hour12?: boolean;
|
|
25
|
+
/** If set to true, a seconds field is shown */
|
|
26
|
+
seconds?: boolean;
|
|
27
|
+
/** The amount by which the minute field steps (default 1) */
|
|
28
|
+
step?: number;
|
|
29
|
+
/** The earliest allowed time, as "HH:mm" or "HH:mm:ss" */
|
|
30
|
+
min?: string;
|
|
31
|
+
/** The latest allowed time, as "HH:mm" or "HH:mm:ss" */
|
|
32
|
+
max?: string;
|
|
33
|
+
/** If set to true, the user cannot interact with this input */
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
/** An accessible name for the whole input */
|
|
36
|
+
ariaLabel?: string;
|
|
37
|
+
/** The name of the component's form data field */
|
|
38
|
+
name?: string;
|
|
39
|
+
/** Raised when the value has changed */
|
|
40
|
+
onchange?: (value: string | undefined) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type Part = "hour" | "minute" | "second";
|
|
44
|
+
|
|
45
|
+
const PAD = [2, 2, 2];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* An input for entering a time of day, made of hour, minute and (optionally)
|
|
49
|
+
* second fields that follow the WAI ARIA spinbutton pattern -- each can be
|
|
50
|
+
* typed into or stepped with the arrow keys -- plus an AM/PM toggle in
|
|
51
|
+
* 12-hour mode.
|
|
52
|
+
*
|
|
53
|
+
* The value is always the canonical 24-hour form ("HH:mm" or "HH:mm:ss"),
|
|
54
|
+
* regardless of display format.
|
|
55
|
+
*/
|
|
56
|
+
export default function TimePicker($props: TimePickerProps | undefined) {
|
|
57
|
+
let hourInput: HTMLInputElement;
|
|
58
|
+
let minuteInput: HTMLInputElement;
|
|
59
|
+
let secondInput: HTMLInputElement;
|
|
60
|
+
let periodButton: HTMLButtonElement;
|
|
61
|
+
|
|
62
|
+
// Wire up the enclosing Form and Field, if any
|
|
63
|
+
const form = createFormField(
|
|
64
|
+
$props?.name,
|
|
65
|
+
$context[FormContextName] as FormContext | undefined,
|
|
66
|
+
$context[FieldContextName] as FieldContext | undefined,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
function getSeconds(): boolean {
|
|
70
|
+
return $props?.seconds === true;
|
|
71
|
+
}
|
|
72
|
+
function getDisabled(): boolean {
|
|
73
|
+
return $props?.disabled === true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseTime(text: string | undefined): { h: number; m: number; s: number } | undefined {
|
|
77
|
+
if (!text) return undefined;
|
|
78
|
+
const match = text.match(/^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/);
|
|
79
|
+
if (!match) return undefined;
|
|
80
|
+
return {
|
|
81
|
+
h: Number(match[1]),
|
|
82
|
+
m: Number(match[2]),
|
|
83
|
+
s: match[3] ? Number(match[3]) : 0,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function clampPart(part: Part, n: number): number {
|
|
88
|
+
switch (part) {
|
|
89
|
+
case "hour":
|
|
90
|
+
return Math.min(23, Math.max(0, n));
|
|
91
|
+
case "minute":
|
|
92
|
+
return Math.min(59, Math.max(0, n));
|
|
93
|
+
case "second":
|
|
94
|
+
return Math.min(59, Math.max(0, n));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function format(n: number): string {
|
|
99
|
+
return String(n).padStart(2, "0");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function formatValue(h: number, m: number, s: number): string {
|
|
103
|
+
return getSeconds() ? `${format(h)}:${format(m)}:${format(s)}` : `${format(h)}:${format(m)}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let $state = $watch({
|
|
107
|
+
value: undefined as string | undefined,
|
|
108
|
+
|
|
109
|
+
// The text being typed in each segment; committed on completion
|
|
110
|
+
hourText: "",
|
|
111
|
+
minuteText: "",
|
|
112
|
+
secondText: "",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
$bind($state, $props, "value");
|
|
116
|
+
|
|
117
|
+
// The committed parts of the current value; missing parts are zero
|
|
118
|
+
function parts(): { h: number; m: number; s: number } {
|
|
119
|
+
const parsed = parseTime($state.value);
|
|
120
|
+
return parsed ?? { h: 0, m: 0, s: 0 };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getMin(): number {
|
|
124
|
+
const parsed = parseTime($props?.min);
|
|
125
|
+
if (!parsed) return 0;
|
|
126
|
+
return parsed.h * 3600 + parsed.m * 60 + parsed.s;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getMax(): number {
|
|
130
|
+
const parsed = parseTime($props?.max);
|
|
131
|
+
if (!parsed) return 24 * 3600 - (getSeconds() ? 1 : 60);
|
|
132
|
+
return parsed.h * 3600 + parsed.m * 60 + parsed.s;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function totalSeconds(h: number, m: number, s: number): number {
|
|
136
|
+
return h * 3600 + m * 60 + s;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function setParts(h: number, m: number, s: number) {
|
|
140
|
+
// Clamp the assembled time between min and max
|
|
141
|
+
let t = totalSeconds(
|
|
142
|
+
clampPart("hour", h),
|
|
143
|
+
clampPart("minute", m),
|
|
144
|
+
clampPart("second", s),
|
|
145
|
+
);
|
|
146
|
+
t = Math.min(getMax(), Math.max(getMin(), t));
|
|
147
|
+
|
|
148
|
+
const next = formatValue(Math.floor(t / 3600), Math.floor((t % 3600) / 60), t % 60);
|
|
149
|
+
if (next !== $state.value) {
|
|
150
|
+
$state.value = next;
|
|
151
|
+
form.handleInput();
|
|
152
|
+
$props?.onchange?.(next);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// --- Spinbutton segments ---
|
|
157
|
+
|
|
158
|
+
function partDisplay(part: Part): string {
|
|
159
|
+
const p = parts();
|
|
160
|
+
const n = part === "hour" ? p.h : part === "minute" ? p.m : p.s;
|
|
161
|
+
if (part !== "hour") {
|
|
162
|
+
return format(n);
|
|
163
|
+
}
|
|
164
|
+
if ($props?.hour12) {
|
|
165
|
+
const twelve = n % 12 === 0 ? 12 : n % 12;
|
|
166
|
+
return format(twelve);
|
|
167
|
+
}
|
|
168
|
+
return format(n);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function isPM(): boolean {
|
|
172
|
+
return parts().h >= 12;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function commitBuffer(part: Part, buffer: string): boolean {
|
|
176
|
+
if (buffer.trim() === "") return false;
|
|
177
|
+
const n = Number(buffer);
|
|
178
|
+
if (!Number.isFinite(n)) return false;
|
|
179
|
+
setPartValue(part, n, false);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Steps a segment by delta hours/minutes/seconds, wrapping within its
|
|
185
|
+
* range like a clock face
|
|
186
|
+
*/
|
|
187
|
+
function stepPart(part: Part, delta: number) {
|
|
188
|
+
if (getDisabled()) return;
|
|
189
|
+
const p = parts();
|
|
190
|
+
let h = p.h;
|
|
191
|
+
let m = p.m;
|
|
192
|
+
let s = p.s;
|
|
193
|
+
switch (part) {
|
|
194
|
+
case "hour": {
|
|
195
|
+
h = ((h + delta) % 24 + 24) % 24;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case "minute": {
|
|
199
|
+
m = ((m + delta) % 60 + 60) % 60;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "second": {
|
|
203
|
+
s = ((s + delta) % 60 + 60) % 60;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
setParts(h, m, s);
|
|
208
|
+
syncInputs();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function setPartValue(part: Part, n: number, sync: boolean) {
|
|
212
|
+
const p = parts();
|
|
213
|
+
switch (part) {
|
|
214
|
+
case "hour":
|
|
215
|
+
setParts(clampPart("hour", n), p.m, p.s);
|
|
216
|
+
break;
|
|
217
|
+
case "minute":
|
|
218
|
+
setParts(p.h, clampPart("minute", n), p.s);
|
|
219
|
+
break;
|
|
220
|
+
case "second":
|
|
221
|
+
setParts(p.h, p.m, clampPart("second", n));
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
if (sync) syncInputs();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Keep segment inputs showing the committed values unless typing there
|
|
228
|
+
function syncInputs() {
|
|
229
|
+
if (!hourInput || !minuteInput) return;
|
|
230
|
+
const editing = (el: HTMLInputElement, buffer: string) =>
|
|
231
|
+
document.activeElement === el && el.value !== buffer;
|
|
232
|
+
if (!editing(hourInput, $state.hourText)) {
|
|
233
|
+
hourInput.value = partDisplay("hour");
|
|
234
|
+
}
|
|
235
|
+
if (!editing(minuteInput, $state.minuteText)) {
|
|
236
|
+
minuteInput.value = partDisplay("minute");
|
|
237
|
+
}
|
|
238
|
+
if (secondInput && !editing(secondInput, $state.secondText)) {
|
|
239
|
+
secondInput.value = partDisplay("second");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function handleInput(part: Part, e: Event) {
|
|
244
|
+
if (getDisabled()) return;
|
|
245
|
+
const el = e.currentTarget as HTMLInputElement;
|
|
246
|
+
|
|
247
|
+
// Accept digits only; strip everything else as it is typed
|
|
248
|
+
let text = el.value.replace(/\D/g, "");
|
|
249
|
+
const limit = part === "hour" ? ($props?.hour12 ? 12 : 23) : 59;
|
|
250
|
+
while (text.length > 0 && Number(text) > limit && text.length > 1) {
|
|
251
|
+
text = text.slice(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
el.value = text;
|
|
255
|
+
setBuffer(part, text);
|
|
256
|
+
|
|
257
|
+
// Two digits fills the segment; move on to the next one
|
|
258
|
+
if (text.length >= (PAD[partIndex(part)] ?? 2)) {
|
|
259
|
+
commitBuffer(part, text);
|
|
260
|
+
focusNextSegment(part);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function handleBlur(part: Part, e: FocusEvent) {
|
|
265
|
+
const el = e.target as HTMLInputElement;
|
|
266
|
+
commitBuffer(part, el.value);
|
|
267
|
+
setBuffer(part, "");
|
|
268
|
+
el.value = partDisplay(part);
|
|
269
|
+
form.handleBlur();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function handleSegmentKey(part: Part, e: KeyboardEvent) {
|
|
273
|
+
if (getDisabled()) return;
|
|
274
|
+
const el = e.currentTarget as HTMLInputElement;
|
|
275
|
+
|
|
276
|
+
switch (e.key) {
|
|
277
|
+
case "ArrowUp": {
|
|
278
|
+
e.preventDefault();
|
|
279
|
+
const s = $props?.step ?? 1;
|
|
280
|
+
stepPart(part, part === "minute" ? s : 1);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case "ArrowDown": {
|
|
284
|
+
e.preventDefault();
|
|
285
|
+
const s = $props?.step ?? 1;
|
|
286
|
+
stepPart(part, -(part === "minute" ? s : 1));
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case "Home": {
|
|
290
|
+
e.preventDefault();
|
|
291
|
+
setPartValue(part, 0, true);
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case "End": {
|
|
295
|
+
e.preventDefault();
|
|
296
|
+
setPartValue(part, part === "hour" ? 23 : 59, true);
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case "Enter": {
|
|
300
|
+
e.preventDefault();
|
|
301
|
+
commitBuffer(part, el.value);
|
|
302
|
+
setBuffer(part, "");
|
|
303
|
+
el.value = partDisplay(part);
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function setBuffer(part: Part, text: string) {
|
|
310
|
+
switch (part) {
|
|
311
|
+
case "hour":
|
|
312
|
+
$state.hourText = text;
|
|
313
|
+
break;
|
|
314
|
+
case "minute":
|
|
315
|
+
$state.minuteText = text;
|
|
316
|
+
break;
|
|
317
|
+
case "second":
|
|
318
|
+
$state.secondText = text;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function partIndex(part: Part): number {
|
|
324
|
+
switch (part) {
|
|
325
|
+
case "hour":
|
|
326
|
+
return 0;
|
|
327
|
+
case "minute":
|
|
328
|
+
return 1;
|
|
329
|
+
case "second":
|
|
330
|
+
return 2;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function focusNextSegment(part: Part) {
|
|
335
|
+
if (part === "hour") {
|
|
336
|
+
minuteInput?.focus();
|
|
337
|
+
} else if (part === "minute" && getSeconds()) {
|
|
338
|
+
secondInput?.focus();
|
|
339
|
+
} else if (part === "minute" && $props?.hour12) {
|
|
340
|
+
periodButton?.focus();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// --- AM/PM toggle ---
|
|
345
|
+
|
|
346
|
+
function togglePeriod() {
|
|
347
|
+
if (getDisabled()) return;
|
|
348
|
+
const p = parts();
|
|
349
|
+
setParts(isPM() ? p.h - 12 : p.h + 12, p.m, p.s);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function handlePeriodKey(e: KeyboardEvent) {
|
|
353
|
+
if (getDisabled()) return;
|
|
354
|
+
switch (e.key) {
|
|
355
|
+
case "ArrowUp":
|
|
356
|
+
case "ArrowDown": {
|
|
357
|
+
e.preventDefault();
|
|
358
|
+
togglePeriod();
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function ariaNow(part: Part): number {
|
|
365
|
+
const p = parts();
|
|
366
|
+
return part === "hour" ? p.h : part === "minute" ? p.m : p.s;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function ariaText(part: Part): string {
|
|
370
|
+
if (part === "hour" && $props?.hour12) {
|
|
371
|
+
return `${partDisplay("hour")} ${isPM() ? "PM" : "AM"}`;
|
|
372
|
+
}
|
|
373
|
+
return String(ariaNow(part));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
@render {
|
|
377
|
+
<div
|
|
378
|
+
id={$props?.id}
|
|
379
|
+
class={["torp-time-picker", $props?.class]}
|
|
380
|
+
{$props?.style}
|
|
381
|
+
role="group"
|
|
382
|
+
aria-label={$props?.ariaLabel}
|
|
383
|
+
data-valid={form.valid ? "valid" : "invalid"}
|
|
384
|
+
aria-describedby={form.messageId}
|
|
385
|
+
>
|
|
386
|
+
<input
|
|
387
|
+
&ref={hourInput}
|
|
388
|
+
class="torp-time-picker-part"
|
|
389
|
+
type="text"
|
|
390
|
+
inputmode="numeric"
|
|
391
|
+
role="spinbutton"
|
|
392
|
+
value={partDisplay("hour")}
|
|
393
|
+
aria-label={$props?.hour12 ? "Hour (12-hour)" : "Hour"}
|
|
394
|
+
aria-valuemin={0}
|
|
395
|
+
aria-valuemax={23}
|
|
396
|
+
aria-valuenow={ariaNow("hour")}
|
|
397
|
+
aria-valuetext={ariaText("hour")}
|
|
398
|
+
disabled={getDisabled()}
|
|
399
|
+
tabindex={getDisabled() ? -1 : 0}
|
|
400
|
+
oninput={(e: Event) => handleInput("hour", e)}
|
|
401
|
+
onblur={(e: FocusEvent) => handleBlur("hour", e)}
|
|
402
|
+
onkeydown={(e: KeyboardEvent) => handleSegmentKey("hour", e)}
|
|
403
|
+
/>
|
|
404
|
+
<span class="torp-time-picker-separator">:</span>
|
|
405
|
+
<input
|
|
406
|
+
&ref={minuteInput}
|
|
407
|
+
class="torp-time-picker-part"
|
|
408
|
+
type="text"
|
|
409
|
+
inputmode="numeric"
|
|
410
|
+
role="spinbutton"
|
|
411
|
+
value={partDisplay("minute")}
|
|
412
|
+
aria-label="Minute"
|
|
413
|
+
aria-valuemin={0}
|
|
414
|
+
aria-valuemax={59}
|
|
415
|
+
aria-valuenow={ariaNow("minute")}
|
|
416
|
+
aria-valuetext={ariaText("minute")}
|
|
417
|
+
disabled={getDisabled()}
|
|
418
|
+
tabindex={getDisabled() ? -1 : 0}
|
|
419
|
+
oninput={(e: Event) => handleInput("minute", e)}
|
|
420
|
+
onblur={(e: FocusEvent) => handleBlur("minute", e)}
|
|
421
|
+
onkeydown={(e: KeyboardEvent) => handleSegmentKey("minute", e)}
|
|
422
|
+
/>
|
|
423
|
+
@if (getSeconds()) {
|
|
424
|
+
<span class="torp-time-picker-separator">:</span>
|
|
425
|
+
<input
|
|
426
|
+
&ref={secondInput}
|
|
427
|
+
class="torp-time-picker-part"
|
|
428
|
+
type="text"
|
|
429
|
+
inputmode="numeric"
|
|
430
|
+
role="spinbutton"
|
|
431
|
+
value={partDisplay("second")}
|
|
432
|
+
aria-label="Second"
|
|
433
|
+
aria-valuemin={0}
|
|
434
|
+
aria-valuemax={59}
|
|
435
|
+
aria-valuenow={ariaNow("second")}
|
|
436
|
+
aria-valuetext={ariaText("second")}
|
|
437
|
+
disabled={getDisabled()}
|
|
438
|
+
tabindex={getDisabled() ? -1 : 0}
|
|
439
|
+
oninput={(e: Event) => handleInput("second", e)}
|
|
440
|
+
onblur={(e: FocusEvent) => handleBlur("second", e)}
|
|
441
|
+
onkeydown={(e: KeyboardEvent) => handleSegmentKey("second", e)}
|
|
442
|
+
/>
|
|
443
|
+
}
|
|
444
|
+
@if ($props?.hour12) {
|
|
445
|
+
<button
|
|
446
|
+
&ref={periodButton}
|
|
447
|
+
type="button"
|
|
448
|
+
class="torp-time-picker-period"
|
|
449
|
+
disabled={getDisabled()}
|
|
450
|
+
tabindex={getDisabled() ? -1 : 0}
|
|
451
|
+
aria-label={isPM() ? "PM" : "AM"}
|
|
452
|
+
onclick={togglePeriod}
|
|
453
|
+
onkeydown={handlePeriodKey}
|
|
454
|
+
>
|
|
455
|
+
{isPM() ? "PM" : "AM"}
|
|
456
|
+
</button>
|
|
457
|
+
}
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
<FormValues name={form.name} value={$state.value} />
|
|
461
|
+
}
|
|
462
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type SlotRender } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface TimePickerProps {
|
|
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
|
+
/**
|
|
12
|
+
* The current time as "HH:mm" or "HH:mm:ss" (24-hour), or undefined if
|
|
13
|
+
* the field is empty
|
|
14
|
+
*/
|
|
15
|
+
value?: Bindable<string | undefined>;
|
|
16
|
+
/** If set to true, times are displayed and entered in 12-hour format with an AM/PM toggle */
|
|
17
|
+
hour12?: boolean;
|
|
18
|
+
/** If set to true, a seconds field is shown */
|
|
19
|
+
seconds?: boolean;
|
|
20
|
+
/** The amount by which the minute field steps (default 1) */
|
|
21
|
+
step?: number;
|
|
22
|
+
/** The earliest allowed time, as "HH:mm" or "HH:mm:ss" */
|
|
23
|
+
min?: string;
|
|
24
|
+
/** The latest allowed time, as "HH:mm" or "HH:mm:ss" */
|
|
25
|
+
max?: string;
|
|
26
|
+
/** If set to true, the user cannot interact with this input */
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
/** An accessible name for the whole input */
|
|
29
|
+
ariaLabel?: string;
|
|
30
|
+
/** The name of the component's form data field */
|
|
31
|
+
name?: string;
|
|
32
|
+
/** Raised when the value has changed */
|
|
33
|
+
onchange?: (value: string | undefined) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* An input for entering a time of day, made of hour, minute and (optionally)
|
|
38
|
+
* second fields that follow the WAI ARIA spinbutton pattern -- each can be
|
|
39
|
+
* typed into or stepped with the arrow keys -- plus an AM/PM toggle in
|
|
40
|
+
* 12-hour mode.
|
|
41
|
+
*
|
|
42
|
+
* The value is always the canonical 24-hour form ("HH:mm" or "HH:mm:ss"),
|
|
43
|
+
* regardless of display format.
|
|
44
|
+
*/
|
|
45
|
+
declare function TimePicker(
|
|
46
|
+
$parent: ParentNode,
|
|
47
|
+
$anchor: Node | null,
|
|
48
|
+
$props: TimePickerProps | undefined,
|
|
49
|
+
$context?: Record<PropertyKey, any>,
|
|
50
|
+
$slots?: Record<string, SlotRender>,
|
|
51
|
+
): void;
|
|
52
|
+
|
|
53
|
+
export { TimePicker };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import { type PopoutContext, PopoutContextName } from "../utils/PopoutTypes";
|
|
3
|
+
import { createFocusGroup } from "../utils/focusGroup";
|
|
4
|
+
import { type ItemState, type ToolBarContext, ToolBarContextName } from "./ToolBarTypes";
|
|
5
|
+
|
|
6
|
+
interface ToolBarProps {
|
|
7
|
+
/** An ID for the root element */
|
|
8
|
+
id?: string;
|
|
9
|
+
/** Classes for the root element */
|
|
10
|
+
class?: ClassValue;
|
|
11
|
+
/** Styles for the root element */
|
|
12
|
+
style?: StyleValue;
|
|
13
|
+
/** An ARIA label for describing the menu to screen readers */
|
|
14
|
+
ariaLabel?: string | undefined;
|
|
15
|
+
/** The orientation of the menu, which affects keyboard navigation */
|
|
16
|
+
orientation?: "horizontal" | "vertical";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A toolbar is a container for grouping a set of controls, such as buttons, menubuttons, or checkboxes.
|
|
21
|
+
*
|
|
22
|
+
* See [the WAI ARIA guidelines for ToolBars](https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/).
|
|
23
|
+
*
|
|
24
|
+
* The structure of a ToolBar is:
|
|
25
|
+
*
|
|
26
|
+
* ```
|
|
27
|
+
* <ToolBar>
|
|
28
|
+
* <ToolBarButton />
|
|
29
|
+
* <ToolBarSeparator /> (optional)
|
|
30
|
+
* <ToolBarPopout /> (optional)
|
|
31
|
+
* ...
|
|
32
|
+
* </ToolBar>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export default function ToolBar($props: ToolBarProps | undefined) {
|
|
36
|
+
let orientation = $props?.orientation ?? "horizontal";
|
|
37
|
+
|
|
38
|
+
// The focus group owns the item ledger and roving-tabindex bookkeeping
|
|
39
|
+
const group = createFocusGroup<ItemState>();
|
|
40
|
+
|
|
41
|
+
// Check whether this is a menu in a popout
|
|
42
|
+
const popoutContext = $context[PopoutContextName] as PopoutContext;
|
|
43
|
+
const inPopout = !!popoutContext;
|
|
44
|
+
let popoutState = popoutContext?.state;
|
|
45
|
+
|
|
46
|
+
// Set the focusFirstElement method in the PopoverContext(s) so that it can be called after everything has been shown
|
|
47
|
+
if (popoutContext) {
|
|
48
|
+
popoutContext.focusFirstElement = group.focusFirstItem;
|
|
49
|
+
popoutContext.focusLastElement = group.focusLastItem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Set the context to pass down to items
|
|
53
|
+
let context: ToolBarContext = {
|
|
54
|
+
handleItemFocus,
|
|
55
|
+
handleItemKey,
|
|
56
|
+
registerItem,
|
|
57
|
+
handlePopout,
|
|
58
|
+
orientation,
|
|
59
|
+
};
|
|
60
|
+
$context[ToolBarContextName] = context;
|
|
61
|
+
|
|
62
|
+
// If the ToolBar is not in a popout, focus its first item. Otherwise the popout will call focusFirstItem, below
|
|
63
|
+
function handleFocus() {
|
|
64
|
+
if (!popoutContext && group.itemStates.length) {
|
|
65
|
+
group.focusFirstItem();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function registerItem(item: ItemState) {
|
|
70
|
+
return group.registerItem(item);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function handleItemFocus(index: number) {
|
|
74
|
+
group.handleItemFocus(index);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function handleItemKey(e: KeyboardEvent) {
|
|
78
|
+
const index = group.getFocusIndex();
|
|
79
|
+
switch (e.key) {
|
|
80
|
+
case "Escape": {
|
|
81
|
+
if (inPopout) {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
popoutState.visible = false;
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
default: {
|
|
88
|
+
group.handleGroupKey(index, e, { vertical: orientation === "vertical" });
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handlePopout(index: number) {
|
|
95
|
+
// Hide all other popouts
|
|
96
|
+
for (let item of group.itemStates) {
|
|
97
|
+
if (item.index !== index) {
|
|
98
|
+
item.setVisible?.(false);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@render {
|
|
104
|
+
<nav
|
|
105
|
+
{$props?.id}
|
|
106
|
+
class={["torp-tool-bar", $props?.class]}
|
|
107
|
+
{$props?.style}
|
|
108
|
+
tabindex="0"
|
|
109
|
+
role="toolbar"
|
|
110
|
+
aria-label={$props?.ariaLabel}
|
|
111
|
+
aria-orientation={orientation === "vertical" ? "vertical" : undefined}
|
|
112
|
+
onfocus={handleFocus}
|
|
113
|
+
>
|
|
114
|
+
<slot />
|
|
115
|
+
</nav>
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import { type ToolBarContext, ToolBarContextName } from "./ToolBarTypes";
|
|
3
|
+
|
|
4
|
+
interface ToolBarButtonProps {
|
|
5
|
+
/** An ID for the root element */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** Classes for the root element */
|
|
8
|
+
class?: ClassValue;
|
|
9
|
+
/** Styles for the root element */
|
|
10
|
+
style?: StyleValue;
|
|
11
|
+
/** Whether this button is disabled */
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Raised when the button is clicked */
|
|
14
|
+
onclick?: (e: MouseEvent) => void;
|
|
15
|
+
/** Raised when a key is pressed down */
|
|
16
|
+
onkeydown?: (e: KeyboardEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A button that is placed in a ToolBar.
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
export default function ToolBarButton($props: ToolBarButtonProps | undefined) {
|
|
24
|
+
let button: HTMLButtonElement;
|
|
25
|
+
|
|
26
|
+
// Get stuff out of the ToolBarContext
|
|
27
|
+
const menuContext = $context[ToolBarContextName] as ToolBarContext;
|
|
28
|
+
if (!menuContext) {
|
|
29
|
+
throw new Error("ToolBarButton must be contained within a ToolBar");
|
|
30
|
+
}
|
|
31
|
+
const { registerItem, handleItemKey, handleItemFocus } = menuContext;
|
|
32
|
+
|
|
33
|
+
// Register this item with the parent ToolBar and store the index
|
|
34
|
+
const { index } = registerItem({ setFocused, disabled: $props?.disabled });
|
|
35
|
+
|
|
36
|
+
function setFocused() {
|
|
37
|
+
button.focus();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@render {
|
|
41
|
+
<button
|
|
42
|
+
{$props?.id}
|
|
43
|
+
class={["torp-tool-bar-button", $props?.class]}
|
|
44
|
+
{$props?.style}
|
|
45
|
+
type="button"
|
|
46
|
+
{$props?.disabled}
|
|
47
|
+
aria-disabled={$props?.disabled ? "true" : undefined}
|
|
48
|
+
{$props?.onclick}
|
|
49
|
+
onkeydown={handleItemKey}
|
|
50
|
+
onfocus={() => handleItemFocus(index)}
|
|
51
|
+
&ref={button}
|
|
52
|
+
>
|
|
53
|
+
<slot />
|
|
54
|
+
</button>
|
|
55
|
+
}
|
|
56
|
+
}
|