@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,152 @@
|
|
|
1
|
+
import { $cache, $run } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
export interface ItemGroupItem {
|
|
4
|
+
index: number;
|
|
5
|
+
value: any;
|
|
6
|
+
parentDisabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ItemGroup<T extends ItemGroupItem> {
|
|
10
|
+
itemStates: T[];
|
|
11
|
+
registerItem: (item: T) => void;
|
|
12
|
+
removeItem: (itemIndex: number) => void;
|
|
13
|
+
toggleItem: (value: any) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a managed collection of selectable items for group-style components
|
|
18
|
+
* (Accordion, ListBox, TabGroup, Tree, etc.).
|
|
19
|
+
*
|
|
20
|
+
* Handles item registration, removal, and toggle logic. Each item's selection
|
|
21
|
+
* state is derived: registration installs a `$cache` getter over
|
|
22
|
+
* `selectedProperty` that computes from the group value, so selection can
|
|
23
|
+
* never drift out of sync with it — eliminating the boilerplate that was
|
|
24
|
+
* duplicated across ~5 components.
|
|
25
|
+
*
|
|
26
|
+
* The returned `itemStates` array is the source of truth; components should
|
|
27
|
+
* store it in their context for child items to access.
|
|
28
|
+
*
|
|
29
|
+
* NOTE: Items must be registered before their selection property is first
|
|
30
|
+
* read, so the derived getter can take the property over.
|
|
31
|
+
*/
|
|
32
|
+
export function createItemGroup<T extends ItemGroupItem>(options: {
|
|
33
|
+
/** The reactive state containing the group value */
|
|
34
|
+
state: { value: any };
|
|
35
|
+
/** Selection type, or a getter for it if it can change */
|
|
36
|
+
type: "single" | "multiple" | (() => "single" | "multiple");
|
|
37
|
+
/** The boolean property on items that reflects selection state */
|
|
38
|
+
selectedProperty: keyof T;
|
|
39
|
+
/** Getter for parent disabled state */
|
|
40
|
+
disabled?: () => boolean | undefined;
|
|
41
|
+
/** Allow deselecting in single-select mode (default: true) */
|
|
42
|
+
allowDeselect?: boolean;
|
|
43
|
+
/** Called for each item after index assignment but before selection check */
|
|
44
|
+
onRegister?: (item: T) => void;
|
|
45
|
+
/** Callback after a toggle, for side effects like closing a popout */
|
|
46
|
+
onToggle?: (value: any) => void;
|
|
47
|
+
}): ItemGroup<T> {
|
|
48
|
+
const { state, selectedProperty } = options;
|
|
49
|
+
const getType = typeof options.type === "function" ? options.type : () => options.type;
|
|
50
|
+
const allowDeselect = options.allowDeselect ?? true;
|
|
51
|
+
const itemStates: T[] = [];
|
|
52
|
+
let nextIndex = 0;
|
|
53
|
+
|
|
54
|
+
// Propagate disabled state from parent
|
|
55
|
+
if (options.disabled) {
|
|
56
|
+
const disabledGetter = options.disabled;
|
|
57
|
+
$run(() => {
|
|
58
|
+
const disabled = disabledGetter() === true;
|
|
59
|
+
for (let item of itemStates) {
|
|
60
|
+
item.parentDisabled = disabled;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isItemSelected(item: T, value: any): boolean {
|
|
66
|
+
if (value === undefined || value === null) return false;
|
|
67
|
+
if (getType() === "multiple") {
|
|
68
|
+
const values = Array.isArray(value) ? value : [value];
|
|
69
|
+
return values.some((v) => sameValue(v, item.value));
|
|
70
|
+
}
|
|
71
|
+
return sameValue(item.value, value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function registerItem(item: T) {
|
|
75
|
+
if (item.index === -1 || item.index === undefined) {
|
|
76
|
+
item.index = nextIndex++;
|
|
77
|
+
}
|
|
78
|
+
if (options.onRegister) {
|
|
79
|
+
options.onRegister(item);
|
|
80
|
+
}
|
|
81
|
+
// Make the selection property derived: a cached getter computed from the
|
|
82
|
+
// group value, so it always reflects (and only reflects) that value
|
|
83
|
+
Object.defineProperty(item, selectedProperty, {
|
|
84
|
+
get() {
|
|
85
|
+
return $cache(() => isItemSelected(item, state.value));
|
|
86
|
+
},
|
|
87
|
+
enumerable: true,
|
|
88
|
+
configurable: true,
|
|
89
|
+
});
|
|
90
|
+
if (options.disabled) {
|
|
91
|
+
item.parentDisabled = options.disabled() === true;
|
|
92
|
+
}
|
|
93
|
+
itemStates.splice(item.index, 0, item);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function removeItem(itemIndex: number) {
|
|
97
|
+
const index = itemStates.findIndex((item) => item.index === itemIndex);
|
|
98
|
+
if (index !== -1) {
|
|
99
|
+
itemStates.splice(index, 1);
|
|
100
|
+
}
|
|
101
|
+
itemStates.forEach((item, i) => (item.index = i));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function toggleItem(value: any) {
|
|
105
|
+
switch (getType()) {
|
|
106
|
+
case "single": {
|
|
107
|
+
const item = itemStates.find((i) => sameValue(i.value, value));
|
|
108
|
+
if (allowDeselect) {
|
|
109
|
+
if (item) {
|
|
110
|
+
state.value = (item as any)[selectedProperty] ? undefined : item.value;
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
// Store the item's own value, so the group value keeps its
|
|
114
|
+
// canonical type
|
|
115
|
+
state.value = item ? item.value : value;
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "multiple": {
|
|
120
|
+
const newValue: any[] = [];
|
|
121
|
+
for (let item of itemStates) {
|
|
122
|
+
const toggled = sameValue(item.value, value);
|
|
123
|
+
if (
|
|
124
|
+
(toggled && !(item as any)[selectedProperty]) ||
|
|
125
|
+
(!toggled && (item as any)[selectedProperty])
|
|
126
|
+
) {
|
|
127
|
+
newValue.push(item.value);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
state.value = newValue.sort((a, b) => a - b);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (options.onToggle) {
|
|
135
|
+
options.onToggle(value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { itemStates, registerItem, removeItem, toggleItem };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Compares an item value with a group/toggle value. Primitives are compared
|
|
144
|
+
* as strings — DOM values are always strings, while callers may pass e.g.
|
|
145
|
+
* numbers — and everything else by strict equality.
|
|
146
|
+
*/
|
|
147
|
+
function sameValue(a: any, b: any): boolean {
|
|
148
|
+
if (a === b) return true;
|
|
149
|
+
if (a === null || b === null || a === undefined || b === undefined) return false;
|
|
150
|
+
if (typeof a === "object" || typeof b === "object") return false;
|
|
151
|
+
return String(a) === String(b);
|
|
152
|
+
}
|
package/dist/utils/dateUtils.ts
CHANGED
|
@@ -25,7 +25,7 @@ const dayNames: string[] = [
|
|
|
25
25
|
"Saturday",
|
|
26
26
|
"Sunday",
|
|
27
27
|
];
|
|
28
|
-
const shortDayNames: string[] = dayNames.map((n) => n.substring(0,
|
|
28
|
+
const shortDayNames: string[] = dayNames.map((n) => n.substring(0, 2));
|
|
29
29
|
|
|
30
30
|
export { monthNames, shortMonthNames, dayNames, shortDayNames };
|
|
31
31
|
|
|
@@ -229,16 +229,17 @@ function formatMonth(date: Date, m: string): string {
|
|
|
229
229
|
} else if (m.length === 3) {
|
|
230
230
|
return monthNames[date.getMonth()].substring(0, 3);
|
|
231
231
|
} else if (m.length == 2) {
|
|
232
|
-
|
|
233
|
-
return
|
|
232
|
+
// getMonth is zero-based
|
|
233
|
+
return String(date.getMonth() + 1).padStart(2, "0");
|
|
234
234
|
} else {
|
|
235
235
|
return date.getMonth().toString();
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
function formatYear(date: Date, y: string): string {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
const year = date.getFullYear().toString();
|
|
241
|
+
if (y.length >= 4) {
|
|
242
|
+
return year;
|
|
243
|
+
}
|
|
244
|
+
return year.substring(4 - y.length);
|
|
244
245
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
export function addDocumentEvent<K extends keyof DocumentEventMap>(
|
|
2
2
|
type: K,
|
|
3
|
-
listener: (this: Document, ev: DocumentEventMap[K]) => any
|
|
3
|
+
listener: (this: Document, ev: DocumentEventMap[K]) => any,
|
|
4
4
|
): void {
|
|
5
|
-
if (typeof document !==
|
|
5
|
+
if (typeof document !== "undefined") {
|
|
6
6
|
document.addEventListener(type, listener);
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function removeDocumentEvent<K extends keyof DocumentEventMap>(
|
|
11
11
|
type: K,
|
|
12
|
-
listener: (this: Document, ev: DocumentEventMap[K]) => any
|
|
12
|
+
listener: (this: Document, ev: DocumentEventMap[K]) => any,
|
|
13
13
|
): void {
|
|
14
|
-
if (typeof document !==
|
|
14
|
+
if (typeof document !== "undefined") {
|
|
15
15
|
document.removeEventListener(type, listener);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { $watch } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared state for HTML5 drag & drop. A source element starts a drag with
|
|
5
|
+
* `beginDrag`; drop targets consult `dragState.current` during dragover and
|
|
6
|
+
* drop. Because the dataTransfer API restricts what can be read outside
|
|
7
|
+
* drop, the store is the reliable channel -- the dataTransfer only carries
|
|
8
|
+
* the drag's id, which doubles as an integrity check and makes drags
|
|
9
|
+
* interoperable across windows when the type is allowed through.
|
|
10
|
+
*
|
|
11
|
+
* Drags don't nest, so a single module-level state is enough.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface DragInfo {
|
|
15
|
+
/** Generated identifier for this drag, mirrored into the dataTransfer */
|
|
16
|
+
id: string;
|
|
17
|
+
/** High-level kind ("list-item", "tree-item", "file"...), for filtering */
|
|
18
|
+
kind: string;
|
|
19
|
+
/** Arbitrary payload attached by the drag source */
|
|
20
|
+
data: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DragState {
|
|
24
|
+
/** The active drag, if any */
|
|
25
|
+
current: DragInfo | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const dragState: DragState = $watch({
|
|
29
|
+
current: undefined as DragInfo | undefined,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
let nextId = 0;
|
|
33
|
+
|
|
34
|
+
/** Starts tracking a drag and returns its id */
|
|
35
|
+
export function beginDrag(kind: string, data: any): DragInfo {
|
|
36
|
+
const info: DragInfo = {
|
|
37
|
+
id: String(nextId++),
|
|
38
|
+
kind,
|
|
39
|
+
data,
|
|
40
|
+
};
|
|
41
|
+
dragState.current = info;
|
|
42
|
+
return info;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Clears the tracked drag */
|
|
46
|
+
export function endDrag(): void {
|
|
47
|
+
dragState.current = undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const TYPE_PREFIX = "application/x-torpor-drag/";
|
|
51
|
+
|
|
52
|
+
/** Encodes a drag id into a dataTransfer-compatible mime type */
|
|
53
|
+
export function encodeDragType(id: string): string {
|
|
54
|
+
return TYPE_PREFIX + id;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Writes the drag's id into the event's dataTransfer. No-op when the event
|
|
59
|
+
* carries no transfer (some synthetic tests construct bare events).
|
|
60
|
+
*/
|
|
61
|
+
export function writeDragType(e: DragEvent, id: string): void {
|
|
62
|
+
e.dataTransfer?.setData(encodeDragType(id), id);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolves which drag an incoming event refers to. Prefers the id encoded
|
|
67
|
+
* in the dataTransfer (verified against the live drag); falls back to the
|
|
68
|
+
* tracked drag itself for dragover events, whose transfers are locked.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveDrag(e: DragEvent): DragInfo | undefined {
|
|
71
|
+
const current = dragState.current;
|
|
72
|
+
if (!current) return undefined;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const types: readonly string[] = e.dataTransfer?.types ?? [];
|
|
76
|
+
for (let type of types) {
|
|
77
|
+
if (type.startsWith(TYPE_PREFIX)) {
|
|
78
|
+
return type === encodeDragType(current.id) ? current : undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Reading types can throw for foreign-origin drags; treat those as
|
|
83
|
+
// unmatched and fall back to the tracked drag below
|
|
84
|
+
}
|
|
85
|
+
return current;
|
|
86
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { $watch } from "@torpor/view";
|
|
2
|
+
import { focusItem, type FocusTarget } from "./focusItem";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A managed collection of focusable items with roving tabindex semantics,
|
|
6
|
+
* for group-style components where tabbing into the group lands on one item
|
|
7
|
+
* (the tab stop) and arrow keys move focus within it (ToolBar, MenuBar,
|
|
8
|
+
* button groups, Rating, SegmentedControl...).
|
|
9
|
+
*
|
|
10
|
+
* Owns the registration ledger (indexed states, ordered add/remove) that
|
|
11
|
+
* every list-like component duplicates today, plus the focus bookkeeping:
|
|
12
|
+
* `focusIndex` tracking via onItemFocus, first-item focus when the group
|
|
13
|
+
* itself takes focus, and keyboard movement over enabled items through
|
|
14
|
+
* utils/focusItem.
|
|
15
|
+
*
|
|
16
|
+
* Items are plain states carrying an index, a setFocused implementation and
|
|
17
|
+
* a disabled flag; each component renders its own elements and wires
|
|
18
|
+
* onfocus/onkeydown to the returned handlers.
|
|
19
|
+
*/
|
|
20
|
+
export interface FocusItemState {
|
|
21
|
+
/** Assigned by the group in registration order */
|
|
22
|
+
index?: number;
|
|
23
|
+
/** Focuses this item's element; implemented by the component's item */
|
|
24
|
+
setFocused: () => void;
|
|
25
|
+
/** Disabled items are skipped by movement */
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface FocusGroup<T extends FocusItemState> {
|
|
30
|
+
/** The registered item states, in order */
|
|
31
|
+
itemStates: T[];
|
|
32
|
+
|
|
33
|
+
/** Adds an item state, assigning its index from position; returns it */
|
|
34
|
+
registerItem: (item: T) => { index: number };
|
|
35
|
+
|
|
36
|
+
/** Removes an item by index, reindexing the rest */
|
|
37
|
+
removeItem: (itemIndex: number) => void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Item onfocus handler: records the current tab stop
|
|
41
|
+
*/
|
|
42
|
+
handleItemFocus: (index: number) => void;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Group-level key handler for a directional press at `index`. Handles
|
|
46
|
+
* left/right (+up/down when vertical), Home and End; arrow keys depend
|
|
47
|
+
* on orientation, matching ToolBar behavior.
|
|
48
|
+
*/
|
|
49
|
+
handleGroupKey: (index: number, e: KeyboardEvent, options?: { vertical?: boolean }) => boolean;
|
|
50
|
+
|
|
51
|
+
/** Moves focus from `index` to the target item (disabled items skipped) */
|
|
52
|
+
moveFocus: (index: number, target: FocusTarget) => void;
|
|
53
|
+
|
|
54
|
+
/** Focuses the first / last enabled item (popout open behavior) */
|
|
55
|
+
focusFirstItem: () => void;
|
|
56
|
+
focusLastItem: () => void;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether any item is the current tab stop; components derive their
|
|
60
|
+
* per-item tabindex from this plus the tracked focus index
|
|
61
|
+
*/
|
|
62
|
+
getFocusIndex: () => number;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Whether item `index` should be the group's single tab stop: when focus
|
|
66
|
+
* hasn't entered the group yet that is item 0, otherwise whichever item
|
|
67
|
+
* focus last rested on.
|
|
68
|
+
*/
|
|
69
|
+
isTabStop: (index: number) => boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function createFocusGroup<T extends FocusItemState>(): FocusGroup<T> {
|
|
73
|
+
const itemStates: T[] = [];
|
|
74
|
+
// The tracked index is reactive, so components can derive each item's
|
|
75
|
+
// roving tabindex from it in their templates
|
|
76
|
+
const $state = $watch({ index: -1 });
|
|
77
|
+
|
|
78
|
+
function registerItem(item: T): { index: number } {
|
|
79
|
+
item.index = itemStates.length;
|
|
80
|
+
itemStates.push(item);
|
|
81
|
+
return { index: item.index };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function removeItem(itemIndex: number) {
|
|
85
|
+
const i = itemStates.findIndex((item) => item.index === itemIndex);
|
|
86
|
+
if (i !== -1) {
|
|
87
|
+
itemStates.splice(i, 1);
|
|
88
|
+
// Reindex the tail so indices stay dense
|
|
89
|
+
for (let j = i; j < itemStates.length; j++) {
|
|
90
|
+
itemStates[j].index = j;
|
|
91
|
+
}
|
|
92
|
+
if ($state.index > i) {
|
|
93
|
+
$state.index--;
|
|
94
|
+
} else if ($state.index === i) {
|
|
95
|
+
$state.index = -1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function handleItemFocus(index: number) {
|
|
101
|
+
$state.index = index;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getFocusIndex(): number {
|
|
105
|
+
return $state.index;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isTabStop(index: number): boolean {
|
|
109
|
+
return ($state.index === -1 ? 0 : $state.index) === index;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function moveFocus(index: number, target: FocusTarget) {
|
|
113
|
+
if (itemStates.length === 0) return;
|
|
114
|
+
$state.index = focusItem(itemStates, index === -1 ? 0 : index, target);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function focusFirstItem() {
|
|
118
|
+
if (itemStates.length) {
|
|
119
|
+
$state.index = 0;
|
|
120
|
+
itemStates[0].setFocused();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function focusLastItem() {
|
|
125
|
+
if (itemStates.length > 0) {
|
|
126
|
+
$state.index = itemStates.length - 1;
|
|
127
|
+
itemStates.at(-1)!.setFocused();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function handleGroupKey(
|
|
132
|
+
index: number,
|
|
133
|
+
e: KeyboardEvent,
|
|
134
|
+
options?: { vertical?: boolean },
|
|
135
|
+
): boolean {
|
|
136
|
+
const vertical = options?.vertical ?? false;
|
|
137
|
+
let handled = true;
|
|
138
|
+
switch (e.key) {
|
|
139
|
+
case "ArrowRight": {
|
|
140
|
+
if (!vertical) {
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
moveFocus(index, "next");
|
|
143
|
+
} else {
|
|
144
|
+
handled = false;
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "ArrowLeft": {
|
|
149
|
+
if (!vertical) {
|
|
150
|
+
e.preventDefault();
|
|
151
|
+
moveFocus(index, "previous");
|
|
152
|
+
} else {
|
|
153
|
+
handled = false;
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case "ArrowDown": {
|
|
158
|
+
if (vertical) {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
moveFocus(index, "next");
|
|
161
|
+
} else {
|
|
162
|
+
handled = false;
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case "ArrowUp": {
|
|
167
|
+
if (vertical) {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
moveFocus(index, "previous");
|
|
170
|
+
} else {
|
|
171
|
+
handled = false;
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case "Home": {
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
moveFocus(index, "start");
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case "End": {
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
moveFocus(index, "end");
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
default: {
|
|
186
|
+
handled = false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return handled;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
itemStates,
|
|
194
|
+
registerItem,
|
|
195
|
+
removeItem,
|
|
196
|
+
handleItemFocus,
|
|
197
|
+
handleGroupKey,
|
|
198
|
+
moveFocus,
|
|
199
|
+
focusFirstItem,
|
|
200
|
+
focusLastItem,
|
|
201
|
+
getFocusIndex,
|
|
202
|
+
isTabStop,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type FocusTarget = "start" | "previous" | "next" | "end";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Standard keyboard navigation for a list of registered items: moves through
|
|
5
|
+
* the items in the given direction and focuses the first one that isn't
|
|
6
|
+
* disabled. Returns the new focus index, or the original when no focusable
|
|
7
|
+
* item was found.
|
|
8
|
+
*/
|
|
9
|
+
export function focusItem<T extends { setFocused: () => void; disabled?: boolean }>(
|
|
10
|
+
items: T[],
|
|
11
|
+
current: number,
|
|
12
|
+
target: FocusTarget,
|
|
13
|
+
): number {
|
|
14
|
+
if (items.length === 0) return current;
|
|
15
|
+
let index = current;
|
|
16
|
+
switch (target) {
|
|
17
|
+
case "start": {
|
|
18
|
+
index = 0;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
case "previous": {
|
|
22
|
+
index = Math.max(0, current - 1);
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
case "next": {
|
|
26
|
+
index = Math.min(items.length - 1, current + 1);
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case "end": {
|
|
30
|
+
index = items.length - 1;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Skip disabled items, searching in the direction of travel
|
|
35
|
+
const step = target === "previous" ? -1 : 1;
|
|
36
|
+
while (index >= 0 && index < items.length && items[index].disabled) {
|
|
37
|
+
index += step;
|
|
38
|
+
}
|
|
39
|
+
if (index < 0 || index >= items.length) {
|
|
40
|
+
return current;
|
|
41
|
+
}
|
|
42
|
+
items[index].setFocused();
|
|
43
|
+
return index;
|
|
44
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { FieldContext, FormContext } from "../Form/FormTypes";
|
|
2
|
+
|
|
3
|
+
export interface FormField {
|
|
4
|
+
/** The resolved form data field name: the component's name, or the enclosing Field's */
|
|
5
|
+
name: string | undefined;
|
|
6
|
+
/** Whether this component is contained within a Form Field */
|
|
7
|
+
inField: boolean;
|
|
8
|
+
/** Whether the Field's value is valid; always true outside a Field */
|
|
9
|
+
valid: boolean;
|
|
10
|
+
/** The ID of the Field's Message component, for aria-describedby */
|
|
11
|
+
messageId: string | undefined;
|
|
12
|
+
/** Called after the value has been changed by user interaction */
|
|
13
|
+
handleInput: () => void;
|
|
14
|
+
/** Called when the component loses focus */
|
|
15
|
+
handleBlur: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Wires a standalone input component into an enclosing Form and Field, in
|
|
20
|
+
* the same way as the components in the Form family: the Field's name is
|
|
21
|
+
* used when the component doesn't set one, validation runs as the user
|
|
22
|
+
* interacts, and the standard validity attributes can be applied.
|
|
23
|
+
*
|
|
24
|
+
* Usage inside a component:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* const form = createFormField(
|
|
28
|
+
* $props?.name,
|
|
29
|
+
* $context[FormContextName] as FormContext | undefined,
|
|
30
|
+
* $context[FieldContextName] as FieldContext | undefined,
|
|
31
|
+
* );
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export default function createFormField(
|
|
35
|
+
name: string | undefined,
|
|
36
|
+
formContext: FormContext | undefined,
|
|
37
|
+
fieldContext: FieldContext | undefined,
|
|
38
|
+
): FormField {
|
|
39
|
+
let blurred = false;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
get name() {
|
|
43
|
+
return name ?? fieldContext?.name;
|
|
44
|
+
},
|
|
45
|
+
get inField(): boolean {
|
|
46
|
+
return fieldContext !== undefined;
|
|
47
|
+
},
|
|
48
|
+
get valid(): boolean {
|
|
49
|
+
if (!fieldContext) return true;
|
|
50
|
+
return fieldContext.state.valid !== false;
|
|
51
|
+
},
|
|
52
|
+
get messageId(): string | undefined {
|
|
53
|
+
return fieldContext?.state.messageId;
|
|
54
|
+
},
|
|
55
|
+
handleInput() {
|
|
56
|
+
if (formContext) {
|
|
57
|
+
formContext.validate();
|
|
58
|
+
}
|
|
59
|
+
if (fieldContext && blurred) {
|
|
60
|
+
fieldContext.validate();
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
handleBlur() {
|
|
64
|
+
if (fieldContext) {
|
|
65
|
+
fieldContext.validate();
|
|
66
|
+
}
|
|
67
|
+
blurred = true;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared hover-to-open behavior for popout triggers: opens after a delay,
|
|
3
|
+
* resetting the timer as the user moves within the element.
|
|
4
|
+
*/
|
|
5
|
+
export function createHoverOpen(
|
|
6
|
+
open: () => void,
|
|
7
|
+
disabled: () => boolean,
|
|
8
|
+
delay: number,
|
|
9
|
+
): {
|
|
10
|
+
handleMouseEnter: () => void;
|
|
11
|
+
handleMouseLeave: () => void;
|
|
12
|
+
handleMouseMove: () => void;
|
|
13
|
+
} {
|
|
14
|
+
let interval: ReturnType<typeof setTimeout> | undefined;
|
|
15
|
+
|
|
16
|
+
function schedule() {
|
|
17
|
+
interval = setTimeout(() => open(), delay);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function handleMouseEnter() {
|
|
21
|
+
if (disabled()) return;
|
|
22
|
+
|
|
23
|
+
// Handle the hover after a delay
|
|
24
|
+
if (!interval) {
|
|
25
|
+
schedule();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function handleMouseLeave() {
|
|
30
|
+
if (disabled()) return;
|
|
31
|
+
|
|
32
|
+
// Prevent short hovers or touches from firing the event
|
|
33
|
+
if (interval) {
|
|
34
|
+
clearTimeout(interval);
|
|
35
|
+
interval = undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function handleMouseMove() {
|
|
40
|
+
if (disabled()) return;
|
|
41
|
+
|
|
42
|
+
// Reset the interval on mouse move
|
|
43
|
+
if (interval) {
|
|
44
|
+
clearTimeout(interval);
|
|
45
|
+
schedule();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { handleMouseEnter, handleMouseLeave, handleMouseMove };
|
|
50
|
+
}
|