@multiplatform.one/frappe-ui 6.6.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/FrappeAssignment.cjs +2 -0
- package/dist/cjs/FrappeAssignment.native.js +2 -0
- package/dist/cjs/FrappeAssignment.native.js.map +1 -1
- package/dist/cjs/FrappeCalendar.cjs +5 -7
- package/dist/cjs/FrappeCalendar.native.js +5 -9
- package/dist/cjs/FrappeCalendar.native.js.map +1 -1
- package/dist/cjs/FrappeChartCard.cjs +2 -0
- package/dist/cjs/FrappeChartCard.native.js +2 -0
- package/dist/cjs/FrappeChartCard.native.js.map +1 -1
- package/dist/cjs/FrappeDashboard.cjs +11 -22
- package/dist/cjs/FrappeDashboard.native.js +20 -22
- package/dist/cjs/FrappeDashboard.native.js.map +1 -1
- package/dist/cjs/FrappeForm.cjs +3 -1
- package/dist/cjs/FrappeForm.native.js +3 -1
- package/dist/cjs/FrappeForm.native.js.map +1 -1
- package/dist/cjs/FrappeGantt.cjs +5 -7
- package/dist/cjs/FrappeGantt.native.js +5 -9
- package/dist/cjs/FrappeGantt.native.js.map +1 -1
- package/dist/cjs/FrappeImageView.cjs +3 -1
- package/dist/cjs/FrappeImageView.native.js +3 -1
- package/dist/cjs/FrappeImageView.native.js.map +1 -1
- package/dist/cjs/FrappeKPICard.cjs +2 -0
- package/dist/cjs/FrappeKPICard.native.js +2 -0
- package/dist/cjs/FrappeKPICard.native.js.map +1 -1
- package/dist/cjs/FrappeKanban.cjs +3 -1
- package/dist/cjs/FrappeKanban.native.js +3 -1
- package/dist/cjs/FrappeKanban.native.js.map +1 -1
- package/dist/cjs/FrappeLikedBy.cjs +2 -0
- package/dist/cjs/FrappeLikedBy.native.js +2 -0
- package/dist/cjs/FrappeLikedBy.native.js.map +1 -1
- package/dist/cjs/FrappeList.cjs +3 -1
- package/dist/cjs/FrappeList.native.js +3 -1
- package/dist/cjs/FrappeList.native.js.map +1 -1
- package/dist/cjs/FrappeListView.cjs +6 -12
- package/dist/cjs/FrappeListView.native.js +13 -12
- package/dist/cjs/FrappeListView.native.js.map +1 -1
- package/dist/cjs/FrappeProvider.cjs +5 -1
- package/dist/cjs/FrappeProvider.native.js +5 -1
- package/dist/cjs/FrappeProvider.native.js.map +1 -1
- package/dist/cjs/FrappeReportBuilder.cjs +2 -0
- package/dist/cjs/FrappeReportBuilder.native.js +2 -0
- package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
- package/dist/cjs/FrappeReportView.cjs +23 -45
- package/dist/cjs/FrappeReportView.native.js +35 -50
- package/dist/cjs/FrappeReportView.native.js.map +1 -1
- package/dist/cjs/FrappeTable.cjs +105 -31
- package/dist/cjs/FrappeTable.native.js +140 -42
- package/dist/cjs/FrappeTable.native.js.map +1 -1
- package/dist/cjs/FrappeTreeView.cjs +3 -1
- package/dist/cjs/FrappeTreeView.native.js +3 -1
- package/dist/cjs/FrappeTreeView.native.js.map +1 -1
- package/dist/cjs/SyncErrorNotice.cjs +76 -0
- package/dist/cjs/SyncErrorNotice.native.js +92 -0
- package/dist/cjs/SyncErrorNotice.native.js.map +1 -0
- package/dist/cjs/commentActions.cjs +120 -0
- package/dist/cjs/commentActions.native.js +129 -0
- package/dist/cjs/commentActions.native.js.map +1 -0
- package/dist/cjs/components/attachments.cjs +2 -0
- package/dist/cjs/components/attachments.native.js +2 -0
- package/dist/cjs/components/attachments.native.js.map +1 -1
- package/dist/cjs/components/tags.cjs +5 -0
- package/dist/cjs/components/tags.native.js +5 -0
- package/dist/cjs/components/tags.native.js.map +1 -1
- package/dist/cjs/components/timeline.cjs +29 -2
- package/dist/cjs/components/timeline.native.js +33 -2
- package/dist/cjs/components/timeline.native.js.map +1 -1
- package/dist/cjs/desk/DeskFormSidebar.cjs +2 -0
- package/dist/cjs/desk/DeskFormSidebar.native.js +2 -0
- package/dist/cjs/desk/DeskFormSidebar.native.js.map +1 -1
- package/dist/cjs/fieldRegistry.cjs +2 -0
- package/dist/cjs/fieldRegistry.native.js +2 -0
- package/dist/cjs/fieldRegistry.native.js.map +1 -1
- package/dist/cjs/fields/Date/index.cjs +71 -7
- package/dist/cjs/fields/Date/index.native.js +325 -37
- package/dist/cjs/fields/Date/index.native.js.map +1 -1
- package/dist/cjs/fields/Date/types.cjs +18 -0
- package/dist/cjs/fields/Date/types.native.js +21 -0
- package/dist/cjs/fields/Date/types.native.js.map +1 -0
- package/dist/cjs/fields/Date/wire.cjs +4 -8
- package/dist/cjs/fields/Date/wire.native.js +4 -11
- package/dist/cjs/fields/Date/wire.native.js.map +1 -1
- package/dist/cjs/fields/DynamicLink.cjs +2 -0
- package/dist/cjs/fields/DynamicLink.native.js +2 -0
- package/dist/cjs/fields/DynamicLink.native.js.map +1 -1
- package/dist/cjs/fields/FileUpload.cjs +2 -0
- package/dist/cjs/fields/FileUpload.native.js +2 -0
- package/dist/cjs/fields/FileUpload.native.js.map +1 -1
- package/dist/cjs/fields/TableMultiSelect.cjs +6 -1
- package/dist/cjs/fields/TableMultiSelect.native.js +6 -1
- package/dist/cjs/fields/TableMultiSelect.native.js.map +1 -1
- package/dist/cjs/frappeStoryTarget.cjs +88 -0
- package/dist/cjs/frappeStoryTarget.native.js +100 -0
- package/dist/cjs/frappeStoryTarget.native.js.map +1 -1
- package/dist/cjs/frappeUsers.cjs +2 -0
- package/dist/cjs/frappeUsers.native.js +2 -0
- package/dist/cjs/frappeUsers.native.js.map +1 -1
- package/dist/cjs/index.cjs +17 -4
- package/dist/cjs/index.native.js +17 -4
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/liveErrors.cjs +5 -1
- package/dist/cjs/liveErrors.native.js +5 -1
- package/dist/cjs/liveErrors.native.js.map +1 -1
- package/dist/cjs/useFrappeAggregate.cjs +13 -1
- package/dist/cjs/useFrappeAggregate.native.js +14 -1
- package/dist/cjs/useFrappeAggregate.native.js.map +1 -1
- package/dist/cjs/useFrappeFacets.cjs +329 -0
- package/dist/cjs/useFrappeFacets.native.js +632 -0
- package/dist/cjs/useFrappeFacets.native.js.map +1 -0
- package/dist/esm/FrappeAssignment.mjs +2 -0
- package/dist/esm/FrappeAssignment.mjs.map +1 -1
- package/dist/esm/FrappeAssignment.native.js +2 -0
- package/dist/esm/FrappeAssignment.native.js.map +1 -1
- package/dist/esm/FrappeCalendar.mjs +6 -8
- package/dist/esm/FrappeCalendar.mjs.map +1 -1
- package/dist/esm/FrappeCalendar.native.js +6 -10
- package/dist/esm/FrappeCalendar.native.js.map +1 -1
- package/dist/esm/FrappeChartCard.mjs +2 -0
- package/dist/esm/FrappeChartCard.mjs.map +1 -1
- package/dist/esm/FrappeChartCard.native.js +2 -0
- package/dist/esm/FrappeChartCard.native.js.map +1 -1
- package/dist/esm/FrappeDashboard.mjs +12 -19
- package/dist/esm/FrappeDashboard.mjs.map +1 -1
- package/dist/esm/FrappeDashboard.native.js +23 -21
- package/dist/esm/FrappeDashboard.native.js.map +1 -1
- package/dist/esm/FrappeForm.mjs +3 -1
- package/dist/esm/FrappeForm.mjs.map +1 -1
- package/dist/esm/FrappeForm.native.js +3 -1
- package/dist/esm/FrappeForm.native.js.map +1 -1
- package/dist/esm/FrappeGantt.mjs +6 -8
- package/dist/esm/FrappeGantt.mjs.map +1 -1
- package/dist/esm/FrappeGantt.native.js +6 -10
- package/dist/esm/FrappeGantt.native.js.map +1 -1
- package/dist/esm/FrappeImageView.mjs +3 -1
- package/dist/esm/FrappeImageView.mjs.map +1 -1
- package/dist/esm/FrappeImageView.native.js +3 -1
- package/dist/esm/FrappeImageView.native.js.map +1 -1
- package/dist/esm/FrappeKPICard.mjs +2 -0
- package/dist/esm/FrappeKPICard.mjs.map +1 -1
- package/dist/esm/FrappeKPICard.native.js +2 -0
- package/dist/esm/FrappeKPICard.native.js.map +1 -1
- package/dist/esm/FrappeKanban.mjs +3 -1
- package/dist/esm/FrappeKanban.mjs.map +1 -1
- package/dist/esm/FrappeKanban.native.js +3 -1
- package/dist/esm/FrappeKanban.native.js.map +1 -1
- package/dist/esm/FrappeLikedBy.mjs +2 -0
- package/dist/esm/FrappeLikedBy.mjs.map +1 -1
- package/dist/esm/FrappeLikedBy.native.js +2 -0
- package/dist/esm/FrappeLikedBy.native.js.map +1 -1
- package/dist/esm/FrappeList.mjs +3 -1
- package/dist/esm/FrappeList.mjs.map +1 -1
- package/dist/esm/FrappeList.native.js +3 -1
- package/dist/esm/FrappeList.native.js.map +1 -1
- package/dist/esm/FrappeListView.mjs +7 -13
- package/dist/esm/FrappeListView.mjs.map +1 -1
- package/dist/esm/FrappeListView.native.js +15 -14
- package/dist/esm/FrappeListView.native.js.map +1 -1
- package/dist/esm/FrappeProvider.mjs +5 -1
- package/dist/esm/FrappeProvider.mjs.map +1 -1
- package/dist/esm/FrappeProvider.native.js +5 -1
- package/dist/esm/FrappeProvider.native.js.map +1 -1
- package/dist/esm/FrappeReportBuilder.mjs +2 -0
- package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
- package/dist/esm/FrappeReportBuilder.native.js +2 -0
- package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
- package/dist/esm/FrappeReportView.mjs +26 -49
- package/dist/esm/FrappeReportView.mjs.map +1 -1
- package/dist/esm/FrappeReportView.native.js +38 -54
- package/dist/esm/FrappeReportView.native.js.map +1 -1
- package/dist/esm/FrappeTable.mjs +107 -33
- package/dist/esm/FrappeTable.mjs.map +1 -1
- package/dist/esm/FrappeTable.native.js +142 -44
- package/dist/esm/FrappeTable.native.js.map +1 -1
- package/dist/esm/FrappeTreeView.mjs +3 -1
- package/dist/esm/FrappeTreeView.mjs.map +1 -1
- package/dist/esm/FrappeTreeView.native.js +3 -1
- package/dist/esm/FrappeTreeView.native.js.map +1 -1
- package/dist/esm/SyncErrorNotice.mjs +51 -0
- package/dist/esm/SyncErrorNotice.mjs.map +1 -0
- package/dist/esm/SyncErrorNotice.native.js +53 -0
- package/dist/esm/SyncErrorNotice.native.js.map +1 -0
- package/dist/esm/commentActions.mjs +95 -0
- package/dist/esm/commentActions.mjs.map +1 -0
- package/dist/esm/commentActions.native.js +101 -0
- package/dist/esm/commentActions.native.js.map +1 -0
- package/dist/esm/components/attachments.mjs +2 -0
- package/dist/esm/components/attachments.mjs.map +1 -1
- package/dist/esm/components/attachments.native.js +2 -0
- package/dist/esm/components/attachments.native.js.map +1 -1
- package/dist/esm/components/tags.mjs +5 -0
- package/dist/esm/components/tags.mjs.map +1 -1
- package/dist/esm/components/tags.native.js +5 -0
- package/dist/esm/components/tags.native.js.map +1 -1
- package/dist/esm/components/timeline.mjs +31 -4
- package/dist/esm/components/timeline.mjs.map +1 -1
- package/dist/esm/components/timeline.native.js +35 -4
- package/dist/esm/components/timeline.native.js.map +1 -1
- package/dist/esm/desk/DeskFormSidebar.mjs +2 -0
- package/dist/esm/desk/DeskFormSidebar.mjs.map +1 -1
- package/dist/esm/desk/DeskFormSidebar.native.js +2 -0
- package/dist/esm/desk/DeskFormSidebar.native.js.map +1 -1
- package/dist/esm/fieldRegistry.mjs +2 -0
- package/dist/esm/fieldRegistry.mjs.map +1 -1
- package/dist/esm/fieldRegistry.native.js +2 -0
- package/dist/esm/fieldRegistry.native.js.map +1 -1
- package/dist/esm/fields/Date/index.mjs +73 -9
- package/dist/esm/fields/Date/index.mjs.map +1 -1
- package/dist/esm/fields/Date/index.native.js +315 -38
- package/dist/esm/fields/Date/index.native.js.map +1 -1
- package/dist/esm/fields/Date/types.mjs +2 -0
- package/dist/esm/fields/Date/types.mjs.map +1 -0
- package/dist/esm/fields/Date/types.native.js +2 -0
- package/dist/esm/fields/Date/types.native.js.map +1 -0
- package/dist/esm/fields/Date/wire.mjs +4 -8
- package/dist/esm/fields/Date/wire.mjs.map +1 -1
- package/dist/esm/fields/Date/wire.native.js +4 -11
- package/dist/esm/fields/Date/wire.native.js.map +1 -1
- package/dist/esm/fields/DynamicLink.mjs +2 -0
- package/dist/esm/fields/DynamicLink.mjs.map +1 -1
- package/dist/esm/fields/DynamicLink.native.js +2 -0
- package/dist/esm/fields/DynamicLink.native.js.map +1 -1
- package/dist/esm/fields/FileUpload.mjs +2 -0
- package/dist/esm/fields/FileUpload.mjs.map +1 -1
- package/dist/esm/fields/FileUpload.native.js +2 -0
- package/dist/esm/fields/FileUpload.native.js.map +1 -1
- package/dist/esm/fields/TableMultiSelect.mjs +7 -2
- package/dist/esm/fields/TableMultiSelect.mjs.map +1 -1
- package/dist/esm/fields/TableMultiSelect.native.js +7 -2
- package/dist/esm/fields/TableMultiSelect.native.js.map +1 -1
- package/dist/esm/frappeStoryTarget.mjs +88 -2
- package/dist/esm/frappeStoryTarget.mjs.map +1 -1
- package/dist/esm/frappeStoryTarget.native.js +100 -2
- package/dist/esm/frappeStoryTarget.native.js.map +1 -1
- package/dist/esm/frappeUsers.mjs +2 -0
- package/dist/esm/frappeUsers.mjs.map +1 -1
- package/dist/esm/frappeUsers.native.js +2 -0
- package/dist/esm/frappeUsers.native.js.map +1 -1
- 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/liveErrors.mjs +5 -1
- package/dist/esm/liveErrors.mjs.map +1 -1
- package/dist/esm/liveErrors.native.js +5 -1
- package/dist/esm/liveErrors.native.js.map +1 -1
- package/dist/esm/useFrappeAggregate.mjs +13 -1
- package/dist/esm/useFrappeAggregate.mjs.map +1 -1
- package/dist/esm/useFrappeAggregate.native.js +14 -1
- package/dist/esm/useFrappeAggregate.native.js.map +1 -1
- package/dist/esm/useFrappeFacets.mjs +299 -0
- package/dist/esm/useFrappeFacets.mjs.map +1 -0
- package/dist/esm/useFrappeFacets.native.js +599 -0
- package/dist/esm/useFrappeFacets.native.js.map +1 -0
- package/dist/jsx/FrappeAssignment.mjs +2 -0
- package/dist/jsx/FrappeAssignment.mjs.map +1 -1
- package/dist/jsx/FrappeAssignment.native.js +2 -0
- package/dist/jsx/FrappeAssignment.native.js.map +1 -1
- package/dist/jsx/FrappeCalendar.mjs +6 -8
- package/dist/jsx/FrappeCalendar.mjs.map +1 -1
- package/dist/jsx/FrappeCalendar.native.js +5 -9
- package/dist/jsx/FrappeCalendar.native.js.map +1 -1
- package/dist/jsx/FrappeChartCard.mjs +2 -0
- package/dist/jsx/FrappeChartCard.mjs.map +1 -1
- package/dist/jsx/FrappeChartCard.native.js +2 -0
- package/dist/jsx/FrappeChartCard.native.js.map +1 -1
- package/dist/jsx/FrappeDashboard.mjs +12 -19
- package/dist/jsx/FrappeDashboard.mjs.map +1 -1
- package/dist/jsx/FrappeDashboard.native.js +20 -22
- package/dist/jsx/FrappeDashboard.native.js.map +1 -1
- package/dist/jsx/FrappeForm.mjs +3 -1
- package/dist/jsx/FrappeForm.mjs.map +1 -1
- package/dist/jsx/FrappeForm.native.js +3 -1
- package/dist/jsx/FrappeForm.native.js.map +1 -1
- package/dist/jsx/FrappeGantt.mjs +6 -8
- package/dist/jsx/FrappeGantt.mjs.map +1 -1
- package/dist/jsx/FrappeGantt.native.js +5 -9
- package/dist/jsx/FrappeGantt.native.js.map +1 -1
- package/dist/jsx/FrappeImageView.mjs +3 -1
- package/dist/jsx/FrappeImageView.mjs.map +1 -1
- package/dist/jsx/FrappeImageView.native.js +3 -1
- package/dist/jsx/FrappeImageView.native.js.map +1 -1
- package/dist/jsx/FrappeKPICard.mjs +2 -0
- package/dist/jsx/FrappeKPICard.mjs.map +1 -1
- package/dist/jsx/FrappeKPICard.native.js +2 -0
- package/dist/jsx/FrappeKPICard.native.js.map +1 -1
- package/dist/jsx/FrappeKanban.mjs +3 -1
- package/dist/jsx/FrappeKanban.mjs.map +1 -1
- package/dist/jsx/FrappeKanban.native.js +3 -1
- package/dist/jsx/FrappeKanban.native.js.map +1 -1
- package/dist/jsx/FrappeLikedBy.mjs +2 -0
- package/dist/jsx/FrappeLikedBy.mjs.map +1 -1
- package/dist/jsx/FrappeLikedBy.native.js +2 -0
- package/dist/jsx/FrappeLikedBy.native.js.map +1 -1
- package/dist/jsx/FrappeList.mjs +3 -1
- package/dist/jsx/FrappeList.mjs.map +1 -1
- package/dist/jsx/FrappeList.native.js +3 -1
- package/dist/jsx/FrappeList.native.js.map +1 -1
- package/dist/jsx/FrappeListView.mjs +7 -13
- package/dist/jsx/FrappeListView.mjs.map +1 -1
- package/dist/jsx/FrappeListView.native.js +13 -12
- package/dist/jsx/FrappeListView.native.js.map +1 -1
- package/dist/jsx/FrappeProvider.mjs +5 -1
- package/dist/jsx/FrappeProvider.mjs.map +1 -1
- package/dist/jsx/FrappeProvider.native.js +5 -1
- package/dist/jsx/FrappeProvider.native.js.map +1 -1
- package/dist/jsx/FrappeReportBuilder.mjs +2 -0
- package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
- package/dist/jsx/FrappeReportBuilder.native.js +2 -0
- package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
- package/dist/jsx/FrappeReportView.mjs +26 -49
- package/dist/jsx/FrappeReportView.mjs.map +1 -1
- package/dist/jsx/FrappeReportView.native.js +35 -50
- package/dist/jsx/FrappeReportView.native.js.map +1 -1
- package/dist/jsx/FrappeTable.mjs +107 -33
- package/dist/jsx/FrappeTable.mjs.map +1 -1
- package/dist/jsx/FrappeTable.native.js +140 -42
- package/dist/jsx/FrappeTable.native.js.map +1 -1
- package/dist/jsx/FrappeTreeView.mjs +3 -1
- package/dist/jsx/FrappeTreeView.mjs.map +1 -1
- package/dist/jsx/FrappeTreeView.native.js +3 -1
- package/dist/jsx/FrappeTreeView.native.js.map +1 -1
- package/dist/jsx/SyncErrorNotice.mjs +51 -0
- package/dist/jsx/SyncErrorNotice.mjs.map +1 -0
- package/dist/jsx/SyncErrorNotice.native.js +92 -0
- package/dist/jsx/SyncErrorNotice.native.js.map +1 -0
- package/dist/jsx/commentActions.mjs +95 -0
- package/dist/jsx/commentActions.mjs.map +1 -0
- package/dist/jsx/commentActions.native.js +129 -0
- package/dist/jsx/commentActions.native.js.map +1 -0
- package/dist/jsx/components/attachments.mjs +2 -0
- package/dist/jsx/components/attachments.mjs.map +1 -1
- package/dist/jsx/components/attachments.native.js +2 -0
- package/dist/jsx/components/attachments.native.js.map +1 -1
- package/dist/jsx/components/tags.mjs +5 -0
- package/dist/jsx/components/tags.mjs.map +1 -1
- package/dist/jsx/components/tags.native.js +5 -0
- package/dist/jsx/components/tags.native.js.map +1 -1
- package/dist/jsx/components/timeline.mjs +31 -4
- package/dist/jsx/components/timeline.mjs.map +1 -1
- package/dist/jsx/components/timeline.native.js +33 -2
- package/dist/jsx/components/timeline.native.js.map +1 -1
- package/dist/jsx/desk/DeskFormSidebar.mjs +2 -0
- package/dist/jsx/desk/DeskFormSidebar.mjs.map +1 -1
- package/dist/jsx/desk/DeskFormSidebar.native.js +2 -0
- package/dist/jsx/desk/DeskFormSidebar.native.js.map +1 -1
- package/dist/jsx/fieldRegistry.mjs +2 -0
- package/dist/jsx/fieldRegistry.mjs.map +1 -1
- package/dist/jsx/fieldRegistry.native.js +2 -0
- package/dist/jsx/fieldRegistry.native.js.map +1 -1
- package/dist/jsx/fields/Date/index.mjs +73 -9
- package/dist/jsx/fields/Date/index.mjs.map +1 -1
- package/dist/jsx/fields/Date/index.native.js +325 -37
- package/dist/jsx/fields/Date/index.native.js.map +1 -1
- package/dist/jsx/fields/Date/types.mjs +2 -0
- package/dist/jsx/fields/Date/types.mjs.map +1 -0
- package/dist/jsx/fields/Date/types.native.js +21 -0
- package/dist/jsx/fields/Date/types.native.js.map +1 -0
- package/dist/jsx/fields/Date/wire.mjs +4 -8
- package/dist/jsx/fields/Date/wire.mjs.map +1 -1
- package/dist/jsx/fields/Date/wire.native.js +4 -11
- package/dist/jsx/fields/Date/wire.native.js.map +1 -1
- package/dist/jsx/fields/DynamicLink.mjs +2 -0
- package/dist/jsx/fields/DynamicLink.mjs.map +1 -1
- package/dist/jsx/fields/DynamicLink.native.js +2 -0
- package/dist/jsx/fields/DynamicLink.native.js.map +1 -1
- package/dist/jsx/fields/FileUpload.mjs +2 -0
- package/dist/jsx/fields/FileUpload.mjs.map +1 -1
- package/dist/jsx/fields/FileUpload.native.js +2 -0
- package/dist/jsx/fields/FileUpload.native.js.map +1 -1
- package/dist/jsx/fields/TableMultiSelect.mjs +7 -2
- package/dist/jsx/fields/TableMultiSelect.mjs.map +1 -1
- package/dist/jsx/fields/TableMultiSelect.native.js +6 -1
- package/dist/jsx/fields/TableMultiSelect.native.js.map +1 -1
- package/dist/jsx/frappeStoryTarget.mjs +88 -2
- package/dist/jsx/frappeStoryTarget.mjs.map +1 -1
- package/dist/jsx/frappeStoryTarget.native.js +100 -0
- package/dist/jsx/frappeStoryTarget.native.js.map +1 -1
- package/dist/jsx/frappeUsers.mjs +2 -0
- package/dist/jsx/frappeUsers.mjs.map +1 -1
- package/dist/jsx/frappeUsers.native.js +2 -0
- package/dist/jsx/frappeUsers.native.js.map +1 -1
- 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 +17 -4
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/liveErrors.mjs +5 -1
- package/dist/jsx/liveErrors.mjs.map +1 -1
- package/dist/jsx/liveErrors.native.js +5 -1
- package/dist/jsx/liveErrors.native.js.map +1 -1
- package/dist/jsx/useFrappeAggregate.mjs +13 -1
- package/dist/jsx/useFrappeAggregate.mjs.map +1 -1
- package/dist/jsx/useFrappeAggregate.native.js +14 -1
- package/dist/jsx/useFrappeAggregate.native.js.map +1 -1
- package/dist/jsx/useFrappeFacets.mjs +299 -0
- package/dist/jsx/useFrappeFacets.mjs.map +1 -0
- package/dist/jsx/useFrappeFacets.native.js +632 -0
- package/dist/jsx/useFrappeFacets.native.js.map +1 -0
- package/package.json +21 -21
- package/src/FormSidebar.stories.tsx +88 -10
- package/src/FrappeAssignment.tsx +2 -0
- package/src/FrappeCalendar.tsx +12 -7
- package/src/FrappeChartCard.tsx +9 -2
- package/src/FrappeDashboard.spec.tsx +95 -0
- package/src/FrappeDashboard.tsx +20 -41
- package/src/FrappeForm.stories.tsx +53 -46
- package/src/FrappeForm.tsx +11 -1
- package/src/FrappeGantt.tsx +12 -7
- package/src/FrappeImageView.tsx +11 -1
- package/src/FrappeKPICard.stories.tsx +4 -1
- package/src/FrappeKPICard.tsx +2 -0
- package/src/FrappeKanban.tsx +4 -0
- package/src/FrappeLikedBy.stories.tsx +26 -10
- package/src/FrappeLikedBy.tsx +2 -0
- package/src/FrappeList.tsx +11 -1
- package/src/FrappeListView.spec.tsx +144 -0
- package/src/FrappeListView.tsx +11 -23
- package/src/FrappeProvider.tsx +30 -2
- package/src/FrappeReportBuilder.tsx +2 -0
- package/src/FrappeReportView.spec.tsx +140 -16
- package/src/FrappeReportView.stories.tsx +12 -6
- package/src/FrappeReportView.tsx +62 -61
- package/src/FrappeTable.keepRows.spec.tsx +200 -0
- package/src/FrappeTable.retryStorm.spec.tsx +140 -0
- package/src/FrappeTable.spec.tsx +222 -0
- package/src/FrappeTable.tsx +211 -54
- package/src/FrappeTreeView.tsx +11 -1
- package/src/SyncErrorNotice.spec.tsx +60 -0
- package/src/SyncErrorNotice.tsx +81 -0
- package/src/commentActions.ts +157 -0
- package/src/components/attachments.tsx +2 -0
- package/src/components/tags.tsx +14 -1
- package/src/components/timeline.optimistic.spec.tsx +106 -0
- package/src/components/timeline.tsx +36 -4
- package/src/desk/DeskFormSidebar.tsx +2 -0
- package/src/fieldRegistry.ts +2 -0
- package/src/fields/Date/index.native.tsx +410 -0
- package/src/fields/Date/index.tsx +138 -57
- package/src/fields/Date/nativePresentation.spec.tsx +251 -0
- package/src/fields/Date/typeParity.spec.tsx +480 -0
- package/src/fields/Date/types.ts +53 -0
- package/src/fields/Date/wire.spec.ts +63 -1
- package/src/fields/Date/wire.ts +17 -17
- package/src/fields/DynamicLink.tsx +2 -0
- package/src/fields/FileUpload.tsx +2 -0
- package/src/fields/TableMultiSelect.tsx +6 -3
- package/src/frappeStoryTarget.tsx +104 -1
- package/src/frappeUsers.ts +2 -0
- package/src/index.spec.ts +62 -0
- package/src/index.ts +36 -2
- package/src/liveErrors.spec.ts +50 -0
- package/src/liveErrors.ts +11 -1
- package/src/useFrappeAggregate.ts +20 -1
- package/src/useFrappeFacets.spec.tsx +436 -0
- package/src/useFrappeFacets.ts +568 -0
- package/types/FrappeAssignment.d.ts.map +1 -1
- package/types/FrappeCalendar.d.ts.map +1 -1
- package/types/FrappeChartCard.d.ts +1 -2
- package/types/FrappeChartCard.d.ts.map +1 -1
- package/types/FrappeDashboard.d.ts +0 -1
- package/types/FrappeDashboard.d.ts.map +1 -1
- package/types/FrappeForm.d.ts.map +1 -1
- package/types/FrappeGantt.d.ts.map +1 -1
- package/types/FrappeKPICard.d.ts.map +1 -1
- package/types/FrappeKanban.d.ts.map +1 -1
- package/types/FrappeLikedBy.d.ts.map +1 -1
- package/types/FrappeList.d.ts.map +1 -1
- package/types/FrappeListView.d.ts.map +1 -1
- package/types/FrappeProvider.d.ts +18 -1
- package/types/FrappeProvider.d.ts.map +1 -1
- package/types/FrappeReportBuilder.d.ts.map +1 -1
- package/types/FrappeReportView.d.ts +22 -15
- package/types/FrappeReportView.d.ts.map +1 -1
- package/types/FrappeTable.d.ts +21 -2
- package/types/FrappeTable.d.ts.map +1 -1
- package/types/FrappeTreeView.d.ts.map +1 -1
- package/types/SyncErrorNotice.d.ts +38 -0
- package/types/SyncErrorNotice.d.ts.map +1 -0
- package/types/commentActions.d.ts +46 -0
- package/types/commentActions.d.ts.map +1 -0
- package/types/components/attachments.d.ts.map +1 -1
- package/types/components/tags.d.ts.map +1 -1
- package/types/components/timeline.d.ts.map +1 -1
- package/types/desk/DeskFormSidebar.d.ts.map +1 -1
- package/types/fieldRegistry.d.ts.map +1 -1
- package/types/fields/Date/index.d.ts +22 -12
- package/types/fields/Date/index.d.ts.map +1 -1
- package/types/fields/Date/index.native.d.ts +14 -0
- package/types/fields/Date/index.native.d.ts.map +1 -0
- package/types/fields/Date/types.d.ts +27 -0
- package/types/fields/Date/types.d.ts.map +1 -0
- package/types/fields/Date/wire.d.ts +13 -9
- package/types/fields/Date/wire.d.ts.map +1 -1
- package/types/fields/DynamicLink.d.ts.map +1 -1
- package/types/fields/FileUpload.d.ts.map +1 -1
- package/types/fields/TableMultiSelect.d.ts.map +1 -1
- package/types/frappeStoryTarget.d.ts +19 -1
- package/types/frappeStoryTarget.d.ts.map +1 -1
- package/types/frappeUsers.d.ts.map +1 -1
- package/types/index.d.ts +6 -2
- package/types/index.d.ts.map +1 -1
- package/types/liveErrors.d.ts +4 -0
- package/types/liveErrors.d.ts.map +1 -1
- package/types/useFrappeAggregate.d.ts +4 -0
- package/types/useFrappeAggregate.d.ts.map +1 -1
- package/types/useFrappeFacets.d.ts +143 -0
- package/types/useFrappeFacets.d.ts.map +1 -0
- package/dist/cjs/fields/Date/date.native.js +0 -277
- package/dist/cjs/fields/Date/date.native.js.map +0 -1
- package/dist/esm/fields/Date/date.native.js +0 -238
- package/dist/esm/fields/Date/date.native.js.map +0 -1
- package/dist/jsx/fields/Date/date.native.js +0 -277
- package/dist/jsx/fields/Date/date.native.js.map +0 -1
- package/src/fields/Date/date.native.tsx +0 -299
- package/types/fields/Date/date.native.d.ts +0 -19
- package/types/fields/Date/date.native.d.ts.map +0 -1
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The web half honours all three Frappe date fieldtypes, and both halves
|
|
3
|
+
* persist the same bytes for the same pick.
|
|
4
|
+
*
|
|
5
|
+
* The web field used to render ONE date-only calendar whatever the type
|
|
6
|
+
* (SB-M-617): a `Datetime` dropped its time component from the picker, and a
|
|
7
|
+
* `Time` rendered EMPTY, because the calendar adopts its value through
|
|
8
|
+
* `coerceToDate` and `new Date("09:00")` is an Invalid Date. Native, meanwhile,
|
|
9
|
+
* had just been activated with all three implemented. These cases pin the
|
|
10
|
+
* three web controls — display, edit, and the exact wire string — and then
|
|
11
|
+
* drive the SAME pick through both platform halves and assert the emitted
|
|
12
|
+
* strings are byte-identical, which is the only reason one `wire.ts` body is
|
|
13
|
+
* worth sharing.
|
|
14
|
+
*
|
|
15
|
+
* The suite runs on web, so the native half's two native-only edges are mocked
|
|
16
|
+
* exactly as `nativePresentation.spec.tsx` mocks them: the OS picker (recorded
|
|
17
|
+
* rather than rendered) and the platform flags. The web half imports neither.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
21
|
+
|
|
22
|
+
// Same cold-fork budget as the sibling Date specs: the first field-module
|
|
23
|
+
// import transforms the whole fields graph.
|
|
24
|
+
vi.setConfig({ testTimeout: 240_000 });
|
|
25
|
+
|
|
26
|
+
const mockState = vi.hoisted(() => ({
|
|
27
|
+
isIos: true,
|
|
28
|
+
isAndroid: false,
|
|
29
|
+
/** Props of every mounted OS picker, newest last. */
|
|
30
|
+
picks: [] as Record<string, unknown>[],
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
vi.mock("@react-native-community/datetimepicker", () => ({
|
|
34
|
+
default: (props: Record<string, unknown>) => {
|
|
35
|
+
mockState.picks.push(props);
|
|
36
|
+
return null;
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
vi.mock("@multiplatform.one/platform", async (importOriginal) => {
|
|
41
|
+
const actual = (await importOriginal()) as Record<string, unknown>;
|
|
42
|
+
const real = actual.platform as Record<string, unknown>;
|
|
43
|
+
return {
|
|
44
|
+
...actual,
|
|
45
|
+
platform: new Proxy(
|
|
46
|
+
{},
|
|
47
|
+
{
|
|
48
|
+
get(_target, prop) {
|
|
49
|
+
if (prop === "isIos") return mockState.isIos;
|
|
50
|
+
if (prop === "isAndroid") return mockState.isAndroid;
|
|
51
|
+
return real[prop as string];
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Mock react-native-reanimated to prevent ESM module resolution failures
|
|
59
|
+
// (same shim as tests/fieldRenderers.spec.tsx).
|
|
60
|
+
vi.mock("react-native-reanimated", () => ({
|
|
61
|
+
default: {
|
|
62
|
+
useSharedValue: (init: any) => ({ value: init }),
|
|
63
|
+
useAnimatedStyle: () => ({}),
|
|
64
|
+
withTiming: (val: any) => val,
|
|
65
|
+
withSpring: (val: any) => val,
|
|
66
|
+
createAnimatedComponent: (component: any) => component,
|
|
67
|
+
runOnJS: (fn: any) => fn,
|
|
68
|
+
runOnUI: (fn: any) => fn,
|
|
69
|
+
useDerivedValue: (fn: () => any) => ({ value: fn() }),
|
|
70
|
+
useAnimatedRef: () => ({ current: null }),
|
|
71
|
+
Easing: { linear: (v: any) => v, ease: (v: any) => v, bezier: () => (v: any) => v },
|
|
72
|
+
FadeIn: { duration: () => ({}) },
|
|
73
|
+
FadeOut: { duration: () => ({}) },
|
|
74
|
+
Layout: { duration: () => ({}) },
|
|
75
|
+
Extrapolation: { CLAMP: "clamp" },
|
|
76
|
+
measure: () => null,
|
|
77
|
+
scrollTo: () => {},
|
|
78
|
+
makeMutable: (init: any) => ({ value: init }),
|
|
79
|
+
},
|
|
80
|
+
useSharedValue: (init: any) => ({ value: init }),
|
|
81
|
+
useAnimatedStyle: () => ({}),
|
|
82
|
+
withTiming: (val: any) => val,
|
|
83
|
+
withSpring: (val: any) => val,
|
|
84
|
+
createAnimatedComponent: (component: any) => component,
|
|
85
|
+
runOnJS: (fn: any) => fn,
|
|
86
|
+
runOnUI: (fn: any) => fn,
|
|
87
|
+
useDerivedValue: (fn: () => any) => ({ value: fn() }),
|
|
88
|
+
useAnimatedRef: () => ({ current: null }),
|
|
89
|
+
Easing: { linear: (v: any) => v, ease: (v: any) => v, bezier: () => (v: any) => v },
|
|
90
|
+
FadeIn: { duration: () => ({}) },
|
|
91
|
+
FadeOut: { duration: () => ({}) },
|
|
92
|
+
Layout: { duration: () => ({}) },
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
96
|
+
import { act, cleanup, fireEvent, waitFor } from "@testing-library/react";
|
|
97
|
+
import React from "react";
|
|
98
|
+
|
|
99
|
+
const loadWebDateField = async () => (await import(".")).Date;
|
|
100
|
+
const loadNativeDateField = async () => (await import("./index.native")).Date;
|
|
101
|
+
|
|
102
|
+
/** Intl puts a narrow no-break space before the meridiem; no test should care. */
|
|
103
|
+
const norm = (value: string) => value.replace(/\s+/gu, " ").trim();
|
|
104
|
+
|
|
105
|
+
const triggerInput = (root: ParentNode) => root.querySelector("input") as HTMLInputElement | null;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Open the panel the way a browser does. FloatingPanel's own opener is
|
|
109
|
+
* `useClick(..., { event: "mousedown" })` while the DatePicker/DatetimePicker
|
|
110
|
+
* triggers carry their own `onPress` — TimePicker's trigger carries none, so
|
|
111
|
+
* a `click`-only test would never open it.
|
|
112
|
+
*/
|
|
113
|
+
async function openPanel(input: Element) {
|
|
114
|
+
await act(async () => {
|
|
115
|
+
fireEvent.mouseDown(input);
|
|
116
|
+
fireEvent.click(input);
|
|
117
|
+
});
|
|
118
|
+
await waitFor(
|
|
119
|
+
() => {
|
|
120
|
+
if (!document.querySelector('[data-testid="floating-panel-viewport"]')) {
|
|
121
|
+
throw new Error("panel never opened");
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{ timeout: 30_000 },
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const panelText = () =>
|
|
129
|
+
norm(
|
|
130
|
+
document.querySelector('[data-testid="floating-panel-viewport"]')?.textContent ??
|
|
131
|
+
document.body.textContent ??
|
|
132
|
+
"",
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
/** An enabled day cell inside the (portaled) panel. */
|
|
136
|
+
function dayCell(day: number): HTMLButtonElement | undefined {
|
|
137
|
+
return (
|
|
138
|
+
Array.from(document.body.querySelectorAll('button, [role="button"]')) as HTMLButtonElement[]
|
|
139
|
+
).find((btn) => {
|
|
140
|
+
const disabled =
|
|
141
|
+
btn.hasAttribute("disabled") ||
|
|
142
|
+
btn.getAttribute("aria-disabled") === "true" ||
|
|
143
|
+
btn.hasAttribute("data-disabled");
|
|
144
|
+
return btn.textContent?.trim() === String(day) && !disabled;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Click whatever leaf carries this text, once it exists. The wait is the
|
|
150
|
+
* point: first paint of this graph is slow on a loaded machine, and a bare
|
|
151
|
+
* query races it. Clicking the leaf is enough — React bubbles the event to
|
|
152
|
+
* whichever ancestor owns the handler.
|
|
153
|
+
*/
|
|
154
|
+
async function pressText(root: ParentNode, text: string) {
|
|
155
|
+
let target: Element | null = null;
|
|
156
|
+
await waitFor(
|
|
157
|
+
() => {
|
|
158
|
+
const node = Array.from(root.querySelectorAll("*")).find(
|
|
159
|
+
(el) => el.children.length === 0 && norm(el.textContent ?? "") === norm(text),
|
|
160
|
+
);
|
|
161
|
+
if (!node) throw new Error(`no element with text "${text}"`);
|
|
162
|
+
target = node;
|
|
163
|
+
},
|
|
164
|
+
{ timeout: 30_000 },
|
|
165
|
+
);
|
|
166
|
+
await act(async () => {
|
|
167
|
+
fireEvent.click(target!);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
beforeEach(() => {
|
|
172
|
+
// Both halves portal their surfaces, so a previous case's chrome would
|
|
173
|
+
// otherwise still be in the document when the next one queries it.
|
|
174
|
+
cleanup();
|
|
175
|
+
mockState.picks.length = 0;
|
|
176
|
+
mockState.isIos = true;
|
|
177
|
+
mockState.isAndroid = false;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// The web half, one control per type
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
describe("web Date field — type=date keeps the calendar", () => {
|
|
185
|
+
it("displays its value, opens a month grid, and writes YYYY-MM-DD", async () => {
|
|
186
|
+
const DateField = await loadWebDateField();
|
|
187
|
+
const onChange = vi.fn();
|
|
188
|
+
const result = renderWithProviders(
|
|
189
|
+
<DateField label="Due Date" value="2026-03-05" onChange={onChange} type="date" />,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(triggerInput(result.container)?.value).toBe("Mar 5, 2026");
|
|
193
|
+
|
|
194
|
+
await openPanel(triggerInput(result.container)!);
|
|
195
|
+
expect(panelText()).toContain("Su");
|
|
196
|
+
// A date-only control offers no clock.
|
|
197
|
+
expect(panelText()).not.toMatch(/\bHour\b/);
|
|
198
|
+
|
|
199
|
+
await waitFor(() => expect(dayCell(15)).toBeTruthy(), { timeout: 30_000 });
|
|
200
|
+
await act(async () => {
|
|
201
|
+
fireEvent.click(dayCell(15)!);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
205
|
+
expect(onChange).toHaveBeenCalledWith("2026-03-15");
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe("web Date field — type=time renders a time control", () => {
|
|
210
|
+
it("DISPLAYS an existing HH:mm:ss value (it used to render empty)", async () => {
|
|
211
|
+
const DateField = await loadWebDateField();
|
|
212
|
+
const result = renderWithProviders(
|
|
213
|
+
<DateField label="Reminder" value="09:00:00" onChange={vi.fn()} type="time" />,
|
|
214
|
+
);
|
|
215
|
+
// The defect: `coerceToDate("09:00:00")` is null, so the calendar showed
|
|
216
|
+
// nothing at all for a Time field that had a value.
|
|
217
|
+
await waitFor(() => expect(norm(triggerInput(result.container)?.value ?? "")).toBe("9:00 AM"), {
|
|
218
|
+
timeout: 30_000,
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("opens hour/minute columns rather than a month grid", async () => {
|
|
223
|
+
const DateField = await loadWebDateField();
|
|
224
|
+
const result = renderWithProviders(
|
|
225
|
+
<DateField label="Reminder" value="09:00:00" onChange={vi.fn()} type="time" />,
|
|
226
|
+
);
|
|
227
|
+
await openPanel(triggerInput(result.container)!);
|
|
228
|
+
const text = panelText();
|
|
229
|
+
expect(text).toContain("Hour");
|
|
230
|
+
expect(text).toContain("Min");
|
|
231
|
+
expect(text).not.toContain("Su");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("an edit writes the Frappe HH:mm:ss wire shape, seconds included", async () => {
|
|
235
|
+
const DateField = await loadWebDateField();
|
|
236
|
+
const onChange = vi.fn();
|
|
237
|
+
const result = renderWithProviders(
|
|
238
|
+
<DateField label="Reminder" value="09:00:00" onChange={onChange} type="time" />,
|
|
239
|
+
);
|
|
240
|
+
await openPanel(triggerInput(result.container)!);
|
|
241
|
+
// 9:00 AM → PM is a deterministic edit with no wheel animation in it.
|
|
242
|
+
await pressText(document.body, "PM");
|
|
243
|
+
|
|
244
|
+
// The control speaks `HH:mm`; the field re-serializes through the shared
|
|
245
|
+
// wire body, so what persists carries seconds like every other writer.
|
|
246
|
+
expect(onChange).toHaveBeenCalledWith("21:00:00");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("adopts a seconds-less HH:mm value too (what Frappe hands a fresh field)", async () => {
|
|
250
|
+
const DateField = await loadWebDateField();
|
|
251
|
+
const result = renderWithProviders(
|
|
252
|
+
<DateField label="Reminder" value="14:30" onChange={vi.fn()} type="time" />,
|
|
253
|
+
);
|
|
254
|
+
await waitFor(() => expect(norm(triggerInput(result.container)?.value ?? "")).toBe("2:30 PM"), {
|
|
255
|
+
timeout: 30_000,
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe("web Date field — type=datetime renders date AND time", () => {
|
|
261
|
+
it("displays both halves of its value", async () => {
|
|
262
|
+
const DateField = await loadWebDateField();
|
|
263
|
+
const result = renderWithProviders(
|
|
264
|
+
<DateField
|
|
265
|
+
label="Starts On"
|
|
266
|
+
value="2026-03-05 14:30:00"
|
|
267
|
+
onChange={vi.fn()}
|
|
268
|
+
type="datetime"
|
|
269
|
+
/>,
|
|
270
|
+
);
|
|
271
|
+
// Before the fix this read "Mar 5, 2026" — the time was simply gone.
|
|
272
|
+
await waitFor(
|
|
273
|
+
() => {
|
|
274
|
+
const value = norm(triggerInput(result.container)?.value ?? "");
|
|
275
|
+
expect(value).toContain("Mar 5, 2026");
|
|
276
|
+
expect(value).toContain("2:30 PM");
|
|
277
|
+
},
|
|
278
|
+
{ timeout: 30_000 },
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("offers a time surface as well as the grid", async () => {
|
|
283
|
+
const DateField = await loadWebDateField();
|
|
284
|
+
const result = renderWithProviders(
|
|
285
|
+
<DateField
|
|
286
|
+
label="Starts On"
|
|
287
|
+
value="2026-03-05 14:30:00"
|
|
288
|
+
onChange={vi.fn()}
|
|
289
|
+
type="datetime"
|
|
290
|
+
/>,
|
|
291
|
+
);
|
|
292
|
+
await openPanel(triggerInput(result.container)!);
|
|
293
|
+
expect(panelText()).toContain("Su");
|
|
294
|
+
await pressText(document.body, "Time");
|
|
295
|
+
await waitFor(() => expect(panelText()).toContain("Hour"), { timeout: 30_000 });
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("a day change keeps the existing time — and the picker, not the serializer, keeps it", async () => {
|
|
299
|
+
const DateField = await loadWebDateField();
|
|
300
|
+
const onChange = vi.fn();
|
|
301
|
+
const result = renderWithProviders(
|
|
302
|
+
<DateField
|
|
303
|
+
label="Starts On"
|
|
304
|
+
value="2026-03-05 14:30:00"
|
|
305
|
+
onChange={onChange}
|
|
306
|
+
type="datetime"
|
|
307
|
+
/>,
|
|
308
|
+
);
|
|
309
|
+
await openPanel(triggerInput(result.container)!);
|
|
310
|
+
await waitFor(() => expect(dayCell(20)).toBeTruthy(), { timeout: 30_000 });
|
|
311
|
+
await act(async () => {
|
|
312
|
+
fireEvent.click(dayCell(20)!);
|
|
313
|
+
});
|
|
314
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
315
|
+
expect(onChange).toHaveBeenCalledWith("2026-03-20 14:30:00");
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("a time change writes the new clock (the branch passes no previousValue)", async () => {
|
|
319
|
+
const DateField = await loadWebDateField();
|
|
320
|
+
const onChange = vi.fn();
|
|
321
|
+
const result = renderWithProviders(
|
|
322
|
+
<DateField
|
|
323
|
+
label="Starts On"
|
|
324
|
+
value="2026-03-05 14:30:00"
|
|
325
|
+
onChange={onChange}
|
|
326
|
+
type="datetime"
|
|
327
|
+
/>,
|
|
328
|
+
);
|
|
329
|
+
await openPanel(triggerInput(result.container)!);
|
|
330
|
+
await pressText(document.body, "Time");
|
|
331
|
+
// 2:30 PM → AM. Carrying `previousValue` here would have pinned the old
|
|
332
|
+
// 14:30 back over the edit the user just made.
|
|
333
|
+
await pressText(document.body, "AM");
|
|
334
|
+
expect(onChange).toHaveBeenLastCalledWith("2026-03-05 02:30:00");
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
// Cross-half parity — the reason one `wire.ts` body is shared
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
|
|
342
|
+
/** Drive the native half through a pick and return the single emitted string. */
|
|
343
|
+
async function nativePick(
|
|
344
|
+
type: "date" | "datetime" | "time",
|
|
345
|
+
value: string,
|
|
346
|
+
triggerText: string,
|
|
347
|
+
steps: Date[],
|
|
348
|
+
): Promise<string> {
|
|
349
|
+
const DateField = await loadNativeDateField();
|
|
350
|
+
const onChange = vi.fn();
|
|
351
|
+
const result = renderWithProviders(
|
|
352
|
+
<DateField label="Field" value={value} onChange={onChange} type={type} />,
|
|
353
|
+
);
|
|
354
|
+
await pressText(result.container, triggerText);
|
|
355
|
+
await waitFor(() => expect(mockState.picks.length).toBeGreaterThan(0), { timeout: 30_000 });
|
|
356
|
+
|
|
357
|
+
for (const [index, step] of steps.entries()) {
|
|
358
|
+
const onPickerChange = mockState.picks[mockState.picks.length - 1].onChange as (
|
|
359
|
+
event: unknown,
|
|
360
|
+
date?: Date,
|
|
361
|
+
) => void;
|
|
362
|
+
await act(async () => {
|
|
363
|
+
onPickerChange({ type: "set" }, step);
|
|
364
|
+
});
|
|
365
|
+
// A datetime runs calendar → Next → wheel; the sheet's Next is the seam.
|
|
366
|
+
if (index === 0 && steps.length > 1) {
|
|
367
|
+
await pressText(document.body, "Next");
|
|
368
|
+
await waitFor(() => expect(mockState.picks[mockState.picks.length - 1].mode).toBe("time"), {
|
|
369
|
+
timeout: 30_000,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
await pressText(document.body, "Done");
|
|
374
|
+
|
|
375
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
376
|
+
return onChange.mock.calls[0][0] as string;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Drive the web half through the equivalent pick and return the last string. */
|
|
380
|
+
async function webPick(
|
|
381
|
+
type: "date" | "datetime" | "time",
|
|
382
|
+
value: string,
|
|
383
|
+
drive: () => Promise<void>,
|
|
384
|
+
): Promise<string> {
|
|
385
|
+
const DateField = await loadWebDateField();
|
|
386
|
+
const onChange = vi.fn();
|
|
387
|
+
const result = renderWithProviders(
|
|
388
|
+
<DateField label="Field" value={value} onChange={onChange} type={type} />,
|
|
389
|
+
);
|
|
390
|
+
await openPanel(triggerInput(result.container)!);
|
|
391
|
+
await drive();
|
|
392
|
+
await waitFor(() => expect(onChange).toHaveBeenCalled(), { timeout: 30_000 });
|
|
393
|
+
const calls = onChange.mock.calls;
|
|
394
|
+
return calls[calls.length - 1][0] as string;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
describe("web and native persist byte-identical wire strings", () => {
|
|
398
|
+
it("date — picking Mar 15 writes the same string on both halves", async () => {
|
|
399
|
+
const web = await webPick("date", "2026-03-05", async () => {
|
|
400
|
+
await waitFor(() => expect(dayCell(15)).toBeTruthy(), { timeout: 30_000 });
|
|
401
|
+
await act(async () => {
|
|
402
|
+
fireEvent.click(dayCell(15)!);
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
cleanup();
|
|
406
|
+
mockState.picks.length = 0;
|
|
407
|
+
const native = await nativePick("date", "2026-03-05", "Mar 5, 2026", [new Date(2026, 2, 15)]);
|
|
408
|
+
|
|
409
|
+
expect({ web, native }).toEqual({ web: "2026-03-15", native: "2026-03-15" });
|
|
410
|
+
expect(web).toBe(native);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it("time — moving 9:00 AM to 9:00 PM writes the same string on both halves", async () => {
|
|
414
|
+
const web = await webPick("time", "09:00:00", async () => {
|
|
415
|
+
await pressText(document.body, "PM");
|
|
416
|
+
});
|
|
417
|
+
cleanup();
|
|
418
|
+
mockState.picks.length = 0;
|
|
419
|
+
const native = await nativePick("time", "09:00:00", "9:00 AM", [
|
|
420
|
+
new Date(2026, 2, 5, 21, 0, 0),
|
|
421
|
+
]);
|
|
422
|
+
|
|
423
|
+
expect({ web, native }).toEqual({ web: "21:00:00", native: "21:00:00" });
|
|
424
|
+
expect(web).toBe(native);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("datetime — moving the day to Mar 20 writes the same string on both halves", async () => {
|
|
428
|
+
const web = await webPick("datetime", "2026-03-05 14:30:00", async () => {
|
|
429
|
+
await waitFor(() => expect(dayCell(20)).toBeTruthy(), { timeout: 30_000 });
|
|
430
|
+
await act(async () => {
|
|
431
|
+
fireEvent.click(dayCell(20)!);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
cleanup();
|
|
435
|
+
mockState.picks.length = 0;
|
|
436
|
+
// The native two-step: the calendar commits the day (dropping the clock,
|
|
437
|
+
// which is why the wheel step re-supplies it), then the wheel commits it.
|
|
438
|
+
const native = await nativePick("datetime", "2026-03-05 14:30:00", "Mar 5, 2026, 2:30 PM", [
|
|
439
|
+
new Date(2026, 2, 20),
|
|
440
|
+
new Date(2026, 0, 1, 14, 30),
|
|
441
|
+
]);
|
|
442
|
+
|
|
443
|
+
expect({ web, native }).toEqual({
|
|
444
|
+
web: "2026-03-20 14:30:00",
|
|
445
|
+
native: "2026-03-20 14:30:00",
|
|
446
|
+
});
|
|
447
|
+
expect(web).toBe(native);
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// ---------------------------------------------------------------------------
|
|
452
|
+
// SB-M-614 — disabled must gate every type's opener
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
|
|
455
|
+
describe("web Date field — a disabled field opens nothing, whatever the type", () => {
|
|
456
|
+
for (const [type, value] of [
|
|
457
|
+
["date", "2026-03-05"],
|
|
458
|
+
["datetime", "2026-03-05 14:30:00"],
|
|
459
|
+
["time", "09:00:00"],
|
|
460
|
+
] as const) {
|
|
461
|
+
it(`type=${type}`, async () => {
|
|
462
|
+
const DateField = await loadWebDateField();
|
|
463
|
+
const result = renderWithProviders(
|
|
464
|
+
<DateField label="Field" value={value} onChange={vi.fn()} type={type} disabled />,
|
|
465
|
+
);
|
|
466
|
+
const input = triggerInput(result.container)!;
|
|
467
|
+
expect(input.getAttribute("aria-disabled")).toBe("true");
|
|
468
|
+
|
|
469
|
+
// Both halves of a real click: the trigger's own handler is gated, but
|
|
470
|
+
// FloatingPanel's opener listens on mousedown and the disabled trigger
|
|
471
|
+
// is pointerEvents-none, so the press falls through to it.
|
|
472
|
+
await act(async () => {
|
|
473
|
+
fireEvent.mouseDown(input);
|
|
474
|
+
fireEvent.click(input);
|
|
475
|
+
});
|
|
476
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
477
|
+
expect(document.querySelector('[data-testid="floating-panel-viewport"]')).toBeNull();
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { FormFieldProps, FieldComponentProps } from "@multiplatform.one/forms";
|
|
2
|
+
import type { DeepKeys } from "@tanstack/form-core";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The public contract for the frappe-ui `Date` field.
|
|
6
|
+
*
|
|
7
|
+
* The package exports one `Date`, and the two platform halves
|
|
8
|
+
* (`index.tsx` / `index.native.tsx`) both satisfy this type, so a consumer
|
|
9
|
+
* never sees a different prop surface depending on where the code runs. The
|
|
10
|
+
* type lives in its own module for the same reason `TextEditor` keeps one:
|
|
11
|
+
* neither half can import it from the other without resolving to itself.
|
|
12
|
+
*/
|
|
13
|
+
export type DateProps<
|
|
14
|
+
TData extends Record<string, unknown> = Record<string, unknown>,
|
|
15
|
+
TName extends string = string,
|
|
16
|
+
> = Omit<
|
|
17
|
+
FormFieldProps<
|
|
18
|
+
TData,
|
|
19
|
+
TName extends DeepKeys<TData> ? TName : DeepKeys<TData>,
|
|
20
|
+
unknown,
|
|
21
|
+
unknown,
|
|
22
|
+
string
|
|
23
|
+
>,
|
|
24
|
+
// "onChange": FormFieldProps carries the DOM ChangeEventHandler from
|
|
25
|
+
// YStackProps; this field exposes a canonical value callback instead.
|
|
26
|
+
"children" | "field" | "onChange"
|
|
27
|
+
> &
|
|
28
|
+
Partial<
|
|
29
|
+
Omit<
|
|
30
|
+
FieldComponentProps<
|
|
31
|
+
TData,
|
|
32
|
+
TName extends DeepKeys<TData> ? TName : DeepKeys<TData>,
|
|
33
|
+
unknown,
|
|
34
|
+
unknown,
|
|
35
|
+
string
|
|
36
|
+
>,
|
|
37
|
+
"children"
|
|
38
|
+
>
|
|
39
|
+
> & {
|
|
40
|
+
/** Frappe wire value: `YYYY-MM-DD`, `YYYY-MM-DD HH:mm:ss`, or `HH:mm:ss`. */
|
|
41
|
+
value?: string;
|
|
42
|
+
onChange?: (value: string) => void;
|
|
43
|
+
type?: "date" | "datetime" | "time";
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
readOnly?: boolean;
|
|
46
|
+
skeleton?: boolean;
|
|
47
|
+
compact?: boolean;
|
|
48
|
+
/** Overrides the type-derived default ("Select date" / "…time"). */
|
|
49
|
+
placeholder?: string;
|
|
50
|
+
minDate?: Date;
|
|
51
|
+
maxDate?: Date;
|
|
52
|
+
inputProps?: Record<string, unknown>;
|
|
53
|
+
};
|
|
@@ -131,7 +131,10 @@ describe("toFrappeDateValue — a datetime pick preserves the instant", () => {
|
|
|
131
131
|
expect(shifted).toEqual({ wire: "2026-08-15 22:00", instantPreserved: false, driftHours: 7 });
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
// `previousValue` has no shipped caller since the web datetime branch moved
|
|
135
|
+
// onto a picker that owns the whole value — kept because it is an exported
|
|
136
|
+
// capability for any date-only writer, and untested capabilities rot.
|
|
137
|
+
it("keeps an existing time part when a caller says only the day was edited", () => {
|
|
135
138
|
for (const separator of [" ", "T"]) {
|
|
136
139
|
expect(
|
|
137
140
|
inZone("Pacific/Kiritimati", () =>
|
|
@@ -153,6 +156,65 @@ describe("toFrappeDateValue — a time pick is a local clock", () => {
|
|
|
153
156
|
});
|
|
154
157
|
});
|
|
155
158
|
|
|
159
|
+
describe("toFrappeDateValue delegates without changing a byte", () => {
|
|
160
|
+
/**
|
|
161
|
+
* The body this function carried before it moved onto `formatCalendarDate`
|
|
162
|
+
* in `@multiplatform.one/theme`, pasted verbatim. Consolidating the repo's
|
|
163
|
+
* date serializers was a de-duplication, not a behavior change, and this is
|
|
164
|
+
* what makes that claim checkable rather than asserted.
|
|
165
|
+
*/
|
|
166
|
+
function previousBody(
|
|
167
|
+
date: Date,
|
|
168
|
+
type: "date" | "datetime" | "time",
|
|
169
|
+
previousValue?: string,
|
|
170
|
+
): string {
|
|
171
|
+
const pad2 = (n: number) => String(n).padStart(2, "0");
|
|
172
|
+
const datePart = `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
|
|
173
|
+
const timePart = `${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}`;
|
|
174
|
+
if (type === "time") return timePart;
|
|
175
|
+
if (type === "datetime") {
|
|
176
|
+
return `${datePart} ${previousValue?.trim().split(/[ T]/)[1] ?? timePart}`;
|
|
177
|
+
}
|
|
178
|
+
return datePart;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Every `previousValue` the web half can hand the datetime branch, including
|
|
182
|
+
// the shapes with no time part at all (where the pick's own clock is used).
|
|
183
|
+
const CARRIED = [
|
|
184
|
+
undefined,
|
|
185
|
+
"",
|
|
186
|
+
"2026-03-05",
|
|
187
|
+
"2026-03-05 14:30:00",
|
|
188
|
+
"2026-03-05T14:30:00",
|
|
189
|
+
"2026-03-05 14:30:00.123456",
|
|
190
|
+
" 2026-03-05 09:00:00 ",
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
it("emits the identical string for every pick, shape and carried value", () => {
|
|
194
|
+
const disagreements: unknown[] = [];
|
|
195
|
+
for (const timeZone of ZONES) {
|
|
196
|
+
inZone(timeZone, () => {
|
|
197
|
+
for (const make of [
|
|
198
|
+
() => new Date(2026, 7, 15),
|
|
199
|
+
() => new Date(2026, 7, 15, 18, 30),
|
|
200
|
+
() => new Date(2026, 7, 15, 15, 0, 0),
|
|
201
|
+
() => new Date(2026, 0, 5, 9, 8, 7),
|
|
202
|
+
() => new Date(2026, 11, 31, 23, 59, 59),
|
|
203
|
+
]) {
|
|
204
|
+
for (const type of ["date", "datetime", "time"] as const) {
|
|
205
|
+
for (const carried of CARRIED) {
|
|
206
|
+
const was = previousBody(make(), type, carried);
|
|
207
|
+
const now = toFrappeDateValue(make(), type, carried);
|
|
208
|
+
if (was !== now) disagreements.push({ timeZone, type, carried, was, now });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
expect(disagreements).toEqual([]);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
156
218
|
describe("fromFrappeDateValue — the read side is local too", () => {
|
|
157
219
|
it("opens a bare YYYY-MM-DD on local midnight in every timezone", () => {
|
|
158
220
|
for (const timeZone of ZONES) {
|
package/src/fields/Date/wire.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Date field's Frappe wire contract, in both directions — one body shared
|
|
3
|
-
* by the web half (`index.tsx`) and the native half (`
|
|
3
|
+
* by the web half (`index.tsx`) and the native half (`index.native.tsx`), so
|
|
4
4
|
* the same pick persists the same string on either platform.
|
|
5
5
|
*
|
|
6
6
|
* Frappe date fields are timezone-NAIVE: `Date` is a bare calendar date
|
|
7
7
|
* (`YYYY-MM-DD`), `Datetime` is a bare local wall clock
|
|
8
8
|
* (`YYYY-MM-DD HH:mm:ss`), `Time` is a bare clock (`HH:mm:ss`). None carries an
|
|
9
|
-
* offset,
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* offset, so both directions build from LOCAL parts: the write half through
|
|
10
|
+
* `formatCalendarDate` and the read half through the local-midnight branch
|
|
11
|
+
* below, matching `parseCalendarDate` — all three in
|
|
12
|
+
* `@multiplatform.one/theme`, which is where the repo's one body for this
|
|
13
|
+
* lives.
|
|
13
14
|
*
|
|
14
15
|
* Serializing through `toISOString()` instead renders the UTC instant, which
|
|
15
16
|
* breaks both halves of that contract: it names the wrong calendar day whenever
|
|
@@ -18,30 +19,29 @@
|
|
|
18
19
|
* whole UTC offset, because the value is then read back as local wall clock.
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
import { formatCalendarDate } from "@multiplatform.one/theme";
|
|
22
23
|
|
|
23
24
|
export type FrappeDateFieldType = "date" | "datetime" | "time";
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Serialize a picked `Date` into the wire shape for the fieldtype.
|
|
27
28
|
*
|
|
28
|
-
* `previousValue` is for
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* branch uses the
|
|
29
|
+
* `previousValue` is for a caller whose picker edits only the day, so that an
|
|
30
|
+
* existing time part survives the edit. NEITHER half passes it any more, and
|
|
31
|
+
* neither should: both now mount a picker that owns the whole value (the web
|
|
32
|
+
* datetime branch uses the forms `DatetimePicker`, which preserves an existing
|
|
33
|
+
* clock across a day change itself), so carrying the old time part here would
|
|
34
|
+
* overwrite a time the user had just set. It stays on the contract because the
|
|
35
|
+
* capability is real for a date-only writer, and it stays covered below.
|
|
32
36
|
*/
|
|
33
37
|
export function toFrappeDateValue(
|
|
34
38
|
date: Date,
|
|
35
39
|
type: FrappeDateFieldType,
|
|
36
40
|
previousValue?: string,
|
|
37
41
|
): string {
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
if (type === "datetime") {
|
|
42
|
-
return `${datePart} ${previousValue?.trim().split(/[ T]/)[1] ?? timePart}`;
|
|
43
|
-
}
|
|
44
|
-
return datePart;
|
|
42
|
+
if (type !== "datetime") return formatCalendarDate(date, type);
|
|
43
|
+
const carriedTime = previousValue?.trim().split(/[ T]/)[1];
|
|
44
|
+
return `${formatCalendarDate(date, "date")} ${carriedTime ?? formatCalendarDate(date, "time")}`;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -52,6 +52,8 @@ export function DynamicLink({
|
|
|
52
52
|
? {
|
|
53
53
|
baseURL: finalBaseURL,
|
|
54
54
|
socketPort: config?.socketPort,
|
|
55
|
+
siteName: config?.siteName,
|
|
56
|
+
socketBaseURL: config?.socketBaseURL,
|
|
55
57
|
auth: auth ?? config?.auth,
|
|
56
58
|
syncOptions: config?.syncOptions,
|
|
57
59
|
fixtures: config?.fixtures,
|