@portnet/ui 6.0.2 → 6.0.3
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/CHANGELOG.md +652 -0
- package/dist/esm/index.js +72 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/v2/__tests__/AppMain.test.js +130 -0
- package/dist/esm/v2/__tests__/AppMain.test.js.map +1 -0
- package/dist/esm/v2/__tests__/ChartFigure.test.js +180 -0
- package/dist/esm/v2/__tests__/ChartFigure.test.js.map +1 -0
- package/dist/esm/v2/__tests__/DataGridRowActionsPropagation.test.js +150 -0
- package/dist/esm/v2/__tests__/DataGridRowActionsPropagation.test.js.map +1 -0
- package/dist/esm/v2/__tests__/DrawerTactileTargets.test.js +93 -0
- package/dist/esm/v2/__tests__/DrawerTactileTargets.test.js.map +1 -0
- package/dist/esm/v2/__tests__/FieldError.test.js +71 -38
- package/dist/esm/v2/__tests__/FieldError.test.js.map +1 -1
- package/dist/esm/v2/__tests__/FieldNaming.test.js +160 -0
- package/dist/esm/v2/__tests__/FieldNaming.test.js.map +1 -0
- package/dist/esm/v2/__tests__/FieldWidth.test.js +11 -11
- package/dist/esm/v2/__tests__/FieldWidth.test.js.map +1 -1
- package/dist/esm/v2/__tests__/FileCard.test.js +18 -17
- package/dist/esm/v2/__tests__/FileCard.test.js.map +1 -1
- package/dist/esm/v2/__tests__/FormikCheckbox.test.js +87 -179
- package/dist/esm/v2/__tests__/FormikCheckbox.test.js.map +1 -1
- package/dist/esm/v2/__tests__/FormikDateTimePicker.test.js +32 -44
- package/dist/esm/v2/__tests__/FormikDateTimePicker.test.js.map +1 -1
- package/dist/esm/v2/__tests__/FormikDisplayWidth.test.js +101 -0
- package/dist/esm/v2/__tests__/FormikDisplayWidth.test.js.map +1 -0
- package/dist/esm/v2/__tests__/FormikRadioGroup.test.js +118 -199
- package/dist/esm/v2/__tests__/FormikRadioGroup.test.js.map +1 -1
- package/dist/esm/v2/__tests__/MobileRowCard.test.js +44 -42
- package/dist/esm/v2/__tests__/MobileRowCard.test.js.map +1 -1
- package/dist/esm/v2/__tests__/PreviewDrawer.test.js +165 -0
- package/dist/esm/v2/__tests__/PreviewDrawer.test.js.map +1 -0
- package/dist/esm/v2/__tests__/ReferentielFieldContainer.test.js +156 -0
- package/dist/esm/v2/__tests__/ReferentielFieldContainer.test.js.map +1 -0
- package/dist/esm/v2/__tests__/SectionErrorBoundary.test.js +37 -52
- package/dist/esm/v2/__tests__/SectionErrorBoundary.test.js.map +1 -1
- package/dist/esm/v2/__tests__/apiError.test.js +50 -69
- package/dist/esm/v2/__tests__/apiError.test.js.map +1 -1
- package/dist/esm/v2/__tests__/banned-patterns.test.js +41 -122
- package/dist/esm/v2/__tests__/banned-patterns.test.js.map +1 -1
- package/dist/esm/v2/__tests__/chart-palette.test.js +204 -0
- package/dist/esm/v2/__tests__/chart-palette.test.js.map +1 -0
- package/dist/esm/v2/__tests__/contrast-palette.test.js +42 -74
- package/dist/esm/v2/__tests__/contrast-palette.test.js.map +1 -1
- package/dist/esm/v2/__tests__/cssVariables.test.js +44 -51
- package/dist/esm/v2/__tests__/cssVariables.test.js.map +1 -1
- package/dist/esm/v2/__tests__/fileValidation.test.js +134 -0
- package/dist/esm/v2/__tests__/fileValidation.test.js.map +1 -0
- package/dist/esm/v2/__tests__/filterCriteria.test.js +321 -0
- package/dist/esm/v2/__tests__/filterCriteria.test.js.map +1 -0
- package/dist/esm/v2/__tests__/fmtDate.test.js +68 -0
- package/dist/esm/v2/__tests__/fmtDate.test.js.map +1 -0
- package/dist/esm/v2/__tests__/gap-leakage-refonte.test.js +218 -0
- package/dist/esm/v2/__tests__/gap-leakage-refonte.test.js.map +1 -0
- package/dist/esm/v2/__tests__/labels-merge.test.js +17 -21
- package/dist/esm/v2/__tests__/labels-merge.test.js.map +1 -1
- package/dist/esm/v2/__tests__/locale-fallback.test.js +35 -40
- package/dist/esm/v2/__tests__/locale-fallback.test.js.map +1 -1
- package/dist/esm/v2/__tests__/reviveSavedCriteria.test.js +143 -0
- package/dist/esm/v2/__tests__/reviveSavedCriteria.test.js.map +1 -0
- package/dist/esm/v2/__tests__/rowActions.test.js +182 -0
- package/dist/esm/v2/__tests__/rowActions.test.js.map +1 -0
- package/dist/esm/v2/__tests__/useFocusOnRouteChange.test.js +155 -0
- package/dist/esm/v2/__tests__/useFocusOnRouteChange.test.js.map +1 -0
- package/dist/esm/v2/__tests__/useFocusTrap.test.js +266 -402
- package/dist/esm/v2/__tests__/useFocusTrap.test.js.map +1 -1
- package/dist/esm/v2/__tests__/usePrefersReducedMotion.test.js +120 -0
- package/dist/esm/v2/__tests__/usePrefersReducedMotion.test.js.map +1 -0
- package/dist/esm/v2/components/Accordion.js +48 -70
- package/dist/esm/v2/components/Accordion.js.map +1 -1
- package/dist/esm/v2/components/ActionStrip.js +31 -43
- package/dist/esm/v2/components/ActionStrip.js.map +1 -1
- package/dist/esm/v2/components/Banner.js +25 -22
- package/dist/esm/v2/components/Banner.js.map +1 -1
- package/dist/esm/v2/components/Busy.js +14 -19
- package/dist/esm/v2/components/Busy.js.map +1 -1
- package/dist/esm/v2/components/Button.js +34 -45
- package/dist/esm/v2/components/Button.js.map +1 -1
- package/dist/esm/v2/components/CategoryTag.js +44 -27
- package/dist/esm/v2/components/CategoryTag.js.map +1 -1
- package/dist/esm/v2/components/Checkbox.js +32 -38
- package/dist/esm/v2/components/Checkbox.js.map +1 -1
- package/dist/esm/v2/components/Chip.js +23 -36
- package/dist/esm/v2/components/Chip.js.map +1 -1
- package/dist/esm/v2/components/Combobox.js +112 -136
- package/dist/esm/v2/components/Combobox.js.map +1 -1
- package/dist/esm/v2/components/CommandPalette.js +113 -198
- package/dist/esm/v2/components/CommandPalette.js.map +1 -1
- package/dist/esm/v2/components/ConfirmDialog.js +36 -45
- package/dist/esm/v2/components/ConfirmDialog.js.map +1 -1
- package/dist/esm/v2/components/DatePicker.js +78 -81
- package/dist/esm/v2/components/DatePicker.js.map +1 -1
- package/dist/esm/v2/components/DateRangePicker.js +63 -74
- package/dist/esm/v2/components/DateRangePicker.js.map +1 -1
- package/dist/esm/v2/components/DateTimePicker.js +81 -86
- package/dist/esm/v2/components/DateTimePicker.js.map +1 -1
- package/dist/esm/v2/components/Drawer.js +71 -65
- package/dist/esm/v2/components/Drawer.js.map +1 -1
- package/dist/esm/v2/components/Dropzone.js +130 -177
- package/dist/esm/v2/components/Dropzone.js.map +1 -1
- package/dist/esm/v2/components/EmptyState.js +9 -9
- package/dist/esm/v2/components/EmptyState.js.map +1 -1
- package/dist/esm/v2/components/FavoriteToggle.js +24 -33
- package/dist/esm/v2/components/FavoriteToggle.js.map +1 -1
- package/dist/esm/v2/components/Field.js +155 -59
- package/dist/esm/v2/components/Field.js.map +1 -1
- package/dist/esm/v2/components/FieldError.js +9 -13
- package/dist/esm/v2/components/FieldError.js.map +1 -1
- package/dist/esm/v2/components/FieldHelp.js +28 -49
- package/dist/esm/v2/components/FieldHelp.js.map +1 -1
- package/dist/esm/v2/components/FileCard.js +14 -19
- package/dist/esm/v2/components/FileCard.js.map +1 -1
- package/dist/esm/v2/components/FilterTab.js +22 -31
- package/dist/esm/v2/components/FilterTab.js.map +1 -1
- package/dist/esm/v2/components/ForbiddenPage.js +28 -8
- package/dist/esm/v2/components/ForbiddenPage.js.map +1 -1
- package/dist/esm/v2/components/FormGrid.js +48 -54
- package/dist/esm/v2/components/FormGrid.js.map +1 -1
- package/dist/esm/v2/components/FormikDisplay.js +98 -24
- package/dist/esm/v2/components/FormikDisplay.js.map +1 -1
- package/dist/esm/v2/components/Input.js +75 -34
- package/dist/esm/v2/components/Input.js.map +1 -1
- package/dist/esm/v2/components/KPICard.js +42 -51
- package/dist/esm/v2/components/KPICard.js.map +1 -1
- package/dist/esm/v2/components/KPIStrip.js +9 -15
- package/dist/esm/v2/components/KPIStrip.js.map +1 -1
- package/dist/esm/v2/components/KV.js +16 -23
- package/dist/esm/v2/components/KV.js.map +1 -1
- package/dist/esm/v2/components/KVGrid.js +15 -23
- package/dist/esm/v2/components/KVGrid.js.map +1 -1
- package/dist/esm/v2/components/Layout.js +81 -117
- package/dist/esm/v2/components/Layout.js.map +1 -1
- package/dist/esm/v2/components/MobileRowCard.js +64 -75
- package/dist/esm/v2/components/MobileRowCard.js.map +1 -1
- package/dist/esm/v2/components/Modal.js +44 -53
- package/dist/esm/v2/components/Modal.js.map +1 -1
- package/dist/esm/v2/components/NetworkErrorPage.js +28 -11
- package/dist/esm/v2/components/NetworkErrorPage.js.map +1 -1
- package/dist/esm/v2/components/NotFoundPage.js +28 -9
- package/dist/esm/v2/components/NotFoundPage.js.map +1 -1
- package/dist/esm/v2/components/NumberInput.js +77 -107
- package/dist/esm/v2/components/NumberInput.js.map +1 -1
- package/dist/esm/v2/components/OverflowMenu.js +24 -42
- package/dist/esm/v2/components/OverflowMenu.js.map +1 -1
- package/dist/esm/v2/components/PdfViewer.js +24 -33
- package/dist/esm/v2/components/PdfViewer.js.map +1 -1
- package/dist/esm/v2/components/ProgressBar.js +28 -36
- package/dist/esm/v2/components/ProgressBar.js.map +1 -1
- package/dist/esm/v2/components/Radio.js +49 -57
- package/dist/esm/v2/components/Radio.js.map +1 -1
- package/dist/esm/v2/components/SectionErrorBoundary.js +84 -130
- package/dist/esm/v2/components/SectionErrorBoundary.js.map +1 -1
- package/dist/esm/v2/components/SectionHeader.js +24 -31
- package/dist/esm/v2/components/SectionHeader.js.map +1 -1
- package/dist/esm/v2/components/Segmented.js +59 -84
- package/dist/esm/v2/components/Segmented.js.map +1 -1
- package/dist/esm/v2/components/Select.js +100 -138
- package/dist/esm/v2/components/Select.js.map +1 -1
- package/dist/esm/v2/components/ServerErrorPage.js +27 -10
- package/dist/esm/v2/components/ServerErrorPage.js.map +1 -1
- package/dist/esm/v2/components/Skeleton.js +81 -96
- package/dist/esm/v2/components/Skeleton.js.map +1 -1
- package/dist/esm/v2/components/SkipLink.js +6 -7
- package/dist/esm/v2/components/SkipLink.js.map +1 -1
- package/dist/esm/v2/components/Spinner.js +13 -22
- package/dist/esm/v2/components/Spinner.js.map +1 -1
- package/dist/esm/v2/components/Status.js +14 -25
- package/dist/esm/v2/components/Status.js.map +1 -1
- package/dist/esm/v2/components/Stepper.js +68 -71
- package/dist/esm/v2/components/Stepper.js.map +1 -1
- package/dist/esm/v2/components/Surface.js +25 -44
- package/dist/esm/v2/components/Surface.js.map +1 -1
- package/dist/esm/v2/components/Switch.js +21 -29
- package/dist/esm/v2/components/Switch.js.map +1 -1
- package/dist/esm/v2/components/TabPanel.js +84 -0
- package/dist/esm/v2/components/TabPanel.js.map +1 -0
- package/dist/esm/v2/components/Tabs.js +43 -19
- package/dist/esm/v2/components/Tabs.js.map +1 -1
- package/dist/esm/v2/components/TelInput.js +75 -108
- package/dist/esm/v2/components/TelInput.js.map +1 -1
- package/dist/esm/v2/components/Textarea.js +21 -30
- package/dist/esm/v2/components/Textarea.js.map +1 -1
- package/dist/esm/v2/components/TimePicker.js +59 -69
- package/dist/esm/v2/components/TimePicker.js.map +1 -1
- package/dist/esm/v2/components/Toast.js +109 -163
- package/dist/esm/v2/components/Toast.js.map +1 -1
- package/dist/esm/v2/components/Tooltip.js +18 -31
- package/dist/esm/v2/components/Tooltip.js.map +1 -1
- package/dist/esm/v2/components/_ErrorPageBody.js +6 -5
- package/dist/esm/v2/components/_ErrorPageBody.js.map +1 -1
- package/dist/esm/v2/components/_pickerShared.js +2 -2
- package/dist/esm/v2/components/_pickerShared.js.map +1 -1
- package/dist/esm/v2/components/index.js +37 -0
- package/dist/esm/v2/components/index.js.map +1 -1
- package/dist/esm/v2/hooks/index.js +62 -0
- package/dist/esm/v2/hooks/index.js.map +1 -1
- package/dist/esm/v2/hooks/useAppliedFilters.js +112 -0
- package/dist/esm/v2/hooks/useAppliedFilters.js.map +1 -0
- package/dist/esm/v2/hooks/useDebouncedCallback.js +82 -0
- package/dist/esm/v2/hooks/useDebouncedCallback.js.map +1 -0
- package/dist/esm/v2/hooks/useDelayedFlag.js +5 -19
- package/dist/esm/v2/hooks/useDelayedFlag.js.map +1 -1
- package/dist/esm/v2/hooks/useDensity.js +13 -17
- package/dist/esm/v2/hooks/useDensity.js.map +1 -1
- package/dist/esm/v2/hooks/useFocusFirstError.js +25 -33
- package/dist/esm/v2/hooks/useFocusFirstError.js.map +1 -1
- package/dist/esm/v2/hooks/useFocusOnRouteChange.js +81 -0
- package/dist/esm/v2/hooks/useFocusOnRouteChange.js.map +1 -0
- package/dist/esm/v2/hooks/useFocusTrap.js +19 -23
- package/dist/esm/v2/hooks/useFocusTrap.js.map +1 -1
- package/dist/esm/v2/hooks/useFormAutoSave.js +38 -53
- package/dist/esm/v2/hooks/useFormAutoSave.js.map +1 -1
- package/dist/esm/v2/hooks/useFormCompletionProgress.js +32 -51
- package/dist/esm/v2/hooks/useFormCompletionProgress.js.map +1 -1
- package/dist/esm/v2/hooks/useKeyboardShortcuts.js +125 -0
- package/dist/esm/v2/hooks/useKeyboardShortcuts.js.map +1 -0
- package/dist/esm/v2/hooks/usePrefersReducedMotion.js +99 -0
- package/dist/esm/v2/hooks/usePrefersReducedMotion.js.map +1 -0
- package/dist/esm/v2/hooks/useResolvedDensity.js +2 -10
- package/dist/esm/v2/hooks/useResolvedDensity.js.map +1 -1
- package/dist/esm/v2/hooks/useSortedRows.js +3 -5
- package/dist/esm/v2/hooks/useSortedRows.js.map +1 -1
- package/dist/esm/v2/hooks/useTabLoading.js +6 -21
- package/dist/esm/v2/hooks/useTabLoading.js.map +1 -1
- package/dist/esm/v2/index.js +2 -3
- package/dist/esm/v2/index.js.map +1 -1
- package/dist/esm/v2/modalContext.js +2 -2
- package/dist/esm/v2/modalContext.js.map +1 -1
- package/dist/esm/v2/models/favorite.js.map +1 -1
- package/dist/esm/v2/models/personalization.js.map +1 -1
- package/dist/esm/v2/models/referentiel.js.map +1 -1
- package/dist/esm/v2/models/savedFilter.js.map +1 -1
- package/dist/esm/v2/patterns/ActionBar.js +115 -159
- package/dist/esm/v2/patterns/ActionBar.js.map +1 -1
- package/dist/esm/v2/patterns/AppMain.js +108 -0
- package/dist/esm/v2/patterns/AppMain.js.map +1 -0
- package/dist/esm/v2/patterns/BulkActionBar.js +31 -34
- package/dist/esm/v2/patterns/BulkActionBar.js.map +1 -1
- package/dist/esm/v2/patterns/ChartFigure.js +151 -0
- package/dist/esm/v2/patterns/ChartFigure.js.map +1 -0
- package/dist/esm/v2/patterns/CollapsibleDataGrid.js +111 -152
- package/dist/esm/v2/patterns/CollapsibleDataGrid.js.map +1 -1
- package/dist/esm/v2/patterns/ColumnsToggle.js +43 -77
- package/dist/esm/v2/patterns/ColumnsToggle.js.map +1 -1
- package/dist/esm/v2/patterns/DataGrid.js +221 -255
- package/dist/esm/v2/patterns/DataGrid.js.map +1 -1
- package/dist/esm/v2/patterns/DataGridSettingsMenu.js +76 -94
- package/dist/esm/v2/patterns/DataGridSettingsMenu.js.map +1 -1
- package/dist/esm/v2/patterns/DataGridToolbar.js +85 -89
- package/dist/esm/v2/patterns/DataGridToolbar.js.map +1 -1
- package/dist/esm/v2/patterns/DataGridVirtualized.js +141 -182
- package/dist/esm/v2/patterns/DataGridVirtualized.js.map +1 -1
- package/dist/esm/v2/patterns/DeleteResourceConfirmDialog.js +26 -30
- package/dist/esm/v2/patterns/DeleteResourceConfirmDialog.js.map +1 -1
- package/dist/esm/v2/patterns/DetailSplit.js +108 -0
- package/dist/esm/v2/patterns/DetailSplit.js.map +1 -0
- package/dist/esm/v2/patterns/FavoriteColumn.js +15 -17
- package/dist/esm/v2/patterns/FavoriteColumn.js.map +1 -1
- package/dist/esm/v2/patterns/FavoritesFilter.js +30 -39
- package/dist/esm/v2/patterns/FavoritesFilter.js.map +1 -1
- package/dist/esm/v2/patterns/FilterTabBar.js +112 -0
- package/dist/esm/v2/patterns/FilterTabBar.js.map +1 -0
- package/dist/esm/v2/patterns/FormErrorSummary.js +12 -13
- package/dist/esm/v2/patterns/FormErrorSummary.js.map +1 -1
- package/dist/esm/v2/patterns/FormPageShell.js +43 -41
- package/dist/esm/v2/patterns/FormPageShell.js.map +1 -1
- package/dist/esm/v2/patterns/FormSection.js +30 -38
- package/dist/esm/v2/patterns/FormSection.js.map +1 -1
- package/dist/esm/v2/patterns/FormikCheckbox.js +29 -39
- package/dist/esm/v2/patterns/FormikCheckbox.js.map +1 -1
- package/dist/esm/v2/patterns/FormikCheckboxGroup.js +56 -60
- package/dist/esm/v2/patterns/FormikCheckboxGroup.js.map +1 -1
- package/dist/esm/v2/patterns/FormikCombobox.js +51 -69
- package/dist/esm/v2/patterns/FormikCombobox.js.map +1 -1
- package/dist/esm/v2/patterns/FormikDatePicker.js +44 -58
- package/dist/esm/v2/patterns/FormikDatePicker.js.map +1 -1
- package/dist/esm/v2/patterns/FormikDateTimePicker.js +44 -58
- package/dist/esm/v2/patterns/FormikDateTimePicker.js.map +1 -1
- package/dist/esm/v2/patterns/FormikErrorFocus.js +22 -23
- package/dist/esm/v2/patterns/FormikErrorFocus.js.map +1 -1
- package/dist/esm/v2/patterns/FormikErrorSummary.js +89 -0
- package/dist/esm/v2/patterns/FormikErrorSummary.js.map +1 -0
- package/dist/esm/v2/patterns/FormikFile.js +94 -78
- package/dist/esm/v2/patterns/FormikFile.js.map +1 -1
- package/dist/esm/v2/patterns/FormikInput.js +40 -53
- package/dist/esm/v2/patterns/FormikInput.js.map +1 -1
- package/dist/esm/v2/patterns/FormikRadioGroup.js +39 -51
- package/dist/esm/v2/patterns/FormikRadioGroup.js.map +1 -1
- package/dist/esm/v2/patterns/FormikReferentielField.js +49 -59
- package/dist/esm/v2/patterns/FormikReferentielField.js.map +1 -1
- package/dist/esm/v2/patterns/FormikSelect.js +64 -87
- package/dist/esm/v2/patterns/FormikSelect.js.map +1 -1
- package/dist/esm/v2/patterns/FormikTextarea.js +30 -46
- package/dist/esm/v2/patterns/FormikTextarea.js.map +1 -1
- package/dist/esm/v2/patterns/KeyboardShortcutsHelp.js +53 -71
- package/dist/esm/v2/patterns/KeyboardShortcutsHelp.js.map +1 -1
- package/dist/esm/v2/patterns/NetworkErrorBanner.js +6 -7
- package/dist/esm/v2/patterns/NetworkErrorBanner.js.map +1 -1
- package/dist/esm/v2/patterns/PageContent.js +10 -14
- package/dist/esm/v2/patterns/PageContent.js.map +1 -1
- package/dist/esm/v2/patterns/PreviewDrawer.js +153 -0
- package/dist/esm/v2/patterns/PreviewDrawer.js.map +1 -0
- package/dist/esm/v2/patterns/ReferentielField.js +327 -312
- package/dist/esm/v2/patterns/ReferentielField.js.map +1 -1
- package/dist/esm/v2/patterns/RowFavoriteToggle.js +27 -22
- package/dist/esm/v2/patterns/RowFavoriteToggle.js.map +1 -1
- package/dist/esm/v2/patterns/SaveFilterDialog.js +38 -66
- package/dist/esm/v2/patterns/SaveFilterDialog.js.map +1 -1
- package/dist/esm/v2/patterns/SavedFiltersMenu.js +62 -81
- package/dist/esm/v2/patterns/SavedFiltersMenu.js.map +1 -1
- package/dist/esm/v2/patterns/SkeletonPageShell.js +106 -129
- package/dist/esm/v2/patterns/SkeletonPageShell.js.map +1 -1
- package/dist/esm/v2/patterns/SortableHeader.js +22 -45
- package/dist/esm/v2/patterns/SortableHeader.js.map +1 -1
- package/dist/esm/v2/patterns/Tour.js +94 -120
- package/dist/esm/v2/patterns/Tour.js.map +1 -1
- package/dist/esm/v2/patterns/_dataGridShared.js +107 -128
- package/dist/esm/v2/patterns/_dataGridShared.js.map +1 -1
- package/dist/esm/v2/patterns/_pickerSerialisation.js +6 -8
- package/dist/esm/v2/patterns/_pickerSerialisation.js.map +1 -1
- package/dist/esm/v2/patterns/_sort.js +17 -33
- package/dist/esm/v2/patterns/_sort.js.map +1 -1
- package/dist/esm/v2/patterns/_useColumnReorder.js +122 -160
- package/dist/esm/v2/patterns/_useColumnReorder.js.map +1 -1
- package/dist/esm/v2/patterns/_useColumnResize.js +78 -96
- package/dist/esm/v2/patterns/_useColumnResize.js.map +1 -1
- package/dist/esm/v2/patterns/filterCriteria.js +102 -0
- package/dist/esm/v2/patterns/filterCriteria.js.map +1 -0
- package/dist/esm/v2/patterns/index.js +152 -0
- package/dist/esm/v2/patterns/index.js.map +1 -1
- package/dist/esm/v2/patterns/rowActions.js +223 -0
- package/dist/esm/v2/patterns/rowActions.js.map +1 -0
- package/dist/esm/v2/personalization/PersonalizationProvider.js +50 -47
- package/dist/esm/v2/personalization/PersonalizationProvider.js.map +1 -1
- package/dist/esm/v2/personalization/adapters/_storageKey.js +5 -5
- package/dist/esm/v2/personalization/adapters/_storageKey.js.map +1 -1
- package/dist/esm/v2/personalization/adapters/createLocalStorageAdapter.js +22 -23
- package/dist/esm/v2/personalization/adapters/createLocalStorageAdapter.js.map +1 -1
- package/dist/esm/v2/personalization/adapters/createMemoryAdapter.js +13 -13
- package/dist/esm/v2/personalization/adapters/createMemoryAdapter.js.map +1 -1
- package/dist/esm/v2/personalization/capabilities/favorites.js +36 -60
- package/dist/esm/v2/personalization/capabilities/favorites.js.map +1 -1
- package/dist/esm/v2/personalization/capabilities/favoritesView.js +22 -34
- package/dist/esm/v2/personalization/capabilities/favoritesView.js.map +1 -1
- package/dist/esm/v2/personalization/capabilities/gridPreferences.js +28 -34
- package/dist/esm/v2/personalization/capabilities/gridPreferences.js.map +1 -1
- package/dist/esm/v2/personalization/capabilities/reviveSavedCriteria.js +119 -0
- package/dist/esm/v2/personalization/capabilities/reviveSavedCriteria.js.map +1 -0
- package/dist/esm/v2/personalization/capabilities/savedFilters.js +90 -98
- package/dist/esm/v2/personalization/capabilities/savedFilters.js.map +1 -1
- package/dist/esm/v2/personalization/createPersonalizationStore.js +78 -181
- package/dist/esm/v2/personalization/createPersonalizationStore.js.map +1 -1
- package/dist/esm/v2/personalization/index.js +17 -0
- package/dist/esm/v2/personalization/index.js.map +1 -1
- package/dist/esm/v2/personalization/personalizationContext.js +9 -9
- package/dist/esm/v2/personalization/personalizationContext.js.map +1 -1
- package/dist/esm/v2/personalization/schema.js +8 -9
- package/dist/esm/v2/personalization/schema.js.map +1 -1
- package/dist/esm/v2/referentiel/ReferentielProvider.js +27 -42
- package/dist/esm/v2/referentiel/ReferentielProvider.js.map +1 -1
- package/dist/esm/v2/referentiel/types.js +2 -0
- package/dist/esm/v2/referentiel/types.js.map +1 -0
- package/dist/esm/v2/referentiel/useReferentielClient.js +10 -11
- package/dist/esm/v2/referentiel/useReferentielClient.js.map +1 -1
- package/dist/esm/v2/referentiel/useReferentielOptions.js +60 -117
- package/dist/esm/v2/referentiel/useReferentielOptions.js.map +1 -1
- package/dist/esm/v2/referentiel/useReferentielSearch.js +37 -91
- package/dist/esm/v2/referentiel/useReferentielSearch.js.map +1 -1
- package/dist/esm/v2/theme/_brand.js +8 -24
- package/dist/esm/v2/theme/_brand.js.map +1 -1
- package/dist/esm/v2/theme/_brandSecondary.js +8 -24
- package/dist/esm/v2/theme/_brandSecondary.js.map +1 -1
- package/dist/esm/v2/theme/createPortnetTheme.js +51 -61
- package/dist/esm/v2/theme/createPortnetTheme.js.map +1 -1
- package/dist/esm/v2/theme/cssVariables.js +62 -69
- package/dist/esm/v2/theme/cssVariables.js.map +1 -1
- package/dist/esm/v2/theme/index.js +5 -5
- package/dist/esm/v2/theme/index.js.map +1 -1
- package/dist/esm/v2/tokens/breakpoints.js +14 -14
- package/dist/esm/v2/tokens/breakpoints.js.map +1 -1
- package/dist/esm/v2/tokens/chart.js +261 -0
- package/dist/esm/v2/tokens/chart.js.map +1 -0
- package/dist/esm/v2/tokens/colors.js +22 -22
- package/dist/esm/v2/tokens/colors.js.map +1 -1
- package/dist/esm/v2/tokens/density.js +62 -7
- package/dist/esm/v2/tokens/density.js.map +1 -1
- package/dist/esm/v2/tokens/index.js +49 -34
- package/dist/esm/v2/tokens/index.js.map +1 -1
- package/dist/esm/v2/tokens/motion.js +12 -20
- package/dist/esm/v2/tokens/motion.js.map +1 -1
- package/dist/esm/v2/tokens/radius.js +1 -1
- package/dist/esm/v2/tokens/radius.js.map +1 -1
- package/dist/esm/v2/tokens/shadows.js +6 -6
- package/dist/esm/v2/tokens/shadows.js.map +1 -1
- package/dist/esm/v2/tokens/spacing.js +2 -2
- package/dist/esm/v2/tokens/spacing.js.map +1 -1
- package/dist/esm/v2/tokens/state.js +1 -1
- package/dist/esm/v2/tokens/state.js.map +1 -1
- package/dist/esm/v2/tokens/statusPalette.js +17 -21
- package/dist/esm/v2/tokens/statusPalette.js.map +1 -1
- package/dist/esm/v2/tokens/tokens.json +6 -1
- package/dist/esm/v2/tokens/typography.js +13 -13
- package/dist/esm/v2/tokens/typography.js.map +1 -1
- package/dist/esm/v2/tokens/zIndex.js +1 -1
- package/dist/esm/v2/tokens/zIndex.js.map +1 -1
- package/dist/esm/v2/types/datagrid.js.map +1 -1
- package/dist/esm/v2/utils/apiError.js +16 -37
- package/dist/esm/v2/utils/apiError.js.map +1 -1
- package/dist/esm/v2/utils/contrast.js +31 -32
- package/dist/esm/v2/utils/contrast.js.map +1 -1
- package/dist/esm/v2/utils/cx.js +5 -25
- package/dist/esm/v2/utils/cx.js.map +1 -1
- package/dist/esm/v2/utils/fileDownload.js +23 -26
- package/dist/esm/v2/utils/fileDownload.js.map +1 -1
- package/dist/esm/v2/utils/fileValidation.js +209 -0
- package/dist/esm/v2/utils/fileValidation.js.map +1 -0
- package/dist/esm/v2/utils/fmtDate.js +111 -0
- package/dist/esm/v2/utils/fmtDate.js.map +1 -0
- package/dist/esm/v2/utils/fmtMoney.js +8 -10
- package/dist/esm/v2/utils/fmtMoney.js.map +1 -1
- package/dist/esm/v2/utils/formikErrors.js +78 -0
- package/dist/esm/v2/utils/formikErrors.js.map +1 -0
- package/dist/esm/v2/utils/index.js +35 -2
- package/dist/esm/v2/utils/index.js.map +1 -1
- package/dist/esm/v2/utils/mergeLabels.js +7 -12
- package/dist/esm/v2/utils/mergeLabels.js.map +1 -1
- package/dist/esm/v2/utils/networkStatus.js +12 -12
- package/dist/esm/v2/utils/networkStatus.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +110 -49
- package/dist/index.js.map +1 -1
- package/dist/v2/__tests__/AppMain.test.js +133 -0
- package/dist/v2/__tests__/AppMain.test.js.map +1 -0
- package/dist/v2/__tests__/ChartFigure.test.js +183 -0
- package/dist/v2/__tests__/ChartFigure.test.js.map +1 -0
- package/dist/v2/__tests__/DataGridRowActionsPropagation.test.js +153 -0
- package/dist/v2/__tests__/DataGridRowActionsPropagation.test.js.map +1 -0
- package/dist/v2/__tests__/DrawerTactileTargets.test.js +94 -0
- package/dist/v2/__tests__/DrawerTactileTargets.test.js.map +1 -0
- package/dist/v2/__tests__/FieldError.test.js +82 -49
- package/dist/v2/__tests__/FieldError.test.js.map +1 -1
- package/dist/v2/__tests__/FieldNaming.test.js +161 -0
- package/dist/v2/__tests__/FieldNaming.test.js.map +1 -0
- package/dist/v2/__tests__/FieldWidth.test.js +23 -25
- package/dist/v2/__tests__/FieldWidth.test.js.map +1 -1
- package/dist/v2/__tests__/FileCard.test.js +29 -28
- package/dist/v2/__tests__/FileCard.test.js.map +1 -1
- package/dist/v2/__tests__/FormikCheckbox.test.js +90 -183
- package/dist/v2/__tests__/FormikCheckbox.test.js.map +1 -1
- package/dist/v2/__tests__/FormikDateTimePicker.test.js +38 -48
- package/dist/v2/__tests__/FormikDateTimePicker.test.js.map +1 -1
- package/dist/v2/__tests__/FormikDisplayWidth.test.js +102 -0
- package/dist/v2/__tests__/FormikDisplayWidth.test.js.map +1 -0
- package/dist/v2/__tests__/FormikRadioGroup.test.js +123 -204
- package/dist/v2/__tests__/FormikRadioGroup.test.js.map +1 -1
- package/dist/v2/__tests__/MobileRowCard.test.js +63 -61
- package/dist/v2/__tests__/MobileRowCard.test.js.map +1 -1
- package/dist/v2/__tests__/PreviewDrawer.test.js +169 -0
- package/dist/v2/__tests__/PreviewDrawer.test.js.map +1 -0
- package/dist/v2/__tests__/ReferentielFieldContainer.test.js +159 -0
- package/dist/v2/__tests__/ReferentielFieldContainer.test.js.map +1 -0
- package/dist/v2/__tests__/SectionErrorBoundary.test.js +53 -68
- package/dist/v2/__tests__/SectionErrorBoundary.test.js.map +1 -1
- package/dist/v2/__tests__/apiError.test.js +50 -69
- package/dist/v2/__tests__/apiError.test.js.map +1 -1
- package/dist/v2/__tests__/banned-patterns.test.js +42 -123
- package/dist/v2/__tests__/banned-patterns.test.js.map +1 -1
- package/dist/v2/__tests__/chart-palette.test.js +205 -0
- package/dist/v2/__tests__/chart-palette.test.js.map +1 -0
- package/dist/v2/__tests__/contrast-palette.test.js +42 -74
- package/dist/v2/__tests__/contrast-palette.test.js.map +1 -1
- package/dist/v2/__tests__/cssVariables.test.js +44 -51
- package/dist/v2/__tests__/cssVariables.test.js.map +1 -1
- package/dist/v2/__tests__/fileValidation.test.js +136 -0
- package/dist/v2/__tests__/fileValidation.test.js.map +1 -0
- package/dist/v2/__tests__/filterCriteria.test.js +324 -0
- package/dist/v2/__tests__/filterCriteria.test.js.map +1 -0
- package/dist/v2/__tests__/fmtDate.test.js +69 -0
- package/dist/v2/__tests__/fmtDate.test.js.map +1 -0
- package/dist/v2/__tests__/gap-leakage-refonte.test.js +221 -0
- package/dist/v2/__tests__/gap-leakage-refonte.test.js.map +1 -0
- package/dist/v2/__tests__/labels-merge.test.js +23 -27
- package/dist/v2/__tests__/labels-merge.test.js.map +1 -1
- package/dist/v2/__tests__/locale-fallback.test.js +47 -52
- package/dist/v2/__tests__/locale-fallback.test.js.map +1 -1
- package/dist/v2/__tests__/reviveSavedCriteria.test.js +144 -0
- package/dist/v2/__tests__/reviveSavedCriteria.test.js.map +1 -0
- package/dist/v2/__tests__/rowActions.test.js +186 -0
- package/dist/v2/__tests__/rowActions.test.js.map +1 -0
- package/dist/v2/__tests__/useFocusOnRouteChange.test.js +156 -0
- package/dist/v2/__tests__/useFocusOnRouteChange.test.js.map +1 -0
- package/dist/v2/__tests__/useFocusTrap.test.js +270 -407
- package/dist/v2/__tests__/useFocusTrap.test.js.map +1 -1
- package/dist/v2/__tests__/usePrefersReducedMotion.test.js +123 -0
- package/dist/v2/__tests__/usePrefersReducedMotion.test.js.map +1 -0
- package/dist/v2/components/Accordion.js +60 -82
- package/dist/v2/components/Accordion.js.map +1 -1
- package/dist/v2/components/ActionStrip.js +40 -52
- package/dist/v2/components/ActionStrip.js.map +1 -1
- package/dist/v2/components/Banner.js +42 -41
- package/dist/v2/components/Banner.js.map +1 -1
- package/dist/v2/components/Busy.js +22 -27
- package/dist/v2/components/Busy.js.map +1 -1
- package/dist/v2/components/Button.js +39 -50
- package/dist/v2/components/Button.js.map +1 -1
- package/dist/v2/components/CategoryTag.d.ts.map +1 -1
- package/dist/v2/components/CategoryTag.js +49 -32
- package/dist/v2/components/CategoryTag.js.map +1 -1
- package/dist/v2/components/Checkbox.js +39 -45
- package/dist/v2/components/Checkbox.js.map +1 -1
- package/dist/v2/components/Chip.js +29 -42
- package/dist/v2/components/Chip.js.map +1 -1
- package/dist/v2/components/Combobox.js +122 -144
- package/dist/v2/components/Combobox.js.map +1 -1
- package/dist/v2/components/CommandPalette.js +138 -225
- package/dist/v2/components/CommandPalette.js.map +1 -1
- package/dist/v2/components/ConfirmDialog.js +46 -56
- package/dist/v2/components/ConfirmDialog.js.map +1 -1
- package/dist/v2/components/DatePicker.js +81 -86
- package/dist/v2/components/DatePicker.js.map +1 -1
- package/dist/v2/components/DateRangePicker.js +77 -87
- package/dist/v2/components/DateRangePicker.js.map +1 -1
- package/dist/v2/components/DateTimePicker.js +84 -91
- package/dist/v2/components/DateTimePicker.js.map +1 -1
- package/dist/v2/components/Drawer.d.ts.map +1 -1
- package/dist/v2/components/Drawer.js +93 -88
- package/dist/v2/components/Drawer.js.map +1 -1
- package/dist/v2/components/Dropzone.js +150 -196
- package/dist/v2/components/Dropzone.js.map +1 -1
- package/dist/v2/components/EmptyState.js +18 -20
- package/dist/v2/components/EmptyState.js.map +1 -1
- package/dist/v2/components/FavoriteToggle.js +28 -39
- package/dist/v2/components/FavoriteToggle.js.map +1 -1
- package/dist/v2/components/Field.d.ts +62 -4
- package/dist/v2/components/Field.d.ts.map +1 -1
- package/dist/v2/components/Field.js +169 -70
- package/dist/v2/components/Field.js.map +1 -1
- package/dist/v2/components/FieldError.js +18 -21
- package/dist/v2/components/FieldError.js.map +1 -1
- package/dist/v2/components/FieldHelp.js +40 -60
- package/dist/v2/components/FieldHelp.js.map +1 -1
- package/dist/v2/components/FileCard.js +27 -31
- package/dist/v2/components/FileCard.js.map +1 -1
- package/dist/v2/components/FilterTab.js +30 -38
- package/dist/v2/components/FilterTab.js.map +1 -1
- package/dist/v2/components/ForbiddenPage.d.ts +25 -1
- package/dist/v2/components/ForbiddenPage.d.ts.map +1 -1
- package/dist/v2/components/ForbiddenPage.js +37 -17
- package/dist/v2/components/ForbiddenPage.js.map +1 -1
- package/dist/v2/components/FormGrid.js +53 -59
- package/dist/v2/components/FormGrid.js.map +1 -1
- package/dist/v2/components/FormikDisplay.d.ts +50 -1
- package/dist/v2/components/FormikDisplay.d.ts.map +1 -1
- package/dist/v2/components/FormikDisplay.js +108 -33
- package/dist/v2/components/FormikDisplay.js.map +1 -1
- package/dist/v2/components/Input.d.ts +40 -0
- package/dist/v2/components/Input.d.ts.map +1 -1
- package/dist/v2/components/Input.js +81 -41
- package/dist/v2/components/Input.js.map +1 -1
- package/dist/v2/components/KPICard.js +57 -67
- package/dist/v2/components/KPICard.js.map +1 -1
- package/dist/v2/components/KPIStrip.js +16 -22
- package/dist/v2/components/KPIStrip.js.map +1 -1
- package/dist/v2/components/KV.js +25 -31
- package/dist/v2/components/KV.js.map +1 -1
- package/dist/v2/components/KVGrid.js +19 -26
- package/dist/v2/components/KVGrid.js.map +1 -1
- package/dist/v2/components/Layout.js +91 -120
- package/dist/v2/components/Layout.js.map +1 -1
- package/dist/v2/components/MobileRowCard.js +79 -91
- package/dist/v2/components/MobileRowCard.js.map +1 -1
- package/dist/v2/components/Modal.js +61 -69
- package/dist/v2/components/Modal.js.map +1 -1
- package/dist/v2/components/NetworkErrorPage.d.ts +19 -1
- package/dist/v2/components/NetworkErrorPage.d.ts.map +1 -1
- package/dist/v2/components/NetworkErrorPage.js +38 -21
- package/dist/v2/components/NetworkErrorPage.js.map +1 -1
- package/dist/v2/components/NotFoundPage.d.ts +17 -1
- package/dist/v2/components/NotFoundPage.d.ts.map +1 -1
- package/dist/v2/components/NotFoundPage.js +37 -18
- package/dist/v2/components/NotFoundPage.js.map +1 -1
- package/dist/v2/components/NumberInput.js +88 -118
- package/dist/v2/components/NumberInput.js.map +1 -1
- package/dist/v2/components/OverflowMenu.js +38 -56
- package/dist/v2/components/OverflowMenu.js.map +1 -1
- package/dist/v2/components/PdfViewer.js +34 -45
- package/dist/v2/components/PdfViewer.js.map +1 -1
- package/dist/v2/components/ProgressBar.js +42 -49
- package/dist/v2/components/ProgressBar.js.map +1 -1
- package/dist/v2/components/Radio.js +59 -67
- package/dist/v2/components/Radio.js.map +1 -1
- package/dist/v2/components/SectionErrorBoundary.js +90 -135
- package/dist/v2/components/SectionErrorBoundary.js.map +1 -1
- package/dist/v2/components/SectionHeader.js +34 -41
- package/dist/v2/components/SectionHeader.js.map +1 -1
- package/dist/v2/components/Segmented.js +69 -93
- package/dist/v2/components/Segmented.js.map +1 -1
- package/dist/v2/components/Select.d.ts +26 -2
- package/dist/v2/components/Select.d.ts.map +1 -1
- package/dist/v2/components/Select.js +120 -155
- package/dist/v2/components/Select.js.map +1 -1
- package/dist/v2/components/ServerErrorPage.d.ts +27 -1
- package/dist/v2/components/ServerErrorPage.d.ts.map +1 -1
- package/dist/v2/components/ServerErrorPage.js +37 -20
- package/dist/v2/components/ServerErrorPage.js.map +1 -1
- package/dist/v2/components/Skeleton.js +86 -100
- package/dist/v2/components/Skeleton.js.map +1 -1
- package/dist/v2/components/SkipLink.js +9 -12
- package/dist/v2/components/SkipLink.js.map +1 -1
- package/dist/v2/components/Spinner.js +16 -25
- package/dist/v2/components/Spinner.js.map +1 -1
- package/dist/v2/components/Status.js +18 -29
- package/dist/v2/components/Status.js.map +1 -1
- package/dist/v2/components/Stepper.js +80 -82
- package/dist/v2/components/Stepper.js.map +1 -1
- package/dist/v2/components/Surface.js +34 -49
- package/dist/v2/components/Surface.js.map +1 -1
- package/dist/v2/components/Switch.js +29 -37
- package/dist/v2/components/Switch.js.map +1 -1
- package/dist/v2/components/TabPanel.d.ts +17 -0
- package/dist/v2/components/TabPanel.d.ts.map +1 -0
- package/dist/v2/components/TabPanel.js +90 -0
- package/dist/v2/components/TabPanel.js.map +1 -0
- package/dist/v2/components/Tabs.d.ts +25 -0
- package/dist/v2/components/Tabs.d.ts.map +1 -1
- package/dist/v2/components/Tabs.js +53 -29
- package/dist/v2/components/Tabs.js.map +1 -1
- package/dist/v2/components/TelInput.js +80 -115
- package/dist/v2/components/TelInput.js.map +1 -1
- package/dist/v2/components/Textarea.js +26 -35
- package/dist/v2/components/Textarea.js.map +1 -1
- package/dist/v2/components/TimePicker.js +62 -74
- package/dist/v2/components/TimePicker.js.map +1 -1
- package/dist/v2/components/Toast.js +119 -174
- package/dist/v2/components/Toast.js.map +1 -1
- package/dist/v2/components/Tooltip.js +23 -38
- package/dist/v2/components/Tooltip.js.map +1 -1
- package/dist/v2/components/_ErrorPageBody.js +15 -16
- package/dist/v2/components/_ErrorPageBody.js.map +1 -1
- package/dist/v2/components/_pickerShared.js +5 -5
- package/dist/v2/components/_pickerShared.js.map +1 -1
- package/dist/v2/components/index.d.ts +40 -1
- package/dist/v2/components/index.d.ts.map +1 -1
- package/dist/v2/components/index.js +153 -136
- package/dist/v2/components/index.js.map +1 -1
- package/dist/v2/hooks/index.d.ts +65 -0
- package/dist/v2/hooks/index.d.ts.map +1 -1
- package/dist/v2/hooks/index.js +63 -24
- package/dist/v2/hooks/index.js.map +1 -1
- package/dist/v2/hooks/useAppliedFilters.d.ts +102 -0
- package/dist/v2/hooks/useAppliedFilters.d.ts.map +1 -0
- package/dist/v2/hooks/useAppliedFilters.js +117 -0
- package/dist/v2/hooks/useAppliedFilters.js.map +1 -0
- package/dist/v2/hooks/useDebouncedCallback.d.ts +64 -0
- package/dist/v2/hooks/useDebouncedCallback.d.ts.map +1 -0
- package/dist/v2/hooks/useDebouncedCallback.js +87 -0
- package/dist/v2/hooks/useDebouncedCallback.js.map +1 -0
- package/dist/v2/hooks/useDelayedFlag.js +6 -20
- package/dist/v2/hooks/useDelayedFlag.js.map +1 -1
- package/dist/v2/hooks/useDensity.d.ts.map +1 -1
- package/dist/v2/hooks/useDensity.js +14 -18
- package/dist/v2/hooks/useDensity.js.map +1 -1
- package/dist/v2/hooks/useFocusFirstError.js +26 -34
- package/dist/v2/hooks/useFocusFirstError.js.map +1 -1
- package/dist/v2/hooks/useFocusOnRouteChange.d.ts +65 -0
- package/dist/v2/hooks/useFocusOnRouteChange.d.ts.map +1 -0
- package/dist/v2/hooks/useFocusOnRouteChange.js +88 -0
- package/dist/v2/hooks/useFocusOnRouteChange.js.map +1 -0
- package/dist/v2/hooks/useFocusTrap.js +20 -24
- package/dist/v2/hooks/useFocusTrap.js.map +1 -1
- package/dist/v2/hooks/useFormAutoSave.d.ts +4 -4
- package/dist/v2/hooks/useFormAutoSave.d.ts.map +1 -1
- package/dist/v2/hooks/useFormAutoSave.js +39 -54
- package/dist/v2/hooks/useFormAutoSave.js.map +1 -1
- package/dist/v2/hooks/useFormCompletionProgress.d.ts +1 -1
- package/dist/v2/hooks/useFormCompletionProgress.d.ts.map +1 -1
- package/dist/v2/hooks/useFormCompletionProgress.js +33 -52
- package/dist/v2/hooks/useFormCompletionProgress.js.map +1 -1
- package/dist/v2/hooks/useKeyboardShortcuts.d.ts +25 -0
- package/dist/v2/hooks/useKeyboardShortcuts.d.ts.map +1 -0
- package/dist/v2/hooks/useKeyboardShortcuts.js +132 -0
- package/dist/v2/hooks/useKeyboardShortcuts.js.map +1 -0
- package/dist/v2/hooks/usePrefersReducedMotion.d.ts +56 -0
- package/dist/v2/hooks/usePrefersReducedMotion.d.ts.map +1 -0
- package/dist/v2/hooks/usePrefersReducedMotion.js +105 -0
- package/dist/v2/hooks/usePrefersReducedMotion.js.map +1 -0
- package/dist/v2/hooks/useResolvedDensity.js +5 -13
- package/dist/v2/hooks/useResolvedDensity.js.map +1 -1
- package/dist/v2/hooks/useSortedRows.js +4 -6
- package/dist/v2/hooks/useSortedRows.js.map +1 -1
- package/dist/v2/hooks/useTabLoading.js +7 -22
- package/dist/v2/hooks/useTabLoading.js.map +1 -1
- package/dist/v2/index.js +143 -145
- package/dist/v2/index.js.map +1 -1
- package/dist/v2/modalContext.js +2 -2
- package/dist/v2/modalContext.js.map +1 -1
- package/dist/v2/models/favorite.d.ts +8 -9
- package/dist/v2/models/favorite.d.ts.map +1 -1
- package/dist/v2/models/favorite.js.map +1 -1
- package/dist/v2/models/index.js +4 -4
- package/dist/v2/models/index.js.map +1 -1
- package/dist/v2/models/personalization.d.ts +44 -48
- package/dist/v2/models/personalization.d.ts.map +1 -1
- package/dist/v2/models/personalization.js.map +1 -1
- package/dist/v2/models/referentiel.d.ts +32 -50
- package/dist/v2/models/referentiel.d.ts.map +1 -1
- package/dist/v2/models/referentiel.js.map +1 -1
- package/dist/v2/models/savedFilter.d.ts +8 -17
- package/dist/v2/models/savedFilter.d.ts.map +1 -1
- package/dist/v2/models/savedFilter.js.map +1 -1
- package/dist/v2/patterns/ActionBar.js +152 -187
- package/dist/v2/patterns/ActionBar.js.map +1 -1
- package/dist/v2/patterns/AppMain.d.ts +109 -0
- package/dist/v2/patterns/AppMain.d.ts.map +1 -0
- package/dist/v2/patterns/AppMain.js +115 -0
- package/dist/v2/patterns/AppMain.js.map +1 -0
- package/dist/v2/patterns/BulkActionBar.js +40 -42
- package/dist/v2/patterns/BulkActionBar.js.map +1 -1
- package/dist/v2/patterns/ChartFigure.d.ts +124 -0
- package/dist/v2/patterns/ChartFigure.d.ts.map +1 -0
- package/dist/v2/patterns/ChartFigure.js +159 -0
- package/dist/v2/patterns/ChartFigure.js.map +1 -0
- package/dist/v2/patterns/CollapsibleDataGrid.js +138 -174
- package/dist/v2/patterns/CollapsibleDataGrid.js.map +1 -1
- package/dist/v2/patterns/ColumnsToggle.js +60 -94
- package/dist/v2/patterns/ColumnsToggle.js.map +1 -1
- package/dist/v2/patterns/DataGrid.d.ts.map +1 -1
- package/dist/v2/patterns/DataGrid.js +262 -297
- package/dist/v2/patterns/DataGrid.js.map +1 -1
- package/dist/v2/patterns/DataGridSettingsMenu.d.ts +12 -0
- package/dist/v2/patterns/DataGridSettingsMenu.d.ts.map +1 -1
- package/dist/v2/patterns/DataGridSettingsMenu.js +104 -121
- package/dist/v2/patterns/DataGridSettingsMenu.js.map +1 -1
- package/dist/v2/patterns/DataGridToolbar.js +103 -106
- package/dist/v2/patterns/DataGridToolbar.js.map +1 -1
- package/dist/v2/patterns/DataGridVirtualized.js +179 -217
- package/dist/v2/patterns/DataGridVirtualized.js.map +1 -1
- package/dist/v2/patterns/DeleteResourceConfirmDialog.js +32 -35
- package/dist/v2/patterns/DeleteResourceConfirmDialog.js.map +1 -1
- package/dist/v2/patterns/DetailSplit.d.ts +87 -0
- package/dist/v2/patterns/DetailSplit.d.ts.map +1 -0
- package/dist/v2/patterns/DetailSplit.js +116 -0
- package/dist/v2/patterns/DetailSplit.js.map +1 -0
- package/dist/v2/patterns/FavoriteColumn.js +22 -23
- package/dist/v2/patterns/FavoriteColumn.js.map +1 -1
- package/dist/v2/patterns/FavoritesFilter.js +36 -44
- package/dist/v2/patterns/FavoritesFilter.js.map +1 -1
- package/dist/v2/patterns/FilterTabBar.d.ts +28 -0
- package/dist/v2/patterns/FilterTabBar.d.ts.map +1 -0
- package/dist/v2/patterns/FilterTabBar.js +120 -0
- package/dist/v2/patterns/FilterTabBar.js.map +1 -0
- package/dist/v2/patterns/FormErrorSummary.js +20 -23
- package/dist/v2/patterns/FormErrorSummary.js.map +1 -1
- package/dist/v2/patterns/FormPageShell.js +53 -50
- package/dist/v2/patterns/FormPageShell.js.map +1 -1
- package/dist/v2/patterns/FormSection.d.ts +8 -3
- package/dist/v2/patterns/FormSection.d.ts.map +1 -1
- package/dist/v2/patterns/FormSection.js +44 -51
- package/dist/v2/patterns/FormSection.js.map +1 -1
- package/dist/v2/patterns/FormikCheckbox.js +36 -45
- package/dist/v2/patterns/FormikCheckbox.js.map +1 -1
- package/dist/v2/patterns/FormikCheckboxGroup.d.ts +17 -1
- package/dist/v2/patterns/FormikCheckboxGroup.d.ts.map +1 -1
- package/dist/v2/patterns/FormikCheckboxGroup.js +62 -65
- package/dist/v2/patterns/FormikCheckboxGroup.js.map +1 -1
- package/dist/v2/patterns/FormikCombobox.js +55 -72
- package/dist/v2/patterns/FormikCombobox.js.map +1 -1
- package/dist/v2/patterns/FormikDatePicker.js +50 -64
- package/dist/v2/patterns/FormikDatePicker.js.map +1 -1
- package/dist/v2/patterns/FormikDateTimePicker.js +50 -64
- package/dist/v2/patterns/FormikDateTimePicker.js.map +1 -1
- package/dist/v2/patterns/FormikErrorFocus.js +25 -26
- package/dist/v2/patterns/FormikErrorFocus.js.map +1 -1
- package/dist/v2/patterns/FormikErrorSummary.d.ts +74 -0
- package/dist/v2/patterns/FormikErrorSummary.d.ts.map +1 -0
- package/dist/v2/patterns/FormikErrorSummary.js +97 -0
- package/dist/v2/patterns/FormikErrorSummary.js.map +1 -0
- package/dist/v2/patterns/FormikFile.d.ts +14 -1
- package/dist/v2/patterns/FormikFile.d.ts.map +1 -1
- package/dist/v2/patterns/FormikFile.js +99 -83
- package/dist/v2/patterns/FormikFile.js.map +1 -1
- package/dist/v2/patterns/FormikInput.js +45 -58
- package/dist/v2/patterns/FormikInput.js.map +1 -1
- package/dist/v2/patterns/FormikRadioGroup.js +45 -56
- package/dist/v2/patterns/FormikRadioGroup.js.map +1 -1
- package/dist/v2/patterns/FormikReferentielField.d.ts +12 -1
- package/dist/v2/patterns/FormikReferentielField.d.ts.map +1 -1
- package/dist/v2/patterns/FormikReferentielField.js +54 -64
- package/dist/v2/patterns/FormikReferentielField.js.map +1 -1
- package/dist/v2/patterns/FormikSelect.js +70 -92
- package/dist/v2/patterns/FormikSelect.js.map +1 -1
- package/dist/v2/patterns/FormikTextarea.js +35 -51
- package/dist/v2/patterns/FormikTextarea.js.map +1 -1
- package/dist/v2/patterns/KeyboardShortcutsHelp.js +60 -80
- package/dist/v2/patterns/KeyboardShortcutsHelp.js.map +1 -1
- package/dist/v2/patterns/NetworkErrorBanner.js +13 -16
- package/dist/v2/patterns/NetworkErrorBanner.js.map +1 -1
- package/dist/v2/patterns/PageContent.js +17 -20
- package/dist/v2/patterns/PageContent.js.map +1 -1
- package/dist/v2/patterns/PreviewDrawer.d.ts +49 -0
- package/dist/v2/patterns/PreviewDrawer.d.ts.map +1 -0
- package/dist/v2/patterns/PreviewDrawer.js +159 -0
- package/dist/v2/patterns/PreviewDrawer.js.map +1 -0
- package/dist/v2/patterns/ReferentielField.d.ts +86 -2
- package/dist/v2/patterns/ReferentielField.d.ts.map +1 -1
- package/dist/v2/patterns/ReferentielField.js +347 -327
- package/dist/v2/patterns/ReferentielField.js.map +1 -1
- package/dist/v2/patterns/RowFavoriteToggle.d.ts +6 -0
- package/dist/v2/patterns/RowFavoriteToggle.d.ts.map +1 -1
- package/dist/v2/patterns/RowFavoriteToggle.js +38 -30
- package/dist/v2/patterns/RowFavoriteToggle.js.map +1 -1
- package/dist/v2/patterns/SaveFilterDialog.js +50 -77
- package/dist/v2/patterns/SaveFilterDialog.js.map +1 -1
- package/dist/v2/patterns/SavedFiltersMenu.js +89 -107
- package/dist/v2/patterns/SavedFiltersMenu.js.map +1 -1
- package/dist/v2/patterns/SkeletonPageShell.js +122 -143
- package/dist/v2/patterns/SkeletonPageShell.js.map +1 -1
- package/dist/v2/patterns/SortableHeader.js +35 -57
- package/dist/v2/patterns/SortableHeader.js.map +1 -1
- package/dist/v2/patterns/Tour.js +121 -142
- package/dist/v2/patterns/Tour.js.map +1 -1
- package/dist/v2/patterns/_dataGridShared.d.ts.map +1 -1
- package/dist/v2/patterns/_dataGridShared.js +131 -147
- package/dist/v2/patterns/_dataGridShared.js.map +1 -1
- package/dist/v2/patterns/_pickerSerialisation.js +9 -10
- package/dist/v2/patterns/_pickerSerialisation.js.map +1 -1
- package/dist/v2/patterns/_sort.js +17 -33
- package/dist/v2/patterns/_sort.js.map +1 -1
- package/dist/v2/patterns/_useColumnReorder.js +124 -163
- package/dist/v2/patterns/_useColumnReorder.js.map +1 -1
- package/dist/v2/patterns/_useColumnResize.js +80 -99
- package/dist/v2/patterns/_useColumnResize.js.map +1 -1
- package/dist/v2/patterns/filterCriteria.d.ts +78 -0
- package/dist/v2/patterns/filterCriteria.d.ts.map +1 -0
- package/dist/v2/patterns/filterCriteria.js +110 -0
- package/dist/v2/patterns/filterCriteria.js.map +1 -0
- package/dist/v2/patterns/index.d.ts +160 -0
- package/dist/v2/patterns/index.d.ts.map +1 -1
- package/dist/v2/patterns/index.js +157 -79
- package/dist/v2/patterns/index.js.map +1 -1
- package/dist/v2/patterns/rowActions.d.ts +150 -0
- package/dist/v2/patterns/rowActions.d.ts.map +1 -0
- package/dist/v2/patterns/rowActions.js +232 -0
- package/dist/v2/patterns/rowActions.js.map +1 -0
- package/dist/v2/personalization/PersonalizationProvider.d.ts.map +1 -1
- package/dist/v2/personalization/PersonalizationProvider.js +57 -53
- package/dist/v2/personalization/PersonalizationProvider.js.map +1 -1
- package/dist/v2/personalization/adapters/_storageKey.js +5 -5
- package/dist/v2/personalization/adapters/_storageKey.js.map +1 -1
- package/dist/v2/personalization/adapters/createLocalStorageAdapter.js +25 -24
- package/dist/v2/personalization/adapters/createLocalStorageAdapter.js.map +1 -1
- package/dist/v2/personalization/adapters/createMemoryAdapter.js +14 -14
- package/dist/v2/personalization/adapters/createMemoryAdapter.js.map +1 -1
- package/dist/v2/personalization/adapters/index.js +5 -5
- package/dist/v2/personalization/adapters/index.js.map +1 -1
- package/dist/v2/personalization/capabilities/favorites.js +39 -62
- package/dist/v2/personalization/capabilities/favorites.js.map +1 -1
- package/dist/v2/personalization/capabilities/favoritesView.js +25 -37
- package/dist/v2/personalization/capabilities/favoritesView.js.map +1 -1
- package/dist/v2/personalization/capabilities/gridPreferences.js +31 -36
- package/dist/v2/personalization/capabilities/gridPreferences.js.map +1 -1
- package/dist/v2/personalization/capabilities/index.js +12 -12
- package/dist/v2/personalization/capabilities/reviveSavedCriteria.d.ts +88 -0
- package/dist/v2/personalization/capabilities/reviveSavedCriteria.d.ts.map +1 -0
- package/dist/v2/personalization/capabilities/reviveSavedCriteria.js +126 -0
- package/dist/v2/personalization/capabilities/reviveSavedCriteria.js.map +1 -0
- package/dist/v2/personalization/capabilities/savedFilters.js +93 -100
- package/dist/v2/personalization/capabilities/savedFilters.js.map +1 -1
- package/dist/v2/personalization/createPersonalizationStore.d.ts.map +1 -1
- package/dist/v2/personalization/createPersonalizationStore.js +81 -182
- package/dist/v2/personalization/createPersonalizationStore.js.map +1 -1
- package/dist/v2/personalization/index.d.ts +16 -0
- package/dist/v2/personalization/index.d.ts.map +1 -1
- package/dist/v2/personalization/index.js +30 -23
- package/dist/v2/personalization/index.js.map +1 -1
- package/dist/v2/personalization/personalizationContext.js +10 -12
- package/dist/v2/personalization/personalizationContext.js.map +1 -1
- package/dist/v2/personalization/schema.js +8 -9
- package/dist/v2/personalization/schema.js.map +1 -1
- package/dist/v2/referentiel/ReferentielProvider.js +31 -45
- package/dist/v2/referentiel/ReferentielProvider.js.map +1 -1
- package/dist/v2/referentiel/index.js +11 -13
- package/dist/v2/referentiel/index.js.map +1 -1
- package/dist/v2/referentiel/types.d.ts +105 -0
- package/dist/v2/referentiel/types.d.ts.map +1 -0
- package/dist/v2/referentiel/types.js +6 -0
- package/dist/v2/referentiel/types.js.map +1 -0
- package/dist/v2/referentiel/useReferentielClient.js +13 -16
- package/dist/v2/referentiel/useReferentielClient.js.map +1 -1
- package/dist/v2/referentiel/useReferentielOptions.d.ts +7 -17
- package/dist/v2/referentiel/useReferentielOptions.d.ts.map +1 -1
- package/dist/v2/referentiel/useReferentielOptions.js +63 -120
- package/dist/v2/referentiel/useReferentielOptions.js.map +1 -1
- package/dist/v2/referentiel/useReferentielSearch.d.ts +7 -48
- package/dist/v2/referentiel/useReferentielSearch.d.ts.map +1 -1
- package/dist/v2/referentiel/useReferentielSearch.js +40 -95
- package/dist/v2/referentiel/useReferentielSearch.js.map +1 -1
- package/dist/v2/theme/_brand.js +12 -26
- package/dist/v2/theme/_brand.js.map +1 -1
- package/dist/v2/theme/_brandSecondary.js +12 -26
- package/dist/v2/theme/_brandSecondary.js.map +1 -1
- package/dist/v2/theme/createPortnetTheme.js +41 -49
- package/dist/v2/theme/createPortnetTheme.js.map +1 -1
- package/dist/v2/theme/cssVariables.d.ts +3 -3
- package/dist/v2/theme/cssVariables.d.ts.map +1 -1
- package/dist/v2/theme/cssVariables.js +65 -70
- package/dist/v2/theme/cssVariables.js.map +1 -1
- package/dist/v2/theme/index.js +10 -10
- package/dist/v2/theme/index.js.map +1 -1
- package/dist/v2/tokens/breakpoints.js +16 -16
- package/dist/v2/tokens/breakpoints.js.map +1 -1
- package/dist/v2/tokens/chart.d.ts +103 -0
- package/dist/v2/tokens/chart.d.ts.map +1 -0
- package/dist/v2/tokens/chart.js +270 -0
- package/dist/v2/tokens/chart.js.map +1 -0
- package/dist/v2/tokens/colors.js +24 -24
- package/dist/v2/tokens/colors.js.map +1 -1
- package/dist/v2/tokens/density.d.ts +104 -0
- package/dist/v2/tokens/density.d.ts.map +1 -1
- package/dist/v2/tokens/density.js +67 -12
- package/dist/v2/tokens/density.js.map +1 -1
- package/dist/v2/tokens/index.d.ts +50 -1
- package/dist/v2/tokens/index.d.ts.map +1 -1
- package/dist/v2/tokens/index.js +116 -68
- package/dist/v2/tokens/index.js.map +1 -1
- package/dist/v2/tokens/motion.js +16 -22
- package/dist/v2/tokens/motion.js.map +1 -1
- package/dist/v2/tokens/radius.js +3 -3
- package/dist/v2/tokens/radius.js.map +1 -1
- package/dist/v2/tokens/shadows.js +8 -8
- package/dist/v2/tokens/shadows.js.map +1 -1
- package/dist/v2/tokens/spacing.js +4 -4
- package/dist/v2/tokens/spacing.js.map +1 -1
- package/dist/v2/tokens/state.js +3 -3
- package/dist/v2/tokens/state.js.map +1 -1
- package/dist/v2/tokens/statusPalette.js +21 -23
- package/dist/v2/tokens/statusPalette.js.map +1 -1
- package/dist/v2/tokens/tokens.json +6 -1
- package/dist/v2/tokens/typography.js +15 -15
- package/dist/v2/tokens/typography.js.map +1 -1
- package/dist/v2/tokens/zIndex.js +3 -3
- package/dist/v2/tokens/zIndex.js.map +1 -1
- package/dist/v2/types/datagrid.d.ts +9 -1
- package/dist/v2/types/datagrid.d.ts.map +1 -1
- package/dist/v2/types/datagrid.js.map +1 -1
- package/dist/v2/types/index.js +2 -2
- package/dist/v2/types/index.js.map +1 -1
- package/dist/v2/utils/apiError.js +16 -37
- package/dist/v2/utils/apiError.js.map +1 -1
- package/dist/v2/utils/contrast.js +31 -32
- package/dist/v2/utils/contrast.js.map +1 -1
- package/dist/v2/utils/cx.js +7 -27
- package/dist/v2/utils/cx.js.map +1 -1
- package/dist/v2/utils/fileDownload.js +25 -28
- package/dist/v2/utils/fileDownload.js.map +1 -1
- package/dist/v2/utils/fileValidation.d.ts +116 -0
- package/dist/v2/utils/fileValidation.d.ts.map +1 -0
- package/dist/v2/utils/fileValidation.js +220 -0
- package/dist/v2/utils/fileValidation.js.map +1 -0
- package/dist/v2/utils/fmtDate.d.ts +103 -0
- package/dist/v2/utils/fmtDate.d.ts.map +1 -0
- package/dist/v2/utils/fmtDate.js +119 -0
- package/dist/v2/utils/fmtDate.js.map +1 -0
- package/dist/v2/utils/fmtMoney.d.ts +3 -3
- package/dist/v2/utils/fmtMoney.d.ts.map +1 -1
- package/dist/v2/utils/fmtMoney.js +9 -11
- package/dist/v2/utils/fmtMoney.js.map +1 -1
- package/dist/v2/utils/formikErrors.d.ts +67 -0
- package/dist/v2/utils/formikErrors.d.ts.map +1 -0
- package/dist/v2/utils/formikErrors.js +85 -0
- package/dist/v2/utils/formikErrors.js.map +1 -0
- package/dist/v2/utils/index.d.ts +5 -0
- package/dist/v2/utils/index.d.ts.map +1 -1
- package/dist/v2/utils/index.js +67 -18
- package/dist/v2/utils/index.js.map +1 -1
- package/dist/v2/utils/mergeLabels.js +9 -14
- package/dist/v2/utils/mergeLabels.js.map +1 -1
- package/dist/v2/utils/networkStatus.js +14 -14
- package/dist/v2/utils/networkStatus.js.map +1 -1
- package/package.json +26 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileDownload.js","names":["MIME_TYPES","Object","freeze","pdf","doc","docx","xls","xlsx","csv","txt","png","jpg","jpeg","gif","bmp","webp","svg","ico","tiff","tif","getMimeType","extension","_MIME_TYPES$extension","toLowerCase","downloadBase64File","base64Data","fileName","document","atob","url","link","binary","bytes","Uint8Array","length","i","charCodeAt","blob","Blob","type","URL","createObjectURL","createElement","href","download","body","appendChild","click","error","console","parentNode","removeChild","revokeObjectURL","PREVIEW_REVOKE_DELAY_MS","openBase64FileInTab","window","open","revokeTarget","setTimeout","downloadBlobFile","fileDownloadApi"],"sources":["../../../../src/lib/v2/utils/fileDownload.ts"],"sourcesContent":["/**\r\n * File download utilities — Portnet UI v2\r\n *\r\n * Turns a base64 payload from the API into a browser download. Every GU\r\n * module that lets a user retrieve an attached document needs this, and\r\n * before this file each one carried its own copy of the same\r\n * atob → Uint8Array → Blob → anchor sequence plus its own MIME map.\r\n *\r\n * DEPENDENCY-FREE ON PURPOSE. Multi-file (zip) bundling is deliberately NOT\r\n * here: it needs a zip library, and making that a hard dependency of the\r\n * design system would put it in every module's bundle against the\r\n * R-8.2 budget (180 KB gz initial JS) for a capability only\r\n * document-heavy modules use. If it is promoted later it belongs behind an\r\n * OPTIONAL peer dependency, the way PdfViewer sits behind pdfjs-dist and\r\n * @react-pdf-viewer/* in peerDependenciesMeta.\r\n *\r\n * Charter ref: §09.3 — the caller owns the user-facing message; these\r\n * return a boolean rather than throwing so a failure surfaces as copy\r\n * (R-4.4) instead of an unhandled rejection.\r\n */\r\n\r\n/**\r\n * Extension → MIME type for the formats the platform accepts as\r\n * attachments.\r\n *\r\n * Anything unmapped resolves to application/octet-stream, which makes the\r\n * browser download the file rather than attempt to render it — the safe\r\n * default for an unknown payload.\r\n */\r\nconst MIME_TYPES: Readonly<Record<string, string>> = Object.freeze({\r\n // Documents\r\n pdf: \"application/pdf\",\r\n doc: \"application/msword\",\r\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\r\n xls: \"application/vnd.ms-excel\",\r\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\r\n csv: \"text/csv\",\r\n txt: \"text/plain\",\r\n\r\n // Images\r\n png: \"image/png\",\r\n jpg: \"image/jpeg\",\r\n jpeg: \"image/jpeg\",\r\n gif: \"image/gif\",\r\n bmp: \"image/bmp\",\r\n webp: \"image/webp\",\r\n svg: \"image/svg+xml\",\r\n ico: \"image/x-icon\",\r\n tiff: \"image/tiff\",\r\n tif: \"image/tiff\",\r\n});\r\n\r\n/**\r\n * Resolve the MIME type for a file extension.\r\n *\r\n * @param extension - Extension WITHOUT a leading dot. Case-insensitive.\r\n * @returns The mapped MIME type, or \"application/octet-stream\".\r\n */\r\nexport function getMimeType(extension: string): string {\r\n return MIME_TYPES[extension.toLowerCase()] ?? \"application/octet-stream\";\r\n}\r\n\r\n/**\r\n * Decode a base64 payload and hand it to the browser as a download.\r\n *\r\n * @param base64Data - Base64 WITHOUT a `data:` prefix, as the getFile /\r\n * downloadFile endpoints return it.\r\n * @param fileName - Filename offered to the user. The extension is NOT\r\n * appended automatically; pass it already suffixed if\r\n * you want one.\r\n * @param extension - Used only to resolve the Blob's MIME type.\r\n * @returns true on success; false if decoding failed or there is no DOM.\r\n *\r\n * @example\r\n * const ok = downloadBase64File(body.content, body.fileName, body.extension);\r\n * if (!ok) toast(\"Le téléchargement a échoué. Veuillez réessayer.\", \"error\");\r\n */\r\nexport function downloadBase64File(\r\n base64Data: string,\r\n fileName: string,\r\n extension: string,\r\n): boolean {\r\n // Guarded rather than assumed: this runs in jsdom under test and could be\r\n // reached during SSR. The host modules' copies of this threw in both.\r\n if (typeof document === \"undefined\" || typeof atob === \"undefined\") {\r\n return false;\r\n }\r\n\r\n let url: string | null = null;\r\n let link: HTMLAnchorElement | null = null;\r\n\r\n try {\r\n const binary = atob(base64Data);\r\n const bytes = new Uint8Array(binary.length);\r\n for (let i = 0; i < binary.length; i += 1) {\r\n bytes[i] = binary.charCodeAt(i);\r\n }\r\n\r\n const blob = new Blob([bytes], { type: getMimeType(extension) });\r\n\r\n url = URL.createObjectURL(blob);\r\n link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = fileName;\r\n document.body.appendChild(link);\r\n link.click();\r\n return true;\r\n } catch (error) {\r\n // eslint-disable-next-line no-console\r\n console.error(\"[@portnet/ui] downloadBase64File failed:\", error);\r\n return false;\r\n } finally {\r\n // finally, not the happy path: the host copies leaked both the anchor\r\n // and the object URL whenever click() or appendChild threw.\r\n if (link && link.parentNode) link.parentNode.removeChild(link);\r\n if (url) URL.revokeObjectURL(url);\r\n }\r\n}\r\n\r\n/**\r\n * Delay before revoking a preview's object URL.\r\n *\r\n * It CANNOT be revoked synchronously: the newly opened tab still has to fetch\r\n * the blob. There is no portable \"that tab finished loading\" signal for a blob\r\n * URL, so a timeout is the only cross-browser option. Deliberately generous:\r\n * revoking early shows the user a blank tab, while being late costs one\r\n * short-lived blob.\r\n */\r\nconst PREVIEW_REVOKE_DELAY_MS = 60_000;\r\n\r\n/**\r\n * Decode a base64 payload and open it in a new tab for preview.\r\n *\r\n * Companion to downloadBase64File: every document-bearing GU module offers a\r\n * « Visualiser » action next to « Télécharger », and each carried its own copy of\r\n * this atob -> Uint8Array -> Blob -> window.open sequence. NONE of those copies\r\n * revoked the object URL, so every preview leaked a blob for the lifetime of the\r\n * page.\r\n *\r\n * RETURN VALUE — read this before relying on it. `window.open` returns null BY\r\n * SPECIFICATION when \"noopener\" is set, so a blocked popup is indistinguishable\r\n * from a successful one. `true` therefore means \"decoded and the open call was\r\n * issued without throwing\" — NOT \"the tab is on screen\". Do not use it to claim\r\n * success to the user; use `false` to report a genuine failure (bad payload, no\r\n * DOM). Dropping \"noopener\" would make blocked-popup detection possible but\r\n * would hand the opened tab a window.opener reference — not a trade worth making\r\n * for a diagnostic.\r\n *\r\n * @param base64Data - Base64 WITHOUT a `data:` prefix.\r\n * @param extension - Resolves the Blob MIME type. Required for inline\r\n * rendering: with the wrong type the browser downloads the\r\n * file instead of displaying it.\r\n * @returns false if there is no DOM or the payload could not be decoded.\r\n */\r\nexport function openBase64FileInTab(base64Data: string, extension: string): boolean {\r\n if (typeof window === \"undefined\" || typeof atob === \"undefined\") {\r\n return false;\r\n }\r\n let url: string | null = null;\r\n try {\r\n const binary = atob(base64Data);\r\n const bytes = new Uint8Array(binary.length);\r\n for (let i = 0; i < binary.length; i += 1) {\r\n bytes[i] = binary.charCodeAt(i);\r\n }\r\n const blob = new Blob([bytes], { type: getMimeType(extension) });\r\n url = URL.createObjectURL(blob);\r\n window.open(url, \"_blank\", \"noopener,noreferrer\");\r\n const revokeTarget = url;\r\n window.setTimeout(() => URL.revokeObjectURL(revokeTarget), PREVIEW_REVOKE_DELAY_MS);\r\n return true;\r\n } catch (error) {\r\n // Revoke straight away here: nothing consumed the URL on this path.\r\n if (url) URL.revokeObjectURL(url);\r\n // eslint-disable-next-line no-console\r\n console.error(\"[@portnet/ui] openBase64FileInTab failed:\", error);\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Hand an already-materialised Blob to the browser as a download.\r\n *\r\n * Companion to downloadBase64File for endpoints that return a binary body\r\n * directly (axios `responseType: \"blob\"`) instead of base64 — Excel exports,\r\n * server-generated PDFs. Host modules built the anchor by hand for this too,\r\n * and every copy found so far never revoked the object URL.\r\n *\r\n * Revoked synchronously in `finally`, unlike openBase64FileInTab: a download is\r\n * snapshotted by the browser at click() time, so nothing needs the URL\r\n * afterwards — only a preview in another tab does.\r\n *\r\n * @param blob - The payload. Its own `type` is used as-is; no MIME lookup,\r\n * the server already declared it.\r\n * @param fileName - Filename offered to the user, extension included.\r\n * @returns true on success; false if there is no DOM.\r\n */\r\nexport function downloadBlobFile(blob: Blob, fileName: string): boolean {\r\n if (typeof document === \"undefined\" || typeof URL === \"undefined\") {\r\n return false;\r\n }\r\n let url: string | null = null;\r\n let link: HTMLAnchorElement | null = null;\r\n try {\r\n url = URL.createObjectURL(blob);\r\n link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = fileName;\r\n document.body.appendChild(link);\r\n link.click();\r\n return true;\r\n } catch (error) {\r\n // eslint-disable-next-line no-console\r\n console.error(\"[@portnet/ui] downloadBlobFile failed:\", error);\r\n return false;\r\n } finally {\r\n if (link && link.parentNode) link.parentNode.removeChild(link);\r\n if (url) URL.revokeObjectURL(url);\r\n }\r\n}\r\n\r\nconst fileDownloadApi = {\r\n getMimeType,\r\n downloadBase64File,\r\n openBase64FileInTab,\r\n downloadBlobFile,\r\n};\r\nexport default fileDownloadApi;\r\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"fileDownload.js","names":["MIME_TYPES","Object","freeze","pdf","doc","docx","xls","xlsx","csv","txt","png","jpg","jpeg","gif","bmp","webp","svg","ico","tiff","tif","getMimeType","extension","toLowerCase","downloadBase64File","base64Data","fileName","document","atob","url","link","binary","bytes","Uint8Array","length","i","charCodeAt","blob","Blob","type","URL","createObjectURL","createElement","href","download","body","appendChild","click","error","console","parentNode","removeChild","revokeObjectURL","PREVIEW_REVOKE_DELAY_MS","openBase64FileInTab","window","open","revokeTarget","setTimeout","downloadBlobFile","fileDownloadApi"],"sources":["../../../../src/lib/v2/utils/fileDownload.ts"],"sourcesContent":["/**\r\n * File download utilities — Portnet UI v2\r\n *\r\n * Turns a base64 payload from the API into a browser download. Every GU\r\n * module that lets a user retrieve an attached document needs this, and\r\n * before this file each one carried its own copy of the same\r\n * atob → Uint8Array → Blob → anchor sequence plus its own MIME map.\r\n *\r\n * DEPENDENCY-FREE ON PURPOSE. Multi-file (zip) bundling is deliberately NOT\r\n * here: it needs a zip library, and making that a hard dependency of the\r\n * design system would put it in every module's bundle against the\r\n * R-8.2 budget (180 KB gz initial JS) for a capability only\r\n * document-heavy modules use. If it is promoted later it belongs behind an\r\n * OPTIONAL peer dependency, the way PdfViewer sits behind pdfjs-dist and\r\n * @react-pdf-viewer/* in peerDependenciesMeta.\r\n *\r\n * Charter ref: §09.3 — the caller owns the user-facing message; these\r\n * return a boolean rather than throwing so a failure surfaces as copy\r\n * (R-4.4) instead of an unhandled rejection.\r\n */\r\n\r\n/**\r\n * Extension → MIME type for the formats the platform accepts as\r\n * attachments.\r\n *\r\n * Anything unmapped resolves to application/octet-stream, which makes the\r\n * browser download the file rather than attempt to render it — the safe\r\n * default for an unknown payload.\r\n */\r\nconst MIME_TYPES: Readonly<Record<string, string>> = Object.freeze({\r\n // Documents\r\n pdf: \"application/pdf\",\r\n doc: \"application/msword\",\r\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\r\n xls: \"application/vnd.ms-excel\",\r\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\r\n csv: \"text/csv\",\r\n txt: \"text/plain\",\r\n\r\n // Images\r\n png: \"image/png\",\r\n jpg: \"image/jpeg\",\r\n jpeg: \"image/jpeg\",\r\n gif: \"image/gif\",\r\n bmp: \"image/bmp\",\r\n webp: \"image/webp\",\r\n svg: \"image/svg+xml\",\r\n ico: \"image/x-icon\",\r\n tiff: \"image/tiff\",\r\n tif: \"image/tiff\",\r\n});\r\n\r\n/**\r\n * Resolve the MIME type for a file extension.\r\n *\r\n * @param extension - Extension WITHOUT a leading dot. Case-insensitive.\r\n * @returns The mapped MIME type, or \"application/octet-stream\".\r\n */\r\nexport function getMimeType(extension: string): string {\r\n return MIME_TYPES[extension.toLowerCase()] ?? \"application/octet-stream\";\r\n}\r\n\r\n/**\r\n * Decode a base64 payload and hand it to the browser as a download.\r\n *\r\n * @param base64Data - Base64 WITHOUT a `data:` prefix, as the getFile /\r\n * downloadFile endpoints return it.\r\n * @param fileName - Filename offered to the user. The extension is NOT\r\n * appended automatically; pass it already suffixed if\r\n * you want one.\r\n * @param extension - Used only to resolve the Blob's MIME type.\r\n * @returns true on success; false if decoding failed or there is no DOM.\r\n *\r\n * @example\r\n * const ok = downloadBase64File(body.content, body.fileName, body.extension);\r\n * if (!ok) toast(\"Le téléchargement a échoué. Veuillez réessayer.\", \"error\");\r\n */\r\nexport function downloadBase64File(\r\n base64Data: string,\r\n fileName: string,\r\n extension: string,\r\n): boolean {\r\n // Guarded rather than assumed: this runs in jsdom under test and could be\r\n // reached during SSR. The host modules' copies of this threw in both.\r\n if (typeof document === \"undefined\" || typeof atob === \"undefined\") {\r\n return false;\r\n }\r\n\r\n let url: string | null = null;\r\n let link: HTMLAnchorElement | null = null;\r\n\r\n try {\r\n const binary = atob(base64Data);\r\n const bytes = new Uint8Array(binary.length);\r\n for (let i = 0; i < binary.length; i += 1) {\r\n bytes[i] = binary.charCodeAt(i);\r\n }\r\n\r\n const blob = new Blob([bytes], { type: getMimeType(extension) });\r\n\r\n url = URL.createObjectURL(blob);\r\n link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = fileName;\r\n document.body.appendChild(link);\r\n link.click();\r\n return true;\r\n } catch (error) {\r\n // eslint-disable-next-line no-console\r\n console.error(\"[@portnet/ui] downloadBase64File failed:\", error);\r\n return false;\r\n } finally {\r\n // finally, not the happy path: the host copies leaked both the anchor\r\n // and the object URL whenever click() or appendChild threw.\r\n if (link && link.parentNode) link.parentNode.removeChild(link);\r\n if (url) URL.revokeObjectURL(url);\r\n }\r\n}\r\n\r\n/**\r\n * Delay before revoking a preview's object URL.\r\n *\r\n * It CANNOT be revoked synchronously: the newly opened tab still has to fetch\r\n * the blob. There is no portable \"that tab finished loading\" signal for a blob\r\n * URL, so a timeout is the only cross-browser option. Deliberately generous:\r\n * revoking early shows the user a blank tab, while being late costs one\r\n * short-lived blob.\r\n */\r\nconst PREVIEW_REVOKE_DELAY_MS = 60_000;\r\n\r\n/**\r\n * Decode a base64 payload and open it in a new tab for preview.\r\n *\r\n * Companion to downloadBase64File: every document-bearing GU module offers a\r\n * « Visualiser » action next to « Télécharger », and each carried its own copy of\r\n * this atob -> Uint8Array -> Blob -> window.open sequence. NONE of those copies\r\n * revoked the object URL, so every preview leaked a blob for the lifetime of the\r\n * page.\r\n *\r\n * RETURN VALUE — read this before relying on it. `window.open` returns null BY\r\n * SPECIFICATION when \"noopener\" is set, so a blocked popup is indistinguishable\r\n * from a successful one. `true` therefore means \"decoded and the open call was\r\n * issued without throwing\" — NOT \"the tab is on screen\". Do not use it to claim\r\n * success to the user; use `false` to report a genuine failure (bad payload, no\r\n * DOM). Dropping \"noopener\" would make blocked-popup detection possible but\r\n * would hand the opened tab a window.opener reference — not a trade worth making\r\n * for a diagnostic.\r\n *\r\n * @param base64Data - Base64 WITHOUT a `data:` prefix.\r\n * @param extension - Resolves the Blob MIME type. Required for inline\r\n * rendering: with the wrong type the browser downloads the\r\n * file instead of displaying it.\r\n * @returns false if there is no DOM or the payload could not be decoded.\r\n */\r\nexport function openBase64FileInTab(base64Data: string, extension: string): boolean {\r\n if (typeof window === \"undefined\" || typeof atob === \"undefined\") {\r\n return false;\r\n }\r\n let url: string | null = null;\r\n try {\r\n const binary = atob(base64Data);\r\n const bytes = new Uint8Array(binary.length);\r\n for (let i = 0; i < binary.length; i += 1) {\r\n bytes[i] = binary.charCodeAt(i);\r\n }\r\n const blob = new Blob([bytes], { type: getMimeType(extension) });\r\n url = URL.createObjectURL(blob);\r\n window.open(url, \"_blank\", \"noopener,noreferrer\");\r\n const revokeTarget = url;\r\n window.setTimeout(() => URL.revokeObjectURL(revokeTarget), PREVIEW_REVOKE_DELAY_MS);\r\n return true;\r\n } catch (error) {\r\n // Revoke straight away here: nothing consumed the URL on this path.\r\n if (url) URL.revokeObjectURL(url);\r\n // eslint-disable-next-line no-console\r\n console.error(\"[@portnet/ui] openBase64FileInTab failed:\", error);\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Hand an already-materialised Blob to the browser as a download.\r\n *\r\n * Companion to downloadBase64File for endpoints that return a binary body\r\n * directly (axios `responseType: \"blob\"`) instead of base64 — Excel exports,\r\n * server-generated PDFs. Host modules built the anchor by hand for this too,\r\n * and every copy found so far never revoked the object URL.\r\n *\r\n * Revoked synchronously in `finally`, unlike openBase64FileInTab: a download is\r\n * snapshotted by the browser at click() time, so nothing needs the URL\r\n * afterwards — only a preview in another tab does.\r\n *\r\n * @param blob - The payload. Its own `type` is used as-is; no MIME lookup,\r\n * the server already declared it.\r\n * @param fileName - Filename offered to the user, extension included.\r\n * @returns true on success; false if there is no DOM.\r\n */\r\nexport function downloadBlobFile(blob: Blob, fileName: string): boolean {\r\n if (typeof document === \"undefined\" || typeof URL === \"undefined\") {\r\n return false;\r\n }\r\n let url: string | null = null;\r\n let link: HTMLAnchorElement | null = null;\r\n try {\r\n url = URL.createObjectURL(blob);\r\n link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = fileName;\r\n document.body.appendChild(link);\r\n link.click();\r\n return true;\r\n } catch (error) {\r\n // eslint-disable-next-line no-console\r\n console.error(\"[@portnet/ui] downloadBlobFile failed:\", error);\r\n return false;\r\n } finally {\r\n if (link && link.parentNode) link.parentNode.removeChild(link);\r\n if (url) URL.revokeObjectURL(url);\r\n }\r\n}\r\n\r\nconst fileDownloadApi = {\r\n getMimeType,\r\n downloadBase64File,\r\n openBase64FileInTab,\r\n downloadBlobFile,\r\n};\r\nexport default fileDownloadApi;\r\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMA,UAA4C,GAAGC,MAAM,CAACC,MAAM,CAAC;EACjE;EACAC,GAAG,EAAE,iBAAiB;EACtBC,GAAG,EAAE,oBAAoB;EACzBC,IAAI,EAAE,yEAAyE;EAC/EC,GAAG,EAAE,0BAA0B;EAC/BC,IAAI,EAAE,mEAAmE;EACzEC,GAAG,EAAE,UAAU;EACfC,GAAG,EAAE,YAAY;EAEjB;EACAC,GAAG,EAAE,WAAW;EAChBC,GAAG,EAAE,YAAY;EACjBC,IAAI,EAAE,YAAY;EAClBC,GAAG,EAAE,WAAW;EAChBC,GAAG,EAAE,WAAW;EAChBC,IAAI,EAAE,YAAY;EAClBC,GAAG,EAAE,eAAe;EACpBC,GAAG,EAAE,cAAc;EACnBC,IAAI,EAAE,YAAY;EAClBC,GAAG,EAAE;AACP,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,WAAWA,CAACC,SAAiB,EAAU;EACrD,OAAOrB,UAAU,CAACqB,SAAS,CAACC,WAAW,CAAC,CAAC,CAAC,IAAI,0BAA0B;AAC1E;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,kBAAkBA,CAChCC,UAAkB,EAClBC,QAAgB,EAChBJ,SAAiB,EACR;EACT;EACA;EACA,IAAI,OAAOK,QAAQ,KAAK,WAAW,IAAI,OAAOC,IAAI,KAAK,WAAW,EAAE;IAClE,OAAO,KAAK;EACd;EAEA,IAAIC,GAAkB,GAAG,IAAI;EAC7B,IAAIC,IAA8B,GAAG,IAAI;EAEzC,IAAI;IACF,MAAMC,MAAM,GAAGH,IAAI,CAACH,UAAU,CAAC;IAC/B,MAAMO,KAAK,GAAG,IAAIC,UAAU,CAACF,MAAM,CAACG,MAAM,CAAC;IAC3C,KAAK,IAAIC,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGJ,MAAM,CAACG,MAAM,EAAEC,CAAC,IAAI,CAAC,EAAE;MACzCH,KAAK,CAACG,CAAC,CAAC,GAAGJ,MAAM,CAACK,UAAU,CAACD,CAAC,CAAC;IACjC;IAEA,MAAME,IAAI,GAAG,IAAIC,IAAI,CAAC,CAACN,KAAK,CAAC,EAAE;MAAEO,IAAI,EAAElB,WAAW,CAACC,SAAS;IAAE,CAAC,CAAC;IAEhEO,GAAG,GAAGW,GAAG,CAACC,eAAe,CAACJ,IAAI,CAAC;IAC/BP,IAAI,GAAGH,QAAQ,CAACe,aAAa,CAAC,GAAG,CAAC;IAClCZ,IAAI,CAACa,IAAI,GAAGd,GAAG;IACfC,IAAI,CAACc,QAAQ,GAAGlB,QAAQ;IACxBC,QAAQ,CAACkB,IAAI,CAACC,WAAW,CAAChB,IAAI,CAAC;IAC/BA,IAAI,CAACiB,KAAK,CAAC,CAAC;IACZ,OAAO,IAAI;EACb,CAAC,CAAC,OAAOC,KAAK,EAAE;IACd;IACAC,OAAO,CAACD,KAAK,CAAC,0CAA0C,EAAEA,KAAK,CAAC;IAChE,OAAO,KAAK;EACd,CAAC,SAAS;IACR;IACA;IACA,IAAIlB,IAAI,IAAIA,IAAI,CAACoB,UAAU,EAAEpB,IAAI,CAACoB,UAAU,CAACC,WAAW,CAACrB,IAAI,CAAC;IAC9D,IAAID,GAAG,EAAEW,GAAG,CAACY,eAAe,CAACvB,GAAG,CAAC;EACnC;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMwB,uBAAuB,GAAG,MAAM;;AAEtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,mBAAmBA,CAAC7B,UAAkB,EAAEH,SAAiB,EAAW;EAClF,IAAI,OAAOiC,MAAM,KAAK,WAAW,IAAI,OAAO3B,IAAI,KAAK,WAAW,EAAE;IAChE,OAAO,KAAK;EACd;EACA,IAAIC,GAAkB,GAAG,IAAI;EAC7B,IAAI;IACF,MAAME,MAAM,GAAGH,IAAI,CAACH,UAAU,CAAC;IAC/B,MAAMO,KAAK,GAAG,IAAIC,UAAU,CAACF,MAAM,CAACG,MAAM,CAAC;IAC3C,KAAK,IAAIC,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGJ,MAAM,CAACG,MAAM,EAAEC,CAAC,IAAI,CAAC,EAAE;MACzCH,KAAK,CAACG,CAAC,CAAC,GAAGJ,MAAM,CAACK,UAAU,CAACD,CAAC,CAAC;IACjC;IACA,MAAME,IAAI,GAAG,IAAIC,IAAI,CAAC,CAACN,KAAK,CAAC,EAAE;MAAEO,IAAI,EAAElB,WAAW,CAACC,SAAS;IAAE,CAAC,CAAC;IAChEO,GAAG,GAAGW,GAAG,CAACC,eAAe,CAACJ,IAAI,CAAC;IAC/BkB,MAAM,CAACC,IAAI,CAAC3B,GAAG,EAAE,QAAQ,EAAE,qBAAqB,CAAC;IACjD,MAAM4B,YAAY,GAAG5B,GAAG;IACxB0B,MAAM,CAACG,UAAU,CAAC,MAAMlB,GAAG,CAACY,eAAe,CAACK,YAAY,CAAC,EAAEJ,uBAAuB,CAAC;IACnF,OAAO,IAAI;EACb,CAAC,CAAC,OAAOL,KAAK,EAAE;IACd;IACA,IAAInB,GAAG,EAAEW,GAAG,CAACY,eAAe,CAACvB,GAAG,CAAC;IACjC;IACAoB,OAAO,CAACD,KAAK,CAAC,2CAA2C,EAAEA,KAAK,CAAC;IACjE,OAAO,KAAK;EACd;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASW,gBAAgBA,CAACtB,IAAU,EAAEX,QAAgB,EAAW;EACtE,IAAI,OAAOC,QAAQ,KAAK,WAAW,IAAI,OAAOa,GAAG,KAAK,WAAW,EAAE;IACjE,OAAO,KAAK;EACd;EACA,IAAIX,GAAkB,GAAG,IAAI;EAC7B,IAAIC,IAA8B,GAAG,IAAI;EACzC,IAAI;IACFD,GAAG,GAAGW,GAAG,CAACC,eAAe,CAACJ,IAAI,CAAC;IAC/BP,IAAI,GAAGH,QAAQ,CAACe,aAAa,CAAC,GAAG,CAAC;IAClCZ,IAAI,CAACa,IAAI,GAAGd,GAAG;IACfC,IAAI,CAACc,QAAQ,GAAGlB,QAAQ;IACxBC,QAAQ,CAACkB,IAAI,CAACC,WAAW,CAAChB,IAAI,CAAC;IAC/BA,IAAI,CAACiB,KAAK,CAAC,CAAC;IACZ,OAAO,IAAI;EACb,CAAC,CAAC,OAAOC,KAAK,EAAE;IACd;IACAC,OAAO,CAACD,KAAK,CAAC,wCAAwC,EAAEA,KAAK,CAAC;IAC9D,OAAO,KAAK;EACd,CAAC,SAAS;IACR,IAAIlB,IAAI,IAAIA,IAAI,CAACoB,UAAU,EAAEpB,IAAI,CAACoB,UAAU,CAACC,WAAW,CAACrB,IAAI,CAAC;IAC9D,IAAID,GAAG,EAAEW,GAAG,CAACY,eAAe,CAACvB,GAAG,CAAC;EACnC;AACF;AAEA,MAAM+B,eAAe,GAAG;EACtBvC,WAAW;EACXG,kBAAkB;EAClB8B,mBAAmB;EACnBK;AACF,CAAC;AACD,eAAeC,eAAe","ignoreList":[]}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fileValidation — is this file allowed to be uploaded?
|
|
3
|
+
*
|
|
4
|
+
* const POLICY = { extensions: ["pdf", "png", "jpg", "jpeg", "doc", "docx"] };
|
|
5
|
+
*
|
|
6
|
+
* const rejection = getFileRejection(file, POLICY);
|
|
7
|
+
* if (rejection) toast(rejection.message, "error");
|
|
8
|
+
*
|
|
9
|
+
* <Dropzone accept={toAcceptAttribute(POLICY.extensions)} … />
|
|
10
|
+
*
|
|
11
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
* WHY THIS BELONGS IN THE LIBRARY (capability gap G-52)
|
|
13
|
+
*
|
|
14
|
+
* `Dropzone` takes an `accept` attribute and reports `DropzoneRejection`, but it
|
|
15
|
+
* validates nothing: the browser's `accept` is a FILE-PICKER FILTER, not a
|
|
16
|
+
* control. It is trivially bypassed — drag and drop ignores it, and so does
|
|
17
|
+
* "All files" in the picker on most platforms. So the library asked every host
|
|
18
|
+
* to decide, itself, whether a dropped file is acceptable, and every host wrote:
|
|
19
|
+
*
|
|
20
|
+
* - an extension allow-list (business policy — rightly theirs), AND
|
|
21
|
+
* - a deny-list of executables and scripts, AND
|
|
22
|
+
* - an extension↔MIME cross-check with its browser tolerances.
|
|
23
|
+
*
|
|
24
|
+
* The last two are NOT business policy. They are one security decision, and a
|
|
25
|
+
* security decision copied into every module is a security decision that WILL
|
|
26
|
+
* drift: the day a file type has to be added to the deny-list, nobody can say
|
|
27
|
+
* which of the fifteen GU modules received it. Worse, divergence here is
|
|
28
|
+
* SILENT — a module missing an entry looks exactly like a module that has it,
|
|
29
|
+
* until something gets uploaded.
|
|
30
|
+
*
|
|
31
|
+
* So the split this file draws: the HOST owns which extensions its business
|
|
32
|
+
* accepts; the LIBRARY owns the executable deny-list and the MIME cross-check.
|
|
33
|
+
*
|
|
34
|
+
* Raised from `autorisation-admis-pour-conforme-fe` (Area 5), which carried a
|
|
35
|
+
* 21-entry executable deny-list and a six-extension MIME table locally.
|
|
36
|
+
*
|
|
37
|
+
* NOT A SUBSTITUTE FOR SERVER-SIDE VALIDATION, and this must stay written down:
|
|
38
|
+
* everything here runs in a browser the user controls. Its purpose is to give a
|
|
39
|
+
* CLEAR, IMMEDIATE reason (R-4.1, R-4.4) instead of a server rejection after a
|
|
40
|
+
* full upload. The back-end remains the authority.
|
|
41
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Executables, scripts and installers — refused whatever the host's policy says.
|
|
46
|
+
*
|
|
47
|
+
* Checked FIRST, and the order carries meaning: a `.exe` gets a message naming
|
|
48
|
+
* it as a dangerous file, not a vague "unsupported type", because those are not
|
|
49
|
+
* the same information for the person at the screen.
|
|
50
|
+
*
|
|
51
|
+
* Windows-centric because GU workstations are: PE binaries, the cmd/PowerShell
|
|
52
|
+
* script families, installer packages, and the HTML-application and
|
|
53
|
+
* control-panel shims that are executable despite innocuous extensions.
|
|
54
|
+
*/
|
|
55
|
+
const EXECUTABLE_EXTENSIONS = new Set(["exe", "dll", "bat", "cmd", "com", "pif", "scr", "msi", "msu", "msp", "vbs", "vbe", "jse", "wsf", "wsh", "ps1", "psm1", "psc1", "cpl", "msc", "hta"]);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Accepted MIME types per extension.
|
|
59
|
+
*
|
|
60
|
+
* DELIBERATELY NOT `getMimeType` from ./fileDownload, though both map
|
|
61
|
+
* extensions to MIME types. That one answers "what type should I stamp on this
|
|
62
|
+
* Blob" — ONE canonical value. This one answers "what types may a browser
|
|
63
|
+
* plausibly report for this extension" — a SET, including variants no canonical
|
|
64
|
+
* table would contain (`image/jpg` is not a registered type, and browsers send
|
|
65
|
+
* it anyway). Collapsing the two would reject valid files.
|
|
66
|
+
*/
|
|
67
|
+
const MIME_BY_EXTENSION = Object.freeze({
|
|
68
|
+
pdf: ["application/pdf"],
|
|
69
|
+
png: ["image/png"],
|
|
70
|
+
jpg: ["image/jpeg", "image/jpg"],
|
|
71
|
+
jpeg: ["image/jpeg", "image/jpg"],
|
|
72
|
+
gif: ["image/gif"],
|
|
73
|
+
webp: ["image/webp"],
|
|
74
|
+
tif: ["image/tiff"],
|
|
75
|
+
tiff: ["image/tiff"],
|
|
76
|
+
txt: ["text/plain"],
|
|
77
|
+
csv: ["text/csv", "application/csv", "text/plain"],
|
|
78
|
+
doc: ["application/msword"],
|
|
79
|
+
docx: ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
|
|
80
|
+
xls: ["application/vnd.ms-excel"],
|
|
81
|
+
xlsx: ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],
|
|
82
|
+
ppt: ["application/vnd.ms-powerpoint"],
|
|
83
|
+
pptx: ["application/vnd.openxmlformats-officedocument.presentationml.presentation"]
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Extensions whose reported MIME type cannot be trusted, so
|
|
88
|
+
* `application/octet-stream` is tolerated for them.
|
|
89
|
+
*
|
|
90
|
+
* WITHOUT THIS TOLERANCE A LEGITIMATE `.docx` IS REFUSED on some machines —
|
|
91
|
+
* Office documents are frequently reported as `application/octet-stream`
|
|
92
|
+
* depending on browser and OS, and the refusal would be INTERMITTENT, which is
|
|
93
|
+
* the hardest kind to diagnose from a support ticket. The extension has already
|
|
94
|
+
* been checked against the host's allow-list at that point, so the tolerance
|
|
95
|
+
* does not widen what is accepted, only what is believed.
|
|
96
|
+
*/
|
|
97
|
+
const UNRELIABLE_MIME_EXTENSIONS = new Set(["doc", "docx", "xls", "xlsx", "ppt", "pptx", "csv"]);
|
|
98
|
+
|
|
99
|
+
/** Why a file was refused. `reason` is for branching and logs, not for display. */
|
|
100
|
+
|
|
101
|
+
const DEFAULT_EXECUTABLE_MESSAGE = "Les fichiers exécutables, scripts et installeurs ne sont pas autorisés.";
|
|
102
|
+
|
|
103
|
+
/** Lowercased extension of a file name, or `""` when there is none. */
|
|
104
|
+
const extensionOf = fileName => {
|
|
105
|
+
if (!fileName) return "";
|
|
106
|
+
const parts = fileName.split(".");
|
|
107
|
+
/* A name with no dot has no extension: "rapport" must not be read as having
|
|
108
|
+
extension "rapport", which a bare `pop()` would do. */
|
|
109
|
+
if (parts.length < 2) return "";
|
|
110
|
+
return (parts[parts.length - 1] ?? "").toLowerCase();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Human list of the accepted types — "PDF, PNG, JPG, JPEG, DOC, DOCX."
|
|
115
|
+
*
|
|
116
|
+
* Order is the host's: the list is read by a person, and a host lists its most
|
|
117
|
+
* common format first for a reason. Sorting it here would silently reorder
|
|
118
|
+
* copy the host wrote deliberately.
|
|
119
|
+
*/
|
|
120
|
+
export function describeAcceptedTypes(extensions) {
|
|
121
|
+
return `${extensions.map(ext => ext.toUpperCase()).join(", ")}.`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The `accept` attribute for an input or `Dropzone` — ".pdf,.png,.jpg".
|
|
126
|
+
*
|
|
127
|
+
* A picker filter, never a control: see the note on server-side validation in
|
|
128
|
+
* the file header.
|
|
129
|
+
*/
|
|
130
|
+
export function toAcceptAttribute(extensions) {
|
|
131
|
+
return extensions.map(ext => `.${ext.toLowerCase()}`).join(",");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Is this an executable, a script or an installer?
|
|
136
|
+
*
|
|
137
|
+
* Exported on its own because a host may want to refuse those EARLIER than its
|
|
138
|
+
* own policy check — on a bulk drop, for instance, where one dangerous file
|
|
139
|
+
* should stop the whole batch rather than be listed among type mismatches.
|
|
140
|
+
*
|
|
141
|
+
* Permissive parameter type on purpose: callers routinely pass an item pulled
|
|
142
|
+
* out of a `FileList` without having checked it.
|
|
143
|
+
*/
|
|
144
|
+
export function isForbiddenExecutable(file) {
|
|
145
|
+
const ext = extensionOf(file?.name);
|
|
146
|
+
return ext !== "" && EXECUTABLE_EXTENSIONS.has(ext);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Does the file satisfy `policy` (and the library's own deny-list)? */
|
|
150
|
+
export function isAcceptedFile(file, policy) {
|
|
151
|
+
return getFileRejection(file, policy) === null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Why this file cannot be uploaded, or `null` when it can.
|
|
156
|
+
*
|
|
157
|
+
* Order of checks is load-bearing:
|
|
158
|
+
* 1. executable / script / installer — named as such;
|
|
159
|
+
* 2. extension against the host's allow-list;
|
|
160
|
+
* 3. reported MIME type against the extension.
|
|
161
|
+
*
|
|
162
|
+
* MIME RULES, both deliberate: an EMPTY type is accepted (some browsers report
|
|
163
|
+
* none, and the extension has just been checked), and `application/octet-stream`
|
|
164
|
+
* is accepted for the extensions listed in `UNRELIABLE_MIME_EXTENSIONS`. An
|
|
165
|
+
* extension with no entry in the MIME table is accepted on its extension alone
|
|
166
|
+
* — the table is a cross-check, not a second allow-list, and a host adding a
|
|
167
|
+
* format to its policy must not have it refused by a table it cannot see.
|
|
168
|
+
*/
|
|
169
|
+
export function getFileRejection(file, policy) {
|
|
170
|
+
const executableMessage = policy.labels?.executable ?? DEFAULT_EXECUTABLE_MESSAGE;
|
|
171
|
+
const typeMessage = policy.labels?.type ?? `Types autorisés : ${describeAcceptedTypes(policy.extensions)}`;
|
|
172
|
+
if (isForbiddenExecutable(file)) {
|
|
173
|
+
return {
|
|
174
|
+
reason: "executable",
|
|
175
|
+
message: executableMessage
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const ext = extensionOf(file?.name);
|
|
179
|
+
if (ext === "") return {
|
|
180
|
+
reason: "extension",
|
|
181
|
+
message: typeMessage
|
|
182
|
+
};
|
|
183
|
+
const accepted = policy.extensions.some(allowed => allowed.toLowerCase() === ext);
|
|
184
|
+
if (!accepted) return {
|
|
185
|
+
reason: "extension",
|
|
186
|
+
message: typeMessage
|
|
187
|
+
};
|
|
188
|
+
const mime = (file?.type ?? "").trim().toLowerCase();
|
|
189
|
+
if (mime === "") return null;
|
|
190
|
+
if (mime === "application/octet-stream" && UNRELIABLE_MIME_EXTENSIONS.has(ext)) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const expected = MIME_BY_EXTENSION[ext];
|
|
194
|
+
if (expected === undefined) return null;
|
|
195
|
+
if (expected.includes(mime)) return null;
|
|
196
|
+
return {
|
|
197
|
+
reason: "mime",
|
|
198
|
+
message: typeMessage
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const fileValidationApi = {
|
|
202
|
+
isForbiddenExecutable,
|
|
203
|
+
isAcceptedFile,
|
|
204
|
+
getFileRejection,
|
|
205
|
+
describeAcceptedTypes,
|
|
206
|
+
toAcceptAttribute
|
|
207
|
+
};
|
|
208
|
+
export default fileValidationApi;
|
|
209
|
+
//# sourceMappingURL=fileValidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileValidation.js","names":["EXECUTABLE_EXTENSIONS","Set","MIME_BY_EXTENSION","Object","freeze","pdf","png","jpg","jpeg","gif","webp","tif","tiff","txt","csv","doc","docx","xls","xlsx","ppt","pptx","UNRELIABLE_MIME_EXTENSIONS","DEFAULT_EXECUTABLE_MESSAGE","extensionOf","fileName","parts","split","length","toLowerCase","describeAcceptedTypes","extensions","map","ext","toUpperCase","join","toAcceptAttribute","isForbiddenExecutable","file","name","has","isAcceptedFile","policy","getFileRejection","executableMessage","labels","executable","typeMessage","type","reason","message","accepted","some","allowed","mime","trim","expected","undefined","includes","fileValidationApi"],"sources":["../../../../src/lib/v2/utils/fileValidation.ts"],"sourcesContent":["/**\r\n * fileValidation — is this file allowed to be uploaded?\r\n *\r\n * const POLICY = { extensions: [\"pdf\", \"png\", \"jpg\", \"jpeg\", \"doc\", \"docx\"] };\r\n *\r\n * const rejection = getFileRejection(file, POLICY);\r\n * if (rejection) toast(rejection.message, \"error\");\r\n *\r\n * <Dropzone accept={toAcceptAttribute(POLICY.extensions)} … />\r\n *\r\n * ─────────────────────────────────────────────────────────────────────────────\r\n * WHY THIS BELONGS IN THE LIBRARY (capability gap G-52)\r\n *\r\n * `Dropzone` takes an `accept` attribute and reports `DropzoneRejection`, but it\r\n * validates nothing: the browser's `accept` is a FILE-PICKER FILTER, not a\r\n * control. It is trivially bypassed — drag and drop ignores it, and so does\r\n * \"All files\" in the picker on most platforms. So the library asked every host\r\n * to decide, itself, whether a dropped file is acceptable, and every host wrote:\r\n *\r\n * - an extension allow-list (business policy — rightly theirs), AND\r\n * - a deny-list of executables and scripts, AND\r\n * - an extension↔MIME cross-check with its browser tolerances.\r\n *\r\n * The last two are NOT business policy. They are one security decision, and a\r\n * security decision copied into every module is a security decision that WILL\r\n * drift: the day a file type has to be added to the deny-list, nobody can say\r\n * which of the fifteen GU modules received it. Worse, divergence here is\r\n * SILENT — a module missing an entry looks exactly like a module that has it,\r\n * until something gets uploaded.\r\n *\r\n * So the split this file draws: the HOST owns which extensions its business\r\n * accepts; the LIBRARY owns the executable deny-list and the MIME cross-check.\r\n *\r\n * Raised from `autorisation-admis-pour-conforme-fe` (Area 5), which carried a\r\n * 21-entry executable deny-list and a six-extension MIME table locally.\r\n *\r\n * NOT A SUBSTITUTE FOR SERVER-SIDE VALIDATION, and this must stay written down:\r\n * everything here runs in a browser the user controls. Its purpose is to give a\r\n * CLEAR, IMMEDIATE reason (R-4.1, R-4.4) instead of a server rejection after a\r\n * full upload. The back-end remains the authority.\r\n * ─────────────────────────────────────────────────────────────────────────────\r\n */\r\n\r\n/**\r\n * Executables, scripts and installers — refused whatever the host's policy says.\r\n *\r\n * Checked FIRST, and the order carries meaning: a `.exe` gets a message naming\r\n * it as a dangerous file, not a vague \"unsupported type\", because those are not\r\n * the same information for the person at the screen.\r\n *\r\n * Windows-centric because GU workstations are: PE binaries, the cmd/PowerShell\r\n * script families, installer packages, and the HTML-application and\r\n * control-panel shims that are executable despite innocuous extensions.\r\n */\r\nconst EXECUTABLE_EXTENSIONS: ReadonlySet<string> = new Set([\r\n \"exe\", \"dll\", \"bat\", \"cmd\", \"com\", \"pif\", \"scr\",\r\n \"msi\", \"msu\", \"msp\",\r\n \"vbs\", \"vbe\", \"jse\", \"wsf\", \"wsh\",\r\n \"ps1\", \"psm1\", \"psc1\",\r\n \"cpl\", \"msc\", \"hta\",\r\n]);\r\n\r\n/**\r\n * Accepted MIME types per extension.\r\n *\r\n * DELIBERATELY NOT `getMimeType` from ./fileDownload, though both map\r\n * extensions to MIME types. That one answers \"what type should I stamp on this\r\n * Blob\" — ONE canonical value. This one answers \"what types may a browser\r\n * plausibly report for this extension\" — a SET, including variants no canonical\r\n * table would contain (`image/jpg` is not a registered type, and browsers send\r\n * it anyway). Collapsing the two would reject valid files.\r\n */\r\nconst MIME_BY_EXTENSION: Readonly<Record<string, readonly string[]>> =\r\n Object.freeze({\r\n pdf: [\"application/pdf\"],\r\n png: [\"image/png\"],\r\n jpg: [\"image/jpeg\", \"image/jpg\"],\r\n jpeg: [\"image/jpeg\", \"image/jpg\"],\r\n gif: [\"image/gif\"],\r\n webp: [\"image/webp\"],\r\n tif: [\"image/tiff\"],\r\n tiff: [\"image/tiff\"],\r\n txt: [\"text/plain\"],\r\n csv: [\"text/csv\", \"application/csv\", \"text/plain\"],\r\n doc: [\"application/msword\"],\r\n docx: [\r\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\r\n ],\r\n xls: [\"application/vnd.ms-excel\"],\r\n xlsx: [\r\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\r\n ],\r\n ppt: [\"application/vnd.ms-powerpoint\"],\r\n pptx: [\r\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\r\n ],\r\n });\r\n\r\n/**\r\n * Extensions whose reported MIME type cannot be trusted, so\r\n * `application/octet-stream` is tolerated for them.\r\n *\r\n * WITHOUT THIS TOLERANCE A LEGITIMATE `.docx` IS REFUSED on some machines —\r\n * Office documents are frequently reported as `application/octet-stream`\r\n * depending on browser and OS, and the refusal would be INTERMITTENT, which is\r\n * the hardest kind to diagnose from a support ticket. The extension has already\r\n * been checked against the host's allow-list at that point, so the tolerance\r\n * does not widen what is accepted, only what is believed.\r\n */\r\nconst UNRELIABLE_MIME_EXTENSIONS: ReadonlySet<string> = new Set([\r\n \"doc\", \"docx\", \"xls\", \"xlsx\", \"ppt\", \"pptx\", \"csv\",\r\n]);\r\n\r\n/** Why a file was refused. `reason` is for branching and logs, not for display. */\r\nexport type FileRejectionReason = \"executable\" | \"extension\" | \"mime\";\r\n\r\nexport interface FileRejection {\r\n reason: FileRejectionReason;\r\n /** Ready to render (French, Charter §09.3 — what, and what is accepted). */\r\n message: string;\r\n}\r\n\r\nexport interface FileAcceptPolicy {\r\n /**\r\n * Extensions the HOST's business accepts, without a leading dot,\r\n * case-insensitive. This is the only part of the decision that belongs to the\r\n * host — an empty list accepts nothing, which is a programming error rather\r\n * than a policy, so it is treated as \"refuse everything\" and not as \"allow\".\r\n */\r\n extensions: readonly string[];\r\n /** Overrides for the default messages. Keep them in French, verb + object. */\r\n labels?:\r\n | {\r\n executable?: string | undefined;\r\n type?: string | undefined;\r\n }\r\n | undefined;\r\n}\r\n\r\nconst DEFAULT_EXECUTABLE_MESSAGE =\r\n \"Les fichiers exécutables, scripts et installeurs ne sont pas autorisés.\";\r\n\r\n/** Lowercased extension of a file name, or `\"\"` when there is none. */\r\nconst extensionOf = (fileName: string | undefined): string => {\r\n if (!fileName) return \"\";\r\n const parts = fileName.split(\".\");\r\n /* A name with no dot has no extension: \"rapport\" must not be read as having\r\n extension \"rapport\", which a bare `pop()` would do. */\r\n if (parts.length < 2) return \"\";\r\n return (parts[parts.length - 1] ?? \"\").toLowerCase();\r\n};\r\n\r\n/**\r\n * Human list of the accepted types — \"PDF, PNG, JPG, JPEG, DOC, DOCX.\"\r\n *\r\n * Order is the host's: the list is read by a person, and a host lists its most\r\n * common format first for a reason. Sorting it here would silently reorder\r\n * copy the host wrote deliberately.\r\n */\r\nexport function describeAcceptedTypes(\r\n extensions: readonly string[]\r\n): string {\r\n return `${extensions.map((ext) => ext.toUpperCase()).join(\", \")}.`;\r\n}\r\n\r\n/**\r\n * The `accept` attribute for an input or `Dropzone` — \".pdf,.png,.jpg\".\r\n *\r\n * A picker filter, never a control: see the note on server-side validation in\r\n * the file header.\r\n */\r\nexport function toAcceptAttribute(extensions: readonly string[]): string {\r\n return extensions.map((ext) => `.${ext.toLowerCase()}`).join(\",\");\r\n}\r\n\r\n/**\r\n * Is this an executable, a script or an installer?\r\n *\r\n * Exported on its own because a host may want to refuse those EARLIER than its\r\n * own policy check — on a bulk drop, for instance, where one dangerous file\r\n * should stop the whole batch rather than be listed among type mismatches.\r\n *\r\n * Permissive parameter type on purpose: callers routinely pass an item pulled\r\n * out of a `FileList` without having checked it.\r\n */\r\nexport function isForbiddenExecutable(file?: File | null | undefined): boolean {\r\n const ext = extensionOf(file?.name);\r\n return ext !== \"\" && EXECUTABLE_EXTENSIONS.has(ext);\r\n}\r\n\r\n/** Does the file satisfy `policy` (and the library's own deny-list)? */\r\nexport function isAcceptedFile(\r\n file: File | null | undefined,\r\n policy: FileAcceptPolicy\r\n): boolean {\r\n return getFileRejection(file, policy) === null;\r\n}\r\n\r\n/**\r\n * Why this file cannot be uploaded, or `null` when it can.\r\n *\r\n * Order of checks is load-bearing:\r\n * 1. executable / script / installer — named as such;\r\n * 2. extension against the host's allow-list;\r\n * 3. reported MIME type against the extension.\r\n *\r\n * MIME RULES, both deliberate: an EMPTY type is accepted (some browsers report\r\n * none, and the extension has just been checked), and `application/octet-stream`\r\n * is accepted for the extensions listed in `UNRELIABLE_MIME_EXTENSIONS`. An\r\n * extension with no entry in the MIME table is accepted on its extension alone\r\n * — the table is a cross-check, not a second allow-list, and a host adding a\r\n * format to its policy must not have it refused by a table it cannot see.\r\n */\r\nexport function getFileRejection(\r\n file: File | null | undefined,\r\n policy: FileAcceptPolicy\r\n): FileRejection | null {\r\n const executableMessage =\r\n policy.labels?.executable ?? DEFAULT_EXECUTABLE_MESSAGE;\r\n const typeMessage =\r\n policy.labels?.type ??\r\n `Types autorisés : ${describeAcceptedTypes(policy.extensions)}`;\r\n\r\n if (isForbiddenExecutable(file)) {\r\n return { reason: \"executable\", message: executableMessage };\r\n }\r\n\r\n const ext = extensionOf(file?.name);\r\n if (ext === \"\") return { reason: \"extension\", message: typeMessage };\r\n\r\n const accepted = policy.extensions.some(\r\n (allowed) => allowed.toLowerCase() === ext\r\n );\r\n if (!accepted) return { reason: \"extension\", message: typeMessage };\r\n\r\n const mime = (file?.type ?? \"\").trim().toLowerCase();\r\n if (mime === \"\") return null;\r\n if (mime === \"application/octet-stream\" && UNRELIABLE_MIME_EXTENSIONS.has(ext)) {\r\n return null;\r\n }\r\n\r\n const expected = MIME_BY_EXTENSION[ext];\r\n if (expected === undefined) return null;\r\n if (expected.includes(mime)) return null;\r\n\r\n return { reason: \"mime\", message: typeMessage };\r\n}\r\n\r\nconst fileValidationApi = {\r\n isForbiddenExecutable,\r\n isAcceptedFile,\r\n getFileRejection,\r\n describeAcceptedTypes,\r\n toAcceptAttribute,\r\n};\r\nexport default fileValidationApi;\r\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fmtDate / fmtDateTime — render a date for DISPLAY.
|
|
3
|
+
*
|
|
4
|
+
* fmtDate("2026-03-05T14:30:00Z") → "05/03/2026"
|
|
5
|
+
* fmtDateTime("2026-03-05T14:30:00Z") → "05/03/2026 15:30" (local time)
|
|
6
|
+
* fmtDate(undefined) → "—"
|
|
7
|
+
* fmtDate("pas une date") → "—"
|
|
8
|
+
* fmtDate(d, { absent: "" }) → "" when there is no date
|
|
9
|
+
*
|
|
10
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
* WHY THIS BELONGS IN THE LIBRARY (capability gap G-51)
|
|
12
|
+
*
|
|
13
|
+
* The library already owns the money side of the same job — `fmtMoney`, same
|
|
14
|
+
* folder, same "—" for an absent value, for the same reason: a table cell must
|
|
15
|
+
* never read "NaN". It owned nothing for dates, which every GU screen renders
|
|
16
|
+
* far more often than amounts. So every module wrote its own, and the copies
|
|
17
|
+
* shared one defect:
|
|
18
|
+
*
|
|
19
|
+
* const fullformatDate = (stringDate) => {
|
|
20
|
+
* const date = new Date(stringDate);
|
|
21
|
+
* const day = formatIntoTwoDigits(date.getDate());
|
|
22
|
+
* …
|
|
23
|
+
*
|
|
24
|
+
* `new Date(undefined)` is an INVALID DATE whose every accessor returns `NaN`,
|
|
25
|
+
* so the helper rendered, literally:
|
|
26
|
+
*
|
|
27
|
+
* NaN/NaN/NaN NaN:NaN
|
|
28
|
+
*
|
|
29
|
+
* on any optional field — an update date, a visit date, a closure date — which
|
|
30
|
+
* is most of them. In `autorisation-admis-pour-conforme-fe` the guard had been
|
|
31
|
+
* added at CALL SITES one lot after another (lots 4 to 7) before being pulled
|
|
32
|
+
* into the module's own helper: treating the symptom eight times, because the
|
|
33
|
+
* one place where it cannot be forgotten is the formatter itself. That is the
|
|
34
|
+
* argument for owning it here rather than in each module.
|
|
35
|
+
*
|
|
36
|
+
* Two smaller things the same copies carried, fixed here by construction: a
|
|
37
|
+
* two-digit helper that returned `"08"` (string) or `12` (number) depending on
|
|
38
|
+
* the value, and an absent date rendered as an EMPTY string — indistinguishable
|
|
39
|
+
* from a cell that failed to render, where "—" says "we looked, there is
|
|
40
|
+
* nothing".
|
|
41
|
+
*
|
|
42
|
+
* WHY NO `moment`, THOUGH THE PACKAGE ALREADY DEPENDS ON IT
|
|
43
|
+
*
|
|
44
|
+
* `moment` is here for the pickers, and specifically for STRICT parsing against
|
|
45
|
+
* an explicit format (`patterns/_pickerSerialisation`) — reading
|
|
46
|
+
* `"31-12-2026"` back without guessing. That is the right use of it.
|
|
47
|
+
*
|
|
48
|
+
* Rendering an already-parsed instant as two numbers and a year is not. This
|
|
49
|
+
* folder is dependency-free on purpose (see `fileDownload`), and
|
|
50
|
+
* `moment(someString)` with no format falls back to `new Date` WHILE EMITTING A
|
|
51
|
+
* CONSOLE DEPRECATION WARNING — noise a display helper called once per table
|
|
52
|
+
* cell must not produce. Native padding has neither cost.
|
|
53
|
+
*
|
|
54
|
+
* TIME ZONE, STATED RATHER THAN IMPLIED. The output is in the VIEWER'S local
|
|
55
|
+
* zone (`getDate`, `getHours`…), which is what the module helpers this replaces
|
|
56
|
+
* did, and what a user expects of "date de dépôt". An instant sent as
|
|
57
|
+
* `…T23:30:00Z` therefore reads as the next day in Casablanca. If a screen ever
|
|
58
|
+
* needs the server's calendar day regardless of the reader, that is a DIFFERENT
|
|
59
|
+
* function and must be argued for on its own — silently switching this one to
|
|
60
|
+
* UTC would move dates by a day across the whole platform.
|
|
61
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/** What a formatter accepts: an API string, an epoch, or a `Date`. */
|
|
65
|
+
|
|
66
|
+
/** The platform's absence mark — same value `fmtMoney` returns. */
|
|
67
|
+
const ABSENT = "—";
|
|
68
|
+
const pad2 = value => String(value).padStart(2, "0");
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse an input into a USABLE `Date`, or `null`.
|
|
72
|
+
*
|
|
73
|
+
* `null` for every unusable case — absent, empty string, unparseable, or an
|
|
74
|
+
* Invalid Date — so no caller can accidentally format `NaN`. The empty-string
|
|
75
|
+
* check is explicit because `new Date("")` is Invalid while `new Date(0)` is a
|
|
76
|
+
* legitimate instant (1970), and a falsy test would conflate them.
|
|
77
|
+
*/
|
|
78
|
+
const toUsableDate = value => {
|
|
79
|
+
if (value === null || value === undefined || value === "") return null;
|
|
80
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
81
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Day, month and year — `DD/MM/YYYY`.
|
|
86
|
+
*
|
|
87
|
+
* @param value - API string, epoch, or `Date`. Absent or unparseable → `absent`.
|
|
88
|
+
*/
|
|
89
|
+
export function fmtDate(value, opts = {}) {
|
|
90
|
+
const date = toUsableDate(value);
|
|
91
|
+
if (date === null) return opts.absent ?? ABSENT;
|
|
92
|
+
return `${pad2(date.getDate())}/${pad2(date.getMonth() + 1)}/${date.getFullYear()}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Day, month, year, then hours and minutes — `DD/MM/YYYY HH:mm`.
|
|
97
|
+
*
|
|
98
|
+
* Seconds are deliberately absent: no GU screen displays them, and a second
|
|
99
|
+
* shown is a second a reader tries to interpret.
|
|
100
|
+
*/
|
|
101
|
+
export function fmtDateTime(value, opts = {}) {
|
|
102
|
+
const date = toUsableDate(value);
|
|
103
|
+
if (date === null) return opts.absent ?? ABSENT;
|
|
104
|
+
return `${fmtDate(date, opts)} ${pad2(date.getHours())}:${pad2(date.getMinutes())}`;
|
|
105
|
+
}
|
|
106
|
+
const fmtDateApi = {
|
|
107
|
+
fmtDate,
|
|
108
|
+
fmtDateTime
|
|
109
|
+
};
|
|
110
|
+
export default fmtDateApi;
|
|
111
|
+
//# sourceMappingURL=fmtDate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fmtDate.js","names":["ABSENT","pad2","value","String","padStart","toUsableDate","undefined","date","Date","Number","isNaN","getTime","fmtDate","opts","absent","getDate","getMonth","getFullYear","fmtDateTime","getHours","getMinutes","fmtDateApi"],"sources":["../../../../src/lib/v2/utils/fmtDate.ts"],"sourcesContent":["/**\r\n * fmtDate / fmtDateTime — render a date for DISPLAY.\r\n *\r\n * fmtDate(\"2026-03-05T14:30:00Z\") → \"05/03/2026\"\r\n * fmtDateTime(\"2026-03-05T14:30:00Z\") → \"05/03/2026 15:30\" (local time)\r\n * fmtDate(undefined) → \"—\"\r\n * fmtDate(\"pas une date\") → \"—\"\r\n * fmtDate(d, { absent: \"\" }) → \"\" when there is no date\r\n *\r\n * ─────────────────────────────────────────────────────────────────────────────\r\n * WHY THIS BELONGS IN THE LIBRARY (capability gap G-51)\r\n *\r\n * The library already owns the money side of the same job — `fmtMoney`, same\r\n * folder, same \"—\" for an absent value, for the same reason: a table cell must\r\n * never read \"NaN\". It owned nothing for dates, which every GU screen renders\r\n * far more often than amounts. So every module wrote its own, and the copies\r\n * shared one defect:\r\n *\r\n * const fullformatDate = (stringDate) => {\r\n * const date = new Date(stringDate);\r\n * const day = formatIntoTwoDigits(date.getDate());\r\n * …\r\n *\r\n * `new Date(undefined)` is an INVALID DATE whose every accessor returns `NaN`,\r\n * so the helper rendered, literally:\r\n *\r\n * NaN/NaN/NaN NaN:NaN\r\n *\r\n * on any optional field — an update date, a visit date, a closure date — which\r\n * is most of them. In `autorisation-admis-pour-conforme-fe` the guard had been\r\n * added at CALL SITES one lot after another (lots 4 to 7) before being pulled\r\n * into the module's own helper: treating the symptom eight times, because the\r\n * one place where it cannot be forgotten is the formatter itself. That is the\r\n * argument for owning it here rather than in each module.\r\n *\r\n * Two smaller things the same copies carried, fixed here by construction: a\r\n * two-digit helper that returned `\"08\"` (string) or `12` (number) depending on\r\n * the value, and an absent date rendered as an EMPTY string — indistinguishable\r\n * from a cell that failed to render, where \"—\" says \"we looked, there is\r\n * nothing\".\r\n *\r\n * WHY NO `moment`, THOUGH THE PACKAGE ALREADY DEPENDS ON IT\r\n *\r\n * `moment` is here for the pickers, and specifically for STRICT parsing against\r\n * an explicit format (`patterns/_pickerSerialisation`) — reading\r\n * `\"31-12-2026\"` back without guessing. That is the right use of it.\r\n *\r\n * Rendering an already-parsed instant as two numbers and a year is not. This\r\n * folder is dependency-free on purpose (see `fileDownload`), and\r\n * `moment(someString)` with no format falls back to `new Date` WHILE EMITTING A\r\n * CONSOLE DEPRECATION WARNING — noise a display helper called once per table\r\n * cell must not produce. Native padding has neither cost.\r\n *\r\n * TIME ZONE, STATED RATHER THAN IMPLIED. The output is in the VIEWER'S local\r\n * zone (`getDate`, `getHours`…), which is what the module helpers this replaces\r\n * did, and what a user expects of \"date de dépôt\". An instant sent as\r\n * `…T23:30:00Z` therefore reads as the next day in Casablanca. If a screen ever\r\n * needs the server's calendar day regardless of the reader, that is a DIFFERENT\r\n * function and must be argued for on its own — silently switching this one to\r\n * UTC would move dates by a day across the whole platform.\r\n * ─────────────────────────────────────────────────────────────────────────────\r\n */\r\n\r\n/** What a formatter accepts: an API string, an epoch, or a `Date`. */\r\nexport type FmtDateInput = string | number | Date | null | undefined;\r\n\r\nexport type FmtDateOptions = {\r\n /**\r\n * Rendered when there is no usable date. Defaults to the em dash used as the\r\n * platform's absence mark in tables and detail sheets.\r\n *\r\n * Pass `\"\"` where an empty cell is the deliberate choice — a DataGrid column\r\n * that must stay visually quiet, or a value on its way into a search payload,\r\n * since an em dash travelling to a back-end as a criterion is worse than\r\n * nothing. Both cases exist in the module this was raised from.\r\n *\r\n * NO `separator` OPTION, and that is a correction rather than an omission.\r\n * The first draft had one, justified by a module helper documented as feeding\r\n * search criteria \"d'où le séparateur paramétrable\". A grep of the module\r\n * showed that helper has ZERO importers: the justification was dead code, so\r\n * the knob was speculative surface on a certified API (§2.8 — minimum API\r\n * surface). The emitted format is the charter's `DD/MM/YYYY`; a host needing\r\n * another separator should say why here.\r\n */\r\n absent?: string | undefined;\r\n};\r\n\r\n/** The platform's absence mark — same value `fmtMoney` returns. */\r\nconst ABSENT = \"—\";\r\n\r\nconst pad2 = (value: number): string => String(value).padStart(2, \"0\");\r\n\r\n/**\r\n * Parse an input into a USABLE `Date`, or `null`.\r\n *\r\n * `null` for every unusable case — absent, empty string, unparseable, or an\r\n * Invalid Date — so no caller can accidentally format `NaN`. The empty-string\r\n * check is explicit because `new Date(\"\")` is Invalid while `new Date(0)` is a\r\n * legitimate instant (1970), and a falsy test would conflate them.\r\n */\r\nconst toUsableDate = (value: FmtDateInput): Date | null => {\r\n if (value === null || value === undefined || value === \"\") return null;\r\n const date = value instanceof Date ? value : new Date(value);\r\n return Number.isNaN(date.getTime()) ? null : date;\r\n};\r\n\r\n/**\r\n * Day, month and year — `DD/MM/YYYY`.\r\n *\r\n * @param value - API string, epoch, or `Date`. Absent or unparseable → `absent`.\r\n */\r\nexport function fmtDate(value: FmtDateInput, opts: FmtDateOptions = {}): string {\r\n const date = toUsableDate(value);\r\n if (date === null) return opts.absent ?? ABSENT;\r\n\r\n return `${pad2(date.getDate())}/${pad2(\r\n date.getMonth() + 1\r\n )}/${date.getFullYear()}`;\r\n}\r\n\r\n/**\r\n * Day, month, year, then hours and minutes — `DD/MM/YYYY HH:mm`.\r\n *\r\n * Seconds are deliberately absent: no GU screen displays them, and a second\r\n * shown is a second a reader tries to interpret.\r\n */\r\nexport function fmtDateTime(\r\n value: FmtDateInput,\r\n opts: FmtDateOptions = {}\r\n): string {\r\n const date = toUsableDate(value);\r\n if (date === null) return opts.absent ?? ABSENT;\r\n\r\n return `${fmtDate(date, opts)} ${pad2(date.getHours())}:${pad2(\r\n date.getMinutes()\r\n )}`;\r\n}\r\n\r\nconst fmtDateApi = { fmtDate, fmtDateTime };\r\nexport default fmtDateApi;\r\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAwBA;AACA,MAAMA,MAAM,GAAG,GAAG;AAElB,MAAMC,IAAI,GAAIC,KAAa,IAAaC,MAAM,CAACD,KAAK,CAAC,CAACE,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC;;AAEtE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMC,YAAY,GAAIH,KAAmB,IAAkB;EACzD,IAAIA,KAAK,KAAK,IAAI,IAAIA,KAAK,KAAKI,SAAS,IAAIJ,KAAK,KAAK,EAAE,EAAE,OAAO,IAAI;EACtE,MAAMK,IAAI,GAAGL,KAAK,YAAYM,IAAI,GAAGN,KAAK,GAAG,IAAIM,IAAI,CAACN,KAAK,CAAC;EAC5D,OAAOO,MAAM,CAACC,KAAK,CAACH,IAAI,CAACI,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,GAAGJ,IAAI;AACnD,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,OAAO,SAASK,OAAOA,CAACV,KAAmB,EAAEW,IAAoB,GAAG,CAAC,CAAC,EAAU;EAC9E,MAAMN,IAAI,GAAGF,YAAY,CAACH,KAAK,CAAC;EAChC,IAAIK,IAAI,KAAK,IAAI,EAAE,OAAOM,IAAI,CAACC,MAAM,IAAId,MAAM;EAE/C,OAAO,GAAGC,IAAI,CAACM,IAAI,CAACQ,OAAO,CAAC,CAAC,CAAC,IAAId,IAAI,CACpCM,IAAI,CAACS,QAAQ,CAAC,CAAC,GAAG,CACpB,CAAC,IAAIT,IAAI,CAACU,WAAW,CAAC,CAAC,EAAE;AAC3B;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,WAAWA,CACzBhB,KAAmB,EACnBW,IAAoB,GAAG,CAAC,CAAC,EACjB;EACR,MAAMN,IAAI,GAAGF,YAAY,CAACH,KAAK,CAAC;EAChC,IAAIK,IAAI,KAAK,IAAI,EAAE,OAAOM,IAAI,CAACC,MAAM,IAAId,MAAM;EAE/C,OAAO,GAAGY,OAAO,CAACL,IAAI,EAAEM,IAAI,CAAC,IAAIZ,IAAI,CAACM,IAAI,CAACY,QAAQ,CAAC,CAAC,CAAC,IAAIlB,IAAI,CAC5DM,IAAI,CAACa,UAAU,CAAC,CAClB,CAAC,EAAE;AACL;AAEA,MAAMC,UAAU,GAAG;EAAET,OAAO;EAAEM;AAAY,CAAC;AAC3C,eAAeG,UAAU","ignoreList":[]}
|
|
@@ -46,33 +46,31 @@
|
|
|
46
46
|
* an unmatched-but-valid tag negotiates down to fr-FR instead of to the host.
|
|
47
47
|
* The `try/catch` is still required for genuinely malformed input.
|
|
48
48
|
*/
|
|
49
|
-
|
|
50
|
-
export default function fmtMoney(n) {
|
|
51
|
-
var currency = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
|
|
52
|
-
var opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
49
|
+
const FALLBACK_LOCALE = "fr-FR";
|
|
50
|
+
export default function fmtMoney(n, currency = "", opts = {}) {
|
|
53
51
|
if (n == null || n === "") return "—";
|
|
54
|
-
|
|
52
|
+
const num = typeof n === "number" ? n : Number(n);
|
|
55
53
|
if (!Number.isFinite(num)) return "—";
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
const locale = opts.locale || FALLBACK_LOCALE;
|
|
55
|
+
const formatOpts = {};
|
|
58
56
|
if (opts.minimumFractionDigits != null) {
|
|
59
57
|
formatOpts.minimumFractionDigits = opts.minimumFractionDigits;
|
|
60
58
|
}
|
|
61
59
|
if (opts.maximumFractionDigits != null) {
|
|
62
60
|
formatOpts.maximumFractionDigits = opts.maximumFractionDigits;
|
|
63
61
|
}
|
|
64
|
-
|
|
62
|
+
let formatted;
|
|
65
63
|
try {
|
|
66
64
|
/* Requested locale first, fr-FR as the declared next preference — see
|
|
67
65
|
* FALLBACK_LOCALE. A resolvable request ("en-US") still wins outright; an
|
|
68
66
|
* unmatched-but-well-formed tag lands on fr-FR rather than on the host's
|
|
69
67
|
* default locale. */
|
|
70
68
|
formatted = new Intl.NumberFormat([locale, FALLBACK_LOCALE], formatOpts).format(num);
|
|
71
|
-
} catch
|
|
69
|
+
} catch {
|
|
72
70
|
/* Reached only for a STRUCTURALLY invalid tag (RangeError) or a non-string
|
|
73
71
|
* value from an untyped host — negotiation cannot help there. */
|
|
74
72
|
formatted = new Intl.NumberFormat(FALLBACK_LOCALE, formatOpts).format(num);
|
|
75
73
|
}
|
|
76
|
-
return currency ?
|
|
74
|
+
return currency ? `${formatted} ${currency}` : formatted;
|
|
77
75
|
}
|
|
78
76
|
//# sourceMappingURL=fmtMoney.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fmtMoney.js","names":["FALLBACK_LOCALE","fmtMoney","n","currency","
|
|
1
|
+
{"version":3,"file":"fmtMoney.js","names":["FALLBACK_LOCALE","fmtMoney","n","currency","opts","num","Number","isFinite","locale","formatOpts","minimumFractionDigits","maximumFractionDigits","formatted","Intl","NumberFormat","format"],"sources":["../../../../src/lib/v2/utils/fmtMoney.ts"],"sourcesContent":["/**\r\n * fmtMoney — format a number with locale-aware grouping.\r\n *\r\n * Returns \"—\" for null / empty / non-finite inputs (graceful empty\r\n * cells in tables — never \"NaN\" or \"∞\").\r\n *\r\n * Defaults to \"fr-FR\" for back-compat with v1 callers; pass `locale`\r\n * (BCP-47 tag) to format under any locale supported by the runtime\r\n * Intl.NumberFormat. Currency placement is a trailing suffix in the\r\n * locale-formatted number; future evolution toward `style:\"currency\"`\r\n * (locale-correct symbol position, proper RTL handling) is deliberately\r\n * deferred to avoid breaking consumers that grep on the suffix.\r\n *\r\n * fmtMoney(1234567.5) → \"1 234 567,5\"\r\n * fmtMoney(1234567.5, \"EUR\") → \"1 234 567,5 EUR\"\r\n * fmtMoney(1234567.5, \"EUR\", { locale: \"en-US\" }) → \"1,234,567.5 EUR\"\r\n * fmtMoney(1234.5, \"\", { minimumFractionDigits: 2 })\r\n * → \"1 234,50\"\r\n * fmtMoney(null) / fmtMoney(\"abc\") / fmtMoney(Infinity) → \"—\"\r\n */\r\ntype FmtMoneyOptions = {\r\n locale?: string | undefined;\r\n minimumFractionDigits?: number | undefined;\r\n maximumFractionDigits?: number | undefined;\r\n};\r\n\r\n/**\r\n * The locale every unresolvable request falls back to.\r\n *\r\n * ── WHY THIS IS NEGOTIATED, NOT JUST CAUGHT ─────────────────────────────\r\n *\r\n * The previous implementation relied ONLY on `try/catch`, on the assumption that\r\n * an unusable locale throws `RangeError`. It does not — and the gap was a live\r\n * defect, not a theoretical one:\r\n *\r\n * `Intl.NumberFormat` throws only on a STRUCTURALLY INVALID tag. A tag that is\r\n * well-formed but has no data does NOT throw — it negotiates, and with a single\r\n * unmatched tag the negotiation lands on the RUNTIME DEFAULT LOCALE.\r\n *\r\n * `\"not-a-real-locale\"` is well-formed: `not` parses as a 3-letter language\r\n * subtag and `-a-real-locale` as a singleton `a` extension sequence. So it never\r\n * threw, the `catch` never ran, and the result was formatted in whatever locale\r\n * the host happened to default to. On a German or Spanish host that renders\r\n * `\"1.234,5\"` — dot grouping — inside a French application.\r\n *\r\n * That is a real consistency breach (Charter §09: a French UI formats numbers in\r\n * French), and it is invisible on a French dev machine, which is exactly why it\r\n * survived: the unit test caught it only because CI ran under a non-FR default.\r\n *\r\n * Listing the fallback as the SECOND preference makes the guarantee explicit:\r\n * an unmatched-but-valid tag negotiates down to fr-FR instead of to the host.\r\n * The `try/catch` is still required for genuinely malformed input.\r\n */\r\nconst FALLBACK_LOCALE = \"fr-FR\";\r\n\r\nexport default function fmtMoney(\r\n n: number | string | null | undefined,\r\n currency: string = \"\",\r\n opts: FmtMoneyOptions = {}\r\n): string {\r\n if (n == null || n === \"\") return \"—\";\r\n const num = typeof n === \"number\" ? n : Number(n);\r\n if (!Number.isFinite(num)) return \"—\";\r\n\r\n const locale = opts.locale || FALLBACK_LOCALE;\r\n\r\n const formatOpts: Intl.NumberFormatOptions = {};\r\n if (opts.minimumFractionDigits != null) {\r\n formatOpts.minimumFractionDigits = opts.minimumFractionDigits;\r\n }\r\n if (opts.maximumFractionDigits != null) {\r\n formatOpts.maximumFractionDigits = opts.maximumFractionDigits;\r\n }\r\n\r\n let formatted: string;\r\n try {\r\n /* Requested locale first, fr-FR as the declared next preference — see\r\n * FALLBACK_LOCALE. A resolvable request (\"en-US\") still wins outright; an\r\n * unmatched-but-well-formed tag lands on fr-FR rather than on the host's\r\n * default locale. */\r\n formatted = new Intl.NumberFormat(\r\n [locale, FALLBACK_LOCALE],\r\n formatOpts\r\n ).format(num);\r\n } catch {\r\n /* Reached only for a STRUCTURALLY invalid tag (RangeError) or a non-string\r\n * value from an untyped host — negotiation cannot help there. */\r\n formatted = new Intl.NumberFormat(FALLBACK_LOCALE, formatOpts).format(num);\r\n }\r\n\r\n return currency ? `${formatted} ${currency}` : formatted;\r\n}\r\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMA,eAAe,GAAG,OAAO;AAE/B,eAAe,SAASC,QAAQA,CAC9BC,CAAqC,EACrCC,QAAgB,GAAG,EAAE,EACrBC,IAAqB,GAAG,CAAC,CAAC,EAClB;EACR,IAAIF,CAAC,IAAI,IAAI,IAAIA,CAAC,KAAK,EAAE,EAAE,OAAO,GAAG;EACrC,MAAMG,GAAG,GAAG,OAAOH,CAAC,KAAK,QAAQ,GAAGA,CAAC,GAAGI,MAAM,CAACJ,CAAC,CAAC;EACjD,IAAI,CAACI,MAAM,CAACC,QAAQ,CAACF,GAAG,CAAC,EAAE,OAAO,GAAG;EAErC,MAAMG,MAAM,GAAGJ,IAAI,CAACI,MAAM,IAAIR,eAAe;EAE7C,MAAMS,UAAoC,GAAG,CAAC,CAAC;EAC/C,IAAIL,IAAI,CAACM,qBAAqB,IAAI,IAAI,EAAE;IACtCD,UAAU,CAACC,qBAAqB,GAAGN,IAAI,CAACM,qBAAqB;EAC/D;EACA,IAAIN,IAAI,CAACO,qBAAqB,IAAI,IAAI,EAAE;IACtCF,UAAU,CAACE,qBAAqB,GAAGP,IAAI,CAACO,qBAAqB;EAC/D;EAEA,IAAIC,SAAiB;EACrB,IAAI;IACF;AACJ;AACA;AACA;IACIA,SAAS,GAAG,IAAIC,IAAI,CAACC,YAAY,CAC/B,CAACN,MAAM,EAAER,eAAe,CAAC,EACzBS,UACF,CAAC,CAACM,MAAM,CAACV,GAAG,CAAC;EACf,CAAC,CAAC,MAAM;IACN;AACJ;IACIO,SAAS,GAAG,IAAIC,IAAI,CAACC,YAAY,CAACd,eAAe,EAAES,UAAU,CAAC,CAACM,MAAM,CAACV,GAAG,CAAC;EAC5E;EAEA,OAAOF,QAAQ,GAAG,GAAGS,SAAS,IAAIT,QAAQ,EAAE,GAAGS,SAAS;AAC1D","ignoreList":[]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* flattenFormikErrors — Formik's nested `errors` → the flat list
|
|
3
|
+
* `FormErrorSummary` consumes (capability gap G-38).
|
|
4
|
+
*
|
|
5
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
* WHY THIS BELONGS IN THE LIBRARY
|
|
7
|
+
*
|
|
8
|
+
* The library already owns BOTH ends of the failed-submit recipe that R-FV.2
|
|
9
|
+
* and R-FV.3 make mandatory:
|
|
10
|
+
*
|
|
11
|
+
* · `FormErrorSummary` — the banner at the top of the form, whose items are
|
|
12
|
+
* `{ name, message }` and whose `onItemClick` hands
|
|
13
|
+
* back the field NAME;
|
|
14
|
+
* · `FormikErrorFocus` / `focusFieldByName` — the counterpart that moves
|
|
15
|
+
* focus, resolving that same name as `[name="…"]`.
|
|
16
|
+
*
|
|
17
|
+
* What it never shipped is the step between them. Formik's `errors` mirrors the
|
|
18
|
+
* shape of `values` — a nested object whose leaves are message strings — and
|
|
19
|
+
* nothing in the library turned that tree into the flat list both ends speak.
|
|
20
|
+
* So every Formik host wrote the recursion itself, which is a duplication the
|
|
21
|
+
* Platform TypeScript Guidelines forbid outright, and which the library invited
|
|
22
|
+
* by publishing the type (`FormErrorSummaryError`) without the one function
|
|
23
|
+
* that produces it.
|
|
24
|
+
*
|
|
25
|
+
* >>> THE FAILURE MODE IS THE PATH, AND IT IS INVISIBLE TO REVIEW. Every
|
|
26
|
+
* >>> re-implementation gets `{ name, message }` right and the ACCUMULATED PATH
|
|
27
|
+
* >>> subtly wrong — a missing separator, a skipped array index, a `[0]`
|
|
28
|
+
* >>> written where Formik writes `.0`. Nothing throws. The banner renders,
|
|
29
|
+
* >>> lists the right messages, and its jump button targets a field that does
|
|
30
|
+
* >>> not exist: `focusFieldByName` finds no node and the click does nothing.
|
|
31
|
+
* >>> A user staring at a rejected form clicks the one affordance offered to
|
|
32
|
+
* >>> find the offending field, and it silently fails.
|
|
33
|
+
* >>>
|
|
34
|
+
* >>> Promoted from `refonte-frontend` (Area 5), where it had already been
|
|
35
|
+
* >>> extracted once — out of a single page and into module-common — for
|
|
36
|
+
* >>> exactly this reason, when a second form needed it. The next module along
|
|
37
|
+
* >>> would have been the third copy. <<<
|
|
38
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
*
|
|
40
|
+
* ARRAY ERRORS ARE TRAVERSED AS OBJECTS, DELIBERATELY. `Yup.array()` produces
|
|
41
|
+
* errors keyed by index, so the path becomes `beneficiaires.0.nom` — dot,
|
|
42
|
+
* not bracket. That is the form Formik itself produces and therefore the form
|
|
43
|
+
* the corresponding field's `name` attribute carries, which is what makes the
|
|
44
|
+
* path usable by `focusFieldByName` and by `setFieldValue` alike. Rewriting it
|
|
45
|
+
* as `beneficiaires[0].nom` would read more naturally and match nothing.
|
|
46
|
+
*
|
|
47
|
+
* Non-string leaves that are not objects (a `false`, a `null` left by a custom
|
|
48
|
+
* validator) yield nothing rather than an item with an empty message: a summary
|
|
49
|
+
* line with no text is worse than one line fewer, since it is clickable, blank,
|
|
50
|
+
* and lists no reason.
|
|
51
|
+
*
|
|
52
|
+
* Usage:
|
|
53
|
+
*
|
|
54
|
+
* <FormErrorSummary
|
|
55
|
+
* errors={flattenFormikErrors(formik.errors)}
|
|
56
|
+
* onItemClick={focusFieldByName}
|
|
57
|
+
* />
|
|
58
|
+
*
|
|
59
|
+
* The `FormErrorSummaryError` import is TYPE-ONLY and therefore erased at
|
|
60
|
+
* build time: this util does not depend on ./patterns at runtime, and the
|
|
61
|
+
* item shape keeps a single declaration next to the component that renders it
|
|
62
|
+
* rather than being restated here.
|
|
63
|
+
*/
|
|
64
|
+
export function flattenFormikErrors(errors, prefix = "") {
|
|
65
|
+
if (!errors || typeof errors !== "object") return [];
|
|
66
|
+
return Object.entries(errors).flatMap(([key, value]) => {
|
|
67
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
68
|
+
if (typeof value === "string") {
|
|
69
|
+
return value.length > 0 ? [{
|
|
70
|
+
name: path,
|
|
71
|
+
message: value
|
|
72
|
+
}] : [];
|
|
73
|
+
}
|
|
74
|
+
return flattenFormikErrors(value, path);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export default flattenFormikErrors;
|
|
78
|
+
//# sourceMappingURL=formikErrors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formikErrors.js","names":["flattenFormikErrors","errors","prefix","Object","entries","flatMap","key","value","path","length","name","message"],"sources":["../../../../src/lib/v2/utils/formikErrors.ts"],"sourcesContent":["import type { FormErrorSummaryError } from \"../patterns/FormErrorSummary\";\r\n\r\n/**\r\n * flattenFormikErrors — Formik's nested `errors` → the flat list\r\n * `FormErrorSummary` consumes (capability gap G-38).\r\n *\r\n * ─────────────────────────────────────────────────────────────────────────────\r\n * WHY THIS BELONGS IN THE LIBRARY\r\n *\r\n * The library already owns BOTH ends of the failed-submit recipe that R-FV.2\r\n * and R-FV.3 make mandatory:\r\n *\r\n * · `FormErrorSummary` — the banner at the top of the form, whose items are\r\n * `{ name, message }` and whose `onItemClick` hands\r\n * back the field NAME;\r\n * · `FormikErrorFocus` / `focusFieldByName` — the counterpart that moves\r\n * focus, resolving that same name as `[name=\"…\"]`.\r\n *\r\n * What it never shipped is the step between them. Formik's `errors` mirrors the\r\n * shape of `values` — a nested object whose leaves are message strings — and\r\n * nothing in the library turned that tree into the flat list both ends speak.\r\n * So every Formik host wrote the recursion itself, which is a duplication the\r\n * Platform TypeScript Guidelines forbid outright, and which the library invited\r\n * by publishing the type (`FormErrorSummaryError`) without the one function\r\n * that produces it.\r\n *\r\n * >>> THE FAILURE MODE IS THE PATH, AND IT IS INVISIBLE TO REVIEW. Every\r\n * >>> re-implementation gets `{ name, message }` right and the ACCUMULATED PATH\r\n * >>> subtly wrong — a missing separator, a skipped array index, a `[0]`\r\n * >>> written where Formik writes `.0`. Nothing throws. The banner renders,\r\n * >>> lists the right messages, and its jump button targets a field that does\r\n * >>> not exist: `focusFieldByName` finds no node and the click does nothing.\r\n * >>> A user staring at a rejected form clicks the one affordance offered to\r\n * >>> find the offending field, and it silently fails.\r\n * >>>\r\n * >>> Promoted from `refonte-frontend` (Area 5), where it had already been\r\n * >>> extracted once — out of a single page and into module-common — for\r\n * >>> exactly this reason, when a second form needed it. The next module along\r\n * >>> would have been the third copy. <<<\r\n * ─────────────────────────────────────────────────────────────────────────────\r\n *\r\n * ARRAY ERRORS ARE TRAVERSED AS OBJECTS, DELIBERATELY. `Yup.array()` produces\r\n * errors keyed by index, so the path becomes `beneficiaires.0.nom` — dot,\r\n * not bracket. That is the form Formik itself produces and therefore the form\r\n * the corresponding field's `name` attribute carries, which is what makes the\r\n * path usable by `focusFieldByName` and by `setFieldValue` alike. Rewriting it\r\n * as `beneficiaires[0].nom` would read more naturally and match nothing.\r\n *\r\n * Non-string leaves that are not objects (a `false`, a `null` left by a custom\r\n * validator) yield nothing rather than an item with an empty message: a summary\r\n * line with no text is worse than one line fewer, since it is clickable, blank,\r\n * and lists no reason.\r\n *\r\n * Usage:\r\n *\r\n * <FormErrorSummary\r\n * errors={flattenFormikErrors(formik.errors)}\r\n * onItemClick={focusFieldByName}\r\n * />\r\n *\r\n * The `FormErrorSummaryError` import is TYPE-ONLY and therefore erased at\r\n * build time: this util does not depend on ./patterns at runtime, and the\r\n * item shape keeps a single declaration next to the component that renders it\r\n * rather than being restated here.\r\n */\r\nexport function flattenFormikErrors(\r\n errors: unknown,\r\n prefix = \"\"\r\n): FormErrorSummaryError[] {\r\n if (!errors || typeof errors !== \"object\") return [];\r\n return Object.entries(errors as Record<string, unknown>).flatMap(\r\n ([key, value]) => {\r\n const path = prefix ? `${prefix}.${key}` : key;\r\n if (typeof value === \"string\") {\r\n return value.length > 0 ? [{ name: path, message: value }] : [];\r\n }\r\n return flattenFormikErrors(value, path);\r\n }\r\n );\r\n}\r\n\r\nexport default flattenFormikErrors;\r\n"],"mappings":"AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASA,mBAAmBA,CACjCC,MAAe,EACfC,MAAM,GAAG,EAAE,EACc;EACzB,IAAI,CAACD,MAAM,IAAI,OAAOA,MAAM,KAAK,QAAQ,EAAE,OAAO,EAAE;EACpD,OAAOE,MAAM,CAACC,OAAO,CAACH,MAAiC,CAAC,CAACI,OAAO,CAC9D,CAAC,CAACC,GAAG,EAAEC,KAAK,CAAC,KAAK;IAChB,MAAMC,IAAI,GAAGN,MAAM,GAAG,GAAGA,MAAM,IAAII,GAAG,EAAE,GAAGA,GAAG;IAC9C,IAAI,OAAOC,KAAK,KAAK,QAAQ,EAAE;MAC7B,OAAOA,KAAK,CAACE,MAAM,GAAG,CAAC,GAAG,CAAC;QAAEC,IAAI,EAAEF,IAAI;QAAEG,OAAO,EAAEJ;MAAM,CAAC,CAAC,GAAG,EAAE;IACjE;IACA,OAAOP,mBAAmB,CAACO,KAAK,EAAEC,IAAI,CAAC;EACzC,CACF,CAAC;AACH;AAEA,eAAeR,mBAAmB","ignoreList":[]}
|