@multiplatform.one/frappe-ui 7.10.0 → 7.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cjs/FrappeDashboard.cjs +8 -10
- package/dist/cjs/FrappeDashboard.native.js +8 -14
- package/dist/cjs/FrappeDashboard.native.js.map +1 -1
- package/dist/cjs/FrappeForm.cjs +465 -175
- package/dist/cjs/FrappeForm.native.js +611 -207
- package/dist/cjs/FrappeForm.native.js.map +1 -1
- package/dist/cjs/FrappeFormConnections.cjs +125 -0
- package/dist/cjs/FrappeFormConnections.native.js +149 -0
- package/dist/cjs/FrappeFormConnections.native.js.map +1 -0
- package/dist/cjs/FrappeImageView.cjs +7 -9
- package/dist/cjs/FrappeImageView.native.js +7 -13
- package/dist/cjs/FrappeImageView.native.js.map +1 -1
- package/dist/cjs/FrappeList.cjs +29 -4
- package/dist/cjs/FrappeList.native.js +37 -5
- package/dist/cjs/FrappeList.native.js.map +1 -1
- package/dist/cjs/FrappeListView.cjs +102 -29
- package/dist/cjs/FrappeListView.native.js +109 -29
- package/dist/cjs/FrappeListView.native.js.map +1 -1
- package/dist/cjs/FrappeProvider.cjs +7 -2
- package/dist/cjs/FrappeProvider.native.js +7 -2
- package/dist/cjs/FrappeProvider.native.js.map +1 -1
- package/dist/cjs/FrappeReportBuilder.cjs +10 -11
- package/dist/cjs/FrappeReportBuilder.native.js +10 -16
- package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
- package/dist/cjs/FrappeRowsReader.cjs +49 -0
- package/dist/cjs/FrappeRowsReader.native.js +53 -0
- package/dist/cjs/FrappeRowsReader.native.js.map +1 -0
- package/dist/cjs/FrappeTable.cjs +18 -6
- package/dist/cjs/FrappeTable.native.js +24 -6
- package/dist/cjs/FrappeTable.native.js.map +1 -1
- package/dist/cjs/fieldRegistry.cjs +36 -11
- package/dist/cjs/fieldRegistry.native.js +59 -11
- package/dist/cjs/fieldRegistry.native.js.map +1 -1
- package/dist/cjs/fields/Date/index.native.js +5 -2
- package/dist/cjs/fields/Date/index.native.js.map +1 -1
- package/dist/cjs/fields/Date/sheetKeyboard.cjs +2 -2
- package/dist/cjs/fields/Date/sheetKeyboard.native.js +2 -2
- package/dist/cjs/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/cjs/fields/DateRange/index.cjs +2 -1
- package/dist/cjs/fields/DateRange/index.native.js +4 -1
- package/dist/cjs/fields/DateRange/index.native.js.map +1 -1
- package/dist/cjs/formEngine/index.cjs +4 -2
- package/dist/cjs/formEngine/index.native.js +3 -1
- package/dist/cjs/formEngine/index.native.js.map +1 -1
- package/dist/cjs/formEngine/useSessionRoles.cjs +77 -0
- package/dist/cjs/formEngine/useSessionRoles.native.js +84 -0
- package/dist/cjs/formEngine/useSessionRoles.native.js.map +1 -0
- package/dist/cjs/frappeConnections.cjs +41 -0
- package/dist/cjs/frappeConnections.native.js +50 -0
- package/dist/cjs/frappeConnections.native.js.map +1 -0
- package/dist/cjs/frappeNavigation.cjs +107 -0
- package/dist/cjs/frappeNavigation.native.js +112 -0
- package/dist/cjs/frappeNavigation.native.js.map +1 -0
- package/dist/cjs/index.cjs +18 -1
- package/dist/cjs/index.native.js +18 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/schemaForm/index.cjs +4 -1
- package/dist/cjs/schemaForm/index.native.js +4 -1
- package/dist/cjs/schemaForm/index.native.js.map +1 -1
- package/dist/cjs/useConnectionFilters.cjs +146 -0
- package/dist/cjs/useConnectionFilters.native.js +213 -0
- package/dist/cjs/useConnectionFilters.native.js.map +1 -0
- package/dist/cjs/useFrappePagination.cjs +37 -18
- package/dist/cjs/useFrappePagination.native.js +59 -23
- package/dist/cjs/useFrappePagination.native.js.map +1 -1
- package/dist/cjs/useFrappeRows.native.js.map +1 -1
- package/dist/esm/FrappeDashboard.mjs +9 -11
- package/dist/esm/FrappeDashboard.mjs.map +1 -1
- package/dist/esm/FrappeDashboard.native.js +9 -15
- package/dist/esm/FrappeDashboard.native.js.map +1 -1
- package/dist/esm/FrappeForm.mjs +471 -181
- package/dist/esm/FrappeForm.mjs.map +1 -1
- package/dist/esm/FrappeForm.native.js +599 -195
- package/dist/esm/FrappeForm.native.js.map +1 -1
- package/dist/esm/FrappeFormConnections.mjs +99 -0
- package/dist/esm/FrappeFormConnections.mjs.map +1 -0
- package/dist/esm/FrappeFormConnections.native.js +120 -0
- package/dist/esm/FrappeFormConnections.native.js.map +1 -0
- package/dist/esm/FrappeImageView.mjs +8 -10
- package/dist/esm/FrappeImageView.mjs.map +1 -1
- package/dist/esm/FrappeImageView.native.js +8 -14
- package/dist/esm/FrappeImageView.native.js.map +1 -1
- package/dist/esm/FrappeList.mjs +31 -6
- package/dist/esm/FrappeList.mjs.map +1 -1
- package/dist/esm/FrappeList.native.js +39 -7
- package/dist/esm/FrappeList.native.js.map +1 -1
- package/dist/esm/FrappeListView.mjs +103 -30
- package/dist/esm/FrappeListView.mjs.map +1 -1
- package/dist/esm/FrappeListView.native.js +110 -30
- package/dist/esm/FrappeListView.native.js.map +1 -1
- package/dist/esm/FrappeProvider.mjs +7 -2
- package/dist/esm/FrappeProvider.mjs.map +1 -1
- package/dist/esm/FrappeProvider.native.js +7 -2
- package/dist/esm/FrappeProvider.native.js.map +1 -1
- package/dist/esm/FrappeReportBuilder.mjs +11 -12
- package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
- package/dist/esm/FrappeReportBuilder.native.js +11 -17
- package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
- package/dist/esm/FrappeRowsReader.mjs +24 -0
- package/dist/esm/FrappeRowsReader.mjs.map +1 -0
- package/dist/esm/FrappeRowsReader.native.js +25 -0
- package/dist/esm/FrappeRowsReader.native.js.map +1 -0
- package/dist/esm/FrappeTable.mjs +18 -6
- package/dist/esm/FrappeTable.mjs.map +1 -1
- package/dist/esm/FrappeTable.native.js +24 -6
- package/dist/esm/FrappeTable.native.js.map +1 -1
- package/dist/esm/fieldRegistry.mjs +35 -12
- package/dist/esm/fieldRegistry.mjs.map +1 -1
- package/dist/esm/fieldRegistry.native.js +58 -12
- package/dist/esm/fieldRegistry.native.js.map +1 -1
- package/dist/esm/fields/Date/index.native.js +6 -3
- package/dist/esm/fields/Date/index.native.js.map +1 -1
- package/dist/esm/fields/Date/sheetKeyboard.mjs +2 -2
- package/dist/esm/fields/Date/sheetKeyboard.mjs.map +1 -1
- package/dist/esm/fields/Date/sheetKeyboard.native.js +2 -2
- package/dist/esm/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/esm/fields/DateRange/index.mjs +2 -1
- package/dist/esm/fields/DateRange/index.mjs.map +1 -1
- package/dist/esm/fields/DateRange/index.native.js +4 -1
- package/dist/esm/fields/DateRange/index.native.js.map +1 -1
- package/dist/esm/formEngine/index.mjs +2 -1
- package/dist/esm/formEngine/index.mjs.map +1 -1
- package/dist/esm/formEngine/index.native.js +2 -1
- package/dist/esm/formEngine/index.native.js.map +1 -1
- package/dist/esm/formEngine/useSessionRoles.mjs +52 -0
- package/dist/esm/formEngine/useSessionRoles.mjs.map +1 -0
- package/dist/esm/formEngine/useSessionRoles.native.js +56 -0
- package/dist/esm/formEngine/useSessionRoles.native.js.map +1 -0
- package/dist/esm/frappeConnections.mjs +15 -0
- package/dist/esm/frappeConnections.mjs.map +1 -0
- package/dist/esm/frappeConnections.native.js +21 -0
- package/dist/esm/frappeConnections.native.js.map +1 -0
- package/dist/esm/frappeNavigation.mjs +78 -0
- package/dist/esm/frappeNavigation.mjs.map +1 -0
- package/dist/esm/frappeNavigation.native.js +80 -0
- package/dist/esm/frappeNavigation.native.js.map +1 -0
- package/dist/esm/index.mjs +7 -3
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +7 -3
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/schemaForm/index.mjs +4 -1
- package/dist/esm/schemaForm/index.mjs.map +1 -1
- package/dist/esm/schemaForm/index.native.js +4 -1
- package/dist/esm/schemaForm/index.native.js.map +1 -1
- package/dist/esm/useConnectionFilters.mjs +121 -0
- package/dist/esm/useConnectionFilters.mjs.map +1 -0
- package/dist/esm/useConnectionFilters.native.js +185 -0
- package/dist/esm/useConnectionFilters.native.js.map +1 -0
- package/dist/esm/useFrappePagination.mjs +38 -19
- package/dist/esm/useFrappePagination.mjs.map +1 -1
- package/dist/esm/useFrappePagination.native.js +60 -24
- package/dist/esm/useFrappePagination.native.js.map +1 -1
- package/dist/esm/useFrappeRows.mjs.map +1 -1
- package/dist/esm/useFrappeRows.native.js.map +1 -1
- package/dist/jsx/FrappeDashboard.mjs +9 -11
- package/dist/jsx/FrappeDashboard.mjs.map +1 -1
- package/dist/jsx/FrappeDashboard.native.js +8 -14
- package/dist/jsx/FrappeDashboard.native.js.map +1 -1
- package/dist/jsx/FrappeForm.mjs +471 -181
- package/dist/jsx/FrappeForm.mjs.map +1 -1
- package/dist/jsx/FrappeForm.native.js +611 -207
- package/dist/jsx/FrappeForm.native.js.map +1 -1
- package/dist/jsx/FrappeFormConnections.mjs +99 -0
- package/dist/jsx/FrappeFormConnections.mjs.map +1 -0
- package/dist/jsx/FrappeFormConnections.native.js +149 -0
- package/dist/jsx/FrappeFormConnections.native.js.map +1 -0
- package/dist/jsx/FrappeImageView.mjs +8 -10
- package/dist/jsx/FrappeImageView.mjs.map +1 -1
- package/dist/jsx/FrappeImageView.native.js +7 -13
- package/dist/jsx/FrappeImageView.native.js.map +1 -1
- package/dist/jsx/FrappeList.mjs +31 -6
- package/dist/jsx/FrappeList.mjs.map +1 -1
- package/dist/jsx/FrappeList.native.js +37 -5
- package/dist/jsx/FrappeList.native.js.map +1 -1
- package/dist/jsx/FrappeListView.mjs +103 -30
- package/dist/jsx/FrappeListView.mjs.map +1 -1
- package/dist/jsx/FrappeListView.native.js +109 -29
- package/dist/jsx/FrappeListView.native.js.map +1 -1
- package/dist/jsx/FrappeProvider.mjs +7 -2
- package/dist/jsx/FrappeProvider.mjs.map +1 -1
- package/dist/jsx/FrappeProvider.native.js +7 -2
- package/dist/jsx/FrappeProvider.native.js.map +1 -1
- package/dist/jsx/FrappeReportBuilder.mjs +11 -12
- package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
- package/dist/jsx/FrappeReportBuilder.native.js +10 -16
- package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
- package/dist/jsx/FrappeRowsReader.mjs +24 -0
- package/dist/jsx/FrappeRowsReader.mjs.map +1 -0
- package/dist/jsx/FrappeRowsReader.native.js +53 -0
- package/dist/jsx/FrappeRowsReader.native.js.map +1 -0
- package/dist/jsx/FrappeTable.mjs +18 -6
- package/dist/jsx/FrappeTable.mjs.map +1 -1
- package/dist/jsx/FrappeTable.native.js +24 -6
- package/dist/jsx/FrappeTable.native.js.map +1 -1
- package/dist/jsx/fieldRegistry.mjs +35 -12
- package/dist/jsx/fieldRegistry.mjs.map +1 -1
- package/dist/jsx/fieldRegistry.native.js +59 -11
- package/dist/jsx/fieldRegistry.native.js.map +1 -1
- package/dist/jsx/fields/Date/index.native.js +5 -2
- package/dist/jsx/fields/Date/index.native.js.map +1 -1
- package/dist/jsx/fields/Date/sheetKeyboard.mjs +2 -2
- package/dist/jsx/fields/Date/sheetKeyboard.mjs.map +1 -1
- package/dist/jsx/fields/Date/sheetKeyboard.native.js +2 -2
- package/dist/jsx/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/jsx/fields/DateRange/index.mjs +2 -1
- package/dist/jsx/fields/DateRange/index.mjs.map +1 -1
- package/dist/jsx/fields/DateRange/index.native.js +4 -1
- package/dist/jsx/fields/DateRange/index.native.js.map +1 -1
- package/dist/jsx/formEngine/index.mjs +2 -1
- package/dist/jsx/formEngine/index.mjs.map +1 -1
- package/dist/jsx/formEngine/index.native.js +3 -1
- package/dist/jsx/formEngine/index.native.js.map +1 -1
- package/dist/jsx/formEngine/useSessionRoles.mjs +52 -0
- package/dist/jsx/formEngine/useSessionRoles.mjs.map +1 -0
- package/dist/jsx/formEngine/useSessionRoles.native.js +84 -0
- package/dist/jsx/formEngine/useSessionRoles.native.js.map +1 -0
- package/dist/jsx/frappeConnections.mjs +15 -0
- package/dist/jsx/frappeConnections.mjs.map +1 -0
- package/dist/jsx/frappeConnections.native.js +50 -0
- package/dist/jsx/frappeConnections.native.js.map +1 -0
- package/dist/jsx/frappeNavigation.mjs +78 -0
- package/dist/jsx/frappeNavigation.mjs.map +1 -0
- package/dist/jsx/frappeNavigation.native.js +112 -0
- package/dist/jsx/frappeNavigation.native.js.map +1 -0
- package/dist/jsx/index.js +7 -3
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +7 -3
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +18 -1
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/schemaForm/index.mjs +4 -1
- package/dist/jsx/schemaForm/index.mjs.map +1 -1
- package/dist/jsx/schemaForm/index.native.js +4 -1
- package/dist/jsx/schemaForm/index.native.js.map +1 -1
- package/dist/jsx/useConnectionFilters.mjs +121 -0
- package/dist/jsx/useConnectionFilters.mjs.map +1 -0
- package/dist/jsx/useConnectionFilters.native.js +213 -0
- package/dist/jsx/useConnectionFilters.native.js.map +1 -0
- package/dist/jsx/useFrappePagination.mjs +38 -19
- package/dist/jsx/useFrappePagination.mjs.map +1 -1
- package/dist/jsx/useFrappePagination.native.js +59 -23
- package/dist/jsx/useFrappePagination.native.js.map +1 -1
- package/dist/jsx/useFrappeRows.mjs.map +1 -1
- package/dist/jsx/useFrappeRows.native.js.map +1 -1
- package/package.json +26 -22
- package/src/FileBrowser/AttachLibrary.stories.tsx +23 -0
- package/src/FrappeDashboard.tsx +11 -16
- package/src/FrappeForm.connections.spec.tsx +280 -0
- package/src/FrappeForm.connections.stories.tsx +93 -0
- package/src/FrappeForm.deskSemantics.spec.tsx +284 -0
- package/src/FrappeForm.tsx +560 -145
- package/src/FrappeFormConnections.tsx +148 -0
- package/src/FrappeGuestMeta.spec.tsx +143 -0
- package/src/FrappeImageView.tsx +8 -12
- package/src/FrappeList.tsx +71 -15
- package/src/FrappeListView.cards.spec.tsx +175 -0
- package/src/FrappeListView.cards.stories.tsx +131 -0
- package/src/FrappeListView.connections.spec.tsx +192 -0
- package/src/FrappeListView.connections.stories.tsx +99 -0
- package/src/FrappeListView.seed.spec.tsx +228 -0
- package/src/FrappeListView.tsx +177 -31
- package/src/FrappeProvider.catchUp.spec.tsx +502 -0
- package/src/FrappeProvider.tsx +15 -3
- package/src/FrappeReportBuilder.tsx +11 -19
- package/src/FrappeRowsReader.tsx +38 -0
- package/src/FrappeTable.optionalProps.spec.tsx +249 -0
- package/src/FrappeTable.spec.tsx +32 -42
- package/src/FrappeTable.stories.spec.tsx +55 -0
- package/src/FrappeTable.stories.tsx +28 -37
- package/src/FrappeTable.tsx +108 -45
- package/src/childTableDependsOn.spec.tsx +85 -0
- package/src/desk/DeskFormSidebar.spec.tsx +20 -0
- package/src/desk/DeskFormSidebar.stories.tsx +42 -0
- package/src/fieldRegistry.spec.tsx +27 -64
- package/src/fieldRegistry.ts +71 -14
- package/src/fields/Date/index.native.tsx +4 -2
- package/src/fields/Date/sheetKeyboard.spec.ts +14 -5
- package/src/fields/Date/sheetKeyboard.ts +12 -4
- package/src/fields/DateRange/DateRange.spec.tsx +67 -0
- package/src/fields/DateRange/index.tsx +5 -1
- package/src/fields/LinkTree.spec.tsx +8 -0
- package/src/fields/LinkTree.stories.tsx +28 -5
- package/src/fields/Select/Select.hydration.spec.tsx +177 -0
- package/src/formEngine/doctypeMeta.ts +15 -114
- package/src/formEngine/index.ts +2 -0
- package/src/formEngine/seam.spec.ts +23 -4
- package/src/formEngine/useSessionRoles.ts +73 -0
- package/src/frappeConnections.ts +50 -0
- package/src/frappeNavigation.tsx +112 -0
- package/src/frappeRoutes.spec.tsx +335 -0
- package/src/index.ts +28 -1
- package/src/schemaForm/index.tsx +1 -0
- package/src/useConnectionFilters.tsx +157 -0
- package/src/useFrappePagination.ts +70 -26
- package/src/useFrappeRows.sharedPath.spec.tsx +206 -0
- package/src/useFrappeRows.ts +5 -4
- package/types/FrappeDashboard.d.ts +3 -4
- package/types/FrappeDashboard.d.ts.map +1 -1
- package/types/FrappeForm.d.ts +57 -5
- package/types/FrappeForm.d.ts.map +1 -1
- package/types/FrappeFormConnections.d.ts +18 -0
- package/types/FrappeFormConnections.d.ts.map +1 -0
- package/types/FrappeImageView.d.ts +2 -3
- package/types/FrappeImageView.d.ts.map +1 -1
- package/types/FrappeList.d.ts +20 -2
- package/types/FrappeList.d.ts.map +1 -1
- package/types/FrappeListView.d.ts +53 -3
- package/types/FrappeListView.d.ts.map +1 -1
- package/types/FrappeProvider.d.ts +10 -3
- package/types/FrappeProvider.d.ts.map +1 -1
- package/types/FrappeReportBuilder.d.ts.map +1 -1
- package/types/FrappeRowsReader.d.ts +26 -0
- package/types/FrappeRowsReader.d.ts.map +1 -0
- package/types/FrappeTable.d.ts +39 -8
- package/types/FrappeTable.d.ts.map +1 -1
- package/types/fieldRegistry.d.ts +17 -3
- package/types/fieldRegistry.d.ts.map +1 -1
- package/types/fields/Date/index.native.d.ts.map +1 -1
- package/types/fields/Date/sheetKeyboard.d.ts +4 -3
- package/types/fields/Date/sheetKeyboard.d.ts.map +1 -1
- package/types/fields/DateRange/index.d.ts.map +1 -1
- package/types/formEngine/doctypeMeta.d.ts +10 -114
- package/types/formEngine/doctypeMeta.d.ts.map +1 -1
- package/types/formEngine/index.d.ts +1 -0
- package/types/formEngine/index.d.ts.map +1 -1
- package/types/formEngine/useSessionRoles.d.ts +19 -0
- package/types/formEngine/useSessionRoles.d.ts.map +1 -0
- package/types/frappeConnections.d.ts +33 -0
- package/types/frappeConnections.d.ts.map +1 -0
- package/types/frappeNavigation.d.ts +49 -0
- package/types/frappeNavigation.d.ts.map +1 -0
- package/types/index.d.ts +7 -3
- package/types/index.d.ts.map +1 -1
- package/types/schemaForm/index.d.ts.map +1 -1
- package/types/useConnectionFilters.d.ts +34 -0
- package/types/useConnectionFilters.d.ts.map +1 -0
- package/types/useFrappePagination.d.ts +18 -5
- package/types/useFrappePagination.d.ts.map +1 -1
- package/types/useFrappeRows.d.ts +5 -4
- package/types/useFrappeRows.d.ts.map +1 -1
- package/src/FileBrowser/AttachLibrary.spec.tsx +0 -74
package/src/FrappeTable.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type DataTableProps,
|
|
13
13
|
type DataTableState,
|
|
14
14
|
type FilterValue,
|
|
15
|
+
type FilterableField,
|
|
15
16
|
type PageLoadQuery,
|
|
16
17
|
} from "@multiplatform.one/table";
|
|
17
18
|
import { AsyncBoundary } from "@multiplatform.one/components";
|
|
@@ -70,7 +71,17 @@ export interface FrappeTableProps<TDoc extends { name: string; doctype: string }
|
|
|
70
71
|
doctype: string;
|
|
71
72
|
/** Column definitions for the table */
|
|
72
73
|
columns: DataTableColumn<Partial<TDoc>>[];
|
|
73
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Fields to fetch. Omitted, a read from the bench (`serverSide`, or client
|
|
76
|
+
* mode under a provider without `fixtures`) requests `name` plus every
|
|
77
|
+
* column's `accessorKey`, and client mode under a provider carrying
|
|
78
|
+
* `fixtures` reads whole documents from the corpus. A column keyed on
|
|
79
|
+
* something that is not a doctype field (a rowAccumulator's derived key,
|
|
80
|
+
* say) makes the bench refuse that read, so name the list to leave such a
|
|
81
|
+
* key out. Name it, too, to share one collection with another reader of
|
|
82
|
+
* the same doctype (see useFrappeRows) or to fetch fields no column shows,
|
|
83
|
+
* such as a title field or a filter-only field.
|
|
84
|
+
*/
|
|
74
85
|
fields?: readonly (keyof TDoc & string)[];
|
|
75
86
|
/** Initial filter criteria */
|
|
76
87
|
filters?: Partial<TDoc> | Array<[keyof TDoc & string, string, any]>;
|
|
@@ -116,45 +127,20 @@ export interface FrappeTableProps<TDoc extends { name: string; doctype: string }
|
|
|
116
127
|
* FrappeTable has to name it or callers (backoffice list) fail the build.
|
|
117
128
|
*/
|
|
118
129
|
compact?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Filters the toolbar offers. Omitted, the list is derived from the
|
|
132
|
+
* doctype's meta: under a provider carrying `fixtures` that is the
|
|
133
|
+
* corpus's "DocType" entry and never the network, whatever `baseURL` the
|
|
134
|
+
* host also carries (a corpus without the entry yields no menu);
|
|
135
|
+
* otherwise it is a `frappe.desk.form.load.getdoctype` call. The
|
|
136
|
+
* meta-derived menu offers the doctype's fields, including ones the
|
|
137
|
+
* server refuses to filter on, so a live app should pass the honoured
|
|
138
|
+
* set by hand. Passing it skips the meta read entirely, which also drops
|
|
139
|
+
* title-field row labels back to the document name.
|
|
140
|
+
*/
|
|
141
|
+
filterableFields?: FilterableField[];
|
|
119
142
|
}
|
|
120
143
|
|
|
121
|
-
/**
|
|
122
|
-
* A data table component connected to Frappe Live data.
|
|
123
|
-
*
|
|
124
|
-
* Displays documents from a Frappe DocType in a sortable, filterable table
|
|
125
|
-
* with real-time updates via WebSocket. Integrates with the FrappeProvider
|
|
126
|
-
* for configuration.
|
|
127
|
-
*
|
|
128
|
-
* @typeParam TDoc - The document type with at least name and doctype fields
|
|
129
|
-
*
|
|
130
|
-
* @example
|
|
131
|
-
* ```tsx
|
|
132
|
-
* interface ToDo {
|
|
133
|
-
* name: string;
|
|
134
|
-
* doctype: string;
|
|
135
|
-
* description: string;
|
|
136
|
-
* status: 'Open' | 'Closed';
|
|
137
|
-
* }
|
|
138
|
-
*
|
|
139
|
-
* function ToDoTable() {
|
|
140
|
-
* return (
|
|
141
|
-
* <FrappeProvider baseURL="https://mysite.frappe.cloud">
|
|
142
|
-
* <FrappeTable<ToDo>
|
|
143
|
-
* doctype="ToDo"
|
|
144
|
-
* columns={[
|
|
145
|
-
* { accessorKey: 'description', header: 'Description' },
|
|
146
|
-
* { accessorKey: 'status', header: 'Status' }
|
|
147
|
-
* ]}
|
|
148
|
-
* fields={['name', 'description', 'status']}
|
|
149
|
-
* onRowClick={(row) => console.log('Clicked:', row)}
|
|
150
|
-
* />
|
|
151
|
-
* </FrappeProvider>
|
|
152
|
-
* );
|
|
153
|
-
* }
|
|
154
|
-
* ```
|
|
155
|
-
*
|
|
156
|
-
* @category Components
|
|
157
|
-
*/
|
|
158
144
|
/**
|
|
159
145
|
* Map one table FilterValue to a Frappe filter tuple. Returns null when the
|
|
160
146
|
* filter carries no usable value (e.g. "contains" with an empty string) so
|
|
@@ -274,6 +260,52 @@ function stateToFrappeParams(state: DataTableState) {
|
|
|
274
260
|
return { limitStart, limit, filters: frappeFilters, orderBy };
|
|
275
261
|
}
|
|
276
262
|
|
|
263
|
+
/**
|
|
264
|
+
* A data table component connected to Frappe Live data.
|
|
265
|
+
*
|
|
266
|
+
* Displays documents from a Frappe DocType in a sortable, filterable table
|
|
267
|
+
* with real-time updates via WebSocket. Integrates with the FrappeProvider
|
|
268
|
+
* for configuration.
|
|
269
|
+
*
|
|
270
|
+
* Two props read as optional and carry defaults that matter (MPO-230).
|
|
271
|
+
* Without `fields`, a read from the bench (`serverSide`, or client mode with
|
|
272
|
+
* no `fixtures`) requests `name` plus every column's `accessorKey`, and
|
|
273
|
+
* client mode under a fixture corpus reads whole documents. Without
|
|
274
|
+
* `filterableFields`, the filter menu comes from the doctype's meta: the
|
|
275
|
+
* corpus's "DocType" entry under a provider carrying `fixtures`, never the
|
|
276
|
+
* network whatever `baseURL` the host also carries, and a `getdoctype` call
|
|
277
|
+
* otherwise. Each prop's own entry has the detail.
|
|
278
|
+
*
|
|
279
|
+
* @typeParam TDoc - The document type with at least name and doctype fields
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```tsx
|
|
283
|
+
* interface ToDo {
|
|
284
|
+
* name: string;
|
|
285
|
+
* doctype: string;
|
|
286
|
+
* description: string;
|
|
287
|
+
* status: 'Open' | 'Closed';
|
|
288
|
+
* }
|
|
289
|
+
*
|
|
290
|
+
* function ToDoTable() {
|
|
291
|
+
* return (
|
|
292
|
+
* <FrappeProvider baseURL="https://mysite.frappe.cloud">
|
|
293
|
+
* <FrappeTable<ToDo>
|
|
294
|
+
* doctype="ToDo"
|
|
295
|
+
* columns={[
|
|
296
|
+
* { accessorKey: 'description', header: 'Description' },
|
|
297
|
+
* { accessorKey: 'status', header: 'Status' }
|
|
298
|
+
* ]}
|
|
299
|
+
* fields={['name', 'description', 'status']}
|
|
300
|
+
* onRowClick={(row) => console.log('Clicked:', row)}
|
|
301
|
+
* />
|
|
302
|
+
* </FrappeProvider>
|
|
303
|
+
* );
|
|
304
|
+
* }
|
|
305
|
+
* ```
|
|
306
|
+
*
|
|
307
|
+
* @category Components
|
|
308
|
+
*/
|
|
277
309
|
export function FrappeTable<TDoc extends { name: string; doctype: string }>({
|
|
278
310
|
baseURL,
|
|
279
311
|
auth,
|
|
@@ -363,11 +395,42 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
|
|
|
363
395
|
return new FrappeApp(finalBaseURL, (finalAuth ?? {}) as Record<string, unknown>);
|
|
364
396
|
}, [serverSide, finalBaseURL, finalAuth]);
|
|
365
397
|
|
|
398
|
+
// Keyed on content, not on the columns array, so an inline `columns`
|
|
399
|
+
// literal cannot re-key the fetch every render and refetch in a loop.
|
|
400
|
+
const columnFieldsKey = fields
|
|
401
|
+
? undefined
|
|
402
|
+
: JSON.stringify(
|
|
403
|
+
columns
|
|
404
|
+
.map((column): unknown => column.accessorKey)
|
|
405
|
+
.filter((key): key is string => typeof key === "string"),
|
|
406
|
+
);
|
|
407
|
+
const serverFields = useMemo<string[]>(
|
|
408
|
+
() =>
|
|
409
|
+
fields
|
|
410
|
+
? (fields as string[])
|
|
411
|
+
: Array.from(new Set<string>(["name", ...JSON.parse(columnFieldsKey ?? "[]")])),
|
|
412
|
+
[fields, columnFieldsKey],
|
|
413
|
+
);
|
|
414
|
+
// Client mode over a fixture corpus keeps whole documents: narrowing it
|
|
415
|
+
// would drop the non-column fields (a title field, a filter-only field)
|
|
416
|
+
// fixture hosts read today.
|
|
417
|
+
const clientFields = (fields ??
|
|
418
|
+
(connectionConfig.fixtures ? undefined : serverFields)) as typeof fields;
|
|
419
|
+
|
|
366
420
|
// Full doctype field list → table filterableFields: hidden / non-column
|
|
367
421
|
// fields become filterable through the toolbar Filter surface. An explicit
|
|
368
422
|
// filterableFields prop overrides the metadata-derived list (fixtures,
|
|
369
|
-
// stories, custom subsets).
|
|
370
|
-
|
|
423
|
+
// stories, custom subsets). A fixture corpus is the whole world for meta
|
|
424
|
+
// too: the read goes to its "DocType" collection with the baseURL a
|
|
425
|
+
// fixture host may still carry withheld, so a corpus without the entry
|
|
426
|
+
// yields no menu instead of dialling getdoctype (MPO-230).
|
|
427
|
+
const skipMeta = !!filterableFieldsProp;
|
|
428
|
+
const metaConnection = useMemo(() => {
|
|
429
|
+
if (skipMeta) return undefined;
|
|
430
|
+
if (!connectionConfig.fixtures) return connectionConfig;
|
|
431
|
+
return { ...connectionConfig, baseURL: undefined };
|
|
432
|
+
}, [skipMeta, connectionConfig]);
|
|
433
|
+
const { meta } = useDocTypeMeta(metaConnection, doctype);
|
|
371
434
|
const metaFilterFields = useMemo(
|
|
372
435
|
() => (meta?.fields ? docFieldsToFilterFields(meta.fields) : undefined),
|
|
373
436
|
[meta],
|
|
@@ -465,7 +528,7 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
|
|
|
465
528
|
try {
|
|
466
529
|
const [data, count] = await Promise.all([
|
|
467
530
|
frappeApp.db().getDocList<TDoc>(doctype, {
|
|
468
|
-
fields:
|
|
531
|
+
fields: serverFields,
|
|
469
532
|
filters: params.filters.length ? params.filters : undefined,
|
|
470
533
|
limitStart: params.limitStart,
|
|
471
534
|
limit: params.limit,
|
|
@@ -507,7 +570,7 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
|
|
|
507
570
|
}
|
|
508
571
|
}
|
|
509
572
|
},
|
|
510
|
-
[frappeApp, serverSide, doctype,
|
|
573
|
+
[frappeApp, serverSide, doctype, serverFields],
|
|
511
574
|
);
|
|
512
575
|
|
|
513
576
|
// Live invalidation for serverSide mode: any realtime event for this
|
|
@@ -565,7 +628,7 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
|
|
|
565
628
|
} as DataTableState);
|
|
566
629
|
const [rows, count] = await Promise.all([
|
|
567
630
|
frappeApp.db().getDocList<TDoc>(doctype, {
|
|
568
|
-
fields:
|
|
631
|
+
fields: serverFields,
|
|
569
632
|
filters: params.filters.length ? params.filters : undefined,
|
|
570
633
|
limitStart: params.limitStart,
|
|
571
634
|
limit: params.limit,
|
|
@@ -577,7 +640,7 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
|
|
|
577
640
|
]);
|
|
578
641
|
return { rows, totalCount: count ?? undefined };
|
|
579
642
|
},
|
|
580
|
-
[frappeApp, doctype,
|
|
643
|
+
[frappeApp, doctype, serverFields],
|
|
581
644
|
);
|
|
582
645
|
|
|
583
646
|
const resolvedLoadPage = loadPageProp ?? (infiniteServerMode ? frappeLoadPage : undefined);
|
|
@@ -632,7 +695,7 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
|
|
|
632
695
|
isQueryLoading: isRowsLoading,
|
|
633
696
|
error: liveError,
|
|
634
697
|
} = useFrappeRows<TDoc>(serverSide ? undefined : connectionConfig, doctype, {
|
|
635
|
-
fields,
|
|
698
|
+
fields: clientFields,
|
|
636
699
|
filters: activeFilters,
|
|
637
700
|
limit: null,
|
|
638
701
|
enabled: !serverSide,
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { fireEvent, screen, waitFor, within } from "@testing-library/react";
|
|
2
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
childCellHidden,
|
|
6
|
+
childFieldsToDetailColumns,
|
|
7
|
+
type ChildTableFieldDef,
|
|
8
|
+
type FieldSchema,
|
|
9
|
+
} from "./fieldRegistry";
|
|
10
|
+
import { SchemaForm, type FormSchema } from "./schemaForm";
|
|
11
|
+
|
|
12
|
+
const childFields: ChildTableFieldDef[] = [
|
|
13
|
+
{ fieldname: "item_code", fieldtype: "Data", label: "Item Code", in_list_view: 1 },
|
|
14
|
+
{ fieldname: "has_serial", fieldtype: "Check", label: "Has Serial", in_list_view: 1 },
|
|
15
|
+
{
|
|
16
|
+
fieldname: "serial_no",
|
|
17
|
+
fieldtype: "Data",
|
|
18
|
+
label: "Serial No",
|
|
19
|
+
in_list_view: 1,
|
|
20
|
+
depends_on: "eval:doc.has_serial == 1",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
fieldname: "warranty",
|
|
24
|
+
fieldtype: "Data",
|
|
25
|
+
label: "Warranty",
|
|
26
|
+
depends_on: 'eval:parent.status == "Draft"',
|
|
27
|
+
},
|
|
28
|
+
{ fieldname: "sec", fieldtype: "Section Break" },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function renderOrder(status: string) {
|
|
32
|
+
const fields: FieldSchema[] = [
|
|
33
|
+
{ type: "Data", name: "status", label: "Status" },
|
|
34
|
+
{ type: "Table", name: "items", label: "Items", options: "Order Item", fields: childFields },
|
|
35
|
+
];
|
|
36
|
+
const schema: FormSchema = { fields, sections: [{ columns: [{ fields }] }] };
|
|
37
|
+
return renderWithProviders(
|
|
38
|
+
<SchemaForm
|
|
39
|
+
schema={schema}
|
|
40
|
+
doc={{
|
|
41
|
+
status,
|
|
42
|
+
items: [
|
|
43
|
+
{ name: "r1", item_code: "SKU-1", has_serial: 1, serial_no: "SN-1", warranty: "1y" },
|
|
44
|
+
{ name: "r2", item_code: "SKU-2", has_serial: 0, serial_no: "SN-2", warranty: "2y" },
|
|
45
|
+
],
|
|
46
|
+
}}
|
|
47
|
+
setValue={vi.fn()}
|
|
48
|
+
/>,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("child-table per-row depends_on (MPO-22)", () => {
|
|
53
|
+
it("compiles child depends_on against the row, with the parent doc in scope", () => {
|
|
54
|
+
const hidden = childCellHidden(childFields, { status: "Draft" })!;
|
|
55
|
+
expect(hidden({ has_serial: 1 }, "serial_no", 0)).toBe(false);
|
|
56
|
+
expect(hidden({ has_serial: 0 }, "serial_no", 1)).toBe(true);
|
|
57
|
+
expect(hidden({ has_serial: 0 }, "item_code", 1)).toBe(false);
|
|
58
|
+
expect(hidden({}, "warranty", 0)).toBe(false);
|
|
59
|
+
expect(childCellHidden(childFields, { status: "Submitted" })!({}, "warranty", 0)).toBe(true);
|
|
60
|
+
expect(childCellHidden([{ fieldname: "a", fieldtype: "Data" }])).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("hides the cell on the row whose depends_on is false and keeps it on the other", () => {
|
|
64
|
+
renderOrder("Draft");
|
|
65
|
+
const serials = screen.getAllByLabelText("Serial No") as HTMLInputElement[];
|
|
66
|
+
expect(serials.map((input) => input.value)).toEqual(["SN-1"]);
|
|
67
|
+
expect(screen.getAllByLabelText("Item Code")).toHaveLength(2);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("the row detail editor shows every child field and applies the same rule", async () => {
|
|
71
|
+
expect(childFieldsToDetailColumns(childFields).map((c) => c.id)).toEqual([
|
|
72
|
+
"item_code",
|
|
73
|
+
"has_serial",
|
|
74
|
+
"serial_no",
|
|
75
|
+
"warranty",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
renderOrder("Submitted");
|
|
79
|
+
fireEvent.click(screen.getByRole("button", { name: "Edit row 2" }));
|
|
80
|
+
const dialog = await waitFor(() => screen.getByRole("dialog", { name: "Row 2" }));
|
|
81
|
+
expect((within(dialog).getByLabelText("Item Code") as HTMLInputElement).value).toBe("SKU-2");
|
|
82
|
+
expect(within(dialog).queryByLabelText("Serial No")).toBeNull();
|
|
83
|
+
expect(within(dialog).queryByLabelText("Warranty")).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -87,4 +87,24 @@ describe("DeskFormSidebar (MPO-111)", () => {
|
|
|
87
87
|
renderSidebar();
|
|
88
88
|
expect(await screen.findByTestId("desk-form-sidebar")).toBeTruthy();
|
|
89
89
|
});
|
|
90
|
+
|
|
91
|
+
it("hands FormSidebar the Assigned To and Shared With sections even when both are empty", async () => {
|
|
92
|
+
const doc = {
|
|
93
|
+
name: "TODO-002",
|
|
94
|
+
doctype: "Todo",
|
|
95
|
+
description: "File",
|
|
96
|
+
owner: "sam@example.com",
|
|
97
|
+
};
|
|
98
|
+
let call = 0;
|
|
99
|
+
mockUseLiveQuery.mockImplementation(() => {
|
|
100
|
+
const slot = call++ % 3;
|
|
101
|
+
return { data: slot === 0 ? [doc] : [] };
|
|
102
|
+
});
|
|
103
|
+
renderSidebar("TODO-002");
|
|
104
|
+
const sidebar = await screen.findByTestId("desk-form-sidebar");
|
|
105
|
+
// buildSidebarSections always emits assignments; shares only when the
|
|
106
|
+
// DocShare subscription exists (it does here). An empty list keeps its
|
|
107
|
+
// section and renders emptyText — the pane never loses a row.
|
|
108
|
+
expect(sidebar.getAttribute("data-section-keys")).toBe("assignments,shares");
|
|
109
|
+
});
|
|
90
110
|
});
|
|
@@ -126,6 +126,31 @@ function makeFixtures() {
|
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Same doc, nothing hanging off it: no `_assign` mirror, no DocShare row, no
|
|
131
|
+
* image. Every section still renders — with its emptyText — so the pane
|
|
132
|
+
* geometry is identical to the populated board.
|
|
133
|
+
*/
|
|
134
|
+
const bareTodoDocs = [
|
|
135
|
+
{
|
|
136
|
+
name: "TODO-002",
|
|
137
|
+
doctype: "Todo",
|
|
138
|
+
description: "File the expense report",
|
|
139
|
+
status: "Open",
|
|
140
|
+
owner: "sam@example.com",
|
|
141
|
+
modified_by: "sam@example.com",
|
|
142
|
+
creation: "2026-02-03",
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
function makeBareFixtures() {
|
|
147
|
+
return new InMemoryFixtureProvider({
|
|
148
|
+
Todo: bareTodoDocs,
|
|
149
|
+
DocShare: [],
|
|
150
|
+
ToDo: [],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
129
154
|
export const Default = () => (
|
|
130
155
|
<FrappeProvider baseURL={MOCK_HOST} fixtures={makeFixtures()}>
|
|
131
156
|
<YStack width={320} height={520}>
|
|
@@ -135,6 +160,23 @@ export const Default = () => (
|
|
|
135
160
|
);
|
|
136
161
|
Default.storyName = "Main";
|
|
137
162
|
|
|
163
|
+
export const EmptySections = () => (
|
|
164
|
+
<FrappeProvider baseURL={MOCK_HOST} fixtures={makeBareFixtures()}>
|
|
165
|
+
<YStack width={320} height={520}>
|
|
166
|
+
<DeskFormSidebar doctype="Todo" name="TODO-002" />
|
|
167
|
+
</YStack>
|
|
168
|
+
</FrappeProvider>
|
|
169
|
+
);
|
|
170
|
+
EmptySections.storyName = "Empty Sections";
|
|
171
|
+
EmptySections.parameters = {
|
|
172
|
+
docs: {
|
|
173
|
+
description: {
|
|
174
|
+
story:
|
|
175
|
+
"Doc with no assignees, no shares and no image: Assigned To reads its emptyText (No one assigned), Shared With reads Not shared, and the metadata rows still come from the doc.",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
|
|
138
180
|
export const Loading = () => (
|
|
139
181
|
<FrappeProvider baseURL={MOCK_HOST} fixtures={makeFixtures()}>
|
|
140
182
|
<YStack width={320} height={200}>
|
|
@@ -9,13 +9,19 @@
|
|
|
9
9
|
* are plain functions without hooks, so calling them directly is safe).
|
|
10
10
|
*/
|
|
11
11
|
import { Button, Combobox, ImageField } from "@multiplatform.one/forms";
|
|
12
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
customFieldTypes,
|
|
14
|
+
frappeFieldTypes,
|
|
15
|
+
type FrappeFieldType,
|
|
16
|
+
} from "@multiplatform.one/frappe";
|
|
13
17
|
import type React from "react";
|
|
14
|
-
import { describe, expect, it } from "vitest";
|
|
18
|
+
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
19
|
+
import { registeredFieldtypesFromSource } from "../tests/registrySource";
|
|
15
20
|
import {
|
|
16
21
|
childFieldsToColumns,
|
|
17
22
|
fieldtypeToTableField,
|
|
18
23
|
getFieldComponent,
|
|
24
|
+
registerField,
|
|
19
25
|
type ChildTableFieldDef,
|
|
20
26
|
type FieldRenderProps,
|
|
21
27
|
} from "./fieldRegistry";
|
|
@@ -274,69 +280,26 @@ describe("form adapters (registry context)", () => {
|
|
|
274
280
|
expect(element.props.value).toBe("mail");
|
|
275
281
|
});
|
|
276
282
|
|
|
277
|
-
it("union-minus-registry holds no real fieldtype (
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
const frappeFieldTypes: readonly FrappeFieldType[] = [
|
|
283
|
-
"Autocomplete",
|
|
284
|
-
"Attach",
|
|
285
|
-
"Attach Image",
|
|
286
|
-
"Barcode",
|
|
287
|
-
"Button",
|
|
288
|
-
"Check",
|
|
289
|
-
"Code",
|
|
290
|
-
"Color",
|
|
291
|
-
"Column Break",
|
|
292
|
-
"Currency",
|
|
293
|
-
"Data",
|
|
294
|
-
"Date",
|
|
295
|
-
"Datetime",
|
|
296
|
-
"Duration",
|
|
297
|
-
"Dynamic Link",
|
|
298
|
-
"Float",
|
|
299
|
-
"Geolocation",
|
|
300
|
-
"HTML",
|
|
301
|
-
"HTML Editor",
|
|
302
|
-
"Heading",
|
|
303
|
-
"Icon",
|
|
304
|
-
"Image",
|
|
305
|
-
"Int",
|
|
306
|
-
"JSON",
|
|
307
|
-
"Link",
|
|
308
|
-
"Long Text",
|
|
309
|
-
"Markdown Editor",
|
|
310
|
-
"Password",
|
|
311
|
-
"Percent",
|
|
312
|
-
"Phone",
|
|
313
|
-
"Rating",
|
|
314
|
-
"Read Only",
|
|
315
|
-
"Section Break",
|
|
316
|
-
"Select",
|
|
317
|
-
"Signature",
|
|
318
|
-
"Small Text",
|
|
319
|
-
"Tab Break",
|
|
320
|
-
"Table",
|
|
321
|
-
"Table MultiSelect",
|
|
322
|
-
"Text",
|
|
323
|
-
"Text Editor",
|
|
324
|
-
"Time",
|
|
325
|
-
];
|
|
326
|
-
const unregistered = frappeFieldTypes.filter((fieldtype) => !getFieldComponent(fieldtype));
|
|
327
|
-
// The three layout breaks never get adapters — FormSection / FormGrid /
|
|
283
|
+
it("union-minus-registry holds no real fieldtype (structural types only)", () => {
|
|
284
|
+
// A real fieldtype missing from the registry renders the unsupported-type
|
|
285
|
+
// string (the MPO-51 Icon hole, reopened once already by 035867446's
|
|
286
|
+
// registry rewrite), so the whole union is asserted against the registry.
|
|
287
|
+
// The three breaks and Fold never get adapters: FormSection / FormGrid /
|
|
328
288
|
// Tabs serve them structurally.
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
289
|
+
const unregistered = frappeFieldTypes.filter((fieldtype) => !getFieldComponent(fieldtype));
|
|
290
|
+
expect([...unregistered].sort()).toEqual(
|
|
291
|
+
["Column Break", "Fold", "Section Break", "Tab Break"].sort(),
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("registers nothing outside the FrappeFieldType union (MPO-15)", () => {
|
|
296
|
+
expectTypeOf(registerField).parameter(0).toEqualTypeOf<FrappeFieldType>();
|
|
297
|
+
const union = new Set<string>(frappeFieldTypes);
|
|
298
|
+
const registered = [...registeredFieldtypesFromSource()];
|
|
299
|
+
expect(registered.length).toBeGreaterThan(40);
|
|
300
|
+
expect(registered.filter((fieldtype) => !union.has(fieldtype))).toEqual([]);
|
|
301
|
+
for (const custom of customFieldTypes) {
|
|
302
|
+
expect(getFieldComponent(custom), custom).toBeDefined();
|
|
340
303
|
}
|
|
341
304
|
});
|
|
342
305
|
|
package/src/fieldRegistry.ts
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
import React, { useMemo } from "react";
|
|
10
10
|
import type { ComponentType } from "react";
|
|
11
|
-
import { useDocTypeMeta } from "@multiplatform.one/frappe";
|
|
11
|
+
import { useDocTypeMeta, type FrappeFieldType } from "@multiplatform.one/frappe";
|
|
12
12
|
import { Button, CheckboxGroup, Combobox, ImageField, Skeleton } from "@multiplatform.one/forms";
|
|
13
13
|
|
|
14
14
|
import { Barcode } from "./fields/Barcode";
|
|
15
|
+
import { compileDependsOn, type DependsDoc, type DependsEvaluator } from "./dependsOn";
|
|
15
16
|
import { useFrappeConfig } from "./FrappeProvider";
|
|
16
17
|
import { Check, type CheckProps } from "./fields/Check";
|
|
17
18
|
import { CodeEditorField, frappeCodeLanguage } from "./fields/CodeEditor";
|
|
@@ -47,6 +48,7 @@ import { SwitchField } from "./fields/Switch";
|
|
|
47
48
|
import { TableMultiSelect } from "./fields/TableMultiSelect";
|
|
48
49
|
import {
|
|
49
50
|
ChildTable,
|
|
51
|
+
type ChildCellHidden,
|
|
50
52
|
type DataTableCellRenderContext,
|
|
51
53
|
type DataTableField,
|
|
52
54
|
} from "@multiplatform.one/table";
|
|
@@ -112,6 +114,8 @@ export interface FieldRenderProps {
|
|
|
112
114
|
doctype?: string;
|
|
113
115
|
/** For Select fields: newline-separated options string or other config */
|
|
114
116
|
options?: string;
|
|
117
|
+
/** For Table fields: the form doc child `depends_on` reads as `parent`. */
|
|
118
|
+
parentDoc?: Record<string, unknown>;
|
|
115
119
|
/** Allow pass-through props */
|
|
116
120
|
[key: string]: any;
|
|
117
121
|
}
|
|
@@ -129,9 +133,13 @@ const registry = new Map<string, ComponentType<FieldRenderProps>>();
|
|
|
129
133
|
* override channel: writing it per provider instance clobbers every other
|
|
130
134
|
* provider on the page (last render wins, silently). Per-subtree overrides
|
|
131
135
|
* belong on `FieldOverridesProvider` (fieldOverrides.tsx), which SchemaForm
|
|
132
|
-
* consults before this table.
|
|
136
|
+
* consults before this table. Only a `FrappeFieldType` registers: the union
|
|
137
|
+
* is the authority on what a fieldtype may be (MPO-15).
|
|
133
138
|
*/
|
|
134
|
-
export function registerField(
|
|
139
|
+
export function registerField(
|
|
140
|
+
fieldtype: FrappeFieldType,
|
|
141
|
+
component: ComponentType<FieldRenderProps>,
|
|
142
|
+
): void {
|
|
135
143
|
registry.set(fieldtype, component);
|
|
136
144
|
}
|
|
137
145
|
|
|
@@ -955,6 +963,8 @@ export interface ChildTableFieldDef {
|
|
|
955
963
|
hidden?: boolean | number;
|
|
956
964
|
precision?: number;
|
|
957
965
|
default?: string;
|
|
966
|
+
/** Per-row visibility, evaluated against the row with the parent doc in scope. */
|
|
967
|
+
depends_on?: string;
|
|
958
968
|
}
|
|
959
969
|
|
|
960
970
|
type TableColumnDef = {
|
|
@@ -1189,22 +1199,49 @@ function isVisibleChildField(f: ChildTableFieldDef): boolean {
|
|
|
1189
1199
|
return !f.hidden && !TABLE_LAYOUT_TYPES.has(f.fieldtype) && !f.fieldtype.startsWith("Table");
|
|
1190
1200
|
}
|
|
1191
1201
|
|
|
1202
|
+
function fieldToColumn(f: ChildTableFieldDef): TableColumnDef {
|
|
1203
|
+
const mapped = fieldtypeToTableField(f.fieldtype, f.options);
|
|
1204
|
+
return {
|
|
1205
|
+
id: f.fieldname,
|
|
1206
|
+
header: f.label || f.fieldname,
|
|
1207
|
+
accessorKey: f.fieldname,
|
|
1208
|
+
field: mapped.field,
|
|
1209
|
+
fieldProps: mapped.fieldProps,
|
|
1210
|
+
readOnly: !!f.read_only,
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1192
1214
|
/** Build editable ChildTable columns from child DocType field defs. */
|
|
1193
1215
|
export function childFieldsToColumns(fields: ChildTableFieldDef[]): TableColumnDef[] {
|
|
1194
1216
|
const visible = fields.filter(isVisibleChildField);
|
|
1195
1217
|
const flagged = visible.filter((f) => !!f.in_list_view);
|
|
1196
1218
|
const source = flagged.length > 0 ? flagged : visible.slice(0, 8);
|
|
1197
|
-
return source.map(
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1219
|
+
return source.map(fieldToColumn);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/** Every editable child field, for the row detail editor (desk's row form). */
|
|
1223
|
+
export function childFieldsToDetailColumns(fields: ChildTableFieldDef[]): TableColumnDef[] {
|
|
1224
|
+
return fields.filter(isVisibleChildField).map(fieldToColumn);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Per-row cell visibility from child `depends_on`: the row is `doc`, the
|
|
1229
|
+
* parent form doc is `parent`. Undefined when no child field declares one.
|
|
1230
|
+
*/
|
|
1231
|
+
export function childCellHidden(
|
|
1232
|
+
fields: ChildTableFieldDef[],
|
|
1233
|
+
parent?: DependsDoc,
|
|
1234
|
+
): ChildCellHidden | undefined {
|
|
1235
|
+
const evaluators = new Map<string, DependsEvaluator>();
|
|
1236
|
+
for (const f of fields) {
|
|
1237
|
+
const evaluator = compileDependsOn(f.depends_on, true);
|
|
1238
|
+
if (evaluator) evaluators.set(f.fieldname, evaluator);
|
|
1239
|
+
}
|
|
1240
|
+
if (evaluators.size === 0) return undefined;
|
|
1241
|
+
return (row, columnId) => {
|
|
1242
|
+
const evaluator = evaluators.get(columnId);
|
|
1243
|
+
return evaluator ? !evaluator(row, parent) : false;
|
|
1244
|
+
};
|
|
1208
1245
|
}
|
|
1209
1246
|
|
|
1210
1247
|
function defaultRowFromFields(fields: ChildTableFieldDef[]): Record<string, unknown> {
|
|
@@ -1305,11 +1342,31 @@ function TableAdapter(props: FieldRenderProps) {
|
|
|
1305
1342
|
return undefined;
|
|
1306
1343
|
}, [resolvedFields]);
|
|
1307
1344
|
|
|
1345
|
+
const detailColumns = useMemo(
|
|
1346
|
+
() =>
|
|
1347
|
+
resolvedFields?.length
|
|
1348
|
+
? childFieldsToDetailColumns(resolvedFields as ChildTableFieldDef[])
|
|
1349
|
+
: undefined,
|
|
1350
|
+
[resolvedFields],
|
|
1351
|
+
);
|
|
1352
|
+
|
|
1353
|
+
const parentDoc = props.parentDoc;
|
|
1354
|
+
const isCellHidden = useMemo(
|
|
1355
|
+
() =>
|
|
1356
|
+
resolvedFields?.length
|
|
1357
|
+
? childCellHidden(resolvedFields as ChildTableFieldDef[], parentDoc)
|
|
1358
|
+
: undefined,
|
|
1359
|
+
[resolvedFields, parentDoc],
|
|
1360
|
+
);
|
|
1361
|
+
|
|
1308
1362
|
return React.createElement(ChildTable, {
|
|
1309
1363
|
label: props.label,
|
|
1310
1364
|
value: Array.isArray(props.value) ? props.value : [],
|
|
1311
1365
|
onChange: props.onChange,
|
|
1312
1366
|
columns,
|
|
1367
|
+
detailColumns: detailColumns?.length ? detailColumns : undefined,
|
|
1368
|
+
rowDetail: true,
|
|
1369
|
+
isCellHidden,
|
|
1313
1370
|
defaultRow,
|
|
1314
1371
|
disabled: props.disabled,
|
|
1315
1372
|
error: props.error,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
useFormField,
|
|
15
15
|
useResolvedValidators,
|
|
16
16
|
} from "@multiplatform.one/forms";
|
|
17
|
-
import { useState } from "react";
|
|
17
|
+
import { useContext, useState } from "react";
|
|
18
18
|
import {
|
|
19
19
|
Modal,
|
|
20
20
|
Pressable,
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
useWindowDimensions,
|
|
24
24
|
View as RNView,
|
|
25
25
|
} from "react-native";
|
|
26
|
+
import { SafeAreaInsetsContext, initialWindowMetrics } from "react-native-safe-area-context";
|
|
26
27
|
import { platform } from "@multiplatform.one/platform";
|
|
27
28
|
import DateTimePicker, { type DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
|
28
29
|
import { Text, View, XStack, YStack, useProps, useTheme, useThemeName } from "tamagui";
|
|
@@ -143,7 +144,8 @@ function DateField<
|
|
|
143
144
|
// MPO-64 leftover: the sheet must end ABOVE the keyboard, not run under it.
|
|
144
145
|
const keyboardInset = useKeyboardInset();
|
|
145
146
|
const { height: windowHeight } = useWindowDimensions();
|
|
146
|
-
const
|
|
147
|
+
const topInset = useContext(SafeAreaInsetsContext)?.top ?? initialWindowMetrics?.insets.top ?? 0;
|
|
148
|
+
const maxHeight = dateSheetMaxHeight(windowHeight, keyboardInset, topInset);
|
|
147
149
|
|
|
148
150
|
const [showPicker, setShowPicker] = useState(false);
|
|
149
151
|
const [showTimePicker, setShowTimePicker] = useState(false);
|