@multiplatform.one/frappe-ui 7.28.0 → 7.29.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/dist/cjs/ChartCard.native.js.map +1 -1
- package/dist/cjs/FileBrowser/AttachLibrary.native.js.map +1 -1
- package/dist/cjs/FileBrowser/folderTree.native.js.map +1 -1
- package/dist/cjs/FileBrowser/index.cjs +20 -3
- package/dist/cjs/FileBrowser/index.native.js +20 -3
- package/dist/cjs/FileBrowser/index.native.js.map +1 -1
- package/dist/cjs/FileBrowser/types.native.js.map +1 -1
- package/dist/cjs/FileBrowser/viewPersistence.native.js.map +1 -1
- package/dist/cjs/FormSidebar.native.js.map +1 -1
- package/dist/cjs/FrappeAssignment.native.js.map +1 -1
- package/dist/cjs/FrappeCalendar.cjs +3 -2
- package/dist/cjs/FrappeCalendar.native.js +3 -2
- package/dist/cjs/FrappeCalendar.native.js.map +1 -1
- package/dist/cjs/FrappeChartCard.native.js.map +1 -1
- package/dist/cjs/FrappeConnectionIndicator.native.js.map +1 -1
- package/dist/cjs/FrappeDashboard.native.js.map +1 -1
- package/dist/cjs/FrappeForm.cjs +148 -464
- package/dist/cjs/FrappeForm.native.js +153 -634
- package/dist/cjs/FrappeForm.native.js.map +1 -1
- package/dist/cjs/FrappeFormActions.native.js.map +1 -1
- package/dist/cjs/FrappeFormConnections.native.js.map +1 -1
- package/dist/cjs/FrappeGantt.native.js.map +1 -1
- package/dist/cjs/FrappeImageView.native.js.map +1 -1
- package/dist/cjs/FrappeInsights.native.js.map +1 -1
- package/dist/cjs/FrappeKPICard.native.js.map +1 -1
- package/dist/cjs/FrappeKanban.native.js.map +1 -1
- package/dist/cjs/FrappeLikedBy.native.js.map +1 -1
- package/dist/cjs/FrappeList.native.js.map +1 -1
- package/dist/cjs/FrappeListView.cjs +5 -27
- package/dist/cjs/FrappeListView.native.js +11 -32
- package/dist/cjs/FrappeListView.native.js.map +1 -1
- package/dist/cjs/FrappeNotConfigured.native.js.map +1 -1
- package/dist/cjs/FrappeProvider.native.js.map +1 -1
- package/dist/cjs/FrappeReconnectIndicator.native.js.map +1 -1
- package/dist/cjs/FrappeRecordView.native.js.map +1 -1
- package/dist/cjs/FrappeReportBuilder.cjs +523 -309
- package/dist/cjs/FrappeReportBuilder.native.js +658 -353
- package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
- package/dist/cjs/FrappeReportView.cjs +35 -234
- package/dist/cjs/FrappeReportView.native.js +42 -286
- package/dist/cjs/FrappeReportView.native.js.map +1 -1
- package/dist/cjs/FrappeRowsReader.native.js.map +1 -1
- package/dist/cjs/FrappeTable.native.js.map +1 -1
- package/dist/cjs/FrappeTreeView.native.js.map +1 -1
- package/dist/cjs/SyncErrorNotice.native.js.map +1 -1
- package/dist/cjs/commentActions.native.js.map +1 -1
- package/dist/cjs/components/attachments.native.js.map +1 -1
- package/dist/cjs/components/tags.native.js.map +1 -1
- package/dist/cjs/components/timeline.native.js.map +1 -1
- package/dist/cjs/dependsOn.cjs +2 -2
- package/dist/cjs/dependsOn.native.js +1 -2
- package/dist/cjs/dependsOn.native.js.map +1 -1
- package/dist/cjs/desk/DeskFormPage.native.js.map +1 -1
- package/dist/cjs/desk/DeskFormSidebar.native.js.map +1 -1
- package/dist/cjs/desk/DeskListPage.native.js.map +1 -1
- package/dist/cjs/desk/DeskShell.native.js.map +1 -1
- package/dist/cjs/desk/DeskSidebar.native.js.map +1 -1
- package/dist/cjs/desk/awesomebar.native.js.map +1 -1
- package/dist/cjs/fieldOverrides.native.js.map +1 -1
- package/dist/cjs/fieldRegistry.native.js +31 -31
- package/dist/cjs/fieldRegistry.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/index.native.js.map +1 -1
- package/dist/cjs/fields/Check/index.native.js.map +1 -1
- package/dist/cjs/fields/CodeEditor/index.native.js.map +1 -1
- package/dist/cjs/fields/Color/index.native.js.map +1 -1
- package/dist/cjs/fields/Data/index.native.js.map +1 -1
- package/dist/cjs/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/cjs/fields/Date/types.native.js.map +1 -1
- package/dist/cjs/fields/Date/wire.native.js.map +1 -1
- package/dist/cjs/fields/DateRange/index.native.js.map +1 -1
- package/dist/cjs/fields/Duration/index.native.js.map +1 -1
- package/dist/cjs/fields/DynamicLink.native.js.map +1 -1
- package/dist/cjs/fields/FileUpload.native.js.map +1 -1
- package/dist/cjs/fields/FileUploadField/index.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/index.native.js.map +1 -1
- package/dist/cjs/fields/HTML/index.native.js.map +1 -1
- package/dist/cjs/fields/Heading/index.native.js.map +1 -1
- package/dist/cjs/fields/Icon/index.native.js.map +1 -1
- package/dist/cjs/fields/Link.native.js.map +1 -1
- package/dist/cjs/fields/LinkTree.native.js.map +1 -1
- package/dist/cjs/fields/LongText/index.native.js.map +1 -1
- package/dist/cjs/fields/MentionInput/index.native.js.map +1 -1
- package/dist/cjs/fields/Numeric/index.native.js.map +1 -1
- package/dist/cjs/fields/OTPInput/index.native.js.map +1 -1
- package/dist/cjs/fields/Password/index.native.js.map +1 -1
- package/dist/cjs/fields/Phone/index.native.js.map +1 -1
- package/dist/cjs/fields/Progress/index.native.js.map +1 -1
- package/dist/cjs/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/Rating/index.native.js.map +1 -1
- package/dist/cjs/fields/ReadOnly/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/index.native.js.map +1 -1
- package/dist/cjs/fields/Signature/index.native.js.map +1 -1
- package/dist/cjs/fields/Slider/index.native.js.map +1 -1
- package/dist/cjs/fields/SmallText/index.native.js.map +1 -1
- package/dist/cjs/fields/Switch/index.native.js.map +1 -1
- package/dist/cjs/fields/TableMultiSelect.native.js.map +1 -1
- package/dist/cjs/fields/TextEditor/types.native.js.map +1 -1
- package/dist/cjs/fields/TimePicker/index.native.js.map +1 -1
- package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/shared/EditorPreviewPanes.cjs +2 -2
- package/dist/cjs/fields/shared/EditorPreviewPanes.native.js +2 -2
- package/dist/cjs/fields/shared/EditorPreviewPanes.native.js.map +1 -1
- package/dist/cjs/filterFields.native.js.map +1 -1
- package/dist/cjs/formEngine/createPrefill.native.js.map +1 -1
- package/dist/cjs/formEngine/dirtyTracking.native.js.map +1 -1
- package/dist/cjs/formEngine/docLifecycle.native.js.map +1 -1
- package/dist/cjs/formEngine/docPermissions.cjs +20 -0
- package/dist/cjs/formEngine/docPermissions.native.js +69 -0
- package/dist/cjs/formEngine/docPermissions.native.js.map +1 -1
- package/dist/cjs/formEngine/doctypeMeta.native.js.map +1 -1
- package/dist/cjs/formEngine/fetchFrom.native.js.map +1 -1
- package/dist/cjs/formEngine/fieldPermissions.native.js.map +1 -1
- package/dist/cjs/formEngine/formActions.native.js.map +1 -1
- package/dist/cjs/formEngine/formLayout.native.js.map +1 -1
- package/dist/cjs/formEngine/index.cjs +2 -0
- package/dist/cjs/formEngine/index.native.js +2 -0
- package/dist/cjs/formEngine/index.native.js.map +1 -1
- package/dist/cjs/formEngine/useAmendedAs.native.js.map +1 -1
- package/dist/cjs/formEngine/useDocRights.native.js.map +1 -1
- package/dist/cjs/formEngine/useFormEngine.native.js.map +1 -1
- package/dist/cjs/formEngine/useSessionRoles.native.js.map +1 -1
- package/dist/cjs/frappeConnections.native.js.map +1 -1
- package/dist/cjs/frappeEventColor.native.js.map +1 -1
- package/dist/cjs/frappeNavigation.native.js.map +1 -1
- package/dist/cjs/frappeStoryTarget.native.js.map +1 -1
- package/dist/cjs/frappeUsers.native.js.map +1 -1
- package/dist/cjs/index.cjs +67 -7
- package/dist/cjs/index.native.js +101 -41
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/insights/InsightsFacetTags.native.js.map +1 -1
- package/dist/cjs/insights/InsightsKpiBand.native.js.map +1 -1
- package/dist/cjs/insights/InsightsLeaders.native.js.map +1 -1
- package/dist/cjs/insights/InsightsRecords.native.js.map +1 -1
- package/dist/cjs/insights/InsightsTable.native.js.map +1 -1
- package/dist/cjs/insights/insightsData.native.js.map +1 -1
- package/dist/cjs/insights/insightsRecord.native.js.map +1 -1
- package/dist/cjs/list/FacetBadge.native.js.map +1 -1
- package/dist/cjs/list/FacetRail.native.js.map +1 -1
- package/dist/cjs/list/ImageWell.native.js.map +1 -1
- package/dist/cjs/list/ListControls.native.js.map +1 -1
- package/dist/cjs/list/ListToastHost.native.js.map +1 -1
- package/dist/cjs/list/RangeFilter.native.js.map +1 -1
- package/dist/cjs/list/ShufflePanel.native.js.map +1 -1
- package/dist/cjs/list/facetReading.native.js.map +1 -1
- package/dist/cjs/list/favorites.native.js.map +1 -1
- package/dist/cjs/list/listColumns.native.js.map +1 -1
- package/dist/cjs/list/listFields.native.js.map +1 -1
- package/dist/cjs/list/listFormat.native.js.map +1 -1
- package/dist/cjs/list/listState.native.js.map +1 -1
- package/dist/cjs/list/recordLook.native.js.map +1 -1
- package/dist/cjs/list/statusColors.native.js.map +1 -1
- package/dist/cjs/list/useListControls.native.js.map +1 -1
- package/dist/cjs/liveErrors.cjs +8 -2
- package/dist/cjs/liveErrors.native.js +8 -2
- package/dist/cjs/liveErrors.native.js.map +1 -1
- package/dist/cjs/measures.cjs +31 -0
- package/dist/cjs/measures.native.js +34 -0
- package/dist/cjs/measures.native.js.map +1 -0
- package/dist/cjs/record/RecordBars.native.js.map +1 -1
- package/dist/cjs/record/RecordGallery.native.js.map +1 -1
- package/dist/cjs/record/RecordHero.native.js.map +1 -1
- package/dist/cjs/record/RecordPage.native.js.map +1 -1
- package/dist/cjs/record/RecordPager.native.js.map +1 -1
- package/dist/cjs/record/RecordRows.native.js.map +1 -1
- package/dist/cjs/record/recordData.native.js.map +1 -1
- package/dist/cjs/record/recordSections.native.js.map +1 -1
- package/dist/cjs/recordShape.cjs +103 -0
- package/dist/cjs/recordShape.native.js +115 -0
- package/dist/cjs/recordShape.native.js.map +1 -0
- package/dist/cjs/report/ReportGrid.cjs +453 -0
- package/dist/cjs/report/ReportGrid.native.js +510 -0
- package/dist/cjs/report/ReportGrid.native.js.map +1 -0
- package/dist/cjs/report/ReportViewToolbar.cjs +253 -0
- package/dist/cjs/report/ReportViewToolbar.native.js +303 -0
- package/dist/cjs/report/ReportViewToolbar.native.js.map +1 -0
- package/dist/cjs/report/queryReport.cjs +444 -0
- package/dist/cjs/report/queryReport.native.js +588 -0
- package/dist/cjs/report/queryReport.native.js.map +1 -0
- package/dist/cjs/report/queryReportColumns.cjs +131 -0
- package/dist/cjs/report/queryReportColumns.native.js +159 -0
- package/dist/cjs/report/queryReportColumns.native.js.map +1 -0
- package/dist/cjs/report/reportViewModel.cjs +386 -0
- package/dist/cjs/report/reportViewModel.native.js +657 -0
- package/dist/cjs/report/reportViewModel.native.js.map +1 -0
- package/dist/cjs/report/savedReports.cjs +137 -0
- package/dist/cjs/report/savedReports.native.js +169 -0
- package/dist/cjs/report/savedReports.native.js.map +1 -0
- package/dist/cjs/report/useFrappeReport.cjs +184 -0
- package/dist/cjs/report/useFrappeReport.native.js +214 -0
- package/dist/cjs/report/useFrappeReport.native.js.map +1 -0
- package/dist/cjs/report/useQueryReport.cjs +201 -0
- package/dist/cjs/report/useQueryReport.native.js +233 -0
- package/dist/cjs/report/useQueryReport.native.js.map +1 -0
- package/dist/cjs/resolveControl.native.js.map +1 -1
- package/dist/cjs/schemaForm/SchemaWizard.native.js.map +1 -1
- package/dist/cjs/schemaForm/applyFieldOrder.native.js.map +1 -1
- package/dist/cjs/schemaForm/index.native.js +1 -1
- package/dist/cjs/schemaForm/index.native.js.map +1 -1
- package/dist/cjs/shared/devWarn.native.js.map +1 -1
- package/dist/cjs/shared/useKeyboardInset.native.js.map +1 -1
- package/dist/cjs/syncError.native.js.map +1 -1
- package/dist/cjs/useConnectionFilters.native.js.map +1 -1
- package/dist/cjs/useFrappeAggregate.native.js.map +1 -1
- package/dist/cjs/useFrappeFacets.native.js.map +1 -1
- package/dist/cjs/useFrappeForm.cjs +558 -0
- package/dist/cjs/useFrappeForm.native.js +714 -0
- package/dist/cjs/useFrappeForm.native.js.map +1 -0
- package/dist/cjs/useFrappePagination.native.js.map +1 -1
- package/dist/cjs/useFrappeRows.native.js.map +1 -1
- package/dist/esm/ChartCard.native.js.map +1 -1
- package/dist/esm/FileBrowser/AttachLibrary.native.js.map +1 -1
- package/dist/esm/FileBrowser/folderTree.native.js.map +1 -1
- package/dist/esm/FileBrowser/index.mjs +20 -3
- package/dist/esm/FileBrowser/index.mjs.map +1 -1
- package/dist/esm/FileBrowser/index.native.js +20 -3
- package/dist/esm/FileBrowser/index.native.js.map +1 -1
- package/dist/esm/FileBrowser/types.native.js.map +1 -1
- package/dist/esm/FileBrowser/viewPersistence.native.js.map +1 -1
- package/dist/esm/FormSidebar.native.js.map +1 -1
- package/dist/esm/FrappeAssignment.native.js.map +1 -1
- package/dist/esm/FrappeCalendar.mjs +4 -3
- package/dist/esm/FrappeCalendar.mjs.map +1 -1
- package/dist/esm/FrappeCalendar.native.js +4 -3
- package/dist/esm/FrappeCalendar.native.js.map +1 -1
- package/dist/esm/FrappeChartCard.native.js.map +1 -1
- package/dist/esm/FrappeConnectionIndicator.native.js.map +1 -1
- package/dist/esm/FrappeDashboard.native.js.map +1 -1
- package/dist/esm/FrappeForm.mjs +151 -467
- package/dist/esm/FrappeForm.mjs.map +1 -1
- package/dist/esm/FrappeForm.native.js +155 -636
- package/dist/esm/FrappeForm.native.js.map +1 -1
- package/dist/esm/FrappeFormActions.native.js.map +1 -1
- package/dist/esm/FrappeFormConnections.native.js.map +1 -1
- package/dist/esm/FrappeGantt.native.js.map +1 -1
- package/dist/esm/FrappeImageView.native.js.map +1 -1
- package/dist/esm/FrappeInsights.native.js.map +1 -1
- package/dist/esm/FrappeKPICard.native.js.map +1 -1
- package/dist/esm/FrappeKanban.native.js.map +1 -1
- package/dist/esm/FrappeLikedBy.native.js.map +1 -1
- package/dist/esm/FrappeList.native.js.map +1 -1
- package/dist/esm/FrappeListView.mjs +6 -28
- package/dist/esm/FrappeListView.mjs.map +1 -1
- package/dist/esm/FrappeListView.native.js +12 -33
- package/dist/esm/FrappeListView.native.js.map +1 -1
- package/dist/esm/FrappeNotConfigured.native.js.map +1 -1
- package/dist/esm/FrappeProvider.native.js.map +1 -1
- package/dist/esm/FrappeReconnectIndicator.native.js.map +1 -1
- package/dist/esm/FrappeRecordView.native.js.map +1 -1
- package/dist/esm/FrappeReportBuilder.mjs +526 -312
- package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
- package/dist/esm/FrappeReportBuilder.native.js +661 -356
- package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
- package/dist/esm/FrappeReportView.mjs +30 -229
- package/dist/esm/FrappeReportView.mjs.map +1 -1
- package/dist/esm/FrappeReportView.native.js +34 -278
- package/dist/esm/FrappeReportView.native.js.map +1 -1
- package/dist/esm/FrappeRowsReader.native.js.map +1 -1
- package/dist/esm/FrappeTable.native.js.map +1 -1
- package/dist/esm/FrappeTreeView.native.js.map +1 -1
- package/dist/esm/SyncErrorNotice.native.js.map +1 -1
- package/dist/esm/commentActions.native.js.map +1 -1
- package/dist/esm/components/attachments.native.js.map +1 -1
- package/dist/esm/components/tags.native.js.map +1 -1
- package/dist/esm/components/timeline.native.js.map +1 -1
- package/dist/esm/dependsOn.mjs +2 -2
- package/dist/esm/dependsOn.mjs.map +1 -1
- package/dist/esm/dependsOn.native.js +1 -2
- package/dist/esm/dependsOn.native.js.map +1 -1
- package/dist/esm/desk/DeskFormPage.native.js.map +1 -1
- package/dist/esm/desk/DeskFormSidebar.native.js.map +1 -1
- package/dist/esm/desk/DeskListPage.native.js.map +1 -1
- package/dist/esm/desk/DeskShell.native.js.map +1 -1
- package/dist/esm/desk/DeskSidebar.native.js.map +1 -1
- package/dist/esm/desk/awesomebar.native.js.map +1 -1
- package/dist/esm/fieldOverrides.native.js.map +1 -1
- package/dist/esm/fieldRegistry.native.js.map +1 -1
- package/dist/esm/fields/Barcode/index.native.js.map +1 -1
- package/dist/esm/fields/Check/index.native.js.map +1 -1
- package/dist/esm/fields/CodeEditor/index.native.js.map +1 -1
- package/dist/esm/fields/Color/index.native.js.map +1 -1
- package/dist/esm/fields/Data/index.native.js.map +1 -1
- package/dist/esm/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/esm/fields/Date/types.native.js.map +1 -1
- package/dist/esm/fields/Date/wire.native.js.map +1 -1
- package/dist/esm/fields/DateRange/index.native.js.map +1 -1
- package/dist/esm/fields/Duration/index.native.js.map +1 -1
- package/dist/esm/fields/DynamicLink.native.js.map +1 -1
- package/dist/esm/fields/FileUpload.native.js.map +1 -1
- package/dist/esm/fields/FileUploadField/index.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/index.native.js.map +1 -1
- package/dist/esm/fields/HTML/index.native.js.map +1 -1
- package/dist/esm/fields/Heading/index.native.js.map +1 -1
- package/dist/esm/fields/Icon/index.native.js.map +1 -1
- package/dist/esm/fields/Link.native.js.map +1 -1
- package/dist/esm/fields/LinkTree.native.js.map +1 -1
- package/dist/esm/fields/LongText/index.native.js.map +1 -1
- package/dist/esm/fields/MentionInput/index.native.js.map +1 -1
- package/dist/esm/fields/Numeric/index.native.js.map +1 -1
- package/dist/esm/fields/OTPInput/index.native.js.map +1 -1
- package/dist/esm/fields/Password/index.native.js.map +1 -1
- package/dist/esm/fields/Phone/index.native.js.map +1 -1
- package/dist/esm/fields/Progress/index.native.js.map +1 -1
- package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/esm/fields/Rating/index.native.js.map +1 -1
- package/dist/esm/fields/ReadOnly/index.native.js.map +1 -1
- package/dist/esm/fields/Select/index.native.js.map +1 -1
- package/dist/esm/fields/Signature/index.native.js.map +1 -1
- package/dist/esm/fields/Slider/index.native.js.map +1 -1
- package/dist/esm/fields/SmallText/index.native.js.map +1 -1
- package/dist/esm/fields/Switch/index.native.js.map +1 -1
- package/dist/esm/fields/TableMultiSelect.native.js.map +1 -1
- package/dist/esm/fields/TextEditor/types.native.js.map +1 -1
- package/dist/esm/fields/TimePicker/index.native.js.map +1 -1
- package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/esm/fields/shared/EditorPreviewPanes.mjs +2 -2
- package/dist/esm/fields/shared/EditorPreviewPanes.mjs.map +1 -1
- package/dist/esm/fields/shared/EditorPreviewPanes.native.js +2 -2
- package/dist/esm/fields/shared/EditorPreviewPanes.native.js.map +1 -1
- package/dist/esm/filterFields.native.js.map +1 -1
- package/dist/esm/formEngine/createPrefill.native.js.map +1 -1
- package/dist/esm/formEngine/dirtyTracking.native.js.map +1 -1
- package/dist/esm/formEngine/docLifecycle.native.js.map +1 -1
- package/dist/esm/formEngine/docPermissions.mjs +19 -1
- package/dist/esm/formEngine/docPermissions.mjs.map +1 -1
- package/dist/esm/formEngine/docPermissions.native.js +68 -1
- package/dist/esm/formEngine/docPermissions.native.js.map +1 -1
- package/dist/esm/formEngine/doctypeMeta.native.js.map +1 -1
- package/dist/esm/formEngine/fetchFrom.native.js.map +1 -1
- package/dist/esm/formEngine/fieldPermissions.native.js.map +1 -1
- package/dist/esm/formEngine/formActions.native.js.map +1 -1
- package/dist/esm/formEngine/formLayout.native.js.map +1 -1
- package/dist/esm/formEngine/index.mjs +2 -2
- package/dist/esm/formEngine/index.mjs.map +1 -1
- package/dist/esm/formEngine/index.native.js +2 -2
- package/dist/esm/formEngine/index.native.js.map +1 -1
- package/dist/esm/formEngine/useAmendedAs.native.js.map +1 -1
- package/dist/esm/formEngine/useDocRights.native.js.map +1 -1
- package/dist/esm/formEngine/useFormEngine.native.js.map +1 -1
- package/dist/esm/formEngine/useSessionRoles.native.js.map +1 -1
- package/dist/esm/frappeConnections.native.js.map +1 -1
- package/dist/esm/frappeEventColor.native.js.map +1 -1
- package/dist/esm/frappeNavigation.native.js.map +1 -1
- package/dist/esm/frappeStoryTarget.native.js.map +1 -1
- package/dist/esm/frappeUsers.native.js.map +1 -1
- package/dist/esm/index.mjs +16 -4
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +16 -4
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/insights/InsightsFacetTags.native.js.map +1 -1
- package/dist/esm/insights/InsightsKpiBand.native.js.map +1 -1
- package/dist/esm/insights/InsightsLeaders.native.js.map +1 -1
- package/dist/esm/insights/InsightsRecords.native.js.map +1 -1
- package/dist/esm/insights/InsightsTable.native.js.map +1 -1
- package/dist/esm/insights/insightsData.native.js.map +1 -1
- package/dist/esm/insights/insightsRecord.native.js.map +1 -1
- package/dist/esm/list/FacetBadge.native.js.map +1 -1
- package/dist/esm/list/FacetRail.native.js.map +1 -1
- package/dist/esm/list/ImageWell.native.js.map +1 -1
- package/dist/esm/list/ListControls.native.js.map +1 -1
- package/dist/esm/list/ListToastHost.native.js.map +1 -1
- package/dist/esm/list/RangeFilter.native.js.map +1 -1
- package/dist/esm/list/ShufflePanel.native.js.map +1 -1
- package/dist/esm/list/facetReading.native.js.map +1 -1
- package/dist/esm/list/favorites.native.js.map +1 -1
- package/dist/esm/list/listColumns.native.js.map +1 -1
- package/dist/esm/list/listFields.native.js.map +1 -1
- package/dist/esm/list/listFormat.native.js.map +1 -1
- package/dist/esm/list/listState.native.js.map +1 -1
- package/dist/esm/list/recordLook.native.js.map +1 -1
- package/dist/esm/list/statusColors.native.js.map +1 -1
- package/dist/esm/list/useListControls.native.js.map +1 -1
- package/dist/esm/liveErrors.mjs +8 -3
- package/dist/esm/liveErrors.mjs.map +1 -1
- package/dist/esm/liveErrors.native.js +8 -3
- package/dist/esm/liveErrors.native.js.map +1 -1
- package/dist/esm/measures.mjs +5 -0
- package/dist/esm/measures.mjs.map +1 -0
- package/dist/esm/measures.native.js +5 -0
- package/dist/esm/measures.native.js.map +1 -0
- package/dist/esm/record/RecordBars.native.js.map +1 -1
- package/dist/esm/record/RecordGallery.native.js.map +1 -1
- package/dist/esm/record/RecordHero.native.js.map +1 -1
- package/dist/esm/record/RecordPage.native.js.map +1 -1
- package/dist/esm/record/RecordPager.native.js.map +1 -1
- package/dist/esm/record/RecordRows.native.js.map +1 -1
- package/dist/esm/record/recordData.native.js.map +1 -1
- package/dist/esm/record/recordSections.native.js.map +1 -1
- package/dist/esm/recordShape.mjs +74 -0
- package/dist/esm/recordShape.mjs.map +1 -0
- package/dist/esm/recordShape.native.js +83 -0
- package/dist/esm/recordShape.native.js.map +1 -0
- package/dist/esm/report/ReportGrid.mjs +428 -0
- package/dist/esm/report/ReportGrid.mjs.map +1 -0
- package/dist/esm/report/ReportGrid.native.js +471 -0
- package/dist/esm/report/ReportGrid.native.js.map +1 -0
- package/dist/esm/report/ReportViewToolbar.mjs +228 -0
- package/dist/esm/report/ReportViewToolbar.mjs.map +1 -0
- package/dist/esm/report/ReportViewToolbar.native.js +264 -0
- package/dist/esm/report/ReportViewToolbar.native.js.map +1 -0
- package/dist/esm/report/queryReport.mjs +404 -0
- package/dist/esm/report/queryReport.mjs.map +1 -0
- package/dist/esm/report/queryReport.native.js +545 -0
- package/dist/esm/report/queryReport.native.js.map +1 -0
- package/dist/esm/report/queryReportColumns.mjs +105 -0
- package/dist/esm/report/queryReportColumns.mjs.map +1 -0
- package/dist/esm/report/queryReportColumns.native.js +119 -0
- package/dist/esm/report/queryReportColumns.native.js.map +1 -0
- package/dist/esm/report/reportViewModel.mjs +343 -0
- package/dist/esm/report/reportViewModel.mjs.map +1 -0
- package/dist/esm/report/reportViewModel.native.js +611 -0
- package/dist/esm/report/reportViewModel.native.js.map +1 -0
- package/dist/esm/report/savedReports.mjs +112 -0
- package/dist/esm/report/savedReports.mjs.map +1 -0
- package/dist/esm/report/savedReports.native.js +141 -0
- package/dist/esm/report/savedReports.native.js.map +1 -0
- package/dist/esm/report/useFrappeReport.mjs +158 -0
- package/dist/esm/report/useFrappeReport.mjs.map +1 -0
- package/dist/esm/report/useFrappeReport.native.js +185 -0
- package/dist/esm/report/useFrappeReport.native.js.map +1 -0
- package/dist/esm/report/useQueryReport.mjs +176 -0
- package/dist/esm/report/useQueryReport.mjs.map +1 -0
- package/dist/esm/report/useQueryReport.native.js +205 -0
- package/dist/esm/report/useQueryReport.native.js.map +1 -0
- package/dist/esm/resolveControl.native.js.map +1 -1
- package/dist/esm/schemaForm/SchemaWizard.native.js.map +1 -1
- package/dist/esm/schemaForm/applyFieldOrder.native.js.map +1 -1
- package/dist/esm/schemaForm/index.native.js.map +1 -1
- package/dist/esm/shared/devWarn.native.js.map +1 -1
- package/dist/esm/shared/useKeyboardInset.native.js.map +1 -1
- package/dist/esm/syncError.native.js.map +1 -1
- package/dist/esm/useConnectionFilters.native.js.map +1 -1
- package/dist/esm/useFrappeAggregate.native.js.map +1 -1
- package/dist/esm/useFrappeFacets.native.js.map +1 -1
- package/dist/esm/useFrappeForm.mjs +533 -0
- package/dist/esm/useFrappeForm.mjs.map +1 -0
- package/dist/esm/useFrappeForm.native.js +686 -0
- package/dist/esm/useFrappeForm.native.js.map +1 -0
- package/dist/esm/useFrappePagination.native.js.map +1 -1
- package/dist/esm/useFrappeRows.native.js.map +1 -1
- package/dist/jsx/FileBrowser/index.mjs +20 -3
- package/dist/jsx/FileBrowser/index.mjs.map +1 -1
- package/dist/jsx/FileBrowser/index.native.js +20 -3
- package/dist/jsx/FileBrowser/index.native.js.map +1 -1
- package/dist/jsx/FrappeCalendar.mjs +4 -3
- package/dist/jsx/FrappeCalendar.mjs.map +1 -1
- package/dist/jsx/FrappeCalendar.native.js +3 -2
- package/dist/jsx/FrappeCalendar.native.js.map +1 -1
- package/dist/jsx/FrappeForm.mjs +151 -467
- package/dist/jsx/FrappeForm.mjs.map +1 -1
- package/dist/jsx/FrappeForm.native.js +152 -633
- package/dist/jsx/FrappeForm.native.js.map +1 -1
- package/dist/jsx/FrappeListView.mjs +6 -28
- package/dist/jsx/FrappeListView.mjs.map +1 -1
- package/dist/jsx/FrappeListView.native.js +11 -32
- package/dist/jsx/FrappeListView.native.js.map +1 -1
- package/dist/jsx/FrappeReportBuilder.mjs +526 -312
- package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
- package/dist/jsx/FrappeReportBuilder.native.js +658 -353
- package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
- package/dist/jsx/FrappeReportView.mjs +30 -229
- package/dist/jsx/FrappeReportView.mjs.map +1 -1
- package/dist/jsx/FrappeReportView.native.js +39 -283
- package/dist/jsx/FrappeReportView.native.js.map +1 -1
- package/dist/jsx/dependsOn.mjs +2 -2
- package/dist/jsx/dependsOn.mjs.map +1 -1
- package/dist/jsx/dependsOn.native.js +1 -2
- package/dist/jsx/dependsOn.native.js.map +1 -1
- package/dist/jsx/fields/shared/EditorPreviewPanes.mjs +2 -2
- package/dist/jsx/fields/shared/EditorPreviewPanes.mjs.map +1 -1
- package/dist/jsx/fields/shared/EditorPreviewPanes.native.js +2 -2
- package/dist/jsx/fields/shared/EditorPreviewPanes.native.js.map +1 -1
- package/dist/jsx/formEngine/docPermissions.mjs +19 -1
- package/dist/jsx/formEngine/docPermissions.mjs.map +1 -1
- package/dist/jsx/formEngine/docPermissions.native.js +69 -0
- package/dist/jsx/formEngine/docPermissions.native.js.map +1 -1
- package/dist/jsx/formEngine/index.mjs +2 -2
- package/dist/jsx/formEngine/index.mjs.map +1 -1
- package/dist/jsx/formEngine/index.native.js +2 -0
- package/dist/jsx/formEngine/index.native.js.map +1 -1
- package/dist/jsx/index.js +16 -4
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +16 -4
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +67 -7
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/liveErrors.mjs +8 -3
- package/dist/jsx/liveErrors.mjs.map +1 -1
- package/dist/jsx/liveErrors.native.js +8 -2
- package/dist/jsx/liveErrors.native.js.map +1 -1
- package/dist/jsx/measures.mjs +5 -0
- package/dist/jsx/measures.mjs.map +1 -0
- package/dist/jsx/measures.native.js +34 -0
- package/dist/jsx/measures.native.js.map +1 -0
- package/dist/jsx/recordShape.mjs +74 -0
- package/dist/jsx/recordShape.mjs.map +1 -0
- package/dist/jsx/recordShape.native.js +115 -0
- package/dist/jsx/recordShape.native.js.map +1 -0
- package/dist/jsx/report/ReportGrid.mjs +428 -0
- package/dist/jsx/report/ReportGrid.mjs.map +1 -0
- package/dist/jsx/report/ReportGrid.native.js +510 -0
- package/dist/jsx/report/ReportGrid.native.js.map +1 -0
- package/dist/jsx/report/ReportViewToolbar.mjs +228 -0
- package/dist/jsx/report/ReportViewToolbar.mjs.map +1 -0
- package/dist/jsx/report/ReportViewToolbar.native.js +303 -0
- package/dist/jsx/report/ReportViewToolbar.native.js.map +1 -0
- package/dist/jsx/report/queryReport.mjs +404 -0
- package/dist/jsx/report/queryReport.mjs.map +1 -0
- package/dist/jsx/report/queryReport.native.js +588 -0
- package/dist/jsx/report/queryReport.native.js.map +1 -0
- package/dist/jsx/report/queryReportColumns.mjs +105 -0
- package/dist/jsx/report/queryReportColumns.mjs.map +1 -0
- package/dist/jsx/report/queryReportColumns.native.js +159 -0
- package/dist/jsx/report/queryReportColumns.native.js.map +1 -0
- package/dist/jsx/report/reportViewModel.mjs +343 -0
- package/dist/jsx/report/reportViewModel.mjs.map +1 -0
- package/dist/jsx/report/reportViewModel.native.js +657 -0
- package/dist/jsx/report/reportViewModel.native.js.map +1 -0
- package/dist/jsx/report/savedReports.mjs +112 -0
- package/dist/jsx/report/savedReports.mjs.map +1 -0
- package/dist/jsx/report/savedReports.native.js +169 -0
- package/dist/jsx/report/savedReports.native.js.map +1 -0
- package/dist/jsx/report/useFrappeReport.mjs +158 -0
- package/dist/jsx/report/useFrappeReport.mjs.map +1 -0
- package/dist/jsx/report/useFrappeReport.native.js +214 -0
- package/dist/jsx/report/useFrappeReport.native.js.map +1 -0
- package/dist/jsx/report/useQueryReport.mjs +176 -0
- package/dist/jsx/report/useQueryReport.mjs.map +1 -0
- package/dist/jsx/report/useQueryReport.native.js +233 -0
- package/dist/jsx/report/useQueryReport.native.js.map +1 -0
- package/dist/jsx/useFrappeForm.mjs +533 -0
- package/dist/jsx/useFrappeForm.mjs.map +1 -0
- package/dist/jsx/useFrappeForm.native.js +714 -0
- package/dist/jsx/useFrappeForm.native.js.map +1 -0
- package/package.json +16 -16
- package/src/FileBrowser/FileBrowser.spec.tsx +21 -0
- package/src/FileBrowser/FileBrowser.stories.tsx +27 -0
- package/src/FileBrowser/index.tsx +25 -2
- package/src/FrappeCalendar.tsx +8 -2
- package/src/FrappeForm.stories.tsx +52 -0
- package/src/FrappeForm.tsx +176 -565
- package/src/FrappeListView.cards.stories.tsx +3 -6
- package/src/FrappeListView.tsx +15 -55
- package/src/FrappeReportBuilder.stories.tsx +34 -7
- package/src/FrappeReportBuilder.tsx +790 -380
- package/src/FrappeReportView.spec.tsx +10 -6
- package/src/FrappeReportView.stories.tsx +20 -1
- package/src/FrappeReportView.tsx +89 -415
- package/src/dependsOn.ts +4 -1
- package/src/fields/shared/EditorPreviewPanes.tsx +2 -2
- package/src/formEngine/docPermissions.spec.ts +36 -1
- package/src/formEngine/docPermissions.ts +34 -0
- package/src/formEngine/index.ts +8 -1
- package/src/frappeRemainder.designLaw.spec.ts +9 -1
- package/src/frappeRoutes.spec.tsx +9 -0
- package/src/frappeViews.designLaw.spec.ts +15 -2
- package/src/guestFallback.spec.tsx +236 -0
- package/src/index.ts +117 -16
- package/src/liveErrors.spec.ts +10 -0
- package/src/liveErrors.ts +16 -3
- package/src/measures.ts +11 -0
- package/src/recordShape.spec.ts +100 -0
- package/src/recordShape.ts +133 -0
- package/src/report/ReportGrid.spec.tsx +175 -0
- package/src/report/ReportGrid.tsx +530 -0
- package/src/report/ReportViewToolbar.spec.tsx +90 -0
- package/src/report/ReportViewToolbar.tsx +314 -0
- package/src/report/queryReport.spec.ts +483 -0
- package/src/report/queryReport.ts +722 -0
- package/src/report/queryReportColumns.tsx +123 -0
- package/src/report/reportViewModel.spec.ts +393 -0
- package/src/report/reportViewModel.ts +617 -0
- package/src/report/savedReports.ts +172 -0
- package/src/report/seam.spec.ts +216 -0
- package/src/report/useFrappeReport.ts +241 -0
- package/src/report/useQueryReport.spec.tsx +131 -0
- package/src/report/useQueryReport.ts +309 -0
- package/src/useFrappeForm.spec.tsx +209 -0
- package/src/useFrappeForm.ts +778 -0
- package/types/FileBrowser/index.d.ts.map +1 -1
- package/types/FrappeCalendar.d.ts.map +1 -1
- package/types/FrappeForm.d.ts +21 -1
- package/types/FrappeForm.d.ts.map +1 -1
- package/types/FrappeListView.d.ts.map +1 -1
- package/types/FrappeReportBuilder.d.ts +66 -16
- package/types/FrappeReportBuilder.d.ts.map +1 -1
- package/types/FrappeReportView.d.ts +9 -127
- package/types/FrappeReportView.d.ts.map +1 -1
- package/types/dependsOn.d.ts.map +1 -1
- package/types/formEngine/docPermissions.d.ts +14 -0
- package/types/formEngine/docPermissions.d.ts.map +1 -1
- package/types/formEngine/index.d.ts +1 -1
- package/types/formEngine/index.d.ts.map +1 -1
- package/types/index.d.ts +15 -3
- package/types/index.d.ts.map +1 -1
- package/types/liveErrors.d.ts +9 -1
- package/types/liveErrors.d.ts.map +1 -1
- package/types/measures.d.ts +3 -0
- package/types/measures.d.ts.map +1 -0
- package/types/recordShape.d.ts +45 -0
- package/types/recordShape.d.ts.map +1 -0
- package/types/report/ReportGrid.d.ts +20 -0
- package/types/report/ReportGrid.d.ts.map +1 -0
- package/types/report/ReportViewToolbar.d.ts +31 -0
- package/types/report/ReportViewToolbar.d.ts.map +1 -0
- package/types/report/queryReport.d.ts +181 -0
- package/types/report/queryReport.d.ts.map +1 -0
- package/types/report/queryReportColumns.d.ts +12 -0
- package/types/report/queryReportColumns.d.ts.map +1 -0
- package/types/report/reportViewModel.d.ts +146 -0
- package/types/report/reportViewModel.d.ts.map +1 -0
- package/types/report/savedReports.d.ts +27 -0
- package/types/report/savedReports.d.ts.map +1 -0
- package/types/report/useFrappeReport.d.ts +60 -0
- package/types/report/useFrappeReport.d.ts.map +1 -0
- package/types/report/useQueryReport.d.ts +37 -0
- package/types/report/useQueryReport.d.ts.map +1 -0
- package/types/useFrappeForm.d.ts +142 -0
- package/types/useFrappeForm.d.ts.map +1 -0
package/src/FrappeForm.tsx
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getFrappeSyncModule,
|
|
3
2
|
useFrappeCollection,
|
|
4
|
-
useFrappeDoctypeEvents,
|
|
5
3
|
useLiveQuery,
|
|
6
|
-
useDocTypeMeta,
|
|
7
4
|
type DocTypeMeta,
|
|
8
|
-
type DocTypeField,
|
|
9
5
|
type FormSection,
|
|
10
6
|
} from "@multiplatform.one/frappe";
|
|
11
7
|
import {
|
|
@@ -17,13 +13,12 @@ import {
|
|
|
17
13
|
Tabs,
|
|
18
14
|
type TabsItem,
|
|
19
15
|
} from "@multiplatform.one/components";
|
|
20
|
-
import { SchemaForm, type FieldOverride } from "./schemaForm";
|
|
16
|
+
import { SchemaForm, type FieldOverride, type SchemaFormProps } from "./schemaForm";
|
|
21
17
|
import type { HeadingLevel } from "./fields/Heading";
|
|
22
18
|
import { FormSidebar, type SidebarSection, type SidebarMetadataEntry } from "./FormSidebar";
|
|
23
|
-
import { eq } from "@tanstack/db";
|
|
24
19
|
import { ArrowLeftIcon } from "@phosphor-icons/react";
|
|
25
20
|
import type React from "react";
|
|
26
|
-
import {
|
|
21
|
+
import { useEffect, useMemo, useState } from "react";
|
|
27
22
|
import type { TFunction } from "i18next";
|
|
28
23
|
import { useTranslation } from "react-i18next";
|
|
29
24
|
import { Button, Input, formCommonColors } from "@multiplatform.one/forms";
|
|
@@ -33,27 +28,21 @@ import { useFrappeConfig } from "./FrappeProvider";
|
|
|
33
28
|
import { FrappeReconnectIndicator } from "./FrappeReconnectIndicator";
|
|
34
29
|
import { Timeline } from "./components/timeline";
|
|
35
30
|
import { dialog } from "@multiplatform.one/desktop";
|
|
36
|
-
import {
|
|
37
|
-
import { buildPermLevelMatrix } from "./formEngine/fieldPermissions";
|
|
38
|
-
import { createHttpClient, validateLink } from "./formEngine/formActions";
|
|
39
|
-
import { buildFormTabs, isPromptNamed, type FormTab } from "./formEngine/formLayout";
|
|
40
|
-
import { setCreatePrefill, takeCreatePrefill } from "./formEngine/createPrefill";
|
|
31
|
+
import type { FormTab } from "./formEngine/formLayout";
|
|
41
32
|
import {
|
|
42
|
-
buildAmendValues,
|
|
43
33
|
canDeleteDoc,
|
|
44
34
|
canEditDoc,
|
|
45
35
|
isAmendable,
|
|
46
36
|
resolveDocAction,
|
|
47
37
|
resolveDocIndicator,
|
|
48
38
|
} from "./formEngine/docLifecycle";
|
|
49
|
-
import { useAmendedAs } from "./formEngine/useAmendedAs";
|
|
50
|
-
import { useDocRights } from "./formEngine/useDocRights";
|
|
51
|
-
import { extractServerError } from "./formEngine/useFormEngine";
|
|
52
39
|
import { FrappeFormActions } from "./FrappeFormActions";
|
|
53
40
|
import { connectionFields, connectionsFromMeta, type FrappeConnection } from "./frappeConnections";
|
|
54
41
|
import { FrappeFormConnections } from "./FrappeFormConnections";
|
|
55
42
|
import { FrappeNotConfigured } from "./FrappeNotConfigured";
|
|
56
43
|
import { FrappeRowsReader, type FrappeRowsSnapshot } from "./FrappeRowsReader";
|
|
44
|
+
import { isPermissionError } from "./liveErrors";
|
|
45
|
+
import { fieldsFromRecord } from "./recordShape";
|
|
57
46
|
import {
|
|
58
47
|
frappeRouteHref,
|
|
59
48
|
frappeRouteTarget,
|
|
@@ -61,17 +50,11 @@ import {
|
|
|
61
50
|
type FrappeRouteTarget,
|
|
62
51
|
} from "./frappeNavigation";
|
|
63
52
|
import type { FieldSchema } from "./fieldRegistry";
|
|
53
|
+
import { useFrappeForm, type FrappeFormController } from "./useFrappeForm";
|
|
64
54
|
|
|
65
55
|
/** Linked rows per connection doctype, as the render prop receives them. */
|
|
66
56
|
export type FrappeConnectionRows = Record<string, Array<Record<string, unknown>>>;
|
|
67
57
|
|
|
68
|
-
function isEmptyFieldValue(value: unknown): boolean {
|
|
69
|
-
if (value === undefined || value === null) return true;
|
|
70
|
-
if (typeof value === "string" && value.trim() === "") return true;
|
|
71
|
-
if (Array.isArray(value) && value.length === 0) return true;
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
58
|
export interface FrappeFormProps<TDoc extends { name: string; doctype: string }> {
|
|
76
59
|
baseURL?: string;
|
|
77
60
|
auth?: { useToken?: boolean; token?: string; type?: string };
|
|
@@ -172,75 +155,36 @@ export interface FrappeFormProps<TDoc extends { name: string; doctype: string }>
|
|
|
172
155
|
* via SchemaForm — sections sit one step below the page h1.
|
|
173
156
|
*/
|
|
174
157
|
sectionHeadingLevel?: HeadingLevel;
|
|
158
|
+
/**
|
|
159
|
+
* The form's state, owned by the caller (`useFrappeForm`). Given, the form
|
|
160
|
+
* renders from it instead of holding its own, so a page can build its
|
|
161
|
+
* chrome on the same controller: turn the form's toolbar, sidebar,
|
|
162
|
+
* timeline and banners off and render them from `form` instead.
|
|
163
|
+
*/
|
|
164
|
+
form?: FrappeFormController<TDoc>;
|
|
165
|
+
/** Section or Tab Breaks to hide, by fieldname or label. */
|
|
166
|
+
hideSections?: string[];
|
|
167
|
+
/** False leaves the deleted and changed-elsewhere banners to the host. */
|
|
168
|
+
showBanners?: boolean;
|
|
169
|
+
/** Host tabs after the form's own (and its Connections tab). */
|
|
170
|
+
extraTabs?: TabsItem[];
|
|
171
|
+
/** Wraps each rendered field, e.g. in an error boundary. */
|
|
172
|
+
renderFieldWrapper?: SchemaFormProps["renderFieldWrapper"];
|
|
173
|
+
/** Wraps each laid-out tab's body. */
|
|
174
|
+
renderTab?: (tab: FormTab, content: React.ReactNode) => React.ReactNode;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
// ---------------------------------------------------------------------------
|
|
178
|
-
// Layout source for the form engine
|
|
179
|
-
// ---------------------------------------------------------------------------
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* A `sections` override re-expressed as the flat break-delimited field list
|
|
183
|
-
* the form engine lays out, so an overridden layout resolves every field
|
|
184
|
-
* exactly like the meta one.
|
|
185
|
-
*/
|
|
186
|
-
function sectionsToLayoutFields(sections: FormSection[]): DocTypeField[] {
|
|
187
|
-
const fields: DocTypeField[] = [];
|
|
188
|
-
sections.forEach((section, sectionIndex) => {
|
|
189
|
-
fields.push({
|
|
190
|
-
fieldname: `__section_${sectionIndex}`,
|
|
191
|
-
fieldtype: section.tab ? "Tab Break" : "Section Break",
|
|
192
|
-
label: section.label,
|
|
193
|
-
collapsible: section.collapsible ? 1 : 0,
|
|
194
|
-
});
|
|
195
|
-
section.columns.forEach((column, columnIndex) => {
|
|
196
|
-
if (columnIndex > 0) {
|
|
197
|
-
fields.push({
|
|
198
|
-
fieldname: `__column_${sectionIndex}_${columnIndex}`,
|
|
199
|
-
fieldtype: "Column Break",
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
fields.push(...column.fields);
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
return fields;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Document bookkeeping, never shown as a field of the record.
|
|
209
|
-
const documentBookkeeping = new Set([
|
|
210
|
-
"name",
|
|
211
|
-
"owner",
|
|
212
|
-
"creation",
|
|
213
|
-
"modified",
|
|
214
|
-
"modified_by",
|
|
215
|
-
"docstatus",
|
|
216
|
-
"idx",
|
|
217
|
-
"doctype",
|
|
218
|
-
"_user_tags",
|
|
219
|
-
"_comments",
|
|
220
|
-
"_assign",
|
|
221
|
-
"_liked_by",
|
|
222
|
-
"_seen",
|
|
223
|
-
]);
|
|
224
|
-
|
|
225
177
|
/**
|
|
226
|
-
* A record's own
|
|
227
|
-
*
|
|
228
|
-
* $collectionId), which the row carries but the record does not.
|
|
178
|
+
* A record's own fields as read-only schema fields, for a reader without meta
|
|
179
|
+
* (LC-96).
|
|
229
180
|
*/
|
|
230
|
-
function
|
|
231
|
-
return
|
|
232
|
-
.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
)
|
|
238
|
-
.map(([key, value]) => ({
|
|
239
|
-
name: key,
|
|
240
|
-
label: key.replace(/_/g, " ").replace(/(^|\s)\S/g, (c) => c.toUpperCase()),
|
|
241
|
-
type: typeof value === "number" ? (Number.isInteger(value) ? "Int" : "Float") : "Data",
|
|
242
|
-
readOnly: true,
|
|
243
|
-
}));
|
|
181
|
+
function schemaFieldsFromRecord(doc: Record<string, unknown>): FieldSchema[] {
|
|
182
|
+
return fieldsFromRecord(doc).map((field) => ({
|
|
183
|
+
name: field.fieldname,
|
|
184
|
+
label: field.label,
|
|
185
|
+
type: field.fieldtype,
|
|
186
|
+
readOnly: true,
|
|
187
|
+
}));
|
|
244
188
|
}
|
|
245
189
|
|
|
246
190
|
// ---------------------------------------------------------------------------
|
|
@@ -447,20 +391,46 @@ function FrappeFormRoute<TDoc extends { name: string; doctype: string }>({
|
|
|
447
391
|
);
|
|
448
392
|
}
|
|
449
393
|
|
|
450
|
-
function FrappeFormBody<TDoc extends { name: string; doctype: string }>(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
394
|
+
function FrappeFormBody<TDoc extends { name: string; doctype: string }>(
|
|
395
|
+
props: FrappeFormBodyProps<TDoc>,
|
|
396
|
+
) {
|
|
397
|
+
if (props.form) return <FrappeFormView<TDoc> {...props} form={props.form} />;
|
|
398
|
+
return <FrappeFormOwned<TDoc> {...props} />;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function FrappeFormOwned<TDoc extends { name: string; doctype: string }>(
|
|
402
|
+
props: FrappeFormBodyProps<TDoc>,
|
|
403
|
+
) {
|
|
404
|
+
const config = useFrappeConfig();
|
|
405
|
+
if (!(props.baseURL ?? config?.baseURL)) {
|
|
406
|
+
throw new Error("FrappeForm requires baseURL prop or FrappeProvider with baseURL");
|
|
407
|
+
}
|
|
408
|
+
const form = useFrappeForm<TDoc>({
|
|
409
|
+
doctype: props.doctype,
|
|
410
|
+
name: props.name,
|
|
411
|
+
baseURL: props.baseURL,
|
|
412
|
+
auth: props.auth,
|
|
413
|
+
syncOptions: props.syncOptions,
|
|
414
|
+
fields: props.fields,
|
|
415
|
+
sections: props.sections,
|
|
416
|
+
hideFields: props.hideFields,
|
|
417
|
+
hideSections: props.hideSections,
|
|
418
|
+
roles: props.roles,
|
|
419
|
+
readShares: props.showSidebar ?? true,
|
|
420
|
+
disabled: props.disabled,
|
|
421
|
+
initialDoc: props.initialDoc,
|
|
422
|
+
});
|
|
423
|
+
return <FrappeFormView<TDoc> {...props} form={form} />;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function FrappeFormView<TDoc extends { name: string; doctype: string }>({
|
|
427
|
+
form,
|
|
454
428
|
doctype,
|
|
455
429
|
name,
|
|
456
|
-
fields,
|
|
457
430
|
children,
|
|
458
431
|
overrides,
|
|
459
|
-
hideFields,
|
|
460
432
|
readOnlyFields,
|
|
461
|
-
sections,
|
|
462
433
|
order,
|
|
463
|
-
roles,
|
|
464
434
|
onSave,
|
|
465
435
|
onCancel,
|
|
466
436
|
onDelete,
|
|
@@ -469,99 +439,53 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
469
439
|
showToolbar = true,
|
|
470
440
|
showSidebar = true,
|
|
471
441
|
showTimeline = true,
|
|
442
|
+
showBanners = true,
|
|
472
443
|
disabled = false,
|
|
473
444
|
sectionHeadingLevel,
|
|
474
445
|
connections,
|
|
475
446
|
showConnections = true,
|
|
476
|
-
|
|
447
|
+
extraTabs,
|
|
448
|
+
renderFieldWrapper,
|
|
449
|
+
renderTab,
|
|
477
450
|
loadingFallback,
|
|
478
451
|
notFoundFallback,
|
|
479
452
|
errorFallback,
|
|
480
453
|
routePage,
|
|
481
|
-
}: FrappeFormBodyProps<TDoc>) {
|
|
482
|
-
const config = useFrappeConfig();
|
|
454
|
+
}: FrappeFormBodyProps<TDoc> & { form: FrappeFormController<TDoc> }) {
|
|
483
455
|
const { knobProps } = useResolvedKnobs();
|
|
484
456
|
const { t } = useTranslation();
|
|
485
|
-
const seedDoc =
|
|
486
|
-
name && initialDoc && (initialDoc as { name?: string }).name === name ? initialDoc : undefined;
|
|
487
|
-
const seedRef = useRef(seedDoc);
|
|
488
|
-
seedRef.current = seedDoc;
|
|
489
|
-
const [localDoc, setLocalDoc] = useState<Partial<TDoc>>(() => seedDoc ?? {});
|
|
490
|
-
/** Last authoritative server snapshot applied to the form (dirty = localDoc ≠ baseline). */
|
|
491
|
-
const [baselineDoc, setBaselineDoc] = useState<Partial<TDoc> | undefined>(() => seedDoc);
|
|
492
|
-
const [docError, setDocError] = useState<Error | null>(null);
|
|
493
|
-
/** Fields the user edited that ALSO changed on the server since baseline. */
|
|
494
|
-
const [conflictFields, setConflictFields] = useState<string[]>([]);
|
|
495
|
-
/** The open document was deleted on the server (doc_deleted / tombstone / eviction). */
|
|
496
|
-
const [wasDeleted, setWasDeleted] = useState(false);
|
|
497
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
498
|
-
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
499
|
-
const [activeTab, setActiveTab] = useState<string | undefined>(undefined);
|
|
500
|
-
const everLoadedRef = useRef(false);
|
|
501
|
-
const localDocRef = useRef(localDoc);
|
|
502
|
-
localDocRef.current = localDoc;
|
|
503
|
-
const baselineRef = useRef(baselineDoc);
|
|
504
|
-
baselineRef.current = baselineDoc;
|
|
505
|
-
// Taken once per create session: effects re-run (StrictMode) must not
|
|
506
|
-
// lose a draft the one-shot stash already handed over.
|
|
507
|
-
const createDraftRef = useRef<{ doctype: string; values?: Record<string, unknown> }>(undefined);
|
|
508
|
-
|
|
509
|
-
const finalBaseURL = baseURL ?? config?.baseURL;
|
|
510
|
-
const finalAuth = auth ?? config?.auth;
|
|
511
|
-
const finalSyncOptions = syncOptions ?? config?.syncOptions;
|
|
512
|
-
|
|
513
|
-
if (!finalBaseURL) {
|
|
514
|
-
throw new Error("FrappeForm requires baseURL prop or FrappeProvider with baseURL");
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const frappeConfig = useMemo(
|
|
518
|
-
() => ({
|
|
519
|
-
baseURL: finalBaseURL,
|
|
520
|
-
socketPort: config?.socketPort,
|
|
521
|
-
siteName: config?.siteName,
|
|
522
|
-
socketBaseURL: config?.socketBaseURL,
|
|
523
|
-
auth: finalAuth,
|
|
524
|
-
syncOptions: finalSyncOptions,
|
|
525
|
-
realtime: config?.realtime,
|
|
526
|
-
fixtures: config?.fixtures,
|
|
527
|
-
}),
|
|
528
|
-
[
|
|
529
|
-
finalBaseURL,
|
|
530
|
-
config?.socketPort,
|
|
531
|
-
config?.siteName,
|
|
532
|
-
config?.socketBaseURL,
|
|
533
|
-
config?.realtime,
|
|
534
|
-
finalAuth,
|
|
535
|
-
finalSyncOptions,
|
|
536
|
-
config?.fixtures,
|
|
537
|
-
],
|
|
538
|
-
);
|
|
539
|
-
|
|
540
|
-
// Fetch DocType metadata
|
|
541
457
|
const {
|
|
542
458
|
meta,
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
459
|
+
metaLoading: isMetaLoading,
|
|
460
|
+
metaError,
|
|
461
|
+
doc: localDoc,
|
|
462
|
+
baseline: baselineDoc,
|
|
463
|
+
setValue,
|
|
464
|
+
errors,
|
|
465
|
+
isSubmitting,
|
|
466
|
+
docstatus,
|
|
467
|
+
isSubmittable,
|
|
468
|
+
rights,
|
|
469
|
+
rightsPending,
|
|
470
|
+
canWrite: canWriteRecord,
|
|
471
|
+
wasDeleted,
|
|
472
|
+
conflictFields,
|
|
473
|
+
docError,
|
|
474
|
+
tabs: formTabs,
|
|
475
|
+
promptNamed,
|
|
476
|
+
activeTab,
|
|
477
|
+
setActiveTab,
|
|
478
|
+
amendedAs,
|
|
479
|
+
} = form;
|
|
480
|
+
const frappeConfig = form.connection;
|
|
548
481
|
const isCreate = !name;
|
|
549
|
-
const
|
|
550
|
-
const layoutFields = useMemo(() => {
|
|
551
|
-
if (!meta) return undefined;
|
|
552
|
-
return sections ? sectionsToLayoutFields(sections) : meta.fields;
|
|
553
|
-
}, [meta, sections]);
|
|
554
|
-
const amendedAs = useAmendedAs(
|
|
555
|
-
isSubmittable && docstatus === 2 ? frappeConfig : undefined,
|
|
556
|
-
doctype,
|
|
557
|
-
name,
|
|
558
|
-
);
|
|
482
|
+
const hasUnsavedChanges = form.isDirty;
|
|
559
483
|
// Meta is permission-gated (Guest gets 403) while the record may still be
|
|
560
484
|
// readable: the form then lays the record's own fields out, read-only.
|
|
561
485
|
const readsWithoutMeta = !children && !meta && Boolean(metaError) && Boolean(name);
|
|
562
486
|
const recordTabs = useMemo((): FormTab[] | undefined => {
|
|
563
487
|
if (!readsWithoutMeta || !baselineDoc) return undefined;
|
|
564
|
-
const recordFields =
|
|
488
|
+
const recordFields = schemaFieldsFromRecord(baselineDoc as Record<string, unknown>);
|
|
565
489
|
return [
|
|
566
490
|
{
|
|
567
491
|
key: "__details",
|
|
@@ -570,89 +494,6 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
570
494
|
},
|
|
571
495
|
];
|
|
572
496
|
}, [readsWithoutMeta, baselineDoc, t]);
|
|
573
|
-
const promptNamed = isCreate && !!meta && isPromptNamed(meta.autoname, meta.naming_rule);
|
|
574
|
-
const fetchMap = useMemo(() => (meta ? buildFetchMap(meta.fields) : undefined), [meta]);
|
|
575
|
-
|
|
576
|
-
const handleDocSyncError = useCallback((err: unknown) => {
|
|
577
|
-
setDocError(err instanceof Error ? err : new Error(String(err)));
|
|
578
|
-
}, []);
|
|
579
|
-
const docCollection = useFrappeCollection<TDoc>(name ? frappeConfig : undefined, doctype, {
|
|
580
|
-
fields,
|
|
581
|
-
filters: name ? { name } : undefined,
|
|
582
|
-
onSyncError: handleDocSyncError,
|
|
583
|
-
});
|
|
584
|
-
const { data: rawDocData, isLoading: isDocLoading } = useLiveQuery(
|
|
585
|
-
(q) => {
|
|
586
|
-
if (!docCollection || !name) return null;
|
|
587
|
-
return q.from({ _doc: docCollection }).where(({ _doc }) => {
|
|
588
|
-
if (!_doc) return eq(name, name);
|
|
589
|
-
return eq(_doc.name, name);
|
|
590
|
-
});
|
|
591
|
-
},
|
|
592
|
-
[docCollection, name],
|
|
593
|
-
);
|
|
594
|
-
const liveDoc = useMemo(() => {
|
|
595
|
-
const arr = Array.isArray(rawDocData) ? rawDocData : rawDocData ? [rawDocData] : [];
|
|
596
|
-
const doc = (arr[0] ?? null) as TDoc | null;
|
|
597
|
-
return doc
|
|
598
|
-
? { data: doc, isLoading: isDocLoading }
|
|
599
|
-
: name
|
|
600
|
-
? { data: null, isLoading: isDocLoading }
|
|
601
|
-
: null;
|
|
602
|
-
}, [rawDocData, isDocLoading, name]);
|
|
603
|
-
|
|
604
|
-
// Create-mode collection (insert path). Edit-mode saves/deletes go through
|
|
605
|
-
// docCollection above — the name-filtered subscription that owns the doc —
|
|
606
|
-
// so edit mode never subscribes to the whole doctype.
|
|
607
|
-
const collection = useFrappeCollection<TDoc>(name ? undefined : frappeConfig, doctype, {
|
|
608
|
-
fields,
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
// Live "Shared With" rows for the sidebar (edit mode only).
|
|
612
|
-
const shareCollection = useFrappeCollection<DocShareRow>(
|
|
613
|
-
showSidebar && name ? frappeConfig : undefined,
|
|
614
|
-
"DocShare",
|
|
615
|
-
{
|
|
616
|
-
fields: ["name", "user", "everyone", "read", "write", "share"],
|
|
617
|
-
filters: { share_doctype: doctype, share_name: name },
|
|
618
|
-
},
|
|
619
|
-
);
|
|
620
|
-
const { data: rawShareRows } = useLiveQuery(
|
|
621
|
-
(q) => (shareCollection ? q.from({ _share: shareCollection }) : null),
|
|
622
|
-
[shareCollection],
|
|
623
|
-
);
|
|
624
|
-
const shareRows = useMemo(
|
|
625
|
-
() => (shareCollection ? ((rawShareRows as DocShareRow[] | undefined) ?? []) : undefined),
|
|
626
|
-
[shareCollection, rawShareRows],
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
const owner = (baselineDoc as Record<string, unknown> | undefined)?.owner;
|
|
630
|
-
const {
|
|
631
|
-
rights,
|
|
632
|
-
pending: rightsPending,
|
|
633
|
-
roles: effectiveRoles,
|
|
634
|
-
} = useDocRights(frappeConfig, meta, {
|
|
635
|
-
roles,
|
|
636
|
-
owner: typeof owner === "string" ? owner : undefined,
|
|
637
|
-
shares: shareRows,
|
|
638
|
-
});
|
|
639
|
-
const permLevels = useMemo(
|
|
640
|
-
() => buildPermLevelMatrix(meta?.permissions, effectiveRoles),
|
|
641
|
-
[meta?.permissions, effectiveRoles],
|
|
642
|
-
);
|
|
643
|
-
const formTabs = useMemo(() => {
|
|
644
|
-
if (!layoutFields) return undefined;
|
|
645
|
-
return buildFormTabs({
|
|
646
|
-
fields: layoutFields,
|
|
647
|
-
doc: { ...(localDoc as Record<string, unknown>), __islocal: isCreate ? 1 : 0 },
|
|
648
|
-
docstatus,
|
|
649
|
-
formReadOnly: !!meta?.read_only,
|
|
650
|
-
isCreate,
|
|
651
|
-
detailsLabel: t("Details"),
|
|
652
|
-
permLevels,
|
|
653
|
-
hiddenFields: hideFields,
|
|
654
|
-
});
|
|
655
|
-
}, [layoutFields, localDoc, docstatus, meta?.read_only, isCreate, permLevels, hideFields, t]);
|
|
656
497
|
|
|
657
498
|
// Live "Assigned To" rows: the open assignment ToDos referencing this doc.
|
|
658
499
|
// The doc's own `_assign` mirror is written silently (update_modified=False,
|
|
@@ -661,7 +502,7 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
661
502
|
// source of truth. Fixture mode keeps the `_assign` fallback (no fixture
|
|
662
503
|
// data exists for referencing ToDos), signalled by assignmentRows=undefined.
|
|
663
504
|
const assignmentCollection = useFrappeCollection<AssignmentTodoRow>(
|
|
664
|
-
showSidebar && name && !frappeConfig.fixtures ? frappeConfig : undefined,
|
|
505
|
+
showSidebar && name && frappeConfig && !frappeConfig.fixtures ? frappeConfig : undefined,
|
|
665
506
|
"ToDo",
|
|
666
507
|
{
|
|
667
508
|
fields: ["name", "allocated_to", "status", "reference_type", "reference_name"],
|
|
@@ -680,18 +521,6 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
680
521
|
[assignmentCollection, rawAssignmentRows],
|
|
681
522
|
);
|
|
682
523
|
|
|
683
|
-
// Reset per-document state when the target document changes.
|
|
684
|
-
useEffect(() => {
|
|
685
|
-
everLoadedRef.current = false;
|
|
686
|
-
if (name) createDraftRef.current = undefined;
|
|
687
|
-
setWasDeleted(false);
|
|
688
|
-
setConflictFields([]);
|
|
689
|
-
setDocError(null);
|
|
690
|
-
const seed = seedRef.current;
|
|
691
|
-
setBaselineDoc(seed);
|
|
692
|
-
if (seed) setLocalDoc(seed);
|
|
693
|
-
}, [doctype, name]);
|
|
694
|
-
|
|
695
524
|
const effectiveConnections = useMemo(
|
|
696
525
|
() => connections ?? connectionsFromMeta(meta),
|
|
697
526
|
[connections, meta],
|
|
@@ -733,279 +562,44 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
733
562
|
({ link }) => connectionSnapshots[link.doctype]?.isLoading ?? true,
|
|
734
563
|
);
|
|
735
564
|
|
|
736
|
-
// Immediate honest deleted state: the live app emits doc_deleted for the
|
|
737
|
-
// open doc's doctype room; tombstone backfill / cache eviction below is the
|
|
738
|
-
// durable fallback for deletions that happen while disconnected.
|
|
739
|
-
useFrappeDoctypeEvents(
|
|
740
|
-
name ? frappeConfig : undefined,
|
|
741
|
-
doctype,
|
|
742
|
-
(event) => {
|
|
743
|
-
if (event.type !== "doc_deleted") return;
|
|
744
|
-
const payload = event.payload as { name?: string } | undefined;
|
|
745
|
-
if (payload?.name === name) setWasDeleted(true);
|
|
746
|
-
},
|
|
747
|
-
Boolean(name),
|
|
748
|
-
);
|
|
749
|
-
|
|
750
|
-
// Field-level in-place sync: apply external updates to clean fields,
|
|
751
|
-
// preserve the user's dirty edits, and flag true conflicts (both sides
|
|
752
|
-
// changed the same field to different values) instead of clobbering.
|
|
753
|
-
useEffect(() => {
|
|
754
|
-
const incoming = liveDoc?.data as Record<string, unknown> | undefined;
|
|
755
|
-
if (incoming) {
|
|
756
|
-
everLoadedRef.current = true;
|
|
757
|
-
setWasDeleted(false);
|
|
758
|
-
const baseline = baselineRef.current as Record<string, unknown> | undefined;
|
|
759
|
-
if (!baseline) {
|
|
760
|
-
setBaselineDoc(incoming as Partial<TDoc>);
|
|
761
|
-
setLocalDoc(incoming as Partial<TDoc>);
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
764
|
-
const local = localDocRef.current as Record<string, unknown>;
|
|
765
|
-
const merged: Record<string, unknown> = { ...incoming };
|
|
766
|
-
const newConflicts: string[] = [];
|
|
767
|
-
for (const key of Object.keys(local)) {
|
|
768
|
-
const localValue = local[key];
|
|
769
|
-
if (localValue === baseline[key]) continue; // clean field → take server value
|
|
770
|
-
merged[key] = localValue; // dirty field → preserve the user's edit
|
|
771
|
-
const remote = incoming[key];
|
|
772
|
-
if (remote !== baseline[key] && remote !== localValue) newConflicts.push(key);
|
|
773
|
-
}
|
|
774
|
-
setBaselineDoc(incoming as Partial<TDoc>);
|
|
775
|
-
setLocalDoc(merged as Partial<TDoc>);
|
|
776
|
-
if (newConflicts.length > 0) {
|
|
777
|
-
setConflictFields((prev) => Array.from(new Set([...prev, ...newConflicts])));
|
|
778
|
-
}
|
|
779
|
-
} else if (name && everLoadedRef.current && !liveDoc?.isLoading) {
|
|
780
|
-
// The doc was live and is now gone from the subscription: deleted on
|
|
781
|
-
// the server (doc_deleted event, CDC tombstone, or 404 eviction).
|
|
782
|
-
setWasDeleted(true);
|
|
783
|
-
} else if (!name) {
|
|
784
|
-
if (createDraftRef.current?.doctype !== doctype) {
|
|
785
|
-
createDraftRef.current = { doctype, values: takeCreatePrefill(doctype) };
|
|
786
|
-
}
|
|
787
|
-
setLocalDoc({ ...createDraftRef.current.values, doctype } as Partial<TDoc>);
|
|
788
|
-
}
|
|
789
|
-
}, [liveDoc?.data, liveDoc?.isLoading, doctype, name]);
|
|
790
|
-
|
|
791
|
-
// Dirty fields = fields where the local value diverges from the baseline.
|
|
792
|
-
const dirtyFieldNames = useMemo(() => {
|
|
793
|
-
const local = localDoc as Record<string, unknown>;
|
|
794
|
-
const base = baselineDoc as Record<string, unknown> | undefined;
|
|
795
|
-
const dirty: string[] = [];
|
|
796
|
-
for (const key of Object.keys(local)) {
|
|
797
|
-
if (key === "doctype" || key === "name") continue;
|
|
798
|
-
if (base ? local[key] !== base[key] : local[key] !== undefined) dirty.push(key);
|
|
799
|
-
}
|
|
800
|
-
return dirty;
|
|
801
|
-
}, [localDoc, baselineDoc]);
|
|
802
|
-
const hasUnsavedChanges = dirtyFieldNames.length > 0;
|
|
803
|
-
|
|
804
|
-
// fetch_from preview: a changed Link pulls its dependent columns off the
|
|
805
|
-
// linked doc (validate_link on a bench, the fixture corpus otherwise) and
|
|
806
|
-
// patches the targets; clearing the link blanks them.
|
|
807
|
-
const runFetchFrom = (field: string, value: unknown) => {
|
|
808
|
-
const rules = fetchMap?.get(field);
|
|
809
|
-
if (!rules || rules.length === 0) return;
|
|
810
|
-
const applyPatch = (fetched: Record<string, unknown>) => {
|
|
811
|
-
setLocalDoc((prev) => {
|
|
812
|
-
const current = prev as Record<string, unknown>;
|
|
813
|
-
if (current[field] !== value) return prev;
|
|
814
|
-
const patch = applyFetchedValues(rules, fetched, current);
|
|
815
|
-
return Object.keys(patch).length > 0 ? ({ ...current, ...patch } as Partial<TDoc>) : prev;
|
|
816
|
-
});
|
|
817
|
-
};
|
|
818
|
-
if (isEmptyFieldValue(value)) {
|
|
819
|
-
applyPatch({});
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
const linkField = meta?.fields.find((metaField) => metaField.fieldname === field);
|
|
823
|
-
const targetDoctype = typeof linkField?.options === "string" ? linkField.options : "";
|
|
824
|
-
if (!targetDoctype) return;
|
|
825
|
-
const sources = Array.from(new Set(rules.map((rule) => rule.source)));
|
|
826
|
-
const fixtures = frappeConfig.fixtures;
|
|
827
|
-
const fetched = fixtures
|
|
828
|
-
? fixtures
|
|
829
|
-
.getDoc(targetDoctype, String(value), sources)
|
|
830
|
-
.then((doc) => (doc ?? {}) as Record<string, unknown>)
|
|
831
|
-
: validateLink(createHttpClient(frappeConfig), targetDoctype, String(value), sources);
|
|
832
|
-
// An unknown link leaves the targets as they are; the server re-validates on save.
|
|
833
|
-
fetched.then(applyPatch).catch(() => undefined);
|
|
834
|
-
};
|
|
835
|
-
|
|
836
|
-
const setValue = (field: keyof TDoc, value: unknown) => {
|
|
837
|
-
setLocalDoc((prev) => ({
|
|
838
|
-
...prev,
|
|
839
|
-
[field]: value,
|
|
840
|
-
}));
|
|
841
|
-
setErrors((prev) => {
|
|
842
|
-
const newErrors = { ...prev };
|
|
843
|
-
delete newErrors[String(field)];
|
|
844
|
-
return newErrors;
|
|
845
|
-
});
|
|
846
|
-
runFetchFrom(String(field), value);
|
|
847
|
-
};
|
|
848
|
-
|
|
849
|
-
// Required = what the engine resolved: reqd or a truthy
|
|
850
|
-
// mandatory_depends_on, on fields that are actually laid out (depends_on,
|
|
851
|
-
// permlevel and host-hidden fields never reach the tabs).
|
|
852
|
-
const validateRequiredFields = (): Record<string, string> => {
|
|
853
|
-
if (!formTabs) return {};
|
|
854
|
-
const validationErrors: Record<string, string> = {};
|
|
855
|
-
const doc = localDoc as Record<string, unknown>;
|
|
856
|
-
if (promptNamed && isEmptyFieldValue(doc.name)) {
|
|
857
|
-
validationErrors.name = t("Name is required");
|
|
858
|
-
}
|
|
859
|
-
for (const tab of formTabs) {
|
|
860
|
-
for (const field of tab.schema.fields) {
|
|
861
|
-
if (!field.required) continue;
|
|
862
|
-
if (isEmptyFieldValue(doc[field.name])) {
|
|
863
|
-
validationErrors[field.name] = t("{{label}} is required", {
|
|
864
|
-
label: field.label || field.name,
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
return validationErrors;
|
|
870
|
-
};
|
|
871
|
-
|
|
872
|
-
const canWriteRecord = isCreate ? rights.create : rights.write;
|
|
873
|
-
|
|
874
565
|
const handleSave = async () => {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
if (Object.keys(requiredErrors).length > 0) {
|
|
879
|
-
setErrors(requiredErrors);
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
setIsSubmitting(true);
|
|
884
|
-
setErrors({});
|
|
885
|
-
|
|
886
|
-
try {
|
|
887
|
-
if (name) {
|
|
888
|
-
if (!docCollection) return;
|
|
889
|
-
// Write only the dirty fields through the optimistic mutation path —
|
|
890
|
-
// clean fields stay untouched so concurrent server edits survive.
|
|
891
|
-
const changedFields = dirtyFieldNames;
|
|
892
|
-
if (changedFields.length > 0) {
|
|
893
|
-
const local = localDoc as Record<string, unknown>;
|
|
894
|
-
const tx = docCollection.update(name, (draft) => {
|
|
895
|
-
for (const key of changedFields) {
|
|
896
|
-
(draft as Record<string, unknown>)[key] = local[key];
|
|
897
|
-
}
|
|
898
|
-
});
|
|
899
|
-
await tx.isPersisted.promise;
|
|
900
|
-
}
|
|
901
|
-
setConflictFields([]);
|
|
902
|
-
} else {
|
|
903
|
-
if (!collection) return;
|
|
904
|
-
const temporaryName =
|
|
905
|
-
((localDoc as Record<string, unknown>).name as string | undefined) ?? `new-${Date.now()}`;
|
|
906
|
-
const docToInsert = { ...localDoc, doctype, name: temporaryName } as TDoc;
|
|
907
|
-
const tx = collection.insert(docToInsert);
|
|
908
|
-
await tx.isPersisted.promise;
|
|
909
|
-
const createdName =
|
|
910
|
-
getFrappeSyncModule(frappeConfig)?.createdName(temporaryName) ?? temporaryName;
|
|
911
|
-
onSave?.({ ...localDoc, name: createdName } as Partial<TDoc>);
|
|
912
|
-
return;
|
|
913
|
-
}
|
|
914
|
-
onSave?.(localDoc as Partial<TDoc>);
|
|
915
|
-
} catch (err: unknown) {
|
|
916
|
-
const e = err as { exc_type?: string; _server_messages?: string; message?: string };
|
|
917
|
-
if (e?.exc_type === "ValidationError") {
|
|
918
|
-
const validationErrors: Record<string, string> = {};
|
|
919
|
-
if (e._server_messages) {
|
|
920
|
-
try {
|
|
921
|
-
const messages = JSON.parse(e._server_messages) as string[];
|
|
922
|
-
for (const msg of messages) {
|
|
923
|
-
const parsed =
|
|
924
|
-
typeof msg === "string" ? (JSON.parse(msg) as { message?: string }) : msg;
|
|
925
|
-
if (parsed.message) {
|
|
926
|
-
validationErrors.general = parsed.message;
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
} catch {
|
|
930
|
-
validationErrors.general = t("Validation error occurred");
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
setErrors(validationErrors);
|
|
934
|
-
} else {
|
|
935
|
-
setErrors({ general: e?.message || t("Failed to save document") });
|
|
936
|
-
}
|
|
937
|
-
} finally {
|
|
938
|
-
setIsSubmitting(false);
|
|
939
|
-
}
|
|
566
|
+
const saved = await form.save();
|
|
567
|
+
if (!saved) return;
|
|
568
|
+
onSave?.((isCreate ? { ...localDoc, name: saved } : localDoc) as Partial<TDoc>);
|
|
940
569
|
};
|
|
941
570
|
|
|
942
571
|
const handleCancel = () => {
|
|
943
|
-
|
|
944
|
-
setLocalDoc(baselineDoc);
|
|
945
|
-
} else if (liveDoc?.data) {
|
|
946
|
-
setLocalDoc(liveDoc.data as Partial<TDoc>);
|
|
947
|
-
} else {
|
|
948
|
-
setLocalDoc({ doctype } as Partial<TDoc>);
|
|
949
|
-
}
|
|
950
|
-
setConflictFields([]);
|
|
951
|
-
setErrors({});
|
|
572
|
+
form.reset();
|
|
952
573
|
onCancel?.();
|
|
953
574
|
};
|
|
954
575
|
|
|
955
576
|
const handleDelete = async () => {
|
|
956
|
-
if (!
|
|
577
|
+
if (!name || wasDeleted || !rights.delete) return;
|
|
957
578
|
if (!(await dialog.confirm(t("Delete this {{doctype}}?", { doctype })))) return;
|
|
958
|
-
|
|
959
|
-
setErrors({});
|
|
960
|
-
try {
|
|
961
|
-
const tx = docCollection.delete(name);
|
|
962
|
-
await tx.isPersisted.promise;
|
|
963
|
-
onDelete?.(localDoc as Partial<TDoc>);
|
|
964
|
-
} catch (err) {
|
|
965
|
-
setErrors({ general: (err as Error)?.message || t("Failed to delete") });
|
|
966
|
-
} finally {
|
|
967
|
-
setIsSubmitting(false);
|
|
968
|
-
}
|
|
579
|
+
if (await form.remove()) onDelete?.(localDoc as Partial<TDoc>);
|
|
969
580
|
};
|
|
970
581
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const requiredErrors = validateRequiredFields();
|
|
978
|
-
if (Object.keys(requiredErrors).length > 0) {
|
|
979
|
-
setErrors(requiredErrors);
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
if (!(await dialog.confirm(question))) return;
|
|
984
|
-
setIsSubmitting(true);
|
|
985
|
-
setErrors({});
|
|
986
|
-
try {
|
|
987
|
-
const tx = docCollection.update(name, (draft) => {
|
|
988
|
-
(draft as Record<string, unknown>).docstatus = to;
|
|
989
|
-
});
|
|
990
|
-
await tx.isPersisted.promise;
|
|
991
|
-
} catch (err) {
|
|
992
|
-
setErrors({ general: extractServerError(err, failure) });
|
|
993
|
-
} finally {
|
|
994
|
-
setIsSubmitting(false);
|
|
582
|
+
const handleSubmitDocument = async () => {
|
|
583
|
+
if (!name || wasDeleted || !rights.submit) return;
|
|
584
|
+
const requiredErrors = form.validate();
|
|
585
|
+
if (Object.keys(requiredErrors).length > 0) {
|
|
586
|
+
form.setErrors(requiredErrors);
|
|
587
|
+
return;
|
|
995
588
|
}
|
|
589
|
+
if (!(await dialog.confirm(t("Permanently Submit {{name}}?", { name })))) return;
|
|
590
|
+
await form.submitDocument();
|
|
996
591
|
};
|
|
997
592
|
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
593
|
+
const handleCancelDocument = async () => {
|
|
594
|
+
if (!name || wasDeleted || !rights.cancel) return;
|
|
595
|
+
if (!(await dialog.confirm(t("Permanently Cancel {{name}}?", { name })))) return;
|
|
596
|
+
await form.cancelDocument();
|
|
597
|
+
};
|
|
1003
598
|
|
|
1004
599
|
const handleAmend = () => {
|
|
1005
|
-
if (!
|
|
1006
|
-
const draft =
|
|
1007
|
-
|
|
1008
|
-
onAmend(draft as Partial<TDoc>);
|
|
600
|
+
if (!onAmend) return;
|
|
601
|
+
const draft = form.amend();
|
|
602
|
+
if (draft) onAmend(draft);
|
|
1009
603
|
};
|
|
1010
604
|
|
|
1011
605
|
const titleField = meta?.title_field;
|
|
@@ -1066,24 +660,33 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
1066
660
|
|
|
1067
661
|
if (!children && metaError && !meta && !name) {
|
|
1068
662
|
return frame(
|
|
1069
|
-
|
|
1070
|
-
<
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
663
|
+
isPermissionError(metaError) ? (
|
|
664
|
+
<EmptyState
|
|
665
|
+
testID="frappe-form-not-permitted"
|
|
666
|
+
title={t("You can't create {{doctype}}", { doctype })}
|
|
667
|
+
description={metaError.message}
|
|
668
|
+
/>
|
|
669
|
+
) : (
|
|
670
|
+
<AsyncBoundary
|
|
671
|
+
{...knobProps.panelPadding}
|
|
672
|
+
error={metaError}
|
|
673
|
+
errorTitle={t("Failed to load {{doctype}} form", { doctype })}
|
|
674
|
+
/>
|
|
675
|
+
),
|
|
1077
676
|
);
|
|
1078
677
|
}
|
|
1079
678
|
|
|
1080
|
-
|
|
1081
|
-
// document is not known to be missing until the live read answers.
|
|
1082
|
-
const isDocReading = Boolean(name) && (!docCollection || Boolean(liveDoc?.isLoading));
|
|
1083
|
-
const showsSeed = Boolean(seedDoc) && isDocReading;
|
|
1084
|
-
|
|
1085
|
-
if (name && docError && !liveDoc?.data && !wasDeleted) {
|
|
679
|
+
if (name && form.status === "error" && docError) {
|
|
1086
680
|
if (errorFallback !== undefined) return frame(errorFallback);
|
|
681
|
+
if (isPermissionError(docError)) {
|
|
682
|
+
return frame(
|
|
683
|
+
<EmptyState
|
|
684
|
+
testID="frappe-form-not-permitted"
|
|
685
|
+
title={t("You can't open {{doctype}} {{name}}", { doctype, name })}
|
|
686
|
+
description={t("Your session can't read this {{doctype}}.", { doctype })}
|
|
687
|
+
/>,
|
|
688
|
+
);
|
|
689
|
+
}
|
|
1087
690
|
return frame(
|
|
1088
691
|
<AsyncBoundary
|
|
1089
692
|
{...knobProps.panelPadding}
|
|
@@ -1093,7 +696,7 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
1093
696
|
);
|
|
1094
697
|
}
|
|
1095
698
|
|
|
1096
|
-
if (name &&
|
|
699
|
+
if (name && form.status === "loading") {
|
|
1097
700
|
if (loadingFallback !== undefined) return frame(loadingFallback);
|
|
1098
701
|
return frame(
|
|
1099
702
|
<YStack {...knobProps.panelPadding} testID="frappe-form-skeleton">
|
|
@@ -1104,7 +707,7 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
1104
707
|
|
|
1105
708
|
// Never-loaded miss (bad name, or read denied) — distinct from the
|
|
1106
709
|
// deleted-while-open state below, which keeps the form mounted.
|
|
1107
|
-
if (name &&
|
|
710
|
+
if (name && form.status === "notFound") {
|
|
1108
711
|
if (notFoundFallback !== undefined) return frame(notFoundFallback);
|
|
1109
712
|
return frame(
|
|
1110
713
|
<YStack {...knobProps.panelPadding} alignItems="center" {...knobProps.gap}>
|
|
@@ -1153,7 +756,7 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
1153
756
|
const shownTabs = formTabs ?? recordTabs;
|
|
1154
757
|
const imageFieldName = meta?.image_field;
|
|
1155
758
|
const sidebarImageUrl = imageFieldName
|
|
1156
|
-
? resolveFileUrl((localDoc as Record<string, unknown>)[imageFieldName],
|
|
759
|
+
? resolveFileUrl((localDoc as Record<string, unknown>)[imageFieldName], frappeConfig?.baseURL)
|
|
1157
760
|
: undefined;
|
|
1158
761
|
const conflictLabels = conflictFields
|
|
1159
762
|
.map((fieldname) => {
|
|
@@ -1162,10 +765,8 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
1162
765
|
})
|
|
1163
766
|
.join(", ");
|
|
1164
767
|
|
|
1165
|
-
const formTabItems: TabsItem[] = (shownTabs ?? []).map((tab) =>
|
|
1166
|
-
|
|
1167
|
-
label: tab.label,
|
|
1168
|
-
content: (
|
|
768
|
+
const formTabItems: TabsItem[] = (shownTabs ?? []).map((tab) => {
|
|
769
|
+
const content = (
|
|
1169
770
|
<SchemaForm
|
|
1170
771
|
schema={tab.schema}
|
|
1171
772
|
doc={localDoc as Record<string, any>}
|
|
@@ -1176,13 +777,19 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
1176
777
|
readOnlyFields={readOnlyFields}
|
|
1177
778
|
sectionHeadingLevel={sectionHeadingLevel}
|
|
1178
779
|
order={order}
|
|
780
|
+
renderFieldWrapper={renderFieldWrapper}
|
|
1179
781
|
/>
|
|
1180
|
-
)
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
782
|
+
);
|
|
783
|
+
return {
|
|
784
|
+
value: tab.key,
|
|
785
|
+
label: tab.label,
|
|
786
|
+
content: renderTab ? renderTab(tab, content) : content,
|
|
787
|
+
};
|
|
788
|
+
});
|
|
789
|
+
const tabItems: TabsItem[] = [
|
|
790
|
+
...formTabItems,
|
|
791
|
+
...(showConnections && name && frappeConfig && effectiveConnections.length > 0
|
|
1184
792
|
? [
|
|
1185
|
-
...formTabItems,
|
|
1186
793
|
{
|
|
1187
794
|
value: "__connections",
|
|
1188
795
|
label: t("Connections"),
|
|
@@ -1195,7 +802,9 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
1195
802
|
),
|
|
1196
803
|
},
|
|
1197
804
|
]
|
|
1198
|
-
:
|
|
805
|
+
: []),
|
|
806
|
+
...(extraTabs ?? []),
|
|
807
|
+
];
|
|
1199
808
|
const effectiveTab =
|
|
1200
809
|
activeTab && tabItems.some((tab) => tab.value === activeTab) ? activeTab : tabItems[0]?.value;
|
|
1201
810
|
const localName = (localDoc as Record<string, unknown>).name;
|
|
@@ -1205,7 +814,7 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
1205
814
|
imageUrl={sidebarImageUrl}
|
|
1206
815
|
sections={buildSidebarSections(
|
|
1207
816
|
localDoc as Record<string, any>,
|
|
1208
|
-
shareRows,
|
|
817
|
+
form.shareRows as DocShareRow[] | undefined,
|
|
1209
818
|
t,
|
|
1210
819
|
assignmentRows,
|
|
1211
820
|
)}
|
|
@@ -1219,18 +828,20 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
1219
828
|
// collapses this stack to zero height on web — content then paints over
|
|
1220
829
|
// whatever follows. Bounded parents still fill (grow 1) exactly as before.
|
|
1221
830
|
<YStack {...knobProps.gapLg} flexGrow={1} flexShrink={1} flexBasis="auto">
|
|
1222
|
-
<FrappeReconnectIndicator config={frappeConfig} />
|
|
1223
|
-
{
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
831
|
+
{frappeConfig ? <FrappeReconnectIndicator config={frappeConfig} /> : null}
|
|
832
|
+
{frappeConfig
|
|
833
|
+
? connectionReads.map((read) => (
|
|
834
|
+
<FrappeRowsReader
|
|
835
|
+
key={read.link.doctype}
|
|
836
|
+
connection={frappeConfig}
|
|
837
|
+
doctype={read.link.doctype}
|
|
838
|
+
options={read.options as never}
|
|
839
|
+
onChange={read.onChange}
|
|
840
|
+
/>
|
|
841
|
+
))
|
|
842
|
+
: null}
|
|
1232
843
|
|
|
1233
|
-
{wasDeleted && (
|
|
844
|
+
{showBanners && wasDeleted && (
|
|
1234
845
|
<XStack
|
|
1235
846
|
role="alert"
|
|
1236
847
|
alignItems="center"
|
|
@@ -1252,7 +863,7 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
1252
863
|
</XStack>
|
|
1253
864
|
)}
|
|
1254
865
|
|
|
1255
|
-
{!wasDeleted && conflictFields.length > 0 && (
|
|
866
|
+
{showBanners && !wasDeleted && conflictFields.length > 0 && (
|
|
1256
867
|
<XStack
|
|
1257
868
|
role="status"
|
|
1258
869
|
alignItems="center"
|
|
@@ -1319,7 +930,7 @@ function FrappeFormBody<TDoc extends { name: string; doctype: string }>({
|
|
|
1319
930
|
)}
|
|
1320
931
|
|
|
1321
932
|
{errors.general && (
|
|
1322
|
-
<Text color={formCommonColors.error} {...knobProps.body}>
|
|
933
|
+
<Text color={formCommonColors.error} {...knobProps.body} role="alert">
|
|
1323
934
|
{errors.general}
|
|
1324
935
|
</Text>
|
|
1325
936
|
)}
|