@torpor/ui 0.4.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -7
- package/dist/Accordion/Accordion.torp +42 -165
- package/dist/Accordion/AccordionContent.torp +6 -6
- package/dist/Accordion/AccordionHeader.torp +7 -11
- package/dist/Accordion/AccordionItem.torp +24 -24
- package/dist/Accordion/AccordionTrigger.torp +17 -8
- package/dist/Accordion/AccordionTypes.ts +1 -1
- package/dist/Accordion/index.d.ts +23 -25
- package/dist/Accordion/index.js +6 -22
- package/dist/Breadcrumb/Breadcrumb.torp +12 -10
- package/dist/Breadcrumb/BreadcrumbItem.torp +6 -6
- package/dist/Breadcrumb/BreadcrumbList.torp +21 -0
- package/dist/Breadcrumb/BreadcrumbSeparator.torp +4 -4
- package/dist/Breadcrumb/index.d.ts +39 -15
- package/dist/Breadcrumb/index.js +5 -14
- package/dist/Calendar/Calendar.torp +163 -156
- package/dist/Calendar/CalendarDay.torp +91 -45
- package/dist/Calendar/CalendarDayHeader.torp +36 -0
- package/dist/Calendar/CalendarGrid.torp +104 -0
- package/dist/Calendar/CalendarGridHeader.torp +43 -0
- package/dist/Calendar/CalendarHeader.torp +48 -0
- package/dist/Calendar/CalendarHeaderTrigger.torp +54 -0
- package/dist/Calendar/CalendarTypes.ts +24 -3
- package/dist/Calendar/buildDays.ts +54 -0
- package/dist/Calendar/index.d.ts +191 -5
- package/dist/Calendar/index.js +8 -10
- package/dist/Carousel/Carousel.torp +180 -0
- package/dist/Carousel/CarouselIndicators.torp +65 -0
- package/dist/Carousel/CarouselNext.torp +43 -0
- package/dist/Carousel/CarouselPrevious.torp +44 -0
- package/dist/Carousel/CarouselSlide.torp +57 -0
- package/dist/Carousel/CarouselTypes.ts +28 -0
- package/dist/Carousel/index.d.ts +147 -0
- package/dist/Carousel/index.js +6 -0
- package/dist/Charts/BarChart.torp +55 -144
- package/dist/Charts/Chart.torp +120 -0
- package/dist/Charts/ChartBars.torp +78 -0
- package/dist/Charts/ChartColumns.torp +66 -0
- package/dist/Charts/ChartGrid.torp +54 -0
- package/dist/Charts/ChartLines.torp +60 -0
- package/dist/Charts/ChartTypes.ts +25 -0
- package/dist/Charts/ChartXAxis.torp +85 -0
- package/dist/Charts/ChartYAxis.torp +107 -0
- package/dist/Charts/ColumnChart.torp +70 -0
- package/dist/Charts/GridLines.torp +4 -4
- package/dist/Charts/LineChart.torp +55 -178
- package/dist/Charts/PieChart.torp +14 -20
- package/dist/Charts/ScatterChart.torp +45 -38
- package/dist/Charts/SparkLine.torp +16 -19
- package/dist/Charts/XAxis.torp +14 -11
- package/dist/Charts/YAxis.torp +10 -8
- package/dist/Charts/index.d.ts +328 -79
- package/dist/Charts/index.js +17 -34
- package/dist/ColorPicker/ColorPalette.torp +154 -0
- package/dist/ColorPicker/ColorPicker.torp +157 -0
- package/dist/ColorPicker/index.d.ts +85 -0
- package/dist/ColorPicker/index.js +3 -0
- package/dist/ComboBox/ComboBox.torp +205 -0
- package/dist/ComboBox/ComboBoxContent.torp +120 -0
- package/dist/ComboBox/ComboBoxInput.torp +161 -0
- package/dist/ComboBox/ComboBoxTrigger.torp +49 -0
- package/dist/ComboBox/ComboBoxTypes.ts +39 -0
- package/dist/ComboBox/index.d.ts +154 -0
- package/dist/ComboBox/index.js +6 -0
- package/dist/CommandPalette/CommandPalette.torp +223 -0
- package/dist/CommandPalette/CommandTypes.ts +14 -0
- package/dist/CommandPalette/index.d.ts +45 -0
- package/dist/CommandPalette/index.js +2 -0
- package/dist/Contextual/Contextual.torp +32 -50
- package/dist/Contextual/ContextualContent.torp +29 -73
- package/dist/Contextual/ContextualHover.torp +112 -0
- package/dist/Contextual/ContextualTrigger.torp +31 -42
- package/dist/Contextual/ContextualTypes.ts +4 -17
- package/dist/Contextual/index.d.ts +44 -27
- package/dist/Contextual/index.js +5 -14
- package/dist/DataGrid/DataGrid.torp +496 -0
- package/dist/DataGrid/DataGridTypes.ts +25 -0
- package/dist/DataGrid/index.d.ts +90 -0
- package/dist/DataGrid/index.js +3 -0
- package/dist/DataGrid/sortRows.ts +32 -0
- package/dist/DatePicker/DatePicker.torp +222 -0
- package/dist/DatePicker/DatePickerHeader.torp +20 -0
- package/dist/DatePicker/index.d.ts +55 -0
- package/dist/DatePicker/index.js +2 -0
- package/dist/DateRangePicker/DateRangePicker.torp +315 -0
- package/dist/DateRangePicker/DateRangePickerDay.torp +137 -0
- package/dist/DateRangePicker/DateRangePickerTypes.ts +21 -0
- package/dist/DateRangePicker/index.d.ts +76 -0
- package/dist/DateRangePicker/index.js +3 -0
- package/dist/Dialog/Dialog.torp +24 -17
- package/dist/Dialog/DialogBody.torp +5 -9
- package/dist/Dialog/DialogButton.torp +12 -12
- package/dist/Dialog/DialogFooter.torp +3 -7
- package/dist/Dialog/DialogHeader.torp +6 -10
- package/dist/Dialog/index.d.ts +27 -27
- package/dist/Dialog/index.js +6 -22
- package/dist/Disclosure/Disclosure.torp +18 -18
- package/dist/Disclosure/DisclosureContent.torp +5 -6
- package/dist/Disclosure/DisclosureHeader.torp +7 -11
- package/dist/Disclosure/DisclosureTrigger.torp +4 -5
- package/dist/Disclosure/index.d.ts +14 -16
- package/dist/Disclosure/index.js +5 -18
- package/dist/Drawer/Drawer.torp +19 -49
- package/dist/Drawer/DrawerContent.torp +70 -13
- package/dist/Drawer/DrawerOverlay.torp +4 -4
- package/dist/Drawer/DrawerTrigger.torp +16 -4
- package/dist/Drawer/DrawerTypes.ts +3 -0
- package/dist/Drawer/index.d.ts +16 -20
- package/dist/Drawer/index.js +5 -18
- package/dist/FocusGroup/FocusGroup.torp +70 -0
- package/dist/FocusGroup/FocusGroupButton.torp +59 -0
- package/dist/FocusGroup/FocusGroupTypes.ts +14 -0
- package/dist/FocusGroup/index.d.ts +44 -0
- package/dist/FocusGroup/index.js +3 -0
- package/dist/Form/CheckBox.torp +27 -18
- package/dist/Form/Field.torp +17 -17
- package/dist/Form/File.torp +31 -22
- package/dist/Form/FileDrop.torp +187 -0
- package/dist/Form/Form.torp +24 -22
- package/dist/Form/FormTypes.ts +2 -0
- package/dist/Form/FormValues.torp +37 -0
- package/dist/Form/Hidden.torp +4 -12
- package/dist/Form/Input.torp +38 -32
- package/dist/Form/Label.torp +3 -3
- package/dist/Form/Message.torp +13 -5
- package/dist/Form/MessageSummary.torp +5 -4
- package/dist/Form/Option.torp +11 -7
- package/dist/Form/Select.torp +32 -28
- package/dist/Form/TextArea.torp +35 -31
- package/dist/Form/Toggle.torp +109 -0
- package/dist/Form/index.d.ts +178 -79
- package/dist/Form/index.js +16 -50
- package/dist/ListBox/ListBox.torp +253 -150
- package/dist/ListBox/ListBoxItem.torp +54 -30
- package/dist/ListBox/ListBoxSeparator.torp +5 -2
- package/dist/ListBox/ListBoxTypes.ts +23 -5
- package/dist/ListBox/LoadedListBoxItems.torp +41 -0
- package/dist/ListBox/index.d.ts +56 -17
- package/dist/ListBox/index.js +4 -14
- package/dist/MaskedInput/MaskedInput.torp +143 -0
- package/dist/MaskedInput/index.d.ts +52 -0
- package/dist/MaskedInput/index.js +2 -0
- package/dist/Menu/Menu.torp +86 -71
- package/dist/Menu/MenuButton.torp +17 -10
- package/dist/Menu/MenuCheck.torp +22 -18
- package/dist/Menu/MenuGroup.torp +4 -4
- package/dist/Menu/MenuIndicator.torp +5 -13
- package/dist/Menu/MenuPopout.torp +20 -50
- package/dist/Menu/MenuPopoutContent.torp +15 -66
- package/dist/Menu/MenuPopoutTrigger.torp +19 -100
- package/dist/Menu/MenuRadio.torp +25 -26
- package/dist/Menu/MenuRadioGroup.torp +25 -68
- package/dist/Menu/MenuSeparator.torp +3 -2
- package/dist/Menu/MenuTypes.ts +9 -19
- package/dist/Menu/index.d.ts +45 -51
- package/dist/Menu/index.js +12 -46
- package/dist/MenuBar/MenuBar.torp +31 -83
- package/dist/MenuBar/MenuBarItem.torp +33 -51
- package/dist/MenuBar/MenuBarItemContent.torp +15 -101
- package/dist/MenuBar/MenuBarItemTrigger.torp +20 -55
- package/dist/MenuBar/MenuBarTypes.ts +11 -10
- package/dist/MenuBar/index.d.ts +17 -19
- package/dist/MenuBar/index.js +5 -18
- package/dist/Modal/Modal.torp +22 -47
- package/dist/Modal/ModalContent.torp +21 -44
- package/dist/Modal/ModalOverlay.torp +4 -4
- package/dist/Modal/ModalTrigger.torp +16 -32
- package/dist/Modal/ModalTypes.ts +5 -16
- package/dist/Modal/index.d.ts +17 -29
- package/dist/Modal/index.js +29 -28
- package/dist/Modal/index.js.map +1 -1
- package/dist/Modal/showPrompt.ts +2 -2
- package/dist/NavMenu/NavMenu.torp +22 -111
- package/dist/NavMenu/NavMenuButton.torp +7 -8
- package/dist/NavMenu/NavMenuElement.torp +6 -7
- package/dist/NavMenu/NavMenuGroup.torp +5 -5
- package/dist/NavMenu/NavMenuGroupHeader.torp +5 -8
- package/dist/NavMenu/NavMenuLink.torp +7 -10
- package/dist/NavMenu/NavMenuPopout.torp +15 -60
- package/dist/NavMenu/NavMenuPopoutContent.torp +14 -73
- package/dist/NavMenu/NavMenuPopoutTrigger.torp +17 -99
- package/dist/NavMenu/NavMenuSeparator.torp +3 -7
- package/dist/NavMenu/NavMenuTypes.ts +4 -14
- package/dist/NavMenu/index.d.ts +40 -44
- package/dist/NavMenu/index.js +11 -42
- package/dist/Notification/Notification.torp +12 -24
- package/dist/Notification/NotificationClose.torp +6 -3
- package/dist/Notification/NotificationContainer.torp +3 -7
- package/dist/Notification/NotificationContent.torp +3 -7
- package/dist/Notification/NotificationTypes.ts +1 -1
- package/dist/Notification/index.d.ts +15 -12
- package/dist/Notification/index.js +22 -32
- package/dist/Notification/index.js.map +1 -1
- package/dist/NumberInput/NumberInput.torp +255 -0
- package/dist/NumberInput/index.d.ts +44 -0
- package/dist/NumberInput/index.js +2 -0
- package/dist/Pagination/Pagination.torp +41 -34
- package/dist/Pagination/PaginationItem.torp +50 -13
- package/dist/Pagination/PaginationTypes.ts +3 -6
- package/dist/Pagination/buildPages.ts +9 -5
- package/dist/Pagination/index.d.ts +19 -9
- package/dist/Pagination/index.js +3 -40
- package/dist/Popover/Popover.torp +32 -50
- package/dist/Popover/PopoverContent.torp +27 -76
- package/dist/Popover/PopoverHover.torp +90 -0
- package/dist/Popover/PopoverTrigger.torp +17 -40
- package/dist/Popover/PopoverTypes.ts +4 -17
- package/dist/Popover/index.d.ts +40 -25
- package/dist/Popover/index.js +18 -18
- package/dist/Popover/index.js.map +1 -1
- package/dist/Progress/Progress.torp +88 -0
- package/dist/Progress/index.d.ts +37 -0
- package/dist/Progress/index.js +2 -0
- package/dist/Rating/Rating.torp +225 -0
- package/dist/Rating/index.d.ts +44 -0
- package/dist/Rating/index.js +2 -0
- package/dist/SegmentedControl/SegmentedControl.torp +233 -0
- package/dist/SegmentedControl/SegmentedControlTypes.ts +26 -0
- package/dist/SegmentedControl/SegmentedItem.torp +78 -0
- package/dist/SegmentedControl/index.d.ts +74 -0
- package/dist/SegmentedControl/index.js +3 -0
- package/dist/SelectBox/SelectBox.torp +107 -53
- package/dist/SelectBox/SelectBoxContent.torp +49 -19
- package/dist/SelectBox/SelectBoxTrigger.torp +108 -8
- package/dist/SelectBox/SelectBoxTypes.ts +26 -11
- package/dist/SelectBox/index.d.ts +39 -54
- package/dist/SelectBox/index.js +5 -18
- package/dist/Slider/Slider.torp +245 -0
- package/dist/Slider/index.d.ts +45 -0
- package/dist/Slider/index.js +2 -0
- package/dist/Splitter/Splitter.torp +156 -0
- package/dist/Splitter/SplitterHandle.torp +161 -0
- package/dist/Splitter/SplitterPane.torp +63 -0
- package/dist/Splitter/SplitterTypes.ts +31 -0
- package/dist/Splitter/index.d.ts +103 -0
- package/dist/Splitter/index.js +4 -0
- package/dist/Stepper/Stepper.torp +146 -0
- package/dist/Stepper/StepperMarker.torp +24 -0
- package/dist/Stepper/StepperNext.torp +44 -0
- package/dist/Stepper/StepperPrevious.torp +44 -0
- package/dist/Stepper/StepperStep.torp +90 -0
- package/dist/Stepper/StepperTypes.ts +30 -0
- package/dist/Stepper/index.d.ts +149 -0
- package/dist/Stepper/index.js +6 -0
- package/dist/TabGroup/TabContent.torp +6 -6
- package/dist/TabGroup/TabGroup.torp +97 -113
- package/dist/TabGroup/TabGroupTypes.ts +1 -1
- package/dist/TabGroup/TabHeader.torp +23 -19
- package/dist/TabGroup/TabList.torp +8 -21
- package/dist/TabGroup/TabTrigger.torp +6 -7
- package/dist/TabGroup/index.d.ts +26 -26
- package/dist/TabGroup/index.js +6 -22
- package/dist/TagInput/TagInput.torp +378 -0
- package/dist/TagInput/index.d.ts +48 -0
- package/dist/TagInput/index.js +2 -0
- package/dist/TimePicker/TimePicker.torp +462 -0
- package/dist/TimePicker/index.d.ts +53 -0
- package/dist/TimePicker/index.js +2 -0
- package/dist/ToolBar/ToolBar.torp +29 -110
- package/dist/ToolBar/ToolBarButton.torp +7 -8
- package/dist/ToolBar/ToolBarElement.torp +7 -11
- package/dist/ToolBar/ToolBarPopout.torp +16 -68
- package/dist/ToolBar/ToolBarPopoutContent.torp +15 -73
- package/dist/ToolBar/ToolBarPopoutTrigger.torp +19 -110
- package/dist/ToolBar/ToolBarSeparator.torp +3 -7
- package/dist/ToolBar/ToolBarTypes.ts +7 -14
- package/dist/ToolBar/index.d.ts +30 -32
- package/dist/ToolBar/index.js +8 -30
- package/dist/Tree/Tree.torp +395 -0
- package/dist/Tree/TreeItem.torp +176 -0
- package/dist/Tree/TreeItemChildren.torp +60 -0
- package/dist/Tree/TreeItemContent.torp +46 -0
- package/dist/Tree/TreeItemLabel.torp +94 -0
- package/dist/Tree/TreeItemTrigger.torp +78 -0
- package/dist/Tree/TreeLoadedChildren.torp +122 -0
- package/dist/Tree/TreeTypes.ts +63 -0
- package/dist/Tree/index.d.ts +262 -0
- package/dist/Tree/index.js +7 -0
- package/dist/icons/ChevronDoubleLeft.torp +19 -0
- package/dist/icons/ChevronDoubleRight.torp +19 -0
- package/dist/icons/index.d.ts +61 -1
- package/dist/icons/index.js +17 -58
- package/dist/loader-DEeG5vFY.js +44 -0
- package/dist/loader-DEeG5vFY.js.map +1 -0
- package/dist/motion/index.d.ts +12 -8
- package/dist/motion/index.d.ts.map +1 -0
- package/dist/motion/index.js +70 -88
- package/dist/motion/index.js.map +1 -1
- package/dist/mount/draggable.ts +63 -0
- package/dist/mount/dropTarget.ts +108 -0
- package/dist/mount/hoverTrigger.ts +1 -1
- package/dist/utils/$handle.ts +23 -0
- package/dist/utils/LoadedItems.torp +47 -0
- package/dist/utils/PopoutClickTrigger.torp +99 -0
- package/dist/utils/PopoutTypes.ts +59 -12
- package/dist/utils/SubmenuPopout.torp +89 -0
- package/dist/utils/SubmenuPopoutContent.torp +87 -0
- package/dist/utils/SubmenuPopoutTrigger.torp +160 -0
- package/dist/utils/chartColors.ts +13 -13
- package/dist/utils/chartLayout.ts +143 -0
- package/dist/utils/colorUtils.ts +103 -0
- package/dist/utils/createItemGroup.ts +152 -0
- package/dist/utils/dateUtils.ts +8 -7
- package/dist/utils/documentEvents.ts +4 -4
- package/dist/utils/dragState.ts +86 -0
- package/dist/utils/focusGroup.ts +204 -0
- package/dist/utils/focusItem.ts +44 -0
- package/dist/utils/formField.ts +70 -0
- package/dist/utils/hoverOpen.ts +50 -0
- package/dist/utils/hoverReveal.ts +194 -0
- package/dist/utils/loader.ts +113 -0
- package/dist/utils/maskUtils.ts +98 -0
- package/dist/utils/popoutContent.ts +137 -0
- package/dist/utils/reorderList.ts +52 -0
- package/package.json +97 -22
- package/dist/Accordion/index.js.map +0 -1
- package/dist/Breadcrumb/index.js.map +0 -1
- package/dist/Calendar/index.js.map +0 -1
- package/dist/Charts/index.js.map +0 -1
- package/dist/Contextual/index.js.map +0 -1
- package/dist/Dialog/index.js.map +0 -1
- package/dist/Disclosure/index.js.map +0 -1
- package/dist/Drawer/index.js.map +0 -1
- package/dist/Form/index.js.map +0 -1
- package/dist/ListBox/index.js.map +0 -1
- package/dist/Menu/index.js.map +0 -1
- package/dist/MenuBar/index.js.map +0 -1
- package/dist/NavMenu/index.js.map +0 -1
- package/dist/Pagination/index.js.map +0 -1
- package/dist/SelectBox/SelectBoxInput.torp +0 -95
- package/dist/SelectBox/index.js.map +0 -1
- package/dist/TabGroup/index.js.map +0 -1
- package/dist/ToolBar/index.js.map +0 -1
- package/dist/icons/index.js.map +0 -1
- package/dist/index.d.ts +0 -19
- package/dist/metafile-esm.json +0 -1
- package/dist/motion/fade.ts +0 -16
- package/dist/motion/grow.ts +0 -23
- package/dist/motion/measure.ts +0 -28
- package/dist/motion/slide.ts +0 -45
- package/dist/mount/index.d.ts +0 -2
- package/dist/utils/index.d.ts +0 -2
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import type { Bindable, ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import { type DataColumn } from "./DataGridTypes";
|
|
3
|
+
import type {
|
|
4
|
+
LoadRequest,
|
|
5
|
+
LoadResult,
|
|
6
|
+
Loader,
|
|
7
|
+
} from "../utils/loader";
|
|
8
|
+
import { normalizeLoadResult } from "../utils/loader";
|
|
9
|
+
import sortRows from "./sortRows";
|
|
10
|
+
import getId from "../utils/getId";
|
|
11
|
+
|
|
12
|
+
interface DataGridProps {
|
|
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
|
+
/** A caption for the table, rendered in a `<caption>` element */
|
|
20
|
+
caption?: string;
|
|
21
|
+
/** The columns to display */
|
|
22
|
+
columns: DataColumn[];
|
|
23
|
+
/** The rows to display. Pass either `data` (static rows) or `load`. */
|
|
24
|
+
data?: any[];
|
|
25
|
+
/**
|
|
26
|
+
* Reads rows from the network (or anywhere async). Receives a request with
|
|
27
|
+
* `page`/`pageSize`, `searchText` and `sortBy`/`sortDirection`, plus an
|
|
28
|
+
* AbortSignal. Pass either `load` or `data`, not both.
|
|
29
|
+
*/
|
|
30
|
+
load?: Loader;
|
|
31
|
+
/** The active page (1-based). Two-way bindable. */
|
|
32
|
+
page?: Bindable<number>;
|
|
33
|
+
/** The number of rows per page. Static data is sliced locally; loaders receive it in the request. */
|
|
34
|
+
pageSize?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Renders only the rows visible in the viewport, for large datasets.
|
|
37
|
+
* Requires `rowHeight`; supersedes local page slicing while enabled
|
|
38
|
+
*/
|
|
39
|
+
virtual?: boolean;
|
|
40
|
+
/** The fixed pixel height of a row, required by virtualization */
|
|
41
|
+
rowHeight?: number;
|
|
42
|
+
/** The scroll viewport height in pixels when virtual (default 400) */
|
|
43
|
+
height?: number;
|
|
44
|
+
/** Extra rows rendered above and below the visible window (default 5) */
|
|
45
|
+
overscan?: number;
|
|
46
|
+
/** The field the data is sorted by. Two-way bindable. */
|
|
47
|
+
sortBy?: Bindable<string | undefined>;
|
|
48
|
+
/** The direction the data is sorted in. Two-way bindable. */
|
|
49
|
+
sortDirection?: Bindable<"asc" | "desc">;
|
|
50
|
+
/** An ARIA label describing the grid to screen readers */
|
|
51
|
+
ariaLabel?: string;
|
|
52
|
+
/** An ID of an element that labels the grid */
|
|
53
|
+
ariaLabelledBy?: string;
|
|
54
|
+
/** Called after each successful load with the normalized result (including `total`) */
|
|
55
|
+
onload?: (result: LoadResult) => void;
|
|
56
|
+
/** Called when the user sorts by a column */
|
|
57
|
+
onsortchange?: (sortBy: string | undefined, sortDirection: "asc" | "desc") => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A table of rows that supports sorting, paging and loading data from the
|
|
62
|
+
* network.
|
|
63
|
+
*
|
|
64
|
+
* See [the WAI-ARIA guidelines for grids](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
|
|
65
|
+
*
|
|
66
|
+
* The structure of a DataGrid is:
|
|
67
|
+
*
|
|
68
|
+
* ```
|
|
69
|
+
* <DataGrid columns={columns} data={rows}>
|
|
70
|
+
* (cell content, using $slot.row / $slot.column / $slot.value)
|
|
71
|
+
* </DataGrid>
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* Cells render their value as text by default. Pass children to customize:
|
|
75
|
+
*
|
|
76
|
+
* ```
|
|
77
|
+
* <DataGrid columns={columns} data={rows}>
|
|
78
|
+
* {@if ($slot.column.key === "avatar") {
|
|
79
|
+
* <img src={$slot.value} alt="" />
|
|
80
|
+
* } else {
|
|
81
|
+
* {$slot.value}
|
|
82
|
+
* }}
|
|
83
|
+
* </DataGrid>
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* Named slots: `loading` (while the first load is pending), `error`
|
|
87
|
+
* (receives nothing -- use it around the message), `empty` (when there are
|
|
88
|
+
* no rows) and `header` isn't needed since headers come from the columns.
|
|
89
|
+
*/
|
|
90
|
+
export default function DataGrid($props: DataGridProps) {
|
|
91
|
+
let defaultId = getId();
|
|
92
|
+
|
|
93
|
+
if (!$props.columns || $props.columns.length === 0) {
|
|
94
|
+
throw new Error("DataGrid: pass a non-empty `columns` array");
|
|
95
|
+
}
|
|
96
|
+
const hasLoader = typeof $props.load === "function";
|
|
97
|
+
if (hasLoader && $props.data !== undefined) {
|
|
98
|
+
throw new Error("DataGrid: pass either `load` or `data`, not both");
|
|
99
|
+
}
|
|
100
|
+
if (!hasLoader && $props.data === undefined) {
|
|
101
|
+
throw new Error("DataGrid: pass either `load` or `data`");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let tableElement: HTMLElement;
|
|
105
|
+
let wrap: HTMLElement;
|
|
106
|
+
|
|
107
|
+
// Columns are assumed static once passed; precompute entries with indices
|
|
108
|
+
// so cells can be keyed and addressed by position
|
|
109
|
+
const columnEntries = $props.columns.map((column, index) => ({ column, index }));
|
|
110
|
+
|
|
111
|
+
let $state = $watch({
|
|
112
|
+
page: $props.page ?? 1,
|
|
113
|
+
sortBy: $props.sortBy,
|
|
114
|
+
sortDirection: $props.sortDirection ?? "asc",
|
|
115
|
+
activeCell: { row: 0, col: 0 },
|
|
116
|
+
|
|
117
|
+
// The result of the current load. Suspends while pending when read,
|
|
118
|
+
// which drives the @await boundary in the template.
|
|
119
|
+
get loadResult(): LoadResult | undefined {
|
|
120
|
+
if (!hasLoader) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
return $async(() => runLoad());
|
|
124
|
+
},
|
|
125
|
+
get sortedRows(): any[] {
|
|
126
|
+
if (hasLoader) {
|
|
127
|
+
return this.loadResult?.items ?? [];
|
|
128
|
+
}
|
|
129
|
+
return sortRows($props.data ?? [], this.sortBy, this.sortDirection, columnEntries.map((e) => e.column));
|
|
130
|
+
},
|
|
131
|
+
get totalCount(): number {
|
|
132
|
+
if (hasLoader && this.loadResult?.total !== undefined) {
|
|
133
|
+
return this.loadResult.total;
|
|
134
|
+
}
|
|
135
|
+
return this.sortedRows.length;
|
|
136
|
+
},
|
|
137
|
+
get rows(): any[] {
|
|
138
|
+
const pageSize = $props.pageSize;
|
|
139
|
+
if (!pageSize || hasLoader) {
|
|
140
|
+
return this.sortedRows;
|
|
141
|
+
}
|
|
142
|
+
const start = (this.page - 1) * pageSize;
|
|
143
|
+
return this.sortedRows.slice(start, start + pageSize);
|
|
144
|
+
},
|
|
145
|
+
get rowEntries() {
|
|
146
|
+
return this.rows.map((row: any, index: number) => ({ row, index }));
|
|
147
|
+
},
|
|
148
|
+
// The rows currently rendered: everything when not virtual, the
|
|
149
|
+
// visible window otherwise
|
|
150
|
+
get bodyEntries() {
|
|
151
|
+
if (!isVirtual()) return this.rowEntries;
|
|
152
|
+
return this.rows.slice(win.start, win.endExclusive).map(
|
|
153
|
+
(row: any, i: number) => ({ row, index: win.start + i }),
|
|
154
|
+
);
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
$bind($state, $props, "page", "sortBy", "sortDirection");
|
|
159
|
+
|
|
160
|
+
// --- Virtualization ---
|
|
161
|
+
|
|
162
|
+
function isVirtual(): boolean {
|
|
163
|
+
return $props?.virtual === true && ($props?.rowHeight ?? 0) > 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getRowHeight(): number {
|
|
167
|
+
return Math.max(1, Math.floor($props?.rowHeight ?? 32));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getOverscan(): number {
|
|
171
|
+
return Math.max(0, Math.floor($props?.overscan ?? 5));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getViewportHeight(): number {
|
|
175
|
+
return wrap?.clientHeight || $props?.height || 400;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// The rendered row window; endExclusive may pass loaded rows when a
|
|
179
|
+
// loader reports a total larger than what it sent so far -- slicing is
|
|
180
|
+
// naturally safe against that
|
|
181
|
+
const win = $watch({ start: 0, endExclusive: 0 });
|
|
182
|
+
|
|
183
|
+
function updateWindow() {
|
|
184
|
+
if (!isVirtual()) return;
|
|
185
|
+
|
|
186
|
+
const total = $state.totalCount;
|
|
187
|
+
if (!total || !wrap) {
|
|
188
|
+
win.start = 0;
|
|
189
|
+
win.endExclusive = 0;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const rh = getRowHeight();
|
|
194
|
+
const overscan = getOverscan();
|
|
195
|
+
const start = Math.min(Math.max(0, Math.floor(wrap.scrollTop / rh) - overscan), total);
|
|
196
|
+
const visible = Math.ceil(getViewportHeight() / rh);
|
|
197
|
+
win.start = start;
|
|
198
|
+
win.endExclusive = Math.min(total, start + visible + overscan * 2);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function handleScroll() {
|
|
202
|
+
updateWindow();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// The active cell, clamped to the current grid size -- rows can shrink
|
|
206
|
+
// (e.g. after a load returns fewer rows) and the stored position must
|
|
207
|
+
// stay addressable so exactly one cell remains the tab stop.
|
|
208
|
+
// Clamping is done at the use sites rather than in an effect: effects
|
|
209
|
+
// can't safely read suspended ($async) getters.
|
|
210
|
+
function activeCell(): { row: number; col: number } {
|
|
211
|
+
const maxRow = Math.max(0, $state.rows.length - 1);
|
|
212
|
+
const maxCol = Math.max(0, $props.columns.length - 1);
|
|
213
|
+
return {
|
|
214
|
+
row: Math.min(Math.max(0, $state.activeCell.row), maxRow),
|
|
215
|
+
col: Math.min(Math.max(0, $state.activeCell.col), maxCol),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function runLoad(): Promise<LoadResult> {
|
|
220
|
+
const request: LoadRequest = {};
|
|
221
|
+
if ($props.pageSize) {
|
|
222
|
+
request.page = $state.page;
|
|
223
|
+
request.pageSize = $props.pageSize;
|
|
224
|
+
}
|
|
225
|
+
if ($state.sortBy != null) {
|
|
226
|
+
request.sortBy = $state.sortBy;
|
|
227
|
+
request.sortDirection = $state.sortDirection;
|
|
228
|
+
}
|
|
229
|
+
const result = Promise.resolve(($props.load as Loader)(request)).then(normalizeLoadResult);
|
|
230
|
+
// Report successful loads to the consumer; failures surface through
|
|
231
|
+
// the component's error boundary, not this notification chain
|
|
232
|
+
result.then((r) => $props.onload?.(r), () => {});
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function cellValue(row: any, column: DataColumn): any {
|
|
237
|
+
if (column.getValue) {
|
|
238
|
+
return column.getValue(row);
|
|
239
|
+
}
|
|
240
|
+
return row?.[column.key];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function cellId(row: number, col: number): string {
|
|
244
|
+
return `${row}:${col}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function isActiveCell(row: number, col: number): boolean {
|
|
248
|
+
const active = activeCell();
|
|
249
|
+
return active.row === row && active.col === col;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function ariaSort(key: string): "ascending" | "descending" | undefined {
|
|
253
|
+
if ($state.sortBy !== key) {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
return $state.sortDirection === "asc" ? "ascending" : "descending";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function toggleSort(column: DataColumn) {
|
|
260
|
+
if (!column.sortable) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if ($state.sortBy === column.key) {
|
|
264
|
+
$state.sortDirection = $state.sortDirection === "asc" ? "desc" : "asc";
|
|
265
|
+
} else {
|
|
266
|
+
$state.sortBy = column.key;
|
|
267
|
+
$state.sortDirection = "asc";
|
|
268
|
+
}
|
|
269
|
+
$props.onsortchange?.($state.sortBy, $state.sortDirection);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Scrolls the virtual window so a logical row is rendered, before it is
|
|
273
|
+
// focused
|
|
274
|
+
function makeRowVisible(row: number) {
|
|
275
|
+
if (!isVirtual() || !wrap) return;
|
|
276
|
+
|
|
277
|
+
const rh = getRowHeight();
|
|
278
|
+
const overscan = getOverscan();
|
|
279
|
+
if (row < win.start) {
|
|
280
|
+
wrap.scrollTop = Math.max(0, (row - overscan) * rh);
|
|
281
|
+
updateWindow();
|
|
282
|
+
} else if (row >= win.endExclusive) {
|
|
283
|
+
const below = Math.max(
|
|
284
|
+
0,
|
|
285
|
+
(row + 1 + overscan) * rh - getViewportHeight(),
|
|
286
|
+
);
|
|
287
|
+
wrap.scrollTop = below;
|
|
288
|
+
updateWindow();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function focusCell(row: number, col: number) {
|
|
293
|
+
makeRowVisible(row);
|
|
294
|
+
$state.activeCell = { row, col };
|
|
295
|
+
tableElement.querySelector<HTMLElement>(`[data-cell="${cellId(row, col)}"]`)?.focus();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Any other way of focusing a cell (tabbing in, script) also moves the
|
|
299
|
+
// keyboard navigation origin
|
|
300
|
+
function handleFocusin(e: FocusEvent) {
|
|
301
|
+
const cell = (e.target as HTMLElement).closest?.("td[data-cell]");
|
|
302
|
+
if (!cell) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const [row, col] = cell.getAttribute("data-cell")!.split(":").map(Number);
|
|
306
|
+
$state.activeCell = { row, col };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Standard grid keyboard navigation (roving tabindex over body cells)
|
|
310
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
311
|
+
const target = e.target as HTMLElement;
|
|
312
|
+
if (!target.matches?.("td[data-cell]")) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (target.closest("button,input,select,textarea,a[href]")) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const maxRow = $state.rows.length - 1;
|
|
319
|
+
const maxCol = $props.columns.length - 1;
|
|
320
|
+
const active = activeCell();
|
|
321
|
+
const row = active.row;
|
|
322
|
+
const col = active.col;
|
|
323
|
+
switch (e.key) {
|
|
324
|
+
case "ArrowLeft": {
|
|
325
|
+
if (col > 0) {
|
|
326
|
+
e.preventDefault();
|
|
327
|
+
focusCell(row, col - 1);
|
|
328
|
+
}
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
case "ArrowRight": {
|
|
332
|
+
if (col < maxCol) {
|
|
333
|
+
e.preventDefault();
|
|
334
|
+
focusCell(row, col + 1);
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
case "ArrowUp": {
|
|
339
|
+
if (row > 0) {
|
|
340
|
+
e.preventDefault();
|
|
341
|
+
focusCell(row - 1, col);
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
case "ArrowDown": {
|
|
346
|
+
if (row < maxRow) {
|
|
347
|
+
e.preventDefault();
|
|
348
|
+
focusCell(row + 1, col);
|
|
349
|
+
}
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
case "Home": {
|
|
353
|
+
e.preventDefault();
|
|
354
|
+
focusCell(e.ctrlKey || e.metaKey ? 0 : row, e.ctrlKey || e.metaKey ? 0 : 0);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
case "End": {
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
focusCell(e.ctrlKey || e.metaKey ? maxRow : row, maxCol);
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
case "PageUp": {
|
|
363
|
+
if (isVirtual()) {
|
|
364
|
+
e.preventDefault();
|
|
365
|
+
const step = Math.max(1, Math.floor(getViewportHeight() / getRowHeight()));
|
|
366
|
+
focusCell(Math.max(0, active.row - step), col);
|
|
367
|
+
}
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
case "PageDown": {
|
|
371
|
+
if (isVirtual()) {
|
|
372
|
+
e.preventDefault();
|
|
373
|
+
const step = Math.max(1, Math.floor(getViewportHeight() / getRowHeight()));
|
|
374
|
+
focusCell(Math.min(maxRow, active.row + step), col);
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const alignClass = (align: "start" | "center" | "end" | undefined) =>
|
|
382
|
+
align ? `torp-data-grid-align-${align}` : undefined;
|
|
383
|
+
|
|
384
|
+
function wrapStyle(): string | undefined {
|
|
385
|
+
if (!isVirtual()) return undefined;
|
|
386
|
+
return `max-height:${getViewportHeight()}px;overflow:auto;`;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@render {
|
|
390
|
+
@try {
|
|
391
|
+
@await {
|
|
392
|
+
<div
|
|
393
|
+
class="torp-data-grid-wrap"
|
|
394
|
+
style={wrapStyle()}
|
|
395
|
+
onscroll={handleScroll}
|
|
396
|
+
onmount={(el: HTMLElement) => {
|
|
397
|
+
wrap = el;
|
|
398
|
+
updateWindow();
|
|
399
|
+
}}
|
|
400
|
+
&ref={wrap}
|
|
401
|
+
>
|
|
402
|
+
<table
|
|
403
|
+
id={$props.id ?? defaultId}
|
|
404
|
+
class={["torp-data-grid", $props.class]}
|
|
405
|
+
{$props?.style}
|
|
406
|
+
&ref={tableElement}
|
|
407
|
+
role="grid"
|
|
408
|
+
aria-label={$props.ariaLabel}
|
|
409
|
+
aria-labelledby={$props.ariaLabelledBy}
|
|
410
|
+
aria-rowcount={hasLoader || isVirtual() ? $state.totalCount : undefined}
|
|
411
|
+
onkeydown={handleKeydown}
|
|
412
|
+
onfocusin={handleFocusin}
|
|
413
|
+
>
|
|
414
|
+
@if ($props.caption) {
|
|
415
|
+
<caption>{$props.caption}</caption>
|
|
416
|
+
}
|
|
417
|
+
<thead>
|
|
418
|
+
<tr role="row">
|
|
419
|
+
@for (let entry of columnEntries) {
|
|
420
|
+
@key = entry.column.key
|
|
421
|
+
<th
|
|
422
|
+
scope="col"
|
|
423
|
+
role="columnheader"
|
|
424
|
+
class={[alignClass(entry.column.align), entry.column.sortable ? "torp-data-grid-sortable" : undefined]}
|
|
425
|
+
aria-sort={ariaSort(entry.column.key)}
|
|
426
|
+
>
|
|
427
|
+
@if (entry.column.sortable) {
|
|
428
|
+
<button type="button" onclick={() => toggleSort(entry.column)}>
|
|
429
|
+
{entry.column.label ?? entry.column.key}
|
|
430
|
+
</button>
|
|
431
|
+
} else {
|
|
432
|
+
<span>{entry.column.label ?? entry.column.key}</span>
|
|
433
|
+
}
|
|
434
|
+
</th>
|
|
435
|
+
}
|
|
436
|
+
</tr>
|
|
437
|
+
</thead>
|
|
438
|
+
<tbody>
|
|
439
|
+
@if ($state.rowEntries.length === 0) {
|
|
440
|
+
<tr role="row">
|
|
441
|
+
<td colspan={$props.columns.length}>
|
|
442
|
+
<slot name="empty">No results.</slot>
|
|
443
|
+
</td>
|
|
444
|
+
</tr>
|
|
445
|
+
} else {
|
|
446
|
+
@if (isVirtual()) {
|
|
447
|
+
<tr aria-hidden="true" class="torp-data-grid-spacer">
|
|
448
|
+
<td colspan={$props.columns.length} style={`height:${win.start * getRowHeight()}px`} />
|
|
449
|
+
</tr>
|
|
450
|
+
}
|
|
451
|
+
@for (let rowEntry of $state.bodyEntries) {
|
|
452
|
+
@key = rowEntry.row
|
|
453
|
+
<tr role="row">
|
|
454
|
+
@for (let entry of columnEntries) {
|
|
455
|
+
@key = entry.column.key
|
|
456
|
+
<td
|
|
457
|
+
role="gridcell"
|
|
458
|
+
class={alignClass(entry.column.align)}
|
|
459
|
+
data-cell={cellId(rowEntry.index, entry.index)}
|
|
460
|
+
tabindex={isActiveCell(rowEntry.index, entry.index) ? 0 : -1}
|
|
461
|
+
>
|
|
462
|
+
<slot
|
|
463
|
+
row={rowEntry.row}
|
|
464
|
+
column={entry.column}
|
|
465
|
+
value={cellValue(rowEntry.row, entry.column)}
|
|
466
|
+
>
|
|
467
|
+
{cellValue(rowEntry.row, entry.column)}
|
|
468
|
+
</slot>
|
|
469
|
+
</td>
|
|
470
|
+
}
|
|
471
|
+
</tr>
|
|
472
|
+
}
|
|
473
|
+
@if (isVirtual()) {
|
|
474
|
+
<tr aria-hidden="true" class="torp-data-grid-spacer">
|
|
475
|
+
<td
|
|
476
|
+
colspan={$props.columns.length}
|
|
477
|
+
style={`height:${Math.max(0, ($state.rows.length - win.endExclusive)) * getRowHeight()}px`}
|
|
478
|
+
/>
|
|
479
|
+
</tr>
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
</tbody>
|
|
483
|
+
</table>
|
|
484
|
+
</div>
|
|
485
|
+
} with {
|
|
486
|
+
<div class="torp-data-grid-loading" role="status">
|
|
487
|
+
<slot name="loading">Loading…</slot>
|
|
488
|
+
</div>
|
|
489
|
+
}
|
|
490
|
+
} catch (err) {
|
|
491
|
+
<div class="torp-data-grid-error" role="alert">
|
|
492
|
+
<slot name="error">{err instanceof Error ? err.message : String(err)}</slot>
|
|
493
|
+
</div>
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { LoadResult } from "../utils/loader";
|
|
2
|
+
|
|
3
|
+
/** A column in a DataGrid */
|
|
4
|
+
export interface DataColumn<T = any> {
|
|
5
|
+
/** The field name to read from each row (or pass a `getValue` function) */
|
|
6
|
+
key: string;
|
|
7
|
+
/** Header text; defaults to the key */
|
|
8
|
+
label?: string;
|
|
9
|
+
/** Cell content alignment */
|
|
10
|
+
align?: "start" | "center" | "end";
|
|
11
|
+
/** Whether clicking this column's header sorts by it */
|
|
12
|
+
sortable?: boolean;
|
|
13
|
+
/** Extracts the cell value from a row; defaults to `row[key]` */
|
|
14
|
+
getValue?: (row: T) => any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** What a DataGrid passes to its cell slot for custom rendering */
|
|
18
|
+
export interface DataGridCellSlot<T = any> {
|
|
19
|
+
row: T;
|
|
20
|
+
column: DataColumn<T>;
|
|
21
|
+
value: any;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Data passed to the `onload` callback after each successful load */
|
|
25
|
+
export type DataGridLoadEvent<T = any> = LoadResult<T>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type SlotRender } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface DataGridProps {
|
|
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
|
+
/** A caption for the table, rendered in a `<caption>` element */
|
|
12
|
+
caption?: string;
|
|
13
|
+
/** The columns to display */
|
|
14
|
+
columns: DataColumn[];
|
|
15
|
+
/** The rows to display. Pass either `data` (static rows) or `load`. */
|
|
16
|
+
data?: any[];
|
|
17
|
+
/**
|
|
18
|
+
* Reads rows from the network (or anywhere async). Receives a request with
|
|
19
|
+
* `page`/`pageSize`, `searchText` and `sortBy`/`sortDirection`, plus an
|
|
20
|
+
* AbortSignal. Pass either `load` or `data`, not both.
|
|
21
|
+
*/
|
|
22
|
+
load?: Loader;
|
|
23
|
+
/** The active page (1-based). Two-way bindable. */
|
|
24
|
+
page?: Bindable<number>;
|
|
25
|
+
/** The number of rows per page. Static data is sliced locally; loaders receive it in the request. */
|
|
26
|
+
pageSize?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Renders only the rows visible in the viewport, for large datasets.
|
|
29
|
+
* Requires `rowHeight`; supersedes local page slicing while enabled
|
|
30
|
+
*/
|
|
31
|
+
virtual?: boolean;
|
|
32
|
+
/** The fixed pixel height of a row, required by virtualization */
|
|
33
|
+
rowHeight?: number;
|
|
34
|
+
/** The scroll viewport height in pixels when virtual (default 400) */
|
|
35
|
+
height?: number;
|
|
36
|
+
/** Extra rows rendered above and below the visible window (default 5) */
|
|
37
|
+
overscan?: number;
|
|
38
|
+
/** The field the data is sorted by. Two-way bindable. */
|
|
39
|
+
sortBy?: Bindable<string | undefined>;
|
|
40
|
+
/** The direction the data is sorted in. Two-way bindable. */
|
|
41
|
+
sortDirection?: Bindable<"asc" | "desc">;
|
|
42
|
+
/** An ARIA label describing the grid to screen readers */
|
|
43
|
+
ariaLabel?: string;
|
|
44
|
+
/** An ID of an element that labels the grid */
|
|
45
|
+
ariaLabelledBy?: string;
|
|
46
|
+
/** Called after each successful load with the normalized result (including `total`) */
|
|
47
|
+
onload?: (result: LoadResult) => void;
|
|
48
|
+
/** Called when the user sorts by a column */
|
|
49
|
+
onsortchange?: (sortBy: string | undefined, sortDirection: "asc" | "desc") => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A table of rows that supports sorting, paging and loading data from the
|
|
54
|
+
* network.
|
|
55
|
+
*
|
|
56
|
+
* See [the WAI-ARIA guidelines for grids](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
|
|
57
|
+
*
|
|
58
|
+
* The structure of a DataGrid is:
|
|
59
|
+
*
|
|
60
|
+
* ```
|
|
61
|
+
* <DataGrid columns={columns} data={rows}>
|
|
62
|
+
* (cell content, using $slot.row / $slot.column / $slot.value)
|
|
63
|
+
* </DataGrid>
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* Cells render their value as text by default. Pass children to customize:
|
|
67
|
+
*
|
|
68
|
+
* ```
|
|
69
|
+
* <DataGrid columns={columns} data={rows}>
|
|
70
|
+
* {@if ($slot.column.key === "avatar") {
|
|
71
|
+
* <img src={$slot.value} alt="" />
|
|
72
|
+
* } else {
|
|
73
|
+
* {$slot.value}
|
|
74
|
+
* }}
|
|
75
|
+
* </DataGrid>
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* Named slots: `loading` (while the first load is pending), `error`
|
|
79
|
+
* (receives nothing -- use it around the message), `empty` (when there are
|
|
80
|
+
* no rows) and `header` isn't needed since headers come from the columns.
|
|
81
|
+
*/
|
|
82
|
+
declare function DataGrid(
|
|
83
|
+
$parent: ParentNode,
|
|
84
|
+
$anchor: Node | null,
|
|
85
|
+
$props: DataGridProps,
|
|
86
|
+
$context?: Record<PropertyKey, any>,
|
|
87
|
+
$slots?: Record<string, SlotRender>,
|
|
88
|
+
): void;
|
|
89
|
+
|
|
90
|
+
export { DataGrid };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Compares two cell values, handling mixed types and nullish values */
|
|
2
|
+
function compareValues(a: any, b: any): number {
|
|
3
|
+
if (a == null && b == null) return 0;
|
|
4
|
+
if (a == null) return -1;
|
|
5
|
+
if (b == null) return 1;
|
|
6
|
+
if (typeof a === "string" || typeof b === "string") {
|
|
7
|
+
return String(a).localeCompare(String(b));
|
|
8
|
+
}
|
|
9
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sorts rows by the given column key (a shallow copy -- the source array is
|
|
14
|
+
* not modified). Returns the rows unchanged when no sort is active.
|
|
15
|
+
*/
|
|
16
|
+
export default function sortRows<T>(
|
|
17
|
+
rows: T[],
|
|
18
|
+
sortBy: string | undefined,
|
|
19
|
+
sortDirection: "asc" | "desc",
|
|
20
|
+
columns: { key: string; getValue?: (row: T) => any }[],
|
|
21
|
+
): T[] {
|
|
22
|
+
if (!sortBy) {
|
|
23
|
+
return rows;
|
|
24
|
+
}
|
|
25
|
+
const column = columns.find((c) => c.key === sortBy);
|
|
26
|
+
const getValue = column?.getValue ?? ((row: any) => row?.[sortBy]);
|
|
27
|
+
const sorted = [...rows].sort((a, b) => compareValues(getValue(a), getValue(b)));
|
|
28
|
+
if (sortDirection === "desc") {
|
|
29
|
+
sorted.reverse();
|
|
30
|
+
}
|
|
31
|
+
return sorted;
|
|
32
|
+
}
|