@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,157 @@
|
|
|
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 { defaultPalette, parseHex } from "../utils/colorUtils";
|
|
10
|
+
import createFormField from "../utils/formField";
|
|
11
|
+
import ColorPalette from "./ColorPalette.torp";
|
|
12
|
+
|
|
13
|
+
interface ColorPickerProps {
|
|
14
|
+
/** An ID for the root element */
|
|
15
|
+
id?: string;
|
|
16
|
+
/** Classes for the root element */
|
|
17
|
+
class?: ClassValue;
|
|
18
|
+
/** Styles for the root element */
|
|
19
|
+
style?: StyleValue;
|
|
20
|
+
/** The selected color as "#rrggbb"; undefined if nothing is selected */
|
|
21
|
+
value?: Bindable<string | undefined>;
|
|
22
|
+
/** The colors offered in the palette */
|
|
23
|
+
colors?: string[];
|
|
24
|
+
/**
|
|
25
|
+
* If set to true, a text field for arbitrary hex values ("#ff8800") is
|
|
26
|
+
* shown below the palette
|
|
27
|
+
*/
|
|
28
|
+
showInput?: boolean;
|
|
29
|
+
/** If set to true, the user cannot interact with this picker */
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
/** An accessible name for the picker */
|
|
32
|
+
ariaLabel?: string;
|
|
33
|
+
/** The name of the component's form data field */
|
|
34
|
+
name?: string;
|
|
35
|
+
/** Raised when the color has changed or been cleared */
|
|
36
|
+
onchange?: (value: string | undefined) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A color input: a ColorPalette with an optional hex text field, sharing a
|
|
41
|
+
* single bound value.
|
|
42
|
+
*
|
|
43
|
+
* ```
|
|
44
|
+
* <ColorPicker name="accent" showInput &value={$state.accent} />
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export default function ColorPicker($props: ColorPickerProps | undefined) {
|
|
48
|
+
let hexInput: HTMLInputElement;
|
|
49
|
+
|
|
50
|
+
// Wire up the enclosing Form and Field, if any
|
|
51
|
+
const form = createFormField(
|
|
52
|
+
$props?.name,
|
|
53
|
+
$context[FormContextName] as FormContext | undefined,
|
|
54
|
+
$context[FieldContextName] as FieldContext | undefined,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
function getDisabled(): boolean {
|
|
58
|
+
return $props?.disabled === true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let $state = $watch({
|
|
62
|
+
value: undefined as string | undefined,
|
|
63
|
+
|
|
64
|
+
// Text while the hex input is being typed into; committed on blur or
|
|
65
|
+
// Enter, invalid text reverting like NumberInput
|
|
66
|
+
text: "",
|
|
67
|
+
editing: false,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
$bind($state, $props, "value");
|
|
71
|
+
|
|
72
|
+
function handleSelect(value: string | undefined) {
|
|
73
|
+
$state.text = value ?? "";
|
|
74
|
+
form.handleInput();
|
|
75
|
+
$props?.onchange?.(value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function commit() {
|
|
79
|
+
$state.editing = false;
|
|
80
|
+
const next = parseHex($state.text);
|
|
81
|
+
if ($state.text.trim() !== "" && !next) {
|
|
82
|
+
// Not a color: revert to the last committed value
|
|
83
|
+
$state.text = $state.value ?? "";
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (next !== $state.value) {
|
|
87
|
+
$state.value = next;
|
|
88
|
+
form.handleInput();
|
|
89
|
+
$props?.onchange?.(next);
|
|
90
|
+
}
|
|
91
|
+
$state.text = $state.value ?? "";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handleInput(e: Event) {
|
|
95
|
+
const el = e.currentTarget as HTMLInputElement;
|
|
96
|
+
$state.editing = true;
|
|
97
|
+
$state.text = el.value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function handleKey(e: KeyboardEvent) {
|
|
101
|
+
if (e.key === "Enter") {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
commit();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function handleChange() {
|
|
108
|
+
commit();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Keep the input's value in sync with the text; typing sets the
|
|
112
|
+
// property directly, so an attribute alone would not update it
|
|
113
|
+
function syncInput() {
|
|
114
|
+
if (hexInput && !$state.editing && hexInput.value !== $state.text) {
|
|
115
|
+
hexInput.value = $state.text;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
$run(() => {
|
|
119
|
+
const text = $state.text;
|
|
120
|
+
const editing = $state.editing;
|
|
121
|
+
if (hexInput && !editing && hexInput.value !== text) {
|
|
122
|
+
hexInput.value = text;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
$onmount(() => syncInput());
|
|
126
|
+
|
|
127
|
+
@render {
|
|
128
|
+
<div class={["torp-color-picker", $props?.class]} {$props?.style}>
|
|
129
|
+
<ColorPalette
|
|
130
|
+
class="torp-color-picker-palette"
|
|
131
|
+
colors={$props?.colors ?? defaultPalette}
|
|
132
|
+
value={$state.value}
|
|
133
|
+
disabled={getDisabled()}
|
|
134
|
+
ariaLabel={$props?.ariaLabel}
|
|
135
|
+
onchange={handleSelect}
|
|
136
|
+
/>
|
|
137
|
+
@if ($props?.showInput) {
|
|
138
|
+
<input
|
|
139
|
+
&ref={hexInput}
|
|
140
|
+
type="text"
|
|
141
|
+
class="torp-color-picker-input"
|
|
142
|
+
spellcheck="false"
|
|
143
|
+
placeholder="#rrggbb"
|
|
144
|
+
aria-label={$props?.ariaLabel ? `${$props.ariaLabel} hex value` : "Hex value"}
|
|
145
|
+
value={$state.editing ? $state.text : $state.value ?? ""}
|
|
146
|
+
disabled={getDisabled()}
|
|
147
|
+
oninput={handleInput}
|
|
148
|
+
onchange={handleChange}
|
|
149
|
+
onblur={handleChange}
|
|
150
|
+
onkeydown={handleKey}
|
|
151
|
+
/>
|
|
152
|
+
}
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<FormValues name={form.name} value={$state.value} />
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type SlotRender } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface ColorPaletteProps {
|
|
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
|
+
/** The selected color as "#rrggbb"; undefined if nothing is selected */
|
|
12
|
+
value?: Bindable<string | undefined>;
|
|
13
|
+
/** The colors to offer (default: a built-in gray + hue palette) */
|
|
14
|
+
colors?: string[];
|
|
15
|
+
/** If set to true, the user cannot interact with this palette */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** An accessible name for the palette */
|
|
18
|
+
ariaLabel?: string;
|
|
19
|
+
/** The name of the component's form data field */
|
|
20
|
+
name?: string;
|
|
21
|
+
/** Raised when a color has been selected or deselected */
|
|
22
|
+
onchange?: (value: string | undefined) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A grid of color swatches that behaves like a radio group: one swatch can
|
|
27
|
+
* be selected, clicking it again deselects, and the arrow keys move through
|
|
28
|
+
* the swatches selecting as they go.
|
|
29
|
+
*
|
|
30
|
+
* Use ColorPicker instead when arbitrary hex values should be enterable
|
|
31
|
+
* alongside the palette.
|
|
32
|
+
*/
|
|
33
|
+
declare function ColorPalette(
|
|
34
|
+
$parent: ParentNode,
|
|
35
|
+
$anchor: Node | null,
|
|
36
|
+
$props: ColorPaletteProps | undefined,
|
|
37
|
+
$context?: Record<PropertyKey, any>,
|
|
38
|
+
$slots?: Record<string, SlotRender>,
|
|
39
|
+
): void;
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
interface ColorPickerProps {
|
|
44
|
+
/** An ID for the root element */
|
|
45
|
+
id?: string;
|
|
46
|
+
/** Classes for the root element */
|
|
47
|
+
class?: ClassValue;
|
|
48
|
+
/** Styles for the root element */
|
|
49
|
+
style?: StyleValue;
|
|
50
|
+
/** The selected color as "#rrggbb"; undefined if nothing is selected */
|
|
51
|
+
value?: Bindable<string | undefined>;
|
|
52
|
+
/** The colors offered in the palette */
|
|
53
|
+
colors?: string[];
|
|
54
|
+
/**
|
|
55
|
+
* If set to true, a text field for arbitrary hex values ("#ff8800") is
|
|
56
|
+
* shown below the palette
|
|
57
|
+
*/
|
|
58
|
+
showInput?: boolean;
|
|
59
|
+
/** If set to true, the user cannot interact with this picker */
|
|
60
|
+
disabled?: boolean;
|
|
61
|
+
/** An accessible name for the picker */
|
|
62
|
+
ariaLabel?: string;
|
|
63
|
+
/** The name of the component's form data field */
|
|
64
|
+
name?: string;
|
|
65
|
+
/** Raised when the color has changed or been cleared */
|
|
66
|
+
onchange?: (value: string | undefined) => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A color input: a ColorPalette with an optional hex text field, sharing a
|
|
71
|
+
* single bound value.
|
|
72
|
+
*
|
|
73
|
+
* ```
|
|
74
|
+
* <ColorPicker name="accent" showInput &value={$state.accent} />
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
declare function ColorPicker(
|
|
78
|
+
$parent: ParentNode,
|
|
79
|
+
$anchor: Node | null,
|
|
80
|
+
$props: ColorPickerProps | undefined,
|
|
81
|
+
$context?: Record<PropertyKey, any>,
|
|
82
|
+
$slots?: Record<string, SlotRender>,
|
|
83
|
+
): void;
|
|
84
|
+
|
|
85
|
+
export { ColorPalette, ColorPicker };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type { Bindable, ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import $handle from "../utils/$handle";
|
|
3
|
+
import {
|
|
4
|
+
type FieldContext,
|
|
5
|
+
FieldContextName,
|
|
6
|
+
type FormContext,
|
|
7
|
+
FormContextName,
|
|
8
|
+
} from "../Form/FormTypes";
|
|
9
|
+
import { type LoadResult, type Loader, createItemLoader } from "../utils/loader";
|
|
10
|
+
import { type PopoutContext, PopoutContextName } from "../utils/PopoutTypes";
|
|
11
|
+
import createFormField from "../utils/formField";
|
|
12
|
+
import { type ComboBoxContext, ComboBoxContextName, type ComboBoxState } from "./ComboBoxTypes";
|
|
13
|
+
import FormValues from "../Form/FormValues.torp";
|
|
14
|
+
|
|
15
|
+
interface ComboBoxProps {
|
|
16
|
+
/** An ID for the root element */
|
|
17
|
+
id?: string;
|
|
18
|
+
/** Classes for the root element */
|
|
19
|
+
class?: ClassValue;
|
|
20
|
+
/** Styles for the root element */
|
|
21
|
+
style?: StyleValue;
|
|
22
|
+
/** The name of the input's form data field */
|
|
23
|
+
name?: string;
|
|
24
|
+
/** The data value selected by the user, or an array of values if multiple is set */
|
|
25
|
+
value: Bindable<any>;
|
|
26
|
+
/** Whether multiple values can be selected at the same time */
|
|
27
|
+
multiple?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Reads options from the network (or anywhere async) as the user types,
|
|
30
|
+
* receiving `{ searchText }`. When set and no content is slotted in, the
|
|
31
|
+
* options are rendered automatically.
|
|
32
|
+
*/
|
|
33
|
+
load?: Loader;
|
|
34
|
+
/** Called after each successful load with the normalized result */
|
|
35
|
+
onload?: (result: LoadResult) => void;
|
|
36
|
+
/** Extracts the display text for a loaded item (defaults to `label ?? text ?? item`) */
|
|
37
|
+
getItemLabel?: (item: any) => any;
|
|
38
|
+
/** Extracts the value for a loaded item (defaults to `value ?? id`) */
|
|
39
|
+
getItemValue?: (item: any) => any;
|
|
40
|
+
/** Text to display when the input value is empty */
|
|
41
|
+
placeholder?: string;
|
|
42
|
+
/** Whether the input's value is required */
|
|
43
|
+
required?: boolean;
|
|
44
|
+
/** An ARIA label for describing the input to screen readers when there is no associated label element */
|
|
45
|
+
ariaLabel?: string;
|
|
46
|
+
/** Whether the popout is visible; only bind this to control it from outside */
|
|
47
|
+
visible?: Bindable<boolean | undefined>;
|
|
48
|
+
/** Raised when the value has changed */
|
|
49
|
+
onchange?: (value: any) => void;
|
|
50
|
+
/** Raised when the popout has opened */
|
|
51
|
+
onopen?: () => void;
|
|
52
|
+
/** Raised when the popout has closed */
|
|
53
|
+
onclose?: (result: any) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* An input box for selecting a value from a popout ListBox or Dialog.
|
|
58
|
+
*
|
|
59
|
+
* See [the WAI ARIA guidelines for ComboBoxes](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/).
|
|
60
|
+
*
|
|
61
|
+
* The structure of a ComboBox is:
|
|
62
|
+
*
|
|
63
|
+
* ```
|
|
64
|
+
* <ComboBox>
|
|
65
|
+
* <ComboBoxInput />
|
|
66
|
+
* <ComboBoxTrigger /> (optional)
|
|
67
|
+
* <ComboBoxContent />
|
|
68
|
+
* </ComboBox>
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export default function ComboBox($props: ComboBoxProps | undefined) {
|
|
72
|
+
// Wire up the enclosing Form and Field, if any
|
|
73
|
+
const form = createFormField(
|
|
74
|
+
$props?.name,
|
|
75
|
+
$context[FormContextName] as FormContext | undefined,
|
|
76
|
+
$context[FieldContextName] as FieldContext | undefined,
|
|
77
|
+
);
|
|
78
|
+
let $state: ComboBoxState = $watch({
|
|
79
|
+
visible: false,
|
|
80
|
+
value: $props?.value,
|
|
81
|
+
contentId: undefined,
|
|
82
|
+
triggerId: undefined,
|
|
83
|
+
contentRole: undefined,
|
|
84
|
+
activeDescendant: undefined,
|
|
85
|
+
searchText: undefined,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
$bind($state, $props, "visible", "value");
|
|
89
|
+
|
|
90
|
+
if ($props?.load !== undefined && typeof $props.load !== "function") {
|
|
91
|
+
throw new Error("ComboBox: `load` must be a function");
|
|
92
|
+
}
|
|
93
|
+
const hasLoader = typeof $props?.load === "function";
|
|
94
|
+
|
|
95
|
+
// Load options from the network as the search text changes
|
|
96
|
+
const $load = hasLoader
|
|
97
|
+
? createItemLoader({
|
|
98
|
+
load: $props!.load!,
|
|
99
|
+
getRequest: () => ({ searchText: $state.searchText }),
|
|
100
|
+
onload: (r) => $props?.onload?.(r),
|
|
101
|
+
})
|
|
102
|
+
: undefined;
|
|
103
|
+
|
|
104
|
+
let result: any;
|
|
105
|
+
|
|
106
|
+
// Set the context to pass down to items
|
|
107
|
+
let context: ComboBoxContext = {
|
|
108
|
+
handleButton,
|
|
109
|
+
handleClickOutside,
|
|
110
|
+
get placeholder() {
|
|
111
|
+
return $props?.placeholder;
|
|
112
|
+
},
|
|
113
|
+
get required() {
|
|
114
|
+
return $props?.required;
|
|
115
|
+
},
|
|
116
|
+
get ariaLabel() {
|
|
117
|
+
return $props?.ariaLabel;
|
|
118
|
+
},
|
|
119
|
+
get name() {
|
|
120
|
+
return $props?.name;
|
|
121
|
+
},
|
|
122
|
+
get multiple() {
|
|
123
|
+
return $props?.multiple === true;
|
|
124
|
+
},
|
|
125
|
+
inField: () => form.inField,
|
|
126
|
+
valid: () => form.valid,
|
|
127
|
+
messageId: () => form.messageId,
|
|
128
|
+
handleBlur: () => form.handleBlur(),
|
|
129
|
+
state: $state,
|
|
130
|
+
getLoadedItems: $load ? () => $load.items : undefined,
|
|
131
|
+
getItemLabel: $props?.getItemLabel,
|
|
132
|
+
getItemValue: $props?.getItemValue,
|
|
133
|
+
};
|
|
134
|
+
$context[ComboBoxContextName] = context;
|
|
135
|
+
|
|
136
|
+
// Set the context to pass down to items
|
|
137
|
+
let popoutContext: PopoutContext = context;
|
|
138
|
+
$context[PopoutContextName] = popoutContext;
|
|
139
|
+
|
|
140
|
+
// Dispatch the change event when the value changes
|
|
141
|
+
$handle((first) => {
|
|
142
|
+
const value = $state.value;
|
|
143
|
+
if (!first) {
|
|
144
|
+
form.handleInput();
|
|
145
|
+
$props?.onchange?.(value);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Dispatch the open/close events when the popout is opened or closed
|
|
150
|
+
$handle((first) => {
|
|
151
|
+
const visible = $state.visible;
|
|
152
|
+
if (first) return;
|
|
153
|
+
if (visible) {
|
|
154
|
+
$props?.onopen?.();
|
|
155
|
+
} else {
|
|
156
|
+
$props?.onclose?.(result);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
function handleButton(type: "confirm" | "cancel" | undefined, value?: any) {
|
|
161
|
+
// Get the button's result
|
|
162
|
+
switch (type) {
|
|
163
|
+
case "confirm": {
|
|
164
|
+
result = value ?? true;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "cancel": {
|
|
168
|
+
result = value ?? false;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
default: {
|
|
172
|
+
result = value;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Update the value
|
|
178
|
+
if (type !== "cancel") {
|
|
179
|
+
$state.value = result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Hide the popout and focus input
|
|
183
|
+
if (type === "confirm") {
|
|
184
|
+
$state.visible = false;
|
|
185
|
+
if (context.focusInput) {
|
|
186
|
+
context.focusInput();
|
|
187
|
+
}
|
|
188
|
+
} else if (type === "cancel") {
|
|
189
|
+
$state.visible = false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function handleClickOutside(e: MouseEvent | KeyboardEvent) {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
$state.visible = false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@render {
|
|
199
|
+
<div {$props?.id} class={["torp-combo-box", $props?.class]} {$props?.style}>
|
|
200
|
+
<slot />
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<FormValues name={form.name} value={$props?.value} />
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import { addDocumentEvent, removeDocumentEvent } from "../utils/documentEvents";
|
|
3
|
+
import LoadedItems from "../utils/LoadedItems.torp";
|
|
4
|
+
import { type ComboBoxContext, ComboBoxContextName } from "./ComboBoxTypes";
|
|
5
|
+
|
|
6
|
+
interface ComboBoxContentProps {
|
|
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
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The content that is displayed for a ComboBox when it is open, displayed
|
|
17
|
+
* alongside an element.
|
|
18
|
+
*/
|
|
19
|
+
export default function ComboBoxContent($props: ComboBoxContentProps | undefined) {
|
|
20
|
+
let div: HTMLDivElement;
|
|
21
|
+
let shown = false;
|
|
22
|
+
let closeOnClick = false;
|
|
23
|
+
|
|
24
|
+
// Get stuff out of the ComboBoxContext
|
|
25
|
+
const context = $context[ComboBoxContextName] as ComboBoxContext;
|
|
26
|
+
if (!context) {
|
|
27
|
+
throw new Error("ComboBoxContent must be contained within a ComboBox");
|
|
28
|
+
}
|
|
29
|
+
let $state = context.state;
|
|
30
|
+
|
|
31
|
+
$run(() => {
|
|
32
|
+
if ($state.visible) {
|
|
33
|
+
show();
|
|
34
|
+
} else {
|
|
35
|
+
hide();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function show() {
|
|
40
|
+
shown = true;
|
|
41
|
+
|
|
42
|
+
// HACK: Because this ComboBox could be shown with a click, we need to wait for another mousedown
|
|
43
|
+
// before we close on click. Otherwise the click immediately bubbles to the document
|
|
44
|
+
closeOnClick = false;
|
|
45
|
+
addDocumentEvent("mousedown", handleDocumentMouseDown);
|
|
46
|
+
addDocumentEvent("click", handleDocumentClick);
|
|
47
|
+
addDocumentEvent("keydown", handleDocumentKeyDown);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hide() {
|
|
51
|
+
if (!shown) return;
|
|
52
|
+
|
|
53
|
+
removeDocumentEvent("mousedown", handleDocumentMouseDown);
|
|
54
|
+
removeDocumentEvent("click", handleDocumentClick);
|
|
55
|
+
removeDocumentEvent("keydown", handleDocumentKeyDown);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function handleDocumentMouseDown() {
|
|
59
|
+
closeOnClick = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function handleDocumentClick(e: MouseEvent) {
|
|
63
|
+
// Don't close if the click is inside the content div OR inside the
|
|
64
|
+
// ComboBox root (which contains the input/trigger)
|
|
65
|
+
if (closeOnClick && div && !div.contains(e.target as HTMLElement) && !div.parentElement?.contains(e.target as HTMLElement)) {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
context.handleClickOutside!(e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleDocumentKeyDown(e: KeyboardEvent) {
|
|
72
|
+
if (e.key === "Escape") {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
$state.visible = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// HACK: We need to keep the items in the document so that we can search through them
|
|
79
|
+
@render {
|
|
80
|
+
<div
|
|
81
|
+
{$props?.id}
|
|
82
|
+
class={["torp-combo-box-content", $props?.class]}
|
|
83
|
+
{$props?.style}
|
|
84
|
+
hidden={!$state.visible}
|
|
85
|
+
aria-hidden={$state.visible ? undefined : "true"}
|
|
86
|
+
&ref={div}
|
|
87
|
+
>
|
|
88
|
+
<slot>
|
|
89
|
+
@if (context.getLoadedItems) {
|
|
90
|
+
@// Options loaded from a network loader, rendered automatically
|
|
91
|
+
@try {
|
|
92
|
+
@await {
|
|
93
|
+
<LoadedItems
|
|
94
|
+
items={context.getLoadedItems!()}
|
|
95
|
+
value={$state.value}
|
|
96
|
+
multiple={context.multiple}
|
|
97
|
+
getItemLabel={context.getItemLabel}
|
|
98
|
+
getItemValue={context.getItemValue}
|
|
99
|
+
/>
|
|
100
|
+
} with {
|
|
101
|
+
<span class="torp-combo-box-loading">Loading…</span>
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
<span class="torp-combo-box-error" role="alert">
|
|
105
|
+
{err instanceof Error ? err.message : String(err)}
|
|
106
|
+
</span>
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
</slot>
|
|
110
|
+
</div>
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import getId from "../utils/getId";
|
|
3
|
+
import { type ComboBoxContext, ComboBoxContextName } from "./ComboBoxTypes";
|
|
4
|
+
|
|
5
|
+
interface ComboBoxInputProps {
|
|
6
|
+
/** An ID for the root element */
|
|
7
|
+
id?: string;
|
|
8
|
+
/** Classes for the root element */
|
|
9
|
+
class?: ClassValue;
|
|
10
|
+
/** Styles for the root element */
|
|
11
|
+
style?: StyleValue;
|
|
12
|
+
|
|
13
|
+
// TODO: Get these from the parent
|
|
14
|
+
/** The name of the input's form data field */
|
|
15
|
+
name?: string;
|
|
16
|
+
/** The value displayed in the input */
|
|
17
|
+
value?: any;
|
|
18
|
+
/** Text to display when the input value is empty */
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
/** Whether the input's value is required */
|
|
21
|
+
required?: boolean;
|
|
22
|
+
|
|
23
|
+
/** An ARIA label for describing the input to screen readers when there is no associated label element */
|
|
24
|
+
ariaLabel?: string;
|
|
25
|
+
/** Raised on change */
|
|
26
|
+
onchange?: (e: Event) => void;
|
|
27
|
+
/** Raised on input */
|
|
28
|
+
oninput?: (e: InputEvent) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The input that causes a ComboBox to appear or disappear.
|
|
33
|
+
*/
|
|
34
|
+
export default function ComboBoxInput($props: ComboBoxInputProps | undefined) {
|
|
35
|
+
let defaultId = getId();
|
|
36
|
+
let input: HTMLInputElement;
|
|
37
|
+
|
|
38
|
+
// Get stuff out of the ComboBoxContext
|
|
39
|
+
const context = $context[ComboBoxContextName] as ComboBoxContext;
|
|
40
|
+
if (!context) {
|
|
41
|
+
throw new Error("ComboBoxInput must be contained within a ComboBox");
|
|
42
|
+
}
|
|
43
|
+
let $state = context.state;
|
|
44
|
+
context.focusInput = focusInput;
|
|
45
|
+
|
|
46
|
+
// Set the trigger ID so that it can be used with aria-labelledby
|
|
47
|
+
$state.triggerId = $props?.id ?? defaultId;
|
|
48
|
+
|
|
49
|
+
function focusInput() {
|
|
50
|
+
input.focus();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function handleClick() {
|
|
54
|
+
$state.visible = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function handleInput(e: InputEvent) {
|
|
58
|
+
if (context.getLoadedItems) {
|
|
59
|
+
// Loaded from a network loader: refetch with the current text
|
|
60
|
+
$state.searchText = input.value;
|
|
61
|
+
$state.visible = true;
|
|
62
|
+
} else if (context.searchItems) {
|
|
63
|
+
context.searchText = input.value;
|
|
64
|
+
const found = context.searchItems(context.searchText);
|
|
65
|
+
if (found !== undefined) {
|
|
66
|
+
$state.visible = true;
|
|
67
|
+
$state.activeDescendant = found.id;
|
|
68
|
+
if (context.markElement) {
|
|
69
|
+
context.markElement(found.id);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if ($props?.oninput) {
|
|
75
|
+
$props.oninput(e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function handleKey(e: KeyboardEvent) {
|
|
80
|
+
switch (e.key) {
|
|
81
|
+
case "ArrowDown": {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
$state.visible = true;
|
|
84
|
+
if (context.focusFirstElement) {
|
|
85
|
+
context.focusFirstElement();
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "ArrowUp": {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
$state.visible = true;
|
|
92
|
+
if (context.focusLastElement) {
|
|
93
|
+
context.focusLastElement();
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case "Enter": {
|
|
98
|
+
if ($state.visible) {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
if (context.selectMarkedElement) {
|
|
101
|
+
context.selectMarkedElement();
|
|
102
|
+
}
|
|
103
|
+
$state.visible = false;
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "Escape": {
|
|
108
|
+
if ($state.visible) {
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
$state.visible = false;
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@render {
|
|
118
|
+
<input
|
|
119
|
+
role="combobox"
|
|
120
|
+
aria-autocomplete="list"
|
|
121
|
+
id={$props?.id ?? defaultId}
|
|
122
|
+
class={["torp-combo-box-input", $props?.class]}
|
|
123
|
+
{$props?.style}
|
|
124
|
+
name={$props?.name !== undefined ? $props.name : context.name}
|
|
125
|
+
&value={Array.isArray($state.value) ? $state.value.join(", ") : $state.value}
|
|
126
|
+
placeholder={$props?.placeholder !== undefined ? $props.placeholder : context.placeholder}
|
|
127
|
+
required={$props?.required !== undefined ? $props.required : context.required}
|
|
128
|
+
aria-readonly
|
|
129
|
+
aria-controls={$state.contentId}
|
|
130
|
+
aria-haspopup={$state.contentRole}
|
|
131
|
+
aria-activedescendant={$state.activeDescendant}
|
|
132
|
+
aria-expanded={$state.visible ? "true" : "false"}
|
|
133
|
+
aria-label={$props?.ariaLabel !== undefined ? $props.ariaLabel : context.ariaLabel}
|
|
134
|
+
data-valid={context.valid?.() ? "valid" : "invalid"}
|
|
135
|
+
aria-describedby={context.messageId?.()}
|
|
136
|
+
aria-invalid={context.inField?.() && !context.valid?.() ? "true" : undefined}
|
|
137
|
+
onblur={() => context.handleBlur?.()}
|
|
138
|
+
{$props?.onchange}
|
|
139
|
+
onclick={handleClick}
|
|
140
|
+
oninput={handleInput}
|
|
141
|
+
onkeydown={handleKey}
|
|
142
|
+
&ref={input}
|
|
143
|
+
/>
|
|
144
|
+
}
|
|
145
|
+
}
|