@sincpro/web-ui 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +184 -2
- package/dist/components/activity/Activity.d.ts +69 -0
- package/dist/components/activity/Activity.js +279 -0
- package/dist/components/activity/Messages.d.ts +66 -0
- package/dist/components/activity/Messages.js +46 -0
- package/dist/components/activity/Timeline.d.ts +21 -0
- package/dist/components/activity/Timeline.js +30 -0
- package/dist/components/activity/index.d.ts +3 -0
- package/dist/components/activity/index.js +4 -0
- package/dist/components/assist/AssistMark.d.ts +64 -0
- package/dist/components/assist/AssistMark.js +108 -0
- package/dist/components/assist/AssistPending.d.ts +24 -0
- package/dist/components/assist/AssistPending.js +26 -0
- package/dist/components/assist/AssistSuggestion.d.ts +42 -0
- package/dist/components/assist/AssistSuggestion.js +55 -0
- package/dist/components/assist/ChangeReview.d.ts +56 -0
- package/dist/components/assist/ChangeReview.js +121 -0
- package/dist/components/assist/assist.d.ts +77 -0
- package/dist/components/assist/assist.js +87 -0
- package/dist/components/assist/index.d.ts +5 -0
- package/dist/components/assist/index.js +7 -0
- package/dist/components/form/Form.d.ts +52 -0
- package/dist/components/form/Form.js +49 -0
- package/dist/components/form/Sheet.d.ts +47 -0
- package/dist/components/form/Sheet.js +90 -0
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.js +4 -0
- package/dist/components/frame/Chart.d.ts +13 -0
- package/dist/components/frame/Chart.js +24 -0
- package/dist/components/frame/FrameView.d.ts +33 -0
- package/dist/components/frame/FrameView.js +125 -0
- package/dist/components/frame/index.d.ts +3 -0
- package/dist/components/frame/index.js +3 -0
- package/dist/components/frame/model.d.ts +43 -0
- package/dist/components/frame/model.js +101 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.js +11 -0
- package/dist/components/lines/LineItems.d.ts +111 -0
- package/dist/components/lines/LineItems.js +317 -0
- package/dist/components/lines/index.d.ts +1 -0
- package/dist/components/lines/index.js +2 -0
- package/dist/components/lines/lines.d.ts +23 -0
- package/dist/components/lines/lines.js +26 -0
- package/dist/components/listing/ColumnsMenu.d.ts +20 -0
- package/dist/components/listing/ColumnsMenu.js +46 -0
- package/dist/components/listing/GroupingLevels.d.ts +35 -0
- package/dist/components/listing/GroupingLevels.js +45 -0
- package/dist/components/listing/Listing.d.ts +27 -0
- package/dist/components/listing/Listing.js +86 -0
- package/dist/components/listing/ListingBar.d.ts +112 -0
- package/dist/components/listing/ListingBar.js +144 -0
- package/dist/components/listing/Pagination.d.ts +36 -0
- package/dist/components/listing/Pagination.js +69 -0
- package/dist/components/listing/RecordCard.d.ts +33 -0
- package/dist/components/listing/RecordCard.js +22 -0
- package/dist/components/listing/RecordLink.d.ts +7 -0
- package/dist/components/listing/RecordLink.js +22 -0
- package/dist/components/listing/Row.d.ts +110 -0
- package/dist/components/listing/Row.js +123 -0
- package/dist/components/listing/SortMenu.d.ts +62 -0
- package/dist/components/listing/SortMenu.js +105 -0
- package/dist/components/listing/ViewSwitcher.d.ts +24 -0
- package/dist/components/listing/ViewSwitcher.js +16 -0
- package/dist/components/listing/columns.d.ts +47 -0
- package/dist/components/listing/columns.js +72 -0
- package/dist/components/listing/grouping.d.ts +35 -0
- package/dist/components/listing/grouping.js +47 -0
- package/dist/components/listing/index.d.ts +17 -0
- package/dist/components/listing/index.js +20 -0
- package/dist/components/listing/modelColumns.d.ts +50 -0
- package/dist/components/listing/modelColumns.js +103 -0
- package/dist/components/listing/pages.d.ts +78 -0
- package/dist/components/listing/pages.js +135 -0
- package/dist/components/listing/selection.d.ts +21 -0
- package/dist/components/listing/selection.js +43 -0
- package/dist/components/listing/set.d.ts +48 -0
- package/dist/components/listing/set.js +24 -0
- package/dist/components/listing/tree.d.ts +28 -0
- package/dist/components/listing/tree.js +23 -0
- package/dist/components/navigation/CommandPalette.d.ts +40 -0
- package/dist/components/navigation/CommandPalette.js +114 -0
- package/dist/components/navigation/NavigationTree.d.ts +31 -0
- package/dist/components/navigation/NavigationTree.js +79 -0
- package/dist/components/navigation/commands.d.ts +30 -0
- package/dist/components/navigation/commands.js +30 -0
- package/dist/components/navigation/index.d.ts +3 -0
- package/dist/components/navigation/index.js +4 -0
- package/dist/components/record/Breadcrumb.d.ts +42 -0
- package/dist/components/record/Breadcrumb.js +98 -0
- package/dist/components/record/DirtyActions.d.ts +26 -0
- package/dist/components/record/DirtyActions.js +22 -0
- package/dist/components/record/Pager.d.ts +18 -0
- package/dist/components/record/Pager.js +33 -0
- package/dist/components/record/RecordHeader.d.ts +75 -0
- package/dist/components/record/RecordHeader.js +104 -0
- package/dist/components/record/RecordNotices.d.ts +37 -0
- package/dist/components/record/RecordNotices.js +75 -0
- package/dist/components/record/RecordSkeleton.d.ts +3 -0
- package/dist/components/record/RecordSkeleton.js +21 -0
- package/dist/components/record/SmartButtons.d.ts +35 -0
- package/dist/components/record/SmartButtons.js +38 -0
- package/dist/components/record/StatusBar.d.ts +51 -0
- package/dist/components/record/StatusBar.js +168 -0
- package/dist/components/record/ThumbBar.d.ts +36 -0
- package/dist/components/record/ThumbBar.js +86 -0
- package/dist/components/record/index.d.ts +6 -0
- package/dist/components/record/index.js +8 -0
- package/dist/components/search/CriteriaBuilder.d.ts +58 -0
- package/dist/components/search/CriteriaBuilder.js +278 -0
- package/dist/components/search/CriteriaChips.d.ts +22 -0
- package/dist/components/search/CriteriaChips.js +64 -0
- package/dist/components/search/FilterBuilder.d.ts +142 -0
- package/dist/components/search/FilterBuilder.js +421 -0
- package/dist/components/search/JoinWord.d.ts +8 -0
- package/dist/components/search/JoinWord.js +47 -0
- package/dist/components/search/JsonEditor.d.ts +19 -0
- package/dist/components/search/JsonEditor.js +78 -0
- package/dist/components/search/SearchBar.d.ts +78 -0
- package/dist/components/search/SearchBar.js +387 -0
- package/dist/components/search/criteria.d.ts +116 -0
- package/dist/components/search/criteria.js +159 -0
- package/dist/components/search/datePeriods.d.ts +122 -0
- package/dist/components/search/datePeriods.js +134 -0
- package/dist/components/search/dateRanges.d.ts +54 -0
- package/dist/components/search/dateRanges.js +89 -0
- package/dist/components/search/index.d.ts +9 -0
- package/dist/components/search/index.js +20 -0
- package/dist/components/search/model.d.ts +54 -0
- package/dist/components/search/model.js +37 -0
- package/dist/components/search/query.d.ts +166 -0
- package/dist/components/search/query.js +436 -0
- package/dist/components/shell/AppFrame.d.ts +14 -0
- package/dist/components/shell/AppFrame.js +80 -0
- package/dist/components/shell/TopBar.d.ts +73 -0
- package/dist/components/shell/TopBar.js +54 -0
- package/dist/components/shell/headerSlot.d.ts +17 -0
- package/dist/components/shell/headerSlot.js +21 -0
- package/dist/components/shell/index.d.ts +2 -0
- package/dist/components/shell/index.js +3 -0
- package/dist/foundations/aria.d.ts +14 -0
- package/dist/foundations/aria.js +18 -0
- package/dist/foundations/calendar.d.ts +28 -0
- package/dist/foundations/calendar.js +183 -0
- package/dist/foundations/copy.d.ts +703 -0
- package/dist/foundations/copy.es.d.ts +2 -0
- package/dist/foundations/copy.es.js +430 -0
- package/dist/foundations/copy.js +520 -0
- package/dist/foundations/density.d.ts +16 -0
- package/dist/foundations/density.js +21 -0
- package/dist/foundations/grouping.d.ts +281 -0
- package/dist/foundations/grouping.js +0 -0
- package/dist/foundations/index.d.ts +14 -0
- package/dist/foundations/index.js +30 -0
- package/dist/foundations/layout.d.ts +9 -0
- package/dist/foundations/layout.js +9 -0
- package/dist/foundations/pivot.d.ts +222 -0
- package/dist/foundations/pivot.js +198 -0
- package/dist/foundations/pivotSheet.d.ts +61 -0
- package/dist/foundations/pivotSheet.js +0 -0
- package/dist/foundations/slots.d.ts +67 -0
- package/dist/foundations/slots.js +63 -0
- package/dist/foundations/sticky.d.ts +53 -0
- package/dist/foundations/sticky.js +150 -0
- package/dist/foundations/surface.d.ts +9 -0
- package/dist/foundations/surface.js +26 -0
- package/dist/foundations/theme.d.ts +42 -0
- package/dist/foundations/theme.js +88 -0
- package/dist/foundations/types.d.ts +23 -0
- package/dist/foundations/types.js +50 -0
- package/dist/foundations/variants.d.ts +19 -0
- package/dist/foundations/variants.js +48 -0
- package/dist/foundations/xlsx.d.ts +86 -0
- package/dist/foundations/xlsx.js +0 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +19 -0
- package/dist/primitives/block/Block.d.ts +66 -0
- package/dist/primitives/block/Block.js +34 -0
- package/dist/primitives/block/Line.d.ts +18 -0
- package/dist/primitives/block/Line.js +45 -0
- package/dist/primitives/block/SectionGrid.d.ts +30 -0
- package/dist/primitives/block/SectionGrid.js +29 -0
- package/dist/primitives/block/Steps.d.ts +76 -0
- package/dist/primitives/block/Steps.js +43 -0
- package/dist/primitives/block/index.d.ts +4 -0
- package/dist/primitives/block/index.js +7 -0
- package/dist/primitives/button/Button.d.ts +137 -0
- package/dist/primitives/button/Button.js +234 -0
- package/dist/primitives/button/index.d.ts +1 -0
- package/dist/primitives/button/index.js +2 -0
- package/dist/primitives/cell/CellStack.d.ts +47 -0
- package/dist/primitives/cell/CellStack.js +33 -0
- package/dist/primitives/cell/index.d.ts +1 -0
- package/dist/primitives/cell/index.js +3 -0
- package/dist/primitives/choice/ComboBox.d.ts +19 -0
- package/dist/primitives/choice/ComboBox.js +52 -0
- package/dist/primitives/choice/RadioGroup.d.ts +18 -0
- package/dist/primitives/choice/RadioGroup.js +50 -0
- package/dist/primitives/choice/SearchField.d.ts +33 -0
- package/dist/primitives/choice/SearchField.js +46 -0
- package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
- package/dist/primitives/choice/SegmentedControl.js +69 -0
- package/dist/primitives/choice/Slider.d.ts +16 -0
- package/dist/primitives/choice/Slider.js +46 -0
- package/dist/primitives/choice/Switch.d.ts +14 -0
- package/dist/primitives/choice/Switch.js +65 -0
- package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
- package/dist/primitives/choice/ThemeSwitcher.js +25 -0
- package/dist/primitives/choice/index.d.ts +8 -0
- package/dist/primitives/choice/index.js +11 -0
- package/dist/primitives/choice/options.d.ts +28 -0
- package/dist/primitives/choice/options.js +6 -0
- package/dist/primitives/date/Calendar.d.ts +31 -0
- package/dist/primitives/date/Calendar.js +134 -0
- package/dist/primitives/date/DatePicker.d.ts +42 -0
- package/dist/primitives/date/DatePicker.js +103 -0
- package/dist/primitives/date/DateTime.d.ts +35 -0
- package/dist/primitives/date/DateTime.js +100 -0
- package/dist/primitives/date/index.d.ts +3 -0
- package/dist/primitives/date/index.js +6 -0
- package/dist/primitives/dialog/Dialog.d.ts +118 -0
- package/dist/primitives/dialog/Dialog.js +121 -0
- package/dist/primitives/dialog/Drill.d.ts +90 -0
- package/dist/primitives/dialog/Drill.js +172 -0
- package/dist/primitives/dialog/index.d.ts +2 -0
- package/dist/primitives/dialog/index.js +4 -0
- package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
- package/dist/primitives/disclosure/Disclosure.js +52 -0
- package/dist/primitives/disclosure/index.d.ts +1 -0
- package/dist/primitives/disclosure/index.js +2 -0
- package/dist/primitives/field/Checks.d.ts +57 -0
- package/dist/primitives/field/Checks.js +72 -0
- package/dist/primitives/field/DropZone.d.ts +42 -0
- package/dist/primitives/field/DropZone.js +52 -0
- package/dist/primitives/field/Field.d.ts +178 -0
- package/dist/primitives/field/Field.js +251 -0
- package/dist/primitives/field/Suggest.d.ts +48 -0
- package/dist/primitives/field/Suggest.js +43 -0
- package/dist/primitives/field/aria.d.ts +61 -0
- package/dist/primitives/field/aria.js +70 -0
- package/dist/primitives/field/context.d.ts +30 -0
- package/dist/primitives/field/context.js +13 -0
- package/dist/primitives/field/copy.d.ts +16 -0
- package/dist/primitives/field/copy.js +69 -0
- package/dist/primitives/field/index.d.ts +9 -0
- package/dist/primitives/field/index.js +15 -0
- package/dist/primitives/field/layout.d.ts +24 -0
- package/dist/primitives/field/layout.js +10 -0
- package/dist/primitives/field/mode.d.ts +9 -0
- package/dist/primitives/field/mode.js +13 -0
- package/dist/primitives/field/note.d.ts +55 -0
- package/dist/primitives/field/note.js +117 -0
- package/dist/primitives/hint/Hint.d.ts +31 -0
- package/dist/primitives/hint/Hint.js +42 -0
- package/dist/primitives/hint/index.d.ts +1 -0
- package/dist/primitives/hint/index.js +1 -0
- package/dist/primitives/index.d.ts +20 -0
- package/dist/primitives/index.js +21 -0
- package/dist/primitives/menu/Menu.d.ts +128 -0
- package/dist/primitives/menu/Menu.js +89 -0
- package/dist/primitives/menu/index.d.ts +1 -0
- package/dist/primitives/menu/index.js +2 -0
- package/dist/primitives/notice/Callout.d.ts +49 -0
- package/dist/primitives/notice/Callout.js +28 -0
- package/dist/primitives/notice/Flag.d.ts +13 -0
- package/dist/primitives/notice/Flag.js +31 -0
- package/dist/primitives/notice/Notice.d.ts +45 -0
- package/dist/primitives/notice/Notice.js +61 -0
- package/dist/primitives/notice/Toast.d.ts +55 -0
- package/dist/primitives/notice/Toast.js +142 -0
- package/dist/primitives/notice/index.d.ts +4 -0
- package/dist/primitives/notice/index.js +6 -0
- package/dist/primitives/overlay/Popover.d.ts +6 -0
- package/dist/primitives/overlay/Popover.js +27 -0
- package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
- package/dist/primitives/overlay/PopoverTrigger.js +38 -0
- package/dist/primitives/overlay/Preview.d.ts +19 -0
- package/dist/primitives/overlay/Preview.js +60 -0
- package/dist/primitives/overlay/Tooltip.d.ts +38 -0
- package/dist/primitives/overlay/Tooltip.js +74 -0
- package/dist/primitives/overlay/index.d.ts +4 -0
- package/dist/primitives/overlay/index.js +7 -0
- package/dist/primitives/picker/Picker.d.ts +89 -0
- package/dist/primitives/picker/Picker.js +204 -0
- package/dist/primitives/picker/index.d.ts +1 -0
- package/dist/primitives/picker/index.js +2 -0
- package/dist/primitives/surface/Peek.d.ts +60 -0
- package/dist/primitives/surface/Peek.js +71 -0
- package/dist/primitives/surface/index.d.ts +1 -0
- package/dist/primitives/surface/index.js +2 -0
- package/dist/primitives/table/CellCopy.d.ts +20 -0
- package/dist/primitives/table/CellCopy.js +23 -0
- package/dist/primitives/table/CellEditor.d.ts +14 -0
- package/dist/primitives/table/CellEditor.js +83 -0
- package/dist/primitives/table/DataTable.d.ts +189 -0
- package/dist/primitives/table/DataTable.js +330 -0
- package/dist/primitives/table/EditableTable.d.ts +191 -0
- package/dist/primitives/table/EditableTable.js +617 -0
- package/dist/primitives/table/PivotTable.d.ts +14 -0
- package/dist/primitives/table/PivotTable.js +0 -0
- package/dist/primitives/table/ReportTable.d.ts +38 -0
- package/dist/primitives/table/ReportTable.js +106 -0
- package/dist/primitives/table/RowActions.d.ts +16 -0
- package/dist/primitives/table/RowActions.js +28 -0
- package/dist/primitives/table/SheetTable.d.ts +73 -0
- package/dist/primitives/table/SheetTable.js +230 -0
- package/dist/primitives/table/Table.d.ts +106 -0
- package/dist/primitives/table/Table.js +116 -0
- package/dist/primitives/table/index.d.ts +7 -0
- package/dist/primitives/table/index.js +9 -0
- package/dist/primitives/tabs/Tabs.d.ts +91 -0
- package/dist/primitives/tabs/Tabs.js +56 -0
- package/dist/primitives/tabs/index.d.ts +1 -0
- package/dist/primitives/tabs/index.js +1 -0
- package/dist/primitives/tag/Tag.d.ts +36 -0
- package/dist/primitives/tag/Tag.js +68 -0
- package/dist/primitives/tag/Tags.d.ts +57 -0
- package/dist/primitives/tag/Tags.js +104 -0
- package/dist/primitives/tag/TagsField.d.ts +35 -0
- package/dist/primitives/tag/TagsField.js +186 -0
- package/dist/primitives/tag/choices.d.ts +38 -0
- package/dist/primitives/tag/choices.js +26 -0
- package/dist/primitives/tag/index.d.ts +4 -0
- package/dist/primitives/tag/index.js +8 -0
- package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
- package/dist/primitives/toolbar/Toolbar.js +36 -0
- package/dist/primitives/toolbar/index.d.ts +1 -0
- package/dist/primitives/toolbar/index.js +2 -0
- package/dist/primitives/tree/Tree.d.ts +56 -0
- package/dist/primitives/tree/Tree.js +74 -0
- package/dist/primitives/tree/index.d.ts +1 -0
- package/dist/primitives/tree/index.js +3 -0
- package/dist/primitives/value/Copyable.d.ts +48 -0
- package/dist/primitives/value/Copyable.js +71 -0
- package/dist/primitives/value/Fecha.d.ts +12 -0
- package/dist/primitives/value/Fecha.js +89 -0
- package/dist/primitives/value/Meter.d.ts +29 -0
- package/dist/primitives/value/Meter.js +46 -0
- package/dist/primitives/value/Metric.d.ts +42 -0
- package/dist/primitives/value/Metric.js +38 -0
- package/dist/primitives/value/Person.d.ts +90 -0
- package/dist/primitives/value/Person.js +208 -0
- package/dist/primitives/value/ProgressBar.d.ts +31 -0
- package/dist/primitives/value/ProgressBar.js +53 -0
- package/dist/primitives/value/Relation.d.ts +74 -0
- package/dist/primitives/value/Relation.js +74 -0
- package/dist/primitives/value/Totals.d.ts +121 -0
- package/dist/primitives/value/Totals.js +54 -0
- package/dist/primitives/value/Value.d.ts +40 -0
- package/dist/primitives/value/Value.js +80 -0
- package/dist/primitives/value/index.d.ts +9 -0
- package/dist/primitives/value/index.js +10 -0
- package/dist/tokens.css +1062 -0
- package/dist/views/index.d.ts +2 -0
- package/dist/views/index.js +3 -0
- package/dist/views/list/List.d.ts +119 -0
- package/dist/views/list/List.js +101 -0
- package/dist/views/list/ListView.d.ts +6 -0
- package/dist/views/list/ListView.js +295 -0
- package/dist/views/list/columns.d.ts +18 -0
- package/dist/views/list/columns.js +27 -0
- package/dist/views/list/derive.d.ts +87 -0
- package/dist/views/list/derive.js +207 -0
- package/dist/views/list/index.d.ts +2 -0
- package/dist/views/list/index.js +4 -0
- package/dist/views/list/presentations.d.ts +39 -0
- package/dist/views/list/presentations.js +105 -0
- package/dist/views/list/types.d.ts +284 -0
- package/dist/views/list/types.js +1 -0
- package/dist/views/list/useNarrowList.d.ts +17 -0
- package/dist/views/list/useNarrowList.js +32 -0
- package/dist/views/record/Record.d.ts +145 -0
- package/dist/views/record/Record.js +211 -0
- package/dist/views/record/index.d.ts +1 -0
- package/dist/views/record/index.js +2 -0
- package/package.json +113 -4
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which FIELDS of a set are on screen.
|
|
3
|
+
*
|
|
4
|
+
* Odoo lists split columns in two: fields without `optional` cannot leave the
|
|
5
|
+
* view; fields with `optional="show"|"hide"` appear in the columns submenu
|
|
6
|
+
* and can be added or removed. ListView and FrameView were inventing that
|
|
7
|
+
* menu each on their own — FrameView let every column hide, ListView had no
|
|
8
|
+
* menu at all — so the same question («may I hide this?») had two answers.
|
|
9
|
+
*
|
|
10
|
+
* This hook is the third knob of a SET, next to `useSetControl` (how I look)
|
|
11
|
+
* and `useSelection` (which rows). Presentation can change; the visible
|
|
12
|
+
* field set stays.
|
|
13
|
+
*/
|
|
14
|
+
export type ColumnSpec = {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
/** When true, the column may leave the view. Required columns stay, and
|
|
18
|
+
* the submenu shows them checked and disabled so the split is visible. */
|
|
19
|
+
optional?: boolean;
|
|
20
|
+
/** Odoo `optional="hide"`: start off. Only for optional columns. */
|
|
21
|
+
defaultHidden?: boolean;
|
|
22
|
+
};
|
|
23
|
+
export type ColumnControl = {
|
|
24
|
+
visibleIds: readonly string[];
|
|
25
|
+
isVisible: (id: string) => boolean;
|
|
26
|
+
isRequired: (id: string) => boolean;
|
|
27
|
+
toggle: (id: string) => void;
|
|
28
|
+
optional: readonly ColumnSpec[];
|
|
29
|
+
required: readonly ColumnSpec[];
|
|
30
|
+
hasOptional: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare function isRequiredColumn(column: ColumnSpec): boolean;
|
|
33
|
+
export declare function defaultVisibleIds(columns: readonly ColumnSpec[]): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Required columns always stay. Unknown ids drop out. If everything optional
|
|
36
|
+
* was unchecked, the first column remains — a table with zero columns is not
|
|
37
|
+
* a table.
|
|
38
|
+
*/
|
|
39
|
+
export declare function reconcileVisible(columns: readonly ColumnSpec[], visibleIds: readonly string[]): string[];
|
|
40
|
+
export declare function toggleColumn(columns: readonly ColumnSpec[], visibleIds: readonly string[], id: string): string[];
|
|
41
|
+
export declare function visibleColumns<T extends {
|
|
42
|
+
id: string;
|
|
43
|
+
}>(columns: readonly T[], visibleIds: readonly string[]): T[];
|
|
44
|
+
export declare function useColumnControl(columns: readonly ColumnSpec[], options?: {
|
|
45
|
+
visibleIds?: readonly string[];
|
|
46
|
+
onVisibleIdsChange?: (ids: readonly string[]) => void;
|
|
47
|
+
}): ColumnControl;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
3
|
+
export function isRequiredColumn(column) {
|
|
4
|
+
return column.optional !== true;
|
|
5
|
+
}
|
|
6
|
+
export function defaultVisibleIds(columns) {
|
|
7
|
+
return columns
|
|
8
|
+
.filter((column) => isRequiredColumn(column) || column.defaultHidden !== true)
|
|
9
|
+
.map((column) => column.id);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Required columns always stay. Unknown ids drop out. If everything optional
|
|
13
|
+
* was unchecked, the first column remains — a table with zero columns is not
|
|
14
|
+
* a table.
|
|
15
|
+
*/
|
|
16
|
+
export function reconcileVisible(columns, visibleIds) {
|
|
17
|
+
if (columns.length === 0)
|
|
18
|
+
return [];
|
|
19
|
+
const known = new Set(columns.map((column) => column.id));
|
|
20
|
+
const visible = new Set(visibleIds.filter((id) => known.has(id)));
|
|
21
|
+
for (const column of columns) {
|
|
22
|
+
if (isRequiredColumn(column))
|
|
23
|
+
visible.add(column.id);
|
|
24
|
+
}
|
|
25
|
+
if (visible.size === 0)
|
|
26
|
+
visible.add(columns[0].id);
|
|
27
|
+
return columns.filter((column) => visible.has(column.id)).map((column) => column.id);
|
|
28
|
+
}
|
|
29
|
+
export function toggleColumn(columns, visibleIds, id) {
|
|
30
|
+
const column = columns.find((one) => one.id === id);
|
|
31
|
+
if (column === undefined || isRequiredColumn(column)) {
|
|
32
|
+
return reconcileVisible(columns, visibleIds);
|
|
33
|
+
}
|
|
34
|
+
const current = reconcileVisible(columns, visibleIds);
|
|
35
|
+
const next = current.includes(id) ? current.filter((one) => one !== id) : [...current, id];
|
|
36
|
+
return reconcileVisible(columns, next);
|
|
37
|
+
}
|
|
38
|
+
export function visibleColumns(columns, visibleIds) {
|
|
39
|
+
const wanted = new Set(visibleIds);
|
|
40
|
+
return columns.filter((column) => wanted.has(column.id));
|
|
41
|
+
}
|
|
42
|
+
export function useColumnControl(columns, options = {}) {
|
|
43
|
+
const signature = columns.map((column) => column.id).join("\0");
|
|
44
|
+
const [owned, setOwned] = useState(() => defaultVisibleIds(columns));
|
|
45
|
+
const [seen, setSeen] = useState(signature);
|
|
46
|
+
const controlled = options.onVisibleIdsChange !== undefined;
|
|
47
|
+
if (signature !== seen) {
|
|
48
|
+
setSeen(signature);
|
|
49
|
+
if (!controlled)
|
|
50
|
+
setOwned(defaultVisibleIds(columns));
|
|
51
|
+
}
|
|
52
|
+
const raw = controlled ? (options.visibleIds ?? defaultVisibleIds(columns)) : owned;
|
|
53
|
+
const visibleIds = reconcileVisible(columns, raw);
|
|
54
|
+
const setVisible = controlled ? options.onVisibleIdsChange : setOwned;
|
|
55
|
+
const toggle = useCallback((id) => {
|
|
56
|
+
setVisible(toggleColumn(columns, visibleIds, id));
|
|
57
|
+
}, [columns, visibleIds, setVisible]);
|
|
58
|
+
const optional = columns.filter((column) => column.optional === true);
|
|
59
|
+
const required = columns.filter((column) => isRequiredColumn(column));
|
|
60
|
+
return {
|
|
61
|
+
visibleIds,
|
|
62
|
+
isVisible: (id) => visibleIds.includes(id),
|
|
63
|
+
isRequired: (id) => {
|
|
64
|
+
const column = columns.find((one) => one.id === id);
|
|
65
|
+
return column === undefined ? true : isRequiredColumn(column);
|
|
66
|
+
},
|
|
67
|
+
toggle,
|
|
68
|
+
optional,
|
|
69
|
+
required,
|
|
70
|
+
hasOptional: optional.length > 0,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type GroupGrain, type GroupLevel } from "../../foundations/grouping.js";
|
|
2
|
+
/**
|
|
3
|
+
* LOS NIVELES, como estado que alguien edita.
|
|
4
|
+
*
|
|
5
|
+
* El vocabulario —qué es un nivel, qué es un montón, cómo se identifica— vive en
|
|
6
|
+
* `system/grouping.ts`, porque lo hablan las DOS capas: la lista arma el árbol y
|
|
7
|
+
* la tabla lo dibuja, y una primitiva no puede importar de un contexto. Acá
|
|
8
|
+
* queda sólo el CONTROL, que es de la lista como lo son el de columnas y el de
|
|
9
|
+
* plegado.
|
|
10
|
+
*/
|
|
11
|
+
/** Los niveles puestos, y las cuatro cosas que se les hacen. El orden ES la
|
|
12
|
+
* jerarquía: mover un nivel de lugar cambia por qué se parte primero. */
|
|
13
|
+
export type GroupingControl = {
|
|
14
|
+
levels: readonly GroupLevel[];
|
|
15
|
+
/**
|
|
16
|
+
* Pone un nivel. Un campo de fecha puede estar puesto con VARIOS granos —«por
|
|
17
|
+
* mes y adentro por día»— así que lo que no se duplica es el par campo+grano,
|
|
18
|
+
* no el campo. Agrupar dos veces por exactamente lo mismo parte en montones de
|
|
19
|
+
* uno, y eso siempre es un error de tipeo.
|
|
20
|
+
*/
|
|
21
|
+
add: (field: string, grain?: GroupGrain) => void;
|
|
22
|
+
remove: (field: string, grain?: GroupGrain) => void;
|
|
23
|
+
/** Tildar y destildar en un gesto, que es lo que hace una casilla. */
|
|
24
|
+
toggle: (field: string, grain?: GroupGrain) => void;
|
|
25
|
+
has: (field: string, grain?: GroupGrain) => boolean;
|
|
26
|
+
/** Mueve un nivel `delta` lugares. Se queda en los bordes en vez de dar la
|
|
27
|
+
* vuelta: llegar al primero y volver al último no es lo que nadie espera de
|
|
28
|
+
* una flecha «subir». */
|
|
29
|
+
move: (field: string, delta: number) => void;
|
|
30
|
+
clear: () => void;
|
|
31
|
+
};
|
|
32
|
+
export declare function useGroupingControl(options?: {
|
|
33
|
+
levels?: readonly GroupLevel[];
|
|
34
|
+
onLevelsChange?: (levels: readonly GroupLevel[]) => void;
|
|
35
|
+
}): GroupingControl;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { levelKey, moveLevel, normalizeLevels, } from "../../foundations/grouping.js";
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
// One empty array for every render: a fresh `[]` would change the callbacks below each time.
|
|
5
|
+
const NO_LEVELS = [];
|
|
6
|
+
export function useGroupingControl(options = {}) {
|
|
7
|
+
const [owned, setOwned] = useState(options.levels ?? NO_LEVELS);
|
|
8
|
+
const controlled = options.onLevelsChange !== undefined;
|
|
9
|
+
const levels = controlled ? (options.levels ?? NO_LEVELS) : owned;
|
|
10
|
+
const setLevels = controlled ? options.onLevelsChange : setOwned;
|
|
11
|
+
const keyOf = (field, grain) => levelKey(grain === undefined ? { field } : { field, grain });
|
|
12
|
+
const has = useCallback((field, grain) => levels.some((one) => levelKey(one) === keyOf(field, grain)), [levels]);
|
|
13
|
+
const add = useCallback((field, grain) => {
|
|
14
|
+
if (levels.some((one) => levelKey(one) === keyOf(field, grain)))
|
|
15
|
+
return;
|
|
16
|
+
/* Se normaliza SIEMPRE y no sólo al agregar fechas: es la única forma de
|
|
17
|
+
que tildar «día» y después «mes» dé «mes › día» sin que quien tilda
|
|
18
|
+
tenga que saber en qué orden hacerlo. */
|
|
19
|
+
setLevels(normalizeLevels([...levels, grain === undefined ? { field } : { field, grain }]));
|
|
20
|
+
}, [levels, setLevels]);
|
|
21
|
+
const remove = useCallback((field, grain) => {
|
|
22
|
+
setLevels(grain === undefined && !levels.some((one) => levelKey(one) === field)
|
|
23
|
+
? /* Sin grano y sin coincidencia exacta: se saca el campo ENTERO, con
|
|
24
|
+
todos sus granos. Es lo que quiere decir la × de un campo de
|
|
25
|
+
fecha que está puesto por mes y por día. */
|
|
26
|
+
levels.filter((one) => one.field !== field)
|
|
27
|
+
: levels.filter((one) => levelKey(one) !== keyOf(field, grain)));
|
|
28
|
+
}, [levels, setLevels]);
|
|
29
|
+
return {
|
|
30
|
+
levels,
|
|
31
|
+
has,
|
|
32
|
+
add,
|
|
33
|
+
remove,
|
|
34
|
+
toggle: useCallback((field, grain) => {
|
|
35
|
+
if (levels.some((one) => levelKey(one) === keyOf(field, grain)))
|
|
36
|
+
remove(field, grain);
|
|
37
|
+
else
|
|
38
|
+
add(field, grain);
|
|
39
|
+
}, [levels, add, remove]),
|
|
40
|
+
move: useCallback((field, delta) => {
|
|
41
|
+
setLevels(moveLevel(levels, field, delta));
|
|
42
|
+
}, [levels, setLevels]),
|
|
43
|
+
clear: useCallback(() => {
|
|
44
|
+
setLevels([]);
|
|
45
|
+
}, [setLevels]),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { type ColumnControl, type ColumnSpec, defaultVisibleIds, reconcileVisible, toggleColumn, useColumnControl, visibleColumns, } from "./columns.js";
|
|
2
|
+
export { ColumnsMenu, specsFromColumns, } from "./ColumnsMenu.js";
|
|
3
|
+
export { Listing } from "./Listing.js";
|
|
4
|
+
export { type ListIdentity, ListingBar } from "./ListingBar.js";
|
|
5
|
+
export { type ClaimedField, claimedFields, columnsFromModel, readModelValue, } from "./modelColumns.js";
|
|
6
|
+
export { type CursorSource, DEFAULT_PAGE_SIZE, PAGE_SIZES, type Pages, type PaginationBridge, type ServerPage, useCursorPagination, usePagination, } from "./pages.js";
|
|
7
|
+
export { Pagination } from "./Pagination.js";
|
|
8
|
+
export { RecordLink } from "./RecordLink.js";
|
|
9
|
+
export { GroupHeader, Row } from "./Row.js";
|
|
10
|
+
export { type Selection, useSelection } from "./selection.js";
|
|
11
|
+
export { type SetControl, useSetControl } from "./set.js";
|
|
12
|
+
export { toggleCollapsed, type TreeControl, useTreeControl, } from "./tree.js";
|
|
13
|
+
export { type ViewOption, ViewSwitcher, } from "./ViewSwitcher.js";
|
|
14
|
+
export { type GroupingControl, useGroupingControl, } from "./grouping.js";
|
|
15
|
+
export { GroupingLevels, type GroupingLevelsProps, } from "./GroupingLevels.js";
|
|
16
|
+
export { RecordCard } from "./RecordCard.js";
|
|
17
|
+
export { type SortField, SortMenu, type SortMenuProps, } from "./SortMenu.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* LISTING — the pieces a collection of N records is drawn with: rows, columns, paging,
|
|
2
|
+
sorting, grouping. The `List` view composes them; any other component may reuse them. */
|
|
3
|
+
export { defaultVisibleIds, reconcileVisible, toggleColumn, useColumnControl, visibleColumns, } from "./columns.js";
|
|
4
|
+
export { ColumnsMenu, specsFromColumns, } from "./ColumnsMenu.js";
|
|
5
|
+
export { Listing } from "./Listing.js";
|
|
6
|
+
export { ListingBar } from "./ListingBar.js";
|
|
7
|
+
export { claimedFields, columnsFromModel, readModelValue, } from "./modelColumns.js";
|
|
8
|
+
export { DEFAULT_PAGE_SIZE, PAGE_SIZES, useCursorPagination, usePagination, } from "./pages.js";
|
|
9
|
+
export { Pagination } from "./Pagination.js";
|
|
10
|
+
export { RecordLink } from "./RecordLink.js";
|
|
11
|
+
export { GroupHeader, Row } from "./Row.js";
|
|
12
|
+
export { useSelection } from "./selection.js";
|
|
13
|
+
export { useSetControl } from "./set.js";
|
|
14
|
+
export { toggleCollapsed, useTreeControl, } from "./tree.js";
|
|
15
|
+
export { ViewSwitcher, } from "./ViewSwitcher.js";
|
|
16
|
+
/* El control de los niveles. El vocabulario está en `system/grouping.ts`. */
|
|
17
|
+
export { useGroupingControl, } from "./grouping.js";
|
|
18
|
+
export { GroupingLevels, } from "./GroupingLevels.js";
|
|
19
|
+
export { RecordCard } from "./RecordCard.js";
|
|
20
|
+
export { SortMenu, } from "./SortMenu.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type ModelDefinition } from "../search/model.js";
|
|
2
|
+
import type { DataColumn } from "../../primitives/table/DataTable.js";
|
|
3
|
+
/** Qué campo del modelo muestra una columna declarada a mano.
|
|
4
|
+
*
|
|
5
|
+
* Sin esto, «filas» y `row_count` son dos cosas distintas y el campo
|
|
6
|
+
* aparecería dos veces: una con el rótulo bueno y otra cruda. */
|
|
7
|
+
export type ClaimedField = {
|
|
8
|
+
id: string;
|
|
9
|
+
field?: string;
|
|
10
|
+
};
|
|
11
|
+
/** Los campos que las columnas declaradas ya contestan.
|
|
12
|
+
*
|
|
13
|
+
* in [{id:"rows", field:"row_count"}, {id:"name"}]
|
|
14
|
+
* out Set {"row_count", "name"}
|
|
15
|
+
*
|
|
16
|
+
* El `id` cuenta como reclamo cuando coincide con el nombre del campo: la
|
|
17
|
+
* mayoría de las columnas se llaman como lo que muestran. */
|
|
18
|
+
export declare function claimedFields(columns: readonly ClaimedField[]): Set<string>;
|
|
19
|
+
/** Un valor del registro, en algo que una celda pueda ordenar y dibujar.
|
|
20
|
+
*
|
|
21
|
+
* in ["edad","sexo"] un `text[]`
|
|
22
|
+
* out "edad, sexo"
|
|
23
|
+
* in [] una lista vacía no es un texto vacío
|
|
24
|
+
* out null la tabla dibuja «—»
|
|
25
|
+
* in "2026-09-13T04:17:00"
|
|
26
|
+
* out "2026-09-13T04:17:00"
|
|
27
|
+
*
|
|
28
|
+
* Las fechas se dejan como vienen —ISO— porque en ISO el orden alfabético ES
|
|
29
|
+
* el cronológico, y así la columna ordena bien sin parsear nada. */
|
|
30
|
+
export declare function readModelValue(raw: unknown): string | number | boolean | null;
|
|
31
|
+
/**
|
|
32
|
+
* Las columnas declaradas, más una por cada campo del modelo que ninguna
|
|
33
|
+
* contestaba.
|
|
34
|
+
*
|
|
35
|
+
* in model el `model_meta_data` de `account.move` — 12 campos
|
|
36
|
+
* in declared 6 columnas escritas a mano en la vista
|
|
37
|
+
* out las 6, y detrás `journal_id`, `currency_id`, `invoice_origin`,
|
|
38
|
+
* `payment_reference`, `fiscal_position_id`, `company_id`,
|
|
39
|
+
* `move_type`, todas apagadas
|
|
40
|
+
*
|
|
41
|
+
* El rótulo de las agregadas sale de `model.translations` en el idioma pedido —el mismo
|
|
42
|
+
* `translations()` que el modelo publica para todo lo demás—, con el `default` que ese mismo
|
|
43
|
+
* modelo declaró si el idioma no está, y el nombre del campo tal cual sólo si el modelo no
|
|
44
|
+
* dijo ninguna palabra sobre él. Nunca puede desincronizarse con el motor porque sale de la
|
|
45
|
+
* MISMA respuesta que nombra el campo — a diferencia de un diccionario mantenido acá.
|
|
46
|
+
*
|
|
47
|
+
* `model === null` —todavía no llegó la definición— devuelve las declaradas sin
|
|
48
|
+
* tocar, que es exactamente lo que había antes.
|
|
49
|
+
*/
|
|
50
|
+
export declare function columnsFromModel<T extends object>(declared: readonly DataColumn<T>[], model: ModelDefinition | null | undefined, locale?: string): DataColumn<T>[];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { fieldLabel } from "../search/model.js";
|
|
3
|
+
/** Los campos que las columnas declaradas ya contestan.
|
|
4
|
+
*
|
|
5
|
+
* in [{id:"rows", field:"row_count"}, {id:"name"}]
|
|
6
|
+
* out Set {"row_count", "name"}
|
|
7
|
+
*
|
|
8
|
+
* El `id` cuenta como reclamo cuando coincide con el nombre del campo: la
|
|
9
|
+
* mayoría de las columnas se llaman como lo que muestran. */
|
|
10
|
+
export function claimedFields(columns) {
|
|
11
|
+
const claimed = new Set();
|
|
12
|
+
for (const column of columns) {
|
|
13
|
+
claimed.add(column.field ?? column.id);
|
|
14
|
+
if (column.field !== undefined)
|
|
15
|
+
claimed.add(column.id);
|
|
16
|
+
}
|
|
17
|
+
return claimed;
|
|
18
|
+
}
|
|
19
|
+
/** Un valor del registro, en algo que una celda pueda ordenar y dibujar.
|
|
20
|
+
*
|
|
21
|
+
* in ["edad","sexo"] un `text[]`
|
|
22
|
+
* out "edad, sexo"
|
|
23
|
+
* in [] una lista vacía no es un texto vacío
|
|
24
|
+
* out null la tabla dibuja «—»
|
|
25
|
+
* in "2026-09-13T04:17:00"
|
|
26
|
+
* out "2026-09-13T04:17:00"
|
|
27
|
+
*
|
|
28
|
+
* Las fechas se dejan como vienen —ISO— porque en ISO el orden alfabético ES
|
|
29
|
+
* el cronológico, y así la columna ordena bien sin parsear nada. */
|
|
30
|
+
export function readModelValue(raw) {
|
|
31
|
+
if (raw === null || raw === undefined)
|
|
32
|
+
return null;
|
|
33
|
+
if (Array.isArray(raw))
|
|
34
|
+
return raw.length === 0 ? null : raw.join(", ");
|
|
35
|
+
if (typeof raw === "number" || typeof raw === "boolean")
|
|
36
|
+
return raw;
|
|
37
|
+
if (typeof raw === "string")
|
|
38
|
+
return raw === "" ? null : raw;
|
|
39
|
+
return JSON.stringify(raw);
|
|
40
|
+
}
|
|
41
|
+
/** Cuánto ancho merece un campo, por su tipo. Una huella son 64 caracteres y
|
|
42
|
+
* una cuenta son seis: darles el mismo ancho arruina las dos. */
|
|
43
|
+
function widthFor(type) {
|
|
44
|
+
if (type === "integer" || type === "number")
|
|
45
|
+
return 120;
|
|
46
|
+
if (type === "datetime" || type === "date")
|
|
47
|
+
return 190;
|
|
48
|
+
if (type === "boolean")
|
|
49
|
+
return 90;
|
|
50
|
+
return 260;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Las columnas declaradas, más una por cada campo del modelo que ninguna
|
|
54
|
+
* contestaba.
|
|
55
|
+
*
|
|
56
|
+
* in model el `model_meta_data` de `account.move` — 12 campos
|
|
57
|
+
* in declared 6 columnas escritas a mano en la vista
|
|
58
|
+
* out las 6, y detrás `journal_id`, `currency_id`, `invoice_origin`,
|
|
59
|
+
* `payment_reference`, `fiscal_position_id`, `company_id`,
|
|
60
|
+
* `move_type`, todas apagadas
|
|
61
|
+
*
|
|
62
|
+
* El rótulo de las agregadas sale de `model.translations` en el idioma pedido —el mismo
|
|
63
|
+
* `translations()` que el modelo publica para todo lo demás—, con el `default` que ese mismo
|
|
64
|
+
* modelo declaró si el idioma no está, y el nombre del campo tal cual sólo si el modelo no
|
|
65
|
+
* dijo ninguna palabra sobre él. Nunca puede desincronizarse con el motor porque sale de la
|
|
66
|
+
* MISMA respuesta que nombra el campo — a diferencia de un diccionario mantenido acá.
|
|
67
|
+
*
|
|
68
|
+
* `model === null` —todavía no llegó la definición— devuelve las declaradas sin
|
|
69
|
+
* tocar, que es exactamente lo que había antes.
|
|
70
|
+
*/
|
|
71
|
+
export function columnsFromModel(declared, model, locale = "default") {
|
|
72
|
+
if (model === null || model === undefined)
|
|
73
|
+
return [...declared];
|
|
74
|
+
const claimed = claimedFields(declared);
|
|
75
|
+
const extra = [];
|
|
76
|
+
for (const [field, definition] of Object.entries(model.fields)) {
|
|
77
|
+
if (claimed.has(field))
|
|
78
|
+
continue;
|
|
79
|
+
const numeric = definition.type === "integer" || definition.type === "number";
|
|
80
|
+
extra.push({
|
|
81
|
+
id: field,
|
|
82
|
+
field,
|
|
83
|
+
header: fieldLabel(model, field, locale),
|
|
84
|
+
value: (row) => readModelValue(row[field]),
|
|
85
|
+
/* El valor entero en el `title`: una huella de 64 caracteres no entra en
|
|
86
|
+
ninguna celda, y cortada no sirve para nada — salvo para pasar el mouse
|
|
87
|
+
por encima y leerla. */
|
|
88
|
+
cell: (row) => {
|
|
89
|
+
const value = readModelValue(row[field]);
|
|
90
|
+
if (value === null)
|
|
91
|
+
return _jsx("span", { className: "text-subtle", children: "\u2014" });
|
|
92
|
+
const text = String(value);
|
|
93
|
+
return (_jsx("span", { className: "block truncate text-muted", title: text, children: text }));
|
|
94
|
+
},
|
|
95
|
+
...(numeric ? { numeric: true } : {}),
|
|
96
|
+
sortable: definition.sortable,
|
|
97
|
+
optional: true,
|
|
98
|
+
defaultHidden: true,
|
|
99
|
+
width: widthFor(definition.type),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return [...declared, ...extra];
|
|
103
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many records a list shows until someone asks for another size.
|
|
3
|
+
*
|
|
4
|
+
* 80 is the product default: enough to scan a catalog without paging every
|
|
5
|
+
* twenty-five rows, still small enough that a table stays a table. 25 / 80 / 100
|
|
6
|
+
* are the presets; any other positive integer is valid and is kept.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_PAGE_SIZE = 80;
|
|
9
|
+
export declare const PAGE_SIZES: readonly [25, 80, 100];
|
|
10
|
+
/** What `Pagination` needs, already computed from a set in memory. */
|
|
11
|
+
export interface PaginationBridge {
|
|
12
|
+
from: number;
|
|
13
|
+
to: number;
|
|
14
|
+
total: number;
|
|
15
|
+
pageSize: number;
|
|
16
|
+
onPageSizeChange: (next: number) => void;
|
|
17
|
+
onPrev?: () => void;
|
|
18
|
+
onNext?: () => void;
|
|
19
|
+
}
|
|
20
|
+
export interface Pages<Row> {
|
|
21
|
+
pageItems: Row[];
|
|
22
|
+
bridge: PaginationBridge;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Cut an in-memory set into pages.
|
|
26
|
+
*
|
|
27
|
+
* `pageSize` is controlled when `onPageSizeChange` is passed — that is how a
|
|
28
|
+
* view context keeps the size across filters. Without it the hook owns the
|
|
29
|
+
* size, defaulting to 80.
|
|
30
|
+
*
|
|
31
|
+
* The current page is clamped in the calculation, not in an effect: if a
|
|
32
|
+
* filter leaves fewer pages than the current one, the next read already
|
|
33
|
+
* lands on the last page that exists.
|
|
34
|
+
*/
|
|
35
|
+
export declare function usePagination<Row>(items: readonly Row[], options?: {
|
|
36
|
+
pageSize?: number;
|
|
37
|
+
onPageSizeChange?: (next: number) => void;
|
|
38
|
+
}): Pages<Row>;
|
|
39
|
+
/** What a server answered about a page: the records, where the next one starts, how many
|
|
40
|
+
* match in total. `exact: false` on the count means *at least* that many — a server that
|
|
41
|
+
* stops counting at a ceiling so its cost does not grow with the table. */
|
|
42
|
+
export interface ServerPage<Row> {
|
|
43
|
+
items: readonly Row[];
|
|
44
|
+
cursor: string | null;
|
|
45
|
+
count: {
|
|
46
|
+
value: number;
|
|
47
|
+
exact: boolean;
|
|
48
|
+
} | null;
|
|
49
|
+
}
|
|
50
|
+
/** What a cursor-paged list needs from whoever fetches it. */
|
|
51
|
+
export interface CursorSource<Row> {
|
|
52
|
+
/** The page at this cursor. `undefined` asks for the first one. */
|
|
53
|
+
fetch: (cursor: string | undefined, pageSize: number) => Promise<ServerPage<Row>>;
|
|
54
|
+
/** Anything that changes WHICH records are being paged — a filter, an ordering. Pagination
|
|
55
|
+
* starts over when this changes, because page 3 of another reading is not page 3. */
|
|
56
|
+
resetKey?: unknown;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Cut a *server-side* set into pages, by cursor.
|
|
60
|
+
*
|
|
61
|
+
* `usePagination` slices an array that is already in hand; this one never holds the whole set.
|
|
62
|
+
* Same `bridge`, so `Pagination` draws it identically — the difference is invisible to a view.
|
|
63
|
+
*
|
|
64
|
+
* **Going back is a walk, not a jump.** A keyset cursor names the last row seen, so it points
|
|
65
|
+
* forward and nothing points back. Rather than fake it with an offset — which would reintroduce
|
|
66
|
+
* exactly the incoherence the cursor exists to avoid — the cursors already walked are kept, and
|
|
67
|
+
* `onPrev` re-fetches with the one before. That is one request, not a re-walk from the start.
|
|
68
|
+
*
|
|
69
|
+
* **Changing the size starts over**, and that is not a limitation: a cursor belongs to the page
|
|
70
|
+
* size it was minted under, so keeping it would land somewhere nobody asked for.
|
|
71
|
+
*/
|
|
72
|
+
export declare function useCursorPagination<Row>(source: CursorSource<Row>, options?: {
|
|
73
|
+
pageSize?: number;
|
|
74
|
+
onPageSizeChange?: (next: number) => void;
|
|
75
|
+
}): Pages<Row> & {
|
|
76
|
+
loading: boolean;
|
|
77
|
+
error: unknown;
|
|
78
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* How many records a list shows until someone asks for another size.
|
|
5
|
+
*
|
|
6
|
+
* 80 is the product default: enough to scan a catalog without paging every
|
|
7
|
+
* twenty-five rows, still small enough that a table stays a table. 25 / 80 / 100
|
|
8
|
+
* are the presets; any other positive integer is valid and is kept.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_PAGE_SIZE = 80;
|
|
11
|
+
export const PAGE_SIZES = [25, 80, 100];
|
|
12
|
+
function clampSize(next) {
|
|
13
|
+
if (!Number.isFinite(next))
|
|
14
|
+
return DEFAULT_PAGE_SIZE;
|
|
15
|
+
return Math.max(1, Math.floor(next));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Cut an in-memory set into pages.
|
|
19
|
+
*
|
|
20
|
+
* `pageSize` is controlled when `onPageSizeChange` is passed — that is how a
|
|
21
|
+
* view context keeps the size across filters. Without it the hook owns the
|
|
22
|
+
* size, defaulting to 80.
|
|
23
|
+
*
|
|
24
|
+
* The current page is clamped in the calculation, not in an effect: if a
|
|
25
|
+
* filter leaves fewer pages than the current one, the next read already
|
|
26
|
+
* lands on the last page that exists.
|
|
27
|
+
*/
|
|
28
|
+
export function usePagination(items, options = {}) {
|
|
29
|
+
const controlled = options.pageSize !== undefined && options.onPageSizeChange !== undefined;
|
|
30
|
+
const [ownedSize, setOwnedSize] = useState(options.pageSize ?? DEFAULT_PAGE_SIZE);
|
|
31
|
+
const [page, setPage] = useState(1);
|
|
32
|
+
const pageSize = clampSize(controlled ? (options.pageSize ?? DEFAULT_PAGE_SIZE) : ownedSize);
|
|
33
|
+
const setPageSize = controlled ? options.onPageSizeChange : setOwnedSize;
|
|
34
|
+
const total = items.length;
|
|
35
|
+
const pages = Math.max(1, Math.ceil(total / pageSize));
|
|
36
|
+
const current = Math.min(page, pages);
|
|
37
|
+
const from = total === 0 ? 0 : (current - 1) * pageSize + 1;
|
|
38
|
+
const to = Math.min(current * pageSize, total);
|
|
39
|
+
return {
|
|
40
|
+
pageItems: items.slice(from > 0 ? from - 1 : 0, to),
|
|
41
|
+
bridge: {
|
|
42
|
+
from,
|
|
43
|
+
to,
|
|
44
|
+
total,
|
|
45
|
+
pageSize,
|
|
46
|
+
onPageSizeChange: (next) => {
|
|
47
|
+
setPageSize(clampSize(next));
|
|
48
|
+
setPage(1);
|
|
49
|
+
},
|
|
50
|
+
/* Se mueve desde `current` y no desde `page`, que es el que puede haber
|
|
51
|
+
quedado alto: si estabas en la 3 y un filtro dejó dos páginas, `page`
|
|
52
|
+
sigue en 3 mientras se muestra la 2, y `p - 1` volvía a 2 — o sea que
|
|
53
|
+
«anterior» no hacía nada hasta el segundo clic. */
|
|
54
|
+
...(current > 1 ? { onPrev: () => setPage(current - 1) } : {}),
|
|
55
|
+
...(current < pages ? { onNext: () => setPage(current + 1) } : {}),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Cut a *server-side* set into pages, by cursor.
|
|
61
|
+
*
|
|
62
|
+
* `usePagination` slices an array that is already in hand; this one never holds the whole set.
|
|
63
|
+
* Same `bridge`, so `Pagination` draws it identically — the difference is invisible to a view.
|
|
64
|
+
*
|
|
65
|
+
* **Going back is a walk, not a jump.** A keyset cursor names the last row seen, so it points
|
|
66
|
+
* forward and nothing points back. Rather than fake it with an offset — which would reintroduce
|
|
67
|
+
* exactly the incoherence the cursor exists to avoid — the cursors already walked are kept, and
|
|
68
|
+
* `onPrev` re-fetches with the one before. That is one request, not a re-walk from the start.
|
|
69
|
+
*
|
|
70
|
+
* **Changing the size starts over**, and that is not a limitation: a cursor belongs to the page
|
|
71
|
+
* size it was minted under, so keeping it would land somewhere nobody asked for.
|
|
72
|
+
*/
|
|
73
|
+
export function useCursorPagination(source, options = {}) {
|
|
74
|
+
const controlled = options.pageSize !== undefined && options.onPageSizeChange !== undefined;
|
|
75
|
+
const [ownedSize, setOwnedSize] = useState(options.pageSize ?? DEFAULT_PAGE_SIZE);
|
|
76
|
+
const pageSize = clampSize(controlled ? options.pageSize : ownedSize);
|
|
77
|
+
/* The cursor that opened each page walked so far. `[undefined]` is the first page, which
|
|
78
|
+
opens with no cursor — so `walked.length` is the page number. */
|
|
79
|
+
const [walked, setWalked] = useState([undefined]);
|
|
80
|
+
const [page, setPage] = useState(null);
|
|
81
|
+
const [loading, setLoading] = useState(true);
|
|
82
|
+
const [error, setError] = useState(null);
|
|
83
|
+
const at = walked[walked.length - 1];
|
|
84
|
+
/* Starting over is keyed on what changes the reading — the filter, the ordering, the size.
|
|
85
|
+
Without this an `onPrev` after a filter change would re-fetch a cursor minted for the
|
|
86
|
+
previous reading, and the server would refuse it. */
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
setWalked([undefined]);
|
|
89
|
+
}, [source.resetKey, pageSize]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
let current = true;
|
|
92
|
+
setLoading(true);
|
|
93
|
+
source
|
|
94
|
+
.fetch(at, pageSize)
|
|
95
|
+
.then((answer) => {
|
|
96
|
+
if (!current)
|
|
97
|
+
return;
|
|
98
|
+
setPage(answer);
|
|
99
|
+
setError(null);
|
|
100
|
+
})
|
|
101
|
+
.catch((failure) => current && setError(failure))
|
|
102
|
+
.finally(() => current && setLoading(false));
|
|
103
|
+
return () => {
|
|
104
|
+
current = false;
|
|
105
|
+
};
|
|
106
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
107
|
+
}, [at, pageSize, source.resetKey]);
|
|
108
|
+
const items = page?.items ?? [];
|
|
109
|
+
const total = page?.count?.value ?? items.length;
|
|
110
|
+
const from = items.length === 0 ? 0 : (walked.length - 1) * pageSize + 1;
|
|
111
|
+
const to = from === 0 ? 0 : from + items.length - 1;
|
|
112
|
+
return {
|
|
113
|
+
pageItems: [...items],
|
|
114
|
+
loading,
|
|
115
|
+
error,
|
|
116
|
+
bridge: {
|
|
117
|
+
from,
|
|
118
|
+
to,
|
|
119
|
+
total,
|
|
120
|
+
pageSize,
|
|
121
|
+
onPageSizeChange: (next) => {
|
|
122
|
+
const size = clampSize(next);
|
|
123
|
+
if (controlled)
|
|
124
|
+
options.onPageSizeChange(size);
|
|
125
|
+
else
|
|
126
|
+
setOwnedSize(size);
|
|
127
|
+
setWalked([undefined]);
|
|
128
|
+
},
|
|
129
|
+
...(walked.length > 1 ? { onPrev: () => setWalked((so) => so.slice(0, -1)) } : {}),
|
|
130
|
+
...(page?.cursor != null
|
|
131
|
+
? { onNext: () => setWalked((so) => [...so, page.cursor ?? undefined]) }
|
|
132
|
+
: {}),
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Choosing rows of a SET. Lives next to pagination because both are how N
|
|
3
|
+
* records are controlled, not how they are drawn.
|
|
4
|
+
*
|
|
5
|
+
* «All» means the ones ON SCREEN: with a filter on, select-all cannot take
|
|
6
|
+
* what the filter hid. Chosen ids survive filtering — the Set stores ids,
|
|
7
|
+
* not positions — so you can choose, filter again, choose more, and act on
|
|
8
|
+
* both batches.
|
|
9
|
+
*/
|
|
10
|
+
export interface Selection {
|
|
11
|
+
chosen: ReadonlySet<string>;
|
|
12
|
+
count: number;
|
|
13
|
+
has: (id: string) => boolean;
|
|
14
|
+
toggle: (id: string) => void;
|
|
15
|
+
toggleAll: () => void;
|
|
16
|
+
/** `true` all, `false` none, `"some"` part — the third state is the half
|
|
17
|
+
* checkbox, and without it the header lies when three of ten are chosen. */
|
|
18
|
+
allState: boolean | "some";
|
|
19
|
+
clear: () => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function useSelection(visible: readonly string[]): Selection;
|