@torpor/ui 0.4.0 → 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 +42 -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 +1 -1
- 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 +24 -3
- 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 +55 -144
- 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/Charts/GridLines.torp +4 -4
- package/dist/Charts/LineChart.torp +55 -178
- package/dist/Charts/PieChart.torp +14 -20
- package/dist/Charts/ScatterChart.torp +45 -38
- package/dist/Charts/SparkLine.torp +16 -19
- package/dist/Charts/XAxis.torp +11 -11
- package/dist/Charts/YAxis.torp +8 -8
- package/dist/Charts/index.d.ts +328 -79
- package/dist/Charts/index.js +17 -34
- 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 +32 -50
- 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 +4 -17
- 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 +24 -17
- package/dist/Dialog/DialogBody.torp +5 -9
- package/dist/Dialog/DialogButton.torp +12 -12
- package/dist/Dialog/DialogFooter.torp +3 -7
- package/dist/Dialog/DialogHeader.torp +6 -10
- package/dist/Dialog/index.d.ts +27 -27
- 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/index.d.ts +14 -16
- package/dist/Disclosure/index.js +5 -18
- package/dist/Drawer/Drawer.torp +19 -49
- 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 +3 -0
- 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 +27 -18
- 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 +5 -4
- 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 +178 -79
- package/dist/Form/index.js +16 -50
- package/dist/ListBox/ListBox.torp +253 -150
- package/dist/ListBox/ListBoxItem.torp +54 -30
- package/dist/ListBox/ListBoxSeparator.torp +5 -2
- package/dist/ListBox/ListBoxTypes.ts +23 -5
- package/dist/ListBox/LoadedListBoxItems.torp +41 -0
- package/dist/ListBox/index.d.ts +56 -17
- package/dist/ListBox/index.js +4 -14
- 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 +86 -71
- 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 +9 -19
- 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 +33 -51
- package/dist/MenuBar/MenuBarItemContent.torp +15 -101
- package/dist/MenuBar/MenuBarItemTrigger.torp +20 -55
- package/dist/MenuBar/MenuBarTypes.ts +11 -10
- package/dist/MenuBar/index.d.ts +17 -19
- package/dist/MenuBar/index.js +5 -18
- package/dist/Modal/Modal.torp +22 -47
- 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 +5 -16
- 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 -111
- package/dist/NavMenu/NavMenuButton.torp +7 -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 +15 -60
- package/dist/NavMenu/NavMenuPopoutContent.torp +14 -73
- package/dist/NavMenu/NavMenuPopoutTrigger.torp +17 -99
- package/dist/NavMenu/NavMenuSeparator.torp +3 -7
- package/dist/NavMenu/NavMenuTypes.ts +4 -14
- package/dist/NavMenu/index.d.ts +40 -44
- 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 +41 -34
- package/dist/Pagination/PaginationItem.torp +50 -13
- package/dist/Pagination/PaginationTypes.ts +3 -6
- package/dist/Pagination/buildPages.ts +9 -5
- package/dist/Pagination/index.d.ts +19 -9
- package/dist/Pagination/index.js +3 -40
- package/dist/Popover/Popover.torp +32 -50
- 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 +4 -17
- 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 +107 -53
- package/dist/SelectBox/SelectBoxContent.torp +45 -19
- package/dist/SelectBox/SelectBoxTrigger.torp +108 -8
- package/dist/SelectBox/SelectBoxTypes.ts +26 -11
- package/dist/SelectBox/index.d.ts +39 -54
- package/dist/SelectBox/index.js +5 -18
- 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 +1 -1
- 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 +29 -110
- package/dist/ToolBar/ToolBarButton.torp +7 -8
- package/dist/ToolBar/ToolBarElement.torp +7 -11
- package/dist/ToolBar/ToolBarPopout.torp +16 -68
- package/dist/ToolBar/ToolBarPopoutContent.torp +15 -73
- package/dist/ToolBar/ToolBarPopoutTrigger.torp +19 -110
- package/dist/ToolBar/ToolBarSeparator.torp +3 -7
- package/dist/ToolBar/ToolBarTypes.ts +7 -14
- package/dist/ToolBar/index.d.ts +30 -32
- package/dist/ToolBar/index.js +8 -30
- 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 +59 -12
- 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/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 +97 -22
- 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/Charts/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/ListBox/index.js.map +0 -1
- package/dist/Menu/index.js.map +0 -1
- package/dist/MenuBar/index.js.map +0 -1
- package/dist/NavMenu/index.js.map +0 -1
- package/dist/Pagination/index.js.map +0 -1
- package/dist/SelectBox/SelectBoxInput.torp +0 -95
- package/dist/SelectBox/index.js.map +0 -1
- package/dist/TabGroup/index.js.map +0 -1
- package/dist/ToolBar/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
|
@@ -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 };
|