@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,374 @@
|
|
|
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 FormValues from "../Form/FormValues.torp";
|
|
10
|
+
import { type LoadResult, type Loader, createItemLoader } from "../utils/loader";
|
|
11
|
+
import { createItemGroup } from "../utils/createItemGroup";
|
|
12
|
+
import getId from "../utils/getId";
|
|
13
|
+
import createFormField from "../utils/formField";
|
|
14
|
+
import { type HandleButtonPress, type PopoutContext, PopoutContextName } from "../utils/PopoutTypes";
|
|
15
|
+
import LoadedListBoxItems from "./LoadedListBoxItems.torp";
|
|
16
|
+
import { type ItemState, type ListBoxContext, ListBoxContextName } from "./ListBoxTypes";
|
|
17
|
+
|
|
18
|
+
// TODO: orientation
|
|
19
|
+
|
|
20
|
+
interface ListBoxProps {
|
|
21
|
+
/** An ID for the root element */
|
|
22
|
+
id?: string;
|
|
23
|
+
/** Classes for the root element */
|
|
24
|
+
class?: ClassValue;
|
|
25
|
+
/** Styles for the root element */
|
|
26
|
+
style?: StyleValue;
|
|
27
|
+
/** Whether a single ListBoxItem or multiple ListBoxItems can be selected at the same time */
|
|
28
|
+
type?: "single" | "multiple";
|
|
29
|
+
/** The value of the selected ListBoxItem, or an array of values if the type is "multiple" */
|
|
30
|
+
value: Bindable<any | any[]>;
|
|
31
|
+
/** If set to true, the user cannot interact with any items in the ListBox */
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
/** An ARIA label for describing the ListBox to screen readers */
|
|
34
|
+
ariaLabel?: string | undefined;
|
|
35
|
+
/** An ID of an element that labels the ListBox */
|
|
36
|
+
ariaLabelledBy?: string | undefined;
|
|
37
|
+
/** The orientation of the ListBox */
|
|
38
|
+
orientation?: "vertical" | "horizontal";
|
|
39
|
+
/** Raised when the selected item(s) have changed */
|
|
40
|
+
/** The name of the component's form data field */
|
|
41
|
+
name?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Reads options from the network (or anywhere async), receiving
|
|
44
|
+
* `{ searchText }` whenever the bound `searchText` changes. When set and
|
|
45
|
+
* no ListBoxItems are slotted in, the options are rendered automatically
|
|
46
|
+
*/
|
|
47
|
+
load?: Loader;
|
|
48
|
+
/** Called after each successful load with the normalized result */
|
|
49
|
+
onload?: (result: LoadResult) => void;
|
|
50
|
+
/** Extracts the display text for a loaded item (defaults to `label ?? text ?? item`) */
|
|
51
|
+
getItemLabel?: (item: any) => any;
|
|
52
|
+
/** Extracts the value for a loaded item (defaults to `value ?? id`) */
|
|
53
|
+
getItemValue?: (item: any) => any;
|
|
54
|
+
/** Filter text for a `load` function; two-way bindable */
|
|
55
|
+
searchText?: Bindable<string | undefined>;
|
|
56
|
+
onchange?: (value: any | any[]) => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A list of options that a user can select.
|
|
61
|
+
*
|
|
62
|
+
* See [the WAI ARIA guidelines for ListBoxes](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/).
|
|
63
|
+
*
|
|
64
|
+
* The structure of an ListBox is:
|
|
65
|
+
*
|
|
66
|
+
* ```
|
|
67
|
+
* <ListBox>
|
|
68
|
+
* <ListBoxItem />
|
|
69
|
+
* <ListBoxSeparator /> (optional)
|
|
70
|
+
* </ListBox>
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export default function ListBox($props: ListBoxProps | undefined) {
|
|
74
|
+
let defaultId = getId();
|
|
75
|
+
let root: HTMLElement;
|
|
76
|
+
|
|
77
|
+
// Wire up the enclosing Form and Field, if any
|
|
78
|
+
const form = createFormField(
|
|
79
|
+
$props?.name,
|
|
80
|
+
$context[FormContextName] as FormContext | undefined,
|
|
81
|
+
$context[FieldContextName] as FieldContext | undefined,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Get the current selection type, re-reading $props so changes are picked up
|
|
85
|
+
function getType(): "single" | "multiple" {
|
|
86
|
+
return $props?.type ?? "single";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// This may come from a PopoutContext, or we may need to handle it ourselves
|
|
90
|
+
let searchText: string | undefined;
|
|
91
|
+
|
|
92
|
+
let $state = $watch({
|
|
93
|
+
value: $props?.value ?? (getType() === "single" ? undefined : []),
|
|
94
|
+
searchText: undefined as string | undefined,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
$bind($state, $props, "value", "searchText");
|
|
98
|
+
|
|
99
|
+
if ($props?.load !== undefined && typeof $props.load !== "function") {
|
|
100
|
+
throw new Error("ListBox: `load` must be a function");
|
|
101
|
+
}
|
|
102
|
+
const hasLoader = typeof $props?.load === "function";
|
|
103
|
+
|
|
104
|
+
// Load options whenever the search text changes; pass the loaded items
|
|
105
|
+
// and getters through the context for the slot fallback to render
|
|
106
|
+
const $load = hasLoader
|
|
107
|
+
? createItemLoader({
|
|
108
|
+
load: $props!.load!,
|
|
109
|
+
getRequest: () => ({ searchText: $state.searchText }),
|
|
110
|
+
onload: (r) => $props?.onload?.(r),
|
|
111
|
+
})
|
|
112
|
+
: undefined;
|
|
113
|
+
|
|
114
|
+
// Maybe get stuff out of a popout context
|
|
115
|
+
let handleButton: HandleButtonPress | undefined = undefined;
|
|
116
|
+
const popoutContext = $context[PopoutContextName] as PopoutContext;
|
|
117
|
+
if (popoutContext) {
|
|
118
|
+
handleButton = popoutContext.handleButton;
|
|
119
|
+
|
|
120
|
+
//popoutContext.handleClickOutside = handleClickOutside;
|
|
121
|
+
// Set the focusFirstElement method in the PopoutContext so that it can be
|
|
122
|
+
// called after everything has been shown
|
|
123
|
+
popoutContext.focusFirstElement = focusFirstElement;
|
|
124
|
+
popoutContext.focusLastElement = focusLastElement;
|
|
125
|
+
popoutContext.markElement = markElement;
|
|
126
|
+
popoutContext.selectMarkedElement = selectMarkedElement;
|
|
127
|
+
popoutContext.searchItems = searchItems;
|
|
128
|
+
|
|
129
|
+
// Set the content ID and role so that triggers can use them with
|
|
130
|
+
// aria-controls / aria-haspopup
|
|
131
|
+
popoutContext.state.contentId = $props?.id ?? defaultId;
|
|
132
|
+
popoutContext.state.contentRole = "listbox";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const group = createItemGroup<ItemState>({
|
|
136
|
+
state: $state,
|
|
137
|
+
type: getType,
|
|
138
|
+
selectedProperty: "selected",
|
|
139
|
+
disabled: () => $props?.disabled,
|
|
140
|
+
onToggle: () => {
|
|
141
|
+
// Clear the keyboard focus marker
|
|
142
|
+
for (let item of group.itemStates) {
|
|
143
|
+
item.active = false;
|
|
144
|
+
}
|
|
145
|
+
form.handleInput();
|
|
146
|
+
// Pass this along to the containing popout if applicable
|
|
147
|
+
if (handleButton) {
|
|
148
|
+
if (getType() === "single") {
|
|
149
|
+
handleButton("confirm", $state.value);
|
|
150
|
+
} else {
|
|
151
|
+
handleButton(undefined, $state.value);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Set the context to pass down to items, with functions for each item to call
|
|
158
|
+
let context: ListBoxContext = {
|
|
159
|
+
get type() {
|
|
160
|
+
return getType();
|
|
161
|
+
},
|
|
162
|
+
state: $state,
|
|
163
|
+
registerItem: group.registerItem,
|
|
164
|
+
removeItem: group.removeItem,
|
|
165
|
+
toggleItem: group.toggleItem,
|
|
166
|
+
handleKey,
|
|
167
|
+
setActiveDescendant: popoutContext ? (id) => { popoutContext.state.activeDescendant = id; } : undefined,
|
|
168
|
+
getLoadedItems: $load ? () => $load.items : undefined,
|
|
169
|
+
getItemLabel: $props?.getItemLabel,
|
|
170
|
+
getItemValue: $props?.getItemValue,
|
|
171
|
+
};
|
|
172
|
+
$context[ListBoxContextName] = context;
|
|
173
|
+
|
|
174
|
+
// Dispatch the change event when the value changes
|
|
175
|
+
$handle((first) => {
|
|
176
|
+
const value = $state.value;
|
|
177
|
+
if (!first) $props?.onchange?.(value);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
function focusFirstElement() {
|
|
181
|
+
if (group.itemStates.length > 0) {
|
|
182
|
+
group.itemStates[0].setFocused();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function focusLastElement() {
|
|
187
|
+
if (group.itemStates.length > 0) {
|
|
188
|
+
group.itemStates.at(-1)!.setFocused();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function markElement(id: string) {
|
|
193
|
+
// selected stays derived from the group value; active marks the
|
|
194
|
+
// keyboard-focus preview
|
|
195
|
+
group.itemStates.forEach((i) => {
|
|
196
|
+
i.active = i.id === id;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function selectMarkedElement() {
|
|
201
|
+
const marked = group.itemStates.find((i) => i.active === true);
|
|
202
|
+
if (marked && handleButton) {
|
|
203
|
+
handleButton("confirm", marked.value);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function searchItems(searchText: string) {
|
|
208
|
+
const normalizedSearchText = searchText.toLocaleLowerCase();
|
|
209
|
+
for (let item of group.itemStates) {
|
|
210
|
+
if (item.text.toLocaleLowerCase().includes(normalizedSearchText)) {
|
|
211
|
+
return item;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function focusSelectedElement() {
|
|
217
|
+
// Called when the list box receives focus: focus the selected option,
|
|
218
|
+
// or the first option if none is selected
|
|
219
|
+
const itemStates = group.itemStates;
|
|
220
|
+
const target = itemStates.find((i) => i.selected) ?? itemStates[0];
|
|
221
|
+
target?.setFocused();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function handleKey(index: number, e: KeyboardEvent) {
|
|
225
|
+
const itemStates = group.itemStates;
|
|
226
|
+
// Finds the nearest focusable item, skipping disabled ones
|
|
227
|
+
const findFocusable = (from: number, step: number): number => {
|
|
228
|
+
let i = from;
|
|
229
|
+
while (i >= 0 && i < itemStates.length && itemStates[i].disabled) {
|
|
230
|
+
i += step;
|
|
231
|
+
}
|
|
232
|
+
return i;
|
|
233
|
+
};
|
|
234
|
+
const clearSearch = () => {
|
|
235
|
+
if (popoutContext) {
|
|
236
|
+
popoutContext.searchText = undefined;
|
|
237
|
+
} else {
|
|
238
|
+
searchText = undefined;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
switch (e.key) {
|
|
242
|
+
case "ArrowUp": {
|
|
243
|
+
const target = findFocusable(index - 1, -1);
|
|
244
|
+
if (target >= 0) {
|
|
245
|
+
e.preventDefault();
|
|
246
|
+
itemStates[target].setFocused();
|
|
247
|
+
clearSearch();
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case "ArrowDown": {
|
|
252
|
+
const target = findFocusable(index + 1, 1);
|
|
253
|
+
if (target < itemStates.length) {
|
|
254
|
+
e.preventDefault();
|
|
255
|
+
itemStates[target].setFocused();
|
|
256
|
+
clearSearch();
|
|
257
|
+
}
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
case "Home": {
|
|
261
|
+
if (index > 0) {
|
|
262
|
+
e.preventDefault();
|
|
263
|
+
itemStates[findFocusable(0, 1)].setFocused();
|
|
264
|
+
clearSearch();
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case "End": {
|
|
269
|
+
if (index < itemStates.length - 1) {
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
itemStates[findFocusable(itemStates.length - 1, -1)].setFocused();
|
|
272
|
+
clearSearch();
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
case "Escape": {
|
|
277
|
+
if (handleButton) {
|
|
278
|
+
handleButton("cancel");
|
|
279
|
+
if (popoutContext) {
|
|
280
|
+
popoutContext.searchText = undefined;
|
|
281
|
+
} else {
|
|
282
|
+
searchText = undefined;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
case "Backspace": {
|
|
288
|
+
if (popoutContext?.searchText || searchText) {
|
|
289
|
+
e.preventDefault();
|
|
290
|
+
const shorten = (str: string) => str.substring(0, str.length - 1);
|
|
291
|
+
if (popoutContext) {
|
|
292
|
+
popoutContext.searchText = shorten(popoutContext.searchText!);
|
|
293
|
+
searchText = popoutContext.searchText;
|
|
294
|
+
} else {
|
|
295
|
+
searchText = shorten(searchText!);
|
|
296
|
+
}
|
|
297
|
+
if (searchText.length > 0) {
|
|
298
|
+
const found = searchItems(searchText);
|
|
299
|
+
if (found) {
|
|
300
|
+
found.setFocused();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
default: {
|
|
307
|
+
// TODO: Handle unicode
|
|
308
|
+
if (e.key.length === 1) {
|
|
309
|
+
e.preventDefault();
|
|
310
|
+
const lengthen = (str: string) => (str ??= "") + e.key;
|
|
311
|
+
if (popoutContext) {
|
|
312
|
+
popoutContext.searchText = lengthen(popoutContext.searchText!);
|
|
313
|
+
searchText = popoutContext.searchText;
|
|
314
|
+
} else {
|
|
315
|
+
searchText = lengthen(searchText!);
|
|
316
|
+
}
|
|
317
|
+
const found = searchItems(searchText);
|
|
318
|
+
if (found) {
|
|
319
|
+
found.setFocused();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
@render {
|
|
327
|
+
<div
|
|
328
|
+
id={$props?.id ?? defaultId}
|
|
329
|
+
class={["torp-list-box", $props?.class]}
|
|
330
|
+
{$props?.style}
|
|
331
|
+
role="listbox"
|
|
332
|
+
tabindex="0"
|
|
333
|
+
onfocus={focusSelectedElement}
|
|
334
|
+
aria-label={$props?.ariaLabel}
|
|
335
|
+
aria-labelledby={$props?.ariaLabelledBy}
|
|
336
|
+
aria-multiselectable={getType() === "multiple" ? "true" : undefined}
|
|
337
|
+
aria-disabled={$props?.disabled ? "true" : undefined}
|
|
338
|
+
aria-orientation={$props?.orientation}
|
|
339
|
+
data-valid={form.valid ? "valid" : "invalid"}
|
|
340
|
+
aria-describedby={form.messageId}
|
|
341
|
+
aria-invalid={form.inField && !form.valid ? "true" : undefined}
|
|
342
|
+
onfocusout={(e: FocusEvent) => {
|
|
343
|
+
// Only validate when focus leaves the whole list box
|
|
344
|
+
if (!root.contains(e.relatedTarget as Node | null)) {
|
|
345
|
+
form.handleBlur();
|
|
346
|
+
}
|
|
347
|
+
}}
|
|
348
|
+
&ref={root}
|
|
349
|
+
>
|
|
350
|
+
<slot>
|
|
351
|
+
@if (context.getLoadedItems) {
|
|
352
|
+
@// Options loaded from a network loader, rendered automatically
|
|
353
|
+
@try {
|
|
354
|
+
@await {
|
|
355
|
+
<LoadedListBoxItems
|
|
356
|
+
items={context.getLoadedItems!()}
|
|
357
|
+
getItemLabel={context.getItemLabel}
|
|
358
|
+
getItemValue={context.getItemValue}
|
|
359
|
+
/>
|
|
360
|
+
} with {
|
|
361
|
+
<div class="torp-list-box-loading" role="status">Loading…</div>
|
|
362
|
+
}
|
|
363
|
+
} catch (err) {
|
|
364
|
+
<div class="torp-list-box-error" role="alert">
|
|
365
|
+
{err instanceof Error ? err.message : String(err)}
|
|
366
|
+
</div>
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
</slot>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<FormValues name={form.name} value={$state.value} />
|
|
373
|
+
}
|
|
374
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import $handle from "../utils/$handle";
|
|
3
|
+
import getId from "../utils/getId";
|
|
4
|
+
import { type ItemState, type ListBoxContext, ListBoxContextName } from "./ListBoxTypes";
|
|
5
|
+
|
|
6
|
+
interface ListBoxItemProps {
|
|
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
|
+
/** The index of this item in the ListBox. Set this if you will be inserting, removing or sorting items. If you don't set this, it will be set automatically */
|
|
14
|
+
index?: number;
|
|
15
|
+
/** The text for the item. If not set, this will come from it's text content */
|
|
16
|
+
text?: string;
|
|
17
|
+
/** A value for the item. Set this if you need more control over which item(s) are selected. If you don't set this, the value will be set to the item's initial index */
|
|
18
|
+
value?: string;
|
|
19
|
+
/** If set to true, the user cannot interact with this ListBoxItem */
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
/** Raised when this item's selected state has changed */
|
|
22
|
+
onchange?: (selected: boolean) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* An item in an ListBox.
|
|
27
|
+
*/
|
|
28
|
+
export default function ListBoxItem($props: ListBoxItemProps | undefined) {
|
|
29
|
+
let defaultId = getId();
|
|
30
|
+
|
|
31
|
+
let $state: ItemState = $watch({
|
|
32
|
+
id: $props?.id ?? defaultId,
|
|
33
|
+
index: -1,
|
|
34
|
+
text: $props?.text ?? "",
|
|
35
|
+
value: $props?.value,
|
|
36
|
+
selected: false,
|
|
37
|
+
active: false,
|
|
38
|
+
disabled: $props?.disabled ?? false,
|
|
39
|
+
parentDisabled: false,
|
|
40
|
+
setFocused,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
let button: HTMLButtonElement;
|
|
44
|
+
|
|
45
|
+
// Get stuff out of the ListBoxContext
|
|
46
|
+
const context = $context[ListBoxContextName] as ListBoxContext;
|
|
47
|
+
if (!context) {
|
|
48
|
+
throw new Error("ListBoxItem must be contained within a ListBox");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Register this item with the parent ListBox, which will handle toggling selected for all items
|
|
52
|
+
context.registerItem($state);
|
|
53
|
+
|
|
54
|
+
// Remove the item from the parent ListBox when this item is destroyed
|
|
55
|
+
$run(() => {
|
|
56
|
+
return () => {
|
|
57
|
+
context.removeItem($state.index);
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Dispatch the change event when the value of selected changes
|
|
62
|
+
$handle((first) => {
|
|
63
|
+
const selected = $state.selected;
|
|
64
|
+
if (!first) $props?.onchange?.(selected);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// This function is called by the parent ListBox to focus this item's button
|
|
68
|
+
function setFocused() {
|
|
69
|
+
button.focus();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function handleClick() {
|
|
73
|
+
// Pass the value to the toggleItem method in the parent ListBox which will handle toggling
|
|
74
|
+
// the value of selected for all items (e.g. to unselect other items if necessary)
|
|
75
|
+
context.toggleItem($state.value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function handleFocus() {
|
|
79
|
+
if (context.setActiveDescendant) {
|
|
80
|
+
context.setActiveDescendant($state.id);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function handleKey(e: KeyboardEvent) {
|
|
85
|
+
switch (e.key) {
|
|
86
|
+
case "Enter": {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
context.toggleItem($state.value);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
default: {
|
|
92
|
+
// Pass the key press up to the parent ListBox which will handle
|
|
93
|
+
// setting the focused button
|
|
94
|
+
context.handleKey($state.index, e);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function handleMount(el: HTMLButtonElement) {
|
|
101
|
+
// If the item has no value, set it to the text content; selected is
|
|
102
|
+
// derived from the group value, so it picks this up automatically
|
|
103
|
+
if (!$state.text) {
|
|
104
|
+
$state.text = el.textContent.trim();
|
|
105
|
+
}
|
|
106
|
+
if ($state.value === undefined || $state.value === null) {
|
|
107
|
+
$state.value = $state.text;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@render {
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
{$state.id}
|
|
115
|
+
class={["torp-list-box-item", $props?.class]}
|
|
116
|
+
{$props?.style}
|
|
117
|
+
role="option"
|
|
118
|
+
tabindex="-1"
|
|
119
|
+
aria-selected={$state.selected ? "true" : "false"}
|
|
120
|
+
aria-disabled={$state.disabled || $state.parentDisabled ? "true" : undefined}
|
|
121
|
+
data-state={$state.selected || $state.active ? "active" : undefined}
|
|
122
|
+
onclick={handleClick}
|
|
123
|
+
onkeydown={handleKey}
|
|
124
|
+
onfocus={handleFocus}
|
|
125
|
+
onmount={handleMount}
|
|
126
|
+
&ref={button}
|
|
127
|
+
>
|
|
128
|
+
<slot />
|
|
129
|
+
</button>
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
interface ListBoxSeparatorProps {
|
|
4
|
+
/** An ID for the root element */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** Classes for the root element */
|
|
7
|
+
class?: ClassValue;
|
|
8
|
+
/** Styles for the root element */
|
|
9
|
+
style?: StyleValue;
|
|
10
|
+
/** The orientation of the separator */
|
|
11
|
+
orientation?: "vertical" | "horizontal";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* An element in a list box that separates logical sets of items.
|
|
16
|
+
*/
|
|
17
|
+
export default function ListBoxSeparator($props: ListBoxSeparatorProps | undefined) {
|
|
18
|
+
@render {
|
|
19
|
+
<div
|
|
20
|
+
{$props?.id}
|
|
21
|
+
class={["torp-list-box-separator", $props?.class]}
|
|
22
|
+
{$props?.style}
|
|
23
|
+
role="separator"
|
|
24
|
+
aria-orientation={$props?.orientation ?? "horizontal"}
|
|
25
|
+
>
|
|
26
|
+
<slot />
|
|
27
|
+
</div>
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const ListBoxContextName: unique symbol = Symbol.for("torp.ListBox");
|
|
2
|
+
export const ListBoxItemContextName: unique symbol = Symbol.for("torp.ListBoxItem");
|
|
3
|
+
|
|
4
|
+
export interface ListBoxContext {
|
|
5
|
+
type: "single" | "multiple";
|
|
6
|
+
state: ListBoxState;
|
|
7
|
+
/** Called from a ListBoxItem when it is added */
|
|
8
|
+
registerItem: (state: ItemState) => void;
|
|
9
|
+
/** Called from a ListBoxItem when it is removed */
|
|
10
|
+
removeItem: (index: number) => void;
|
|
11
|
+
/** Called from a ListBoxItem when it is toggled */
|
|
12
|
+
toggleItem: (value: string) => void;
|
|
13
|
+
/** Called from a ListBoxItem when it receives a keyboard event */
|
|
14
|
+
handleKey: (index: number, e: KeyboardEvent) => void;
|
|
15
|
+
/** Called from a ListBoxItem when it receives focus */
|
|
16
|
+
setActiveDescendant?: (id: string) => void;
|
|
17
|
+
/** Reads the items loaded from the network loader, when one is set */
|
|
18
|
+
getLoadedItems?: () => any[];
|
|
19
|
+
/** Extracts the display text for a loaded item */
|
|
20
|
+
getItemLabel?: (item: any) => any;
|
|
21
|
+
/** Extracts the value for a loaded item */
|
|
22
|
+
getItemValue?: (item: any) => any;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ListBoxState {
|
|
26
|
+
value: any;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ItemState {
|
|
30
|
+
id: string;
|
|
31
|
+
index: number;
|
|
32
|
+
text: string;
|
|
33
|
+
value: any;
|
|
34
|
+
selected: boolean;
|
|
35
|
+
/** Used for styling when item has keyboard focus */
|
|
36
|
+
active: boolean;
|
|
37
|
+
disabled: boolean;
|
|
38
|
+
parentDisabled: boolean;
|
|
39
|
+
/** Called in the ListBox when an item needs to be focused */
|
|
40
|
+
setFocused: () => void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import ListBoxItem from "./ListBoxItem.torp";
|
|
2
|
+
|
|
3
|
+
interface LoadedListBoxItemsProps {
|
|
4
|
+
/** The loaded items */
|
|
5
|
+
items: any[];
|
|
6
|
+
/** Extracts the display text for an item (defaults to `label ?? text ?? item`) */
|
|
7
|
+
getItemLabel?: (item: any) => any;
|
|
8
|
+
/** Extracts the value for an item (defaults to `value ?? id`) */
|
|
9
|
+
getItemValue?: (item: any) => any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function defaultLabel(item: any): string {
|
|
13
|
+
const label = item?.label ?? item?.text ?? item;
|
|
14
|
+
return typeof label === "string" ? label : String(label);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function defaultValue(item: any): any {
|
|
18
|
+
return item?.value ?? item?.id;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The internally rendered options for a ListBox that loads them from a
|
|
23
|
+
* network loader. Used as the slot fallback when no ListBoxItems are slotted
|
|
24
|
+
* in; not part of the public API.
|
|
25
|
+
*/
|
|
26
|
+
export default function LoadedListBoxItems($props?: LoadedListBoxItemsProps) {
|
|
27
|
+
const getLabel = $props?.getItemLabel ?? defaultLabel;
|
|
28
|
+
const getValue = $props?.getItemValue ?? defaultValue;
|
|
29
|
+
|
|
30
|
+
@render {
|
|
31
|
+
@for (let item of $props!.items) {
|
|
32
|
+
@key = getValue(item)
|
|
33
|
+
<ListBoxItem value={getValue(item)}>
|
|
34
|
+
{getLabel(item)}
|
|
35
|
+
</ListBoxItem>
|
|
36
|
+
}
|
|
37
|
+
@if ($props!.items.length === 0) {
|
|
38
|
+
<span class="torp-list-box-empty">No results.</span>
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|