@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,225 @@
|
|
|
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 createFormField from "../utils/formField";
|
|
11
|
+
|
|
12
|
+
interface RatingProps {
|
|
13
|
+
/** An ID for the root element */
|
|
14
|
+
id?: string;
|
|
15
|
+
/** Classes for the root element */
|
|
16
|
+
class?: ClassValue;
|
|
17
|
+
/** Styles for the root element */
|
|
18
|
+
style?: StyleValue;
|
|
19
|
+
/** The maximum rating, i.e. the number of stars (default 5) */
|
|
20
|
+
max?: number;
|
|
21
|
+
/** The current rating */
|
|
22
|
+
value?: Bindable<number>;
|
|
23
|
+
/** If set to true, the user cannot interact with this rating */
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
/** If set to false, clicking or re-selecting the current rating does not clear it (default true) */
|
|
26
|
+
allowClear?: boolean;
|
|
27
|
+
/** An accessible name for the rating */
|
|
28
|
+
ariaLabel?: string;
|
|
29
|
+
/** The name of the component's form data field */
|
|
30
|
+
name?: string;
|
|
31
|
+
/** Raised when the rating has changed */
|
|
32
|
+
onchange?: (value: number) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A star rating input, where the user selects a value from 1 to a maximum
|
|
37
|
+
* number of stars by clicking or by using the keyboard.
|
|
38
|
+
*
|
|
39
|
+
* Implements the radio group pattern: each star is a radio button, the star
|
|
40
|
+
* matching the current rating is checked, and arrow keys move the rating.
|
|
41
|
+
*
|
|
42
|
+
* See [the WAI ARIA guidelines for Rating](https://www.w3.org/WAI/ARIA/apg/patterns/rating/).
|
|
43
|
+
*/
|
|
44
|
+
export default function Rating($props: RatingProps | undefined) {
|
|
45
|
+
let stars: (HTMLButtonElement | undefined)[] = [];
|
|
46
|
+
let root: HTMLElement;
|
|
47
|
+
|
|
48
|
+
// Wire up the enclosing Form and Field, if any
|
|
49
|
+
const form = createFormField(
|
|
50
|
+
$props?.name,
|
|
51
|
+
$context[FormContextName] as FormContext | undefined,
|
|
52
|
+
$context[FieldContextName] as FieldContext | undefined,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
function getMax(): number {
|
|
56
|
+
const max = $props?.max ?? 5;
|
|
57
|
+
return max > 0 ? Math.floor(max) : 5;
|
|
58
|
+
}
|
|
59
|
+
function getDisabled(): boolean {
|
|
60
|
+
return $props?.disabled === true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let $state = $watch({
|
|
64
|
+
value: 0,
|
|
65
|
+
hover: 0,
|
|
66
|
+
get stars(): number[] {
|
|
67
|
+
return Array.from({ length: getMax() }, (_, i) => i + 1);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
$bind($state, $props, "value");
|
|
72
|
+
|
|
73
|
+
// Keep the stored value clamped, so that out-of-range initial values
|
|
74
|
+
// don't later surface as user changes
|
|
75
|
+
$run(() => {
|
|
76
|
+
const clamped = clamp($state.value);
|
|
77
|
+
if (clamped !== $state.value) {
|
|
78
|
+
$state.value = clamped;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Dispatch the change event when the value changes
|
|
83
|
+
$handle((first) => {
|
|
84
|
+
const value = $state.value;
|
|
85
|
+
if (!first) $props?.onchange?.(value);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
function clamp(value: number): number {
|
|
89
|
+
return Math.min(getMax(), Math.max(0, value));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getValue(): number {
|
|
93
|
+
return clamp($state.value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function setValue(value: number, focus = false) {
|
|
97
|
+
const next = clamp(value);
|
|
98
|
+
if (next !== $state.value) {
|
|
99
|
+
$state.value = next;
|
|
100
|
+
}
|
|
101
|
+
if (focus) {
|
|
102
|
+
stars[next - 1]?.focus();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// The number of filled stars to display, taking the hovered star into
|
|
107
|
+
// account for the mouse preview
|
|
108
|
+
function getDisplayValue(): number {
|
|
109
|
+
return getDisabled() ? getValue() : Math.max(getValue(), $state.hover);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function handleClick(value: number) {
|
|
113
|
+
if (getDisabled()) return;
|
|
114
|
+
if (value === $state.value && ($props?.allowClear ?? true)) {
|
|
115
|
+
setValue(0);
|
|
116
|
+
} else {
|
|
117
|
+
setValue(value);
|
|
118
|
+
}
|
|
119
|
+
form.handleInput();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function handleKey(e: KeyboardEvent) {
|
|
123
|
+
if (getDisabled()) return;
|
|
124
|
+
|
|
125
|
+
switch (e.key) {
|
|
126
|
+
case "ArrowRight":
|
|
127
|
+
case "ArrowUp": {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
setValue(Math.max(1, $state.value + 1), true);
|
|
130
|
+
form.handleInput();
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case "ArrowLeft":
|
|
134
|
+
case "ArrowDown": {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
setValue($state.value <= 1 ? 1 : $state.value - 1, true);
|
|
137
|
+
form.handleInput();
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case "Home": {
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
setValue(1, true);
|
|
143
|
+
form.handleInput();
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "End": {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
setValue(getMax(), true);
|
|
149
|
+
form.handleInput();
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Only blur when focus leaves the whole rating
|
|
156
|
+
function handleFocusOut(e: FocusEvent) {
|
|
157
|
+
if (!root.contains(e.relatedTarget as Node | null)) {
|
|
158
|
+
form.handleBlur();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Roving tabindex: the star matching the current rating (or the first
|
|
163
|
+
// star when there is no rating) is the single tab stop
|
|
164
|
+
function tabStop(value: number): boolean {
|
|
165
|
+
return value === ($state.value > 0 ? $state.value : 1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@render {
|
|
169
|
+
<div
|
|
170
|
+
{$props?.id}
|
|
171
|
+
class={["torp-rating", getDisabled() ? "torp-rating-disabled" : undefined, $props?.class]}
|
|
172
|
+
{$props?.style}
|
|
173
|
+
role="radiogroup"
|
|
174
|
+
aria-label={$props?.ariaLabel}
|
|
175
|
+
aria-disabled={getDisabled() ? "true" : undefined}
|
|
176
|
+
data-valid={form.valid ? "valid" : "invalid"}
|
|
177
|
+
aria-describedby={form.messageId}
|
|
178
|
+
aria-invalid={form.inField && !form.valid ? "true" : undefined}
|
|
179
|
+
onmouseleave={() => ($state.hover = 0)}
|
|
180
|
+
onfocusout={handleFocusOut}
|
|
181
|
+
&ref={root}
|
|
182
|
+
>
|
|
183
|
+
@for (let value of $state.stars) {
|
|
184
|
+
@key = value
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
class="torp-rating-star"
|
|
188
|
+
role="radio"
|
|
189
|
+
tabindex={tabStop(value) ? 0 : -1}
|
|
190
|
+
aria-checked={value === getValue() ? "true" : "false"}
|
|
191
|
+
aria-label={`${value} ${value === 1 ? "star" : "stars"}`}
|
|
192
|
+
aria-disabled={getDisabled() ? "true" : undefined}
|
|
193
|
+
data-state={value <= getDisplayValue() ? "filled" : "empty"}
|
|
194
|
+
onmount={(el) => {
|
|
195
|
+
stars[value - 1] = el;
|
|
196
|
+
return () => {
|
|
197
|
+
if (stars[value - 1] === el) {
|
|
198
|
+
stars[value - 1] = undefined;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}}
|
|
202
|
+
onclick={() => handleClick(value)}
|
|
203
|
+
onkeydown={handleKey}
|
|
204
|
+
onmouseenter={() => {
|
|
205
|
+
if (!getDisabled()) $state.hover = value;
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
<slot item={{ value: value, filled: value <= getDisplayValue() }}>
|
|
209
|
+
<span aria-hidden="true">{value <= getDisplayValue() ? "★" : "☆"}</span>
|
|
210
|
+
</slot>
|
|
211
|
+
</button>
|
|
212
|
+
}
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<FormValues name={form.name} value={getValue() > 0 ? getValue() : undefined} />
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@style {
|
|
219
|
+
/* Headless: layout only, so the stars sit in a row. All appearance is
|
|
220
|
+
up to the consuming theme */
|
|
221
|
+
.torp-rating {
|
|
222
|
+
display: inline-flex;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type SlotRender } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface RatingProps {
|
|
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 maximum rating, i.e. the number of stars (default 5) */
|
|
12
|
+
max?: number;
|
|
13
|
+
/** The current rating */
|
|
14
|
+
value?: Bindable<number>;
|
|
15
|
+
/** If set to true, the user cannot interact with this rating */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** If set to false, clicking or re-selecting the current rating does not clear it (default true) */
|
|
18
|
+
allowClear?: boolean;
|
|
19
|
+
/** An accessible name for the rating */
|
|
20
|
+
ariaLabel?: string;
|
|
21
|
+
/** The name of the component's form data field */
|
|
22
|
+
name?: string;
|
|
23
|
+
/** Raised when the rating has changed */
|
|
24
|
+
onchange?: (value: number) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A star rating input, where the user selects a value from 1 to a maximum
|
|
29
|
+
* number of stars by clicking or by using the keyboard.
|
|
30
|
+
*
|
|
31
|
+
* Implements the radio group pattern: each star is a radio button, the star
|
|
32
|
+
* matching the current rating is checked, and arrow keys move the rating.
|
|
33
|
+
*
|
|
34
|
+
* See [the WAI ARIA guidelines for Rating](https://www.w3.org/WAI/ARIA/apg/patterns/rating/).
|
|
35
|
+
*/
|
|
36
|
+
declare function Rating(
|
|
37
|
+
$parent: ParentNode,
|
|
38
|
+
$anchor: Node | null,
|
|
39
|
+
$props: RatingProps | undefined,
|
|
40
|
+
$context?: Record<PropertyKey, any>,
|
|
41
|
+
$slots?: Record<string, SlotRender>,
|
|
42
|
+
): void;
|
|
43
|
+
|
|
44
|
+
export { Rating };
|
|
@@ -0,0 +1,233 @@
|
|
|
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 { createFocusGroup } from "../utils/focusGroup";
|
|
11
|
+
import { createItemGroup } from "../utils/createItemGroup";
|
|
12
|
+
import createFormField from "../utils/formField";
|
|
13
|
+
import type { FocusTarget } from "../utils/focusItem";
|
|
14
|
+
import {
|
|
15
|
+
type ItemState,
|
|
16
|
+
type SegmentedControlContext,
|
|
17
|
+
SegmentedControlContextName,
|
|
18
|
+
} from "./SegmentedControlTypes";
|
|
19
|
+
|
|
20
|
+
interface SegmentedControlProps {
|
|
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
|
+
/** The value of the active segment */
|
|
28
|
+
value?: Bindable<string | undefined>;
|
|
29
|
+
/** If set to true, the user cannot interact with any segments */
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
/** An accessible name for the control */
|
|
32
|
+
ariaLabel?: string;
|
|
33
|
+
/** The name of the component's form data field */
|
|
34
|
+
name?: string;
|
|
35
|
+
/** Raised when the active segment has changed */
|
|
36
|
+
onchange?: (value: string) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A compact group of segments, of which exactly one is active. The user moves
|
|
41
|
+
* between segments with the arrow keys, or selects one directly.
|
|
42
|
+
*
|
|
43
|
+
* See [the WAI ARIA guidelines for Radio Groups](https://www.w3.org/WAI/ARIA/apg/patterns/radiogroup/).
|
|
44
|
+
*
|
|
45
|
+
* The structure of a SegmentedControl is:
|
|
46
|
+
*
|
|
47
|
+
* ```
|
|
48
|
+
* <SegmentedControl ariaLabel="View">
|
|
49
|
+
* <SegmentedItem value="day">Day</SegmentedItem>
|
|
50
|
+
* <SegmentedItem value="week">Week</SegmentedItem>
|
|
51
|
+
* </SegmentedControl>
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export default function SegmentedControl($props: SegmentedControlProps | undefined) {
|
|
55
|
+
let nextItemIndex = 0;
|
|
56
|
+
let root: HTMLElement;
|
|
57
|
+
|
|
58
|
+
// Wire up the enclosing Form and Field, if any
|
|
59
|
+
const form = createFormField(
|
|
60
|
+
$props?.name,
|
|
61
|
+
$context[FormContextName] as FormContext | undefined,
|
|
62
|
+
$context[FieldContextName] as FieldContext | undefined,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
let $state = $watch({
|
|
66
|
+
value: $props?.value ?? "",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
$bind($state, $props, "value");
|
|
70
|
+
|
|
71
|
+
const group = createItemGroup<ItemState>({
|
|
72
|
+
state: $state,
|
|
73
|
+
type: "single",
|
|
74
|
+
selectedProperty: "active",
|
|
75
|
+
allowDeselect: false,
|
|
76
|
+
disabled: () => $props?.disabled,
|
|
77
|
+
// Select the first enabled item as items register, when nothing is
|
|
78
|
+
// selected yet; this is a default, not a user change, so it doesn't
|
|
79
|
+
// raise onchange
|
|
80
|
+
onRegister: (item) => {
|
|
81
|
+
if ($state.value === "" && !item.disabled) {
|
|
82
|
+
skipNextChange = true;
|
|
83
|
+
$state.value = item.value;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// The focus group owns the roving-tabindex bookkeeping; the selection
|
|
89
|
+
// group above stays the source of truth for the active segment
|
|
90
|
+
const focusGroup = createFocusGroup<ItemState>();
|
|
91
|
+
|
|
92
|
+
// Set the context to pass down to items
|
|
93
|
+
const context: SegmentedControlContext = {
|
|
94
|
+
registerItem,
|
|
95
|
+
removeItem: (itemIndex) => {
|
|
96
|
+
group.removeItem(itemIndex);
|
|
97
|
+
focusGroup.removeItem(itemIndex);
|
|
98
|
+
},
|
|
99
|
+
triggerItem: (value) => {
|
|
100
|
+
group.toggleItem(value);
|
|
101
|
+
form.handleInput();
|
|
102
|
+
},
|
|
103
|
+
handleItemKey,
|
|
104
|
+
};
|
|
105
|
+
$context[SegmentedControlContextName] = context;
|
|
106
|
+
|
|
107
|
+
// Dispatch the change event when the value changes
|
|
108
|
+
let skipNextChange = false;
|
|
109
|
+
$handle((first) => {
|
|
110
|
+
const value = $state.value;
|
|
111
|
+
if (skipNextChange) {
|
|
112
|
+
skipNextChange = false;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!first && value !== "") $props?.onchange?.(value);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
function registerItem(
|
|
119
|
+
index: number | undefined,
|
|
120
|
+
value: string | undefined,
|
|
121
|
+
disabled: boolean | undefined,
|
|
122
|
+
): ItemState {
|
|
123
|
+
// If an index wasn't set for the item, set it automatically
|
|
124
|
+
if (index === undefined) {
|
|
125
|
+
index = nextItemIndex++;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// If we already have an item with this index, just return it
|
|
129
|
+
const existing = group.itemStates.find((item) => item.index === index);
|
|
130
|
+
if (existing) {
|
|
131
|
+
return existing;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// If a value wasn't set for the item, set it to the index
|
|
135
|
+
if (value === undefined) {
|
|
136
|
+
value = index.toString();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const itemState: ItemState = $watch({
|
|
140
|
+
index,
|
|
141
|
+
value,
|
|
142
|
+
active: false,
|
|
143
|
+
disabled: disabled ?? false,
|
|
144
|
+
parentDisabled: $props?.disabled ?? false,
|
|
145
|
+
setFocused: () => {},
|
|
146
|
+
});
|
|
147
|
+
group.registerItem(itemState);
|
|
148
|
+
focusGroup.registerItem(itemState);
|
|
149
|
+
|
|
150
|
+
return itemState;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function handleItemKey(index: number, e: KeyboardEvent) {
|
|
154
|
+
switch (e.key) {
|
|
155
|
+
case "ArrowRight":
|
|
156
|
+
case "ArrowDown": {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
move(index, "next");
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case "ArrowLeft":
|
|
162
|
+
case "ArrowUp": {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
move(index, "previous");
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "Home": {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
move(index, "start");
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "End": {
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
move(index, "end");
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Moving with the keyboard both selects and focuses the target segment,
|
|
181
|
+
// like a radio group
|
|
182
|
+
function move(index: number, target: FocusTarget) {
|
|
183
|
+
const before = focusGroup.getFocusIndex();
|
|
184
|
+
focusGroup.moveFocus(index, target);
|
|
185
|
+
const next = focusGroup.getFocusIndex();
|
|
186
|
+
if (next !== before) {
|
|
187
|
+
const item = focusGroup.itemStates[next];
|
|
188
|
+
if (item) {
|
|
189
|
+
group.toggleItem(item.value);
|
|
190
|
+
form.handleInput();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Only blur when focus leaves the whole control
|
|
196
|
+
function handleFocusOut(e: FocusEvent) {
|
|
197
|
+
if (!root.contains(e.relatedTarget as Node | null)) {
|
|
198
|
+
form.handleBlur();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@render {
|
|
203
|
+
<div
|
|
204
|
+
{$props?.id}
|
|
205
|
+
class={[
|
|
206
|
+
"torp-segmented-control",
|
|
207
|
+
$props?.disabled ? "torp-segmented-control-disabled" : undefined,
|
|
208
|
+
$props?.class,
|
|
209
|
+
]}
|
|
210
|
+
{$props?.style}
|
|
211
|
+
role="radiogroup"
|
|
212
|
+
aria-label={$props?.ariaLabel}
|
|
213
|
+
aria-disabled={$props?.disabled ? "true" : undefined}
|
|
214
|
+
data-valid={form.valid ? "valid" : "invalid"}
|
|
215
|
+
aria-describedby={form.messageId}
|
|
216
|
+
aria-invalid={form.inField && !form.valid ? "true" : undefined}
|
|
217
|
+
onfocusout={handleFocusOut}
|
|
218
|
+
&ref={root}
|
|
219
|
+
>
|
|
220
|
+
<slot />
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<FormValues name={form.name} value={$state.value} />
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@style {
|
|
227
|
+
/* Headless: layout only, so the segments sit in a row. All appearance
|
|
228
|
+
is up to the consuming theme */
|
|
229
|
+
.torp-segmented-control {
|
|
230
|
+
display: inline-flex;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const SegmentedControlContextName: unique symbol = Symbol.for("torp.SegmentedControl");
|
|
2
|
+
|
|
3
|
+
export interface SegmentedControlContext {
|
|
4
|
+
/** Called from an item when it is added */
|
|
5
|
+
registerItem: (
|
|
6
|
+
index: number | undefined,
|
|
7
|
+
value: string | undefined,
|
|
8
|
+
disabled: boolean | undefined,
|
|
9
|
+
) => ItemState;
|
|
10
|
+
/** Called from an item when it is removed */
|
|
11
|
+
removeItem: (index: number) => void;
|
|
12
|
+
/** Called from an item when it is selected */
|
|
13
|
+
triggerItem: (value: string) => void;
|
|
14
|
+
/** Called from an item when it receives a keyboard event */
|
|
15
|
+
handleItemKey: (index: number, e: KeyboardEvent) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ItemState {
|
|
19
|
+
index: number;
|
|
20
|
+
value: string;
|
|
21
|
+
active: boolean;
|
|
22
|
+
disabled: boolean;
|
|
23
|
+
parentDisabled: boolean;
|
|
24
|
+
/** Called in the SegmentedControl when an item needs to be focused */
|
|
25
|
+
setFocused: () => void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import { type SegmentedControlContext, SegmentedControlContextName } from "./SegmentedControlTypes";
|
|
3
|
+
|
|
4
|
+
interface SegmentedItemProps {
|
|
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 index of this item */
|
|
12
|
+
index?: number;
|
|
13
|
+
/** The value for this item */
|
|
14
|
+
value?: string;
|
|
15
|
+
/** If set to true, the user cannot interact with this item */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A segment in a SegmentedControl, one of which is always active.
|
|
21
|
+
*/
|
|
22
|
+
export default function SegmentedItem($props: SegmentedItemProps | undefined) {
|
|
23
|
+
let button: HTMLButtonElement;
|
|
24
|
+
|
|
25
|
+
// Get stuff out of the SegmentedControl context
|
|
26
|
+
const context = $context[SegmentedControlContextName] as SegmentedControlContext;
|
|
27
|
+
if (!context) {
|
|
28
|
+
throw new Error("SegmentedItem must be contained within a SegmentedControl");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Register this item with the parent SegmentedControl
|
|
32
|
+
let $state = context.registerItem($props?.index, $props?.value, $props?.disabled);
|
|
33
|
+
|
|
34
|
+
$run(() => {
|
|
35
|
+
return () => {
|
|
36
|
+
context.removeItem($state.index);
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// This function is called by the parent SegmentedControl to focus this item
|
|
41
|
+
function setFocused() {
|
|
42
|
+
button.focus();
|
|
43
|
+
}
|
|
44
|
+
$state.setFocused = setFocused;
|
|
45
|
+
|
|
46
|
+
function getDisabled(): boolean {
|
|
47
|
+
return $state.disabled || $state.parentDisabled;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function handleClick() {
|
|
51
|
+
if (getDisabled()) return;
|
|
52
|
+
context.triggerItem($state.value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleKey(e: KeyboardEvent) {
|
|
56
|
+
if (getDisabled()) return;
|
|
57
|
+
context.handleItemKey($state.index, e);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@render {
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
{$props?.id}
|
|
64
|
+
class={["torp-segmented-item", $props?.class]}
|
|
65
|
+
{$props?.style}
|
|
66
|
+
role="radio"
|
|
67
|
+
tabindex={$state.active ? 0 : -1}
|
|
68
|
+
aria-checked={$state.active ? "true" : "false"}
|
|
69
|
+
aria-disabled={getDisabled() ? "true" : undefined}
|
|
70
|
+
data-state={$state.active ? "active" : "inactive"}
|
|
71
|
+
onclick={handleClick}
|
|
72
|
+
onkeydown={handleKey}
|
|
73
|
+
&ref={button}
|
|
74
|
+
>
|
|
75
|
+
<slot />
|
|
76
|
+
</button>
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type SlotRender } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface SegmentedControlProps {
|
|
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 value of the active segment */
|
|
12
|
+
value?: Bindable<string | undefined>;
|
|
13
|
+
/** If set to true, the user cannot interact with any segments */
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
/** An accessible name for the control */
|
|
16
|
+
ariaLabel?: string;
|
|
17
|
+
/** The name of the component's form data field */
|
|
18
|
+
name?: string;
|
|
19
|
+
/** Raised when the active segment has changed */
|
|
20
|
+
onchange?: (value: string) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A compact group of segments, of which exactly one is active. The user moves
|
|
25
|
+
* between segments with the arrow keys, or selects one directly.
|
|
26
|
+
*
|
|
27
|
+
* See [the WAI ARIA guidelines for Radio Groups](https://www.w3.org/WAI/ARIA/apg/patterns/radiogroup/).
|
|
28
|
+
*
|
|
29
|
+
* The structure of a SegmentedControl is:
|
|
30
|
+
*
|
|
31
|
+
* ```
|
|
32
|
+
* <SegmentedControl ariaLabel="View">
|
|
33
|
+
* <SegmentedItem value="day">Day</SegmentedItem>
|
|
34
|
+
* <SegmentedItem value="week">Week</SegmentedItem>
|
|
35
|
+
* </SegmentedControl>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function SegmentedControl(
|
|
39
|
+
$parent: ParentNode,
|
|
40
|
+
$anchor: Node | null,
|
|
41
|
+
$props: SegmentedControlProps | undefined,
|
|
42
|
+
$context?: Record<PropertyKey, any>,
|
|
43
|
+
$slots?: Record<string, SlotRender>,
|
|
44
|
+
): void;
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
interface SegmentedItemProps {
|
|
49
|
+
/** An ID for the root element */
|
|
50
|
+
id?: string;
|
|
51
|
+
/** Classes for the root element */
|
|
52
|
+
class?: ClassValue;
|
|
53
|
+
/** Styles for the root element */
|
|
54
|
+
style?: StyleValue;
|
|
55
|
+
/** The index of this item */
|
|
56
|
+
index?: number;
|
|
57
|
+
/** The value for this item */
|
|
58
|
+
value?: string;
|
|
59
|
+
/** If set to true, the user cannot interact with this item */
|
|
60
|
+
disabled?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A segment in a SegmentedControl, one of which is always active.
|
|
65
|
+
*/
|
|
66
|
+
declare function SegmentedItem(
|
|
67
|
+
$parent: ParentNode,
|
|
68
|
+
$anchor: Node | null,
|
|
69
|
+
$props: SegmentedItemProps | undefined,
|
|
70
|
+
$context?: Record<PropertyKey, any>,
|
|
71
|
+
$slots?: Record<string, SlotRender>,
|
|
72
|
+
): void;
|
|
73
|
+
|
|
74
|
+
export { SegmentedControl, SegmentedItem };
|