@cratis/scene.primereact 4.5.0 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/preview.tsx +8 -1
- package/EnumerationResolution.ts +7 -0
- package/README.md +42 -10
- package/chart/ChartGallery.stories.tsx +63 -0
- package/chart/ChartType.ts +14 -0
- package/chart/ChartTypeResolution.ts +14 -0
- package/chart/PrimeChart.tsx +62 -0
- package/chart/chartHasData.ts +22 -0
- package/chart/chartTypeChoices.ts +32 -0
- package/chart/index.ts +7 -0
- package/chart/resolveChartType.ts +61 -0
- package/chart/useChart.ts +61 -0
- package/data/DataGallery.stories.tsx +6 -0
- package/data/DataScrollerRow.tsx +35 -0
- package/data/PrimeDataScroller.tsx +84 -0
- package/data/PrimeTreeTable.tsx +145 -0
- package/data/TreeTableCheckState.ts +9 -0
- package/data/TreeTableRow.ts +22 -0
- package/data/TreeTableRowView.tsx +86 -0
- package/data/TreeTableSelectionMode.ts +10 -0
- package/data/checkboxSelection.ts +92 -0
- package/data/index.ts +4 -0
- package/data/isSerializedLegacyElement.ts +30 -0
- package/data/readableValue.ts +32 -0
- package/data/selectionKeysOf.ts +40 -0
- package/data/treeTableRows.ts +32 -0
- package/data/useIncrementalLoad.ts +36 -0
- package/data/useLoadOnReach.ts +31 -0
- package/data/useTreeGridNavigation.ts +81 -0
- package/dist/cjs/chart/ChartType.js +18 -0
- package/dist/cjs/chart/ChartType.js.map +1 -0
- package/dist/cjs/chart/PrimeChart.js +92 -0
- package/dist/cjs/chart/PrimeChart.js.map +1 -0
- package/dist/cjs/chart/chartHasData.js +24 -0
- package/dist/cjs/chart/chartHasData.js.map +1 -0
- package/dist/cjs/chart/chartTypeChoices.js +43 -0
- package/dist/cjs/chart/chartTypeChoices.js.map +1 -0
- package/dist/cjs/chart/resolveChartType.js +69 -0
- package/dist/cjs/chart/resolveChartType.js.map +1 -0
- package/dist/cjs/chart/useChart.js +62 -0
- package/dist/cjs/chart/useChart.js.map +1 -0
- package/dist/cjs/data/DataScrollerRow.js +50 -0
- package/dist/cjs/data/DataScrollerRow.js.map +1 -0
- package/dist/cjs/data/PrimeDataScroller.js +120 -0
- package/dist/cjs/data/PrimeDataScroller.js.map +1 -0
- package/dist/cjs/data/PrimeTreeTable.js +217 -0
- package/dist/cjs/data/PrimeTreeTable.js.map +1 -0
- package/dist/cjs/data/TreeTableCheckState.js +13 -0
- package/dist/cjs/data/TreeTableCheckState.js.map +1 -0
- package/dist/cjs/data/TreeTableRowView.js +76 -0
- package/dist/cjs/data/TreeTableRowView.js.map +1 -0
- package/dist/cjs/data/TreeTableSelectionMode.js +14 -0
- package/dist/cjs/data/TreeTableSelectionMode.js.map +1 -0
- package/dist/cjs/data/checkboxSelection.js +88 -0
- package/dist/cjs/data/checkboxSelection.js.map +1 -0
- package/dist/cjs/data/isSerializedLegacyElement.js +30 -0
- package/dist/cjs/data/isSerializedLegacyElement.js.map +1 -0
- package/dist/cjs/data/readableValue.js +31 -0
- package/dist/cjs/data/readableValue.js.map +1 -0
- package/dist/cjs/data/selectionKeysOf.js +40 -0
- package/dist/cjs/data/selectionKeysOf.js.map +1 -0
- package/dist/cjs/data/treeTableRows.js +42 -0
- package/dist/cjs/data/treeTableRows.js.map +1 -0
- package/dist/cjs/data/useIncrementalLoad.js +46 -0
- package/dist/cjs/data/useIncrementalLoad.js.map +1 -0
- package/dist/cjs/data/useLoadOnReach.js +42 -0
- package/dist/cjs/data/useLoadOnReach.js.map +1 -0
- package/dist/cjs/data/useTreeGridNavigation.js +75 -0
- package/dist/cjs/data/useTreeGridNavigation.js.map +1 -0
- package/dist/cjs/editor/PrimeEditor.js +96 -0
- package/dist/cjs/editor/PrimeEditor.js.map +1 -0
- package/dist/cjs/editor/QuillLoaderContext.js +10 -0
- package/dist/cjs/editor/QuillLoaderContext.js.map +1 -0
- package/dist/cjs/editor/QuillLoaderProvider.js +21 -0
- package/dist/cjs/editor/QuillLoaderProvider.js.map +1 -0
- package/dist/cjs/editor/applyHtml.js +25 -0
- package/dist/cjs/editor/applyHtml.js.map +1 -0
- package/dist/cjs/editor/characterCount.js +10 -0
- package/dist/cjs/editor/characterCount.js.map +1 -0
- package/dist/cjs/editor/htmlToPlainText.js +101 -0
- package/dist/cjs/editor/htmlToPlainText.js.map +1 -0
- package/dist/cjs/editor/renderSafeNodes.js +35 -0
- package/dist/cjs/editor/renderSafeNodes.js.map +1 -0
- package/dist/cjs/editor/safeUrls.js +44 -0
- package/dist/cjs/editor/safeUrls.js.map +1 -0
- package/dist/cjs/editor/sanitizeHtml.js +140 -0
- package/dist/cjs/editor/sanitizeHtml.js.map +1 -0
- package/dist/cjs/editor/serializeSafeNodes.js +33 -0
- package/dist/cjs/editor/serializeSafeNodes.js.map +1 -0
- package/dist/cjs/editor/toolbarLayout.js +35 -0
- package/dist/cjs/editor/toolbarLayout.js.map +1 -0
- package/dist/cjs/editor/useQuillEditor.js +137 -0
- package/dist/cjs/editor/useQuillEditor.js.map +1 -0
- package/dist/cjs/file/FileUploadContext.js +13 -0
- package/dist/cjs/file/FileUploadContext.js.map +1 -0
- package/dist/cjs/file/FileUploadHandlerProvider.js +25 -0
- package/dist/cjs/file/FileUploadHandlerProvider.js.map +1 -0
- package/dist/cjs/file/FileUploadMode.js +13 -0
- package/dist/cjs/file/FileUploadMode.js.map +1 -0
- package/dist/cjs/file/PrimeFileUpload.js +161 -0
- package/dist/cjs/file/PrimeFileUpload.js.map +1 -0
- package/dist/cjs/file/postFiles.js +31 -0
- package/dist/cjs/file/postFiles.js.map +1 -0
- package/dist/cjs/file/resolveUploadTarget.js +68 -0
- package/dist/cjs/file/resolveUploadTarget.js.map +1 -0
- package/dist/cjs/file/useUploadSession.js +62 -0
- package/dist/cjs/file/useUploadSession.js.map +1 -0
- package/dist/cjs/form/MultiStateGlyph.js +59 -0
- package/dist/cjs/form/MultiStateGlyph.js.map +1 -0
- package/dist/cjs/form/PrimeMultiStateCheckbox.js +123 -0
- package/dist/cjs/form/PrimeMultiStateCheckbox.js.map +1 -0
- package/dist/cjs/form/multiStateOptions.js +77 -0
- package/dist/cjs/form/multiStateOptions.js.map +1 -0
- package/dist/cjs/form/multiStateStates.js +32 -0
- package/dist/cjs/form/multiStateStates.js.map +1 -0
- package/dist/cjs/form/useMultiStateSelection.js +51 -0
- package/dist/cjs/form/useMultiStateSelection.js.map +1 -0
- package/dist/cjs/index.js +44 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/legacyEnumerationChoices.js +39 -0
- package/dist/cjs/legacyEnumerationChoices.js.map +1 -0
- package/dist/cjs/loadQuill/index.js +22 -0
- package/dist/cjs/loadQuill/index.js.map +1 -0
- package/dist/cjs/primeReactComponents.js +15 -0
- package/dist/cjs/primeReactComponents.js.map +1 -1
- package/dist/cjs/primeReactDescriptors.js +400 -0
- package/dist/cjs/primeReactDescriptors.js.map +1 -0
- package/dist/cjs/primeReactPackage.js +8 -0
- package/dist/cjs/primeReactPackage.js.map +1 -1
- package/dist/cjs/properties.js +11 -0
- package/dist/cjs/properties.js.map +1 -1
- package/dist/cjs/resolveEnumeration.js +55 -0
- package/dist/cjs/resolveEnumeration.js.map +1 -0
- package/dist/cjs/structurallyEqual.js +27 -0
- package/dist/cjs/structurallyEqual.js.map +1 -0
- package/dist/cjs/treeNodes.js +42 -7
- package/dist/cjs/treeNodes.js.map +1 -1
- package/dist/cjs/useResettingState.js +47 -0
- package/dist/cjs/useResettingState.js.map +1 -0
- package/dist/cjs/useStructuralValue.js +20 -0
- package/dist/cjs/useStructuralValue.js.map +1 -0
- package/dist/esm/EnumerationResolution.d.ts +9 -0
- package/dist/esm/EnumerationResolution.d.ts.map +1 -0
- package/dist/esm/EnumerationResolution.js +2 -0
- package/dist/esm/EnumerationResolution.js.map +1 -0
- package/dist/esm/chart/ChartGallery.stories.d.ts +14 -0
- package/dist/esm/chart/ChartGallery.stories.d.ts.map +1 -0
- package/dist/esm/chart/ChartGallery.stories.js +47 -0
- package/dist/esm/chart/ChartGallery.stories.js.map +1 -0
- package/dist/esm/chart/ChartType.d.ts +11 -0
- package/dist/esm/chart/ChartType.d.ts.map +1 -0
- package/dist/esm/chart/ChartType.js +16 -0
- package/dist/esm/chart/ChartType.js.map +1 -0
- package/dist/esm/chart/ChartTypeResolution.d.ts +10 -0
- package/dist/esm/chart/ChartTypeResolution.d.ts.map +1 -0
- package/dist/esm/chart/ChartTypeResolution.js +2 -0
- package/dist/esm/chart/ChartTypeResolution.js.map +1 -0
- package/dist/esm/chart/PrimeChart.d.ts +3 -0
- package/dist/esm/chart/PrimeChart.d.ts.map +1 -0
- package/dist/esm/chart/PrimeChart.js +90 -0
- package/dist/esm/chart/PrimeChart.js.map +1 -0
- package/dist/esm/chart/chartHasData.d.ts +2 -0
- package/dist/esm/chart/chartHasData.d.ts.map +1 -0
- package/dist/esm/chart/chartHasData.js +22 -0
- package/dist/esm/chart/chartHasData.js.map +1 -0
- package/dist/esm/chart/chartTypeChoices.d.ts +3 -0
- package/dist/esm/chart/chartTypeChoices.d.ts.map +1 -0
- package/dist/esm/chart/chartTypeChoices.js +41 -0
- package/dist/esm/chart/chartTypeChoices.js.map +1 -0
- package/dist/esm/chart/index.d.ts +5 -0
- package/dist/esm/chart/index.d.ts.map +1 -0
- package/dist/esm/chart/index.js +5 -0
- package/dist/esm/chart/index.js.map +1 -0
- package/dist/esm/chart/resolveChartType.d.ts +6 -0
- package/dist/esm/chart/resolveChartType.d.ts.map +1 -0
- package/dist/esm/chart/resolveChartType.js +66 -0
- package/dist/esm/chart/resolveChartType.js.map +1 -0
- package/dist/esm/chart/useChart.d.ts +4 -0
- package/dist/esm/chart/useChart.d.ts.map +1 -0
- package/dist/esm/chart/useChart.js +60 -0
- package/dist/esm/chart/useChart.js.map +1 -0
- package/dist/esm/data/DataGallery.stories.d.ts.map +1 -1
- package/dist/esm/data/DataGallery.stories.js +6 -0
- package/dist/esm/data/DataGallery.stories.js.map +1 -1
- package/dist/esm/data/DataScrollerRow.d.ts +4 -0
- package/dist/esm/data/DataScrollerRow.d.ts.map +1 -0
- package/dist/esm/data/DataScrollerRow.js +48 -0
- package/dist/esm/data/DataScrollerRow.js.map +1 -0
- package/dist/esm/data/PrimeDataScroller.d.ts +3 -0
- package/dist/esm/data/PrimeDataScroller.d.ts.map +1 -0
- package/dist/esm/data/PrimeDataScroller.js +118 -0
- package/dist/esm/data/PrimeDataScroller.js.map +1 -0
- package/dist/esm/data/PrimeTreeTable.d.ts +3 -0
- package/dist/esm/data/PrimeTreeTable.d.ts.map +1 -0
- package/dist/esm/data/PrimeTreeTable.js +215 -0
- package/dist/esm/data/PrimeTreeTable.js.map +1 -0
- package/dist/esm/data/TreeTableCheckState.d.ts +6 -0
- package/dist/esm/data/TreeTableCheckState.d.ts.map +1 -0
- package/dist/esm/data/TreeTableCheckState.js +11 -0
- package/dist/esm/data/TreeTableCheckState.js.map +1 -0
- package/dist/esm/data/TreeTableRow.d.ts +10 -0
- package/dist/esm/data/TreeTableRow.d.ts.map +1 -0
- package/dist/esm/data/TreeTableRow.js +2 -0
- package/dist/esm/data/TreeTableRow.js.map +1 -0
- package/dist/esm/data/TreeTableRowView.d.ts +21 -0
- package/dist/esm/data/TreeTableRowView.d.ts.map +1 -0
- package/dist/esm/data/TreeTableRowView.js +74 -0
- package/dist/esm/data/TreeTableRowView.js.map +1 -0
- package/dist/esm/data/TreeTableSelectionMode.d.ts +7 -0
- package/dist/esm/data/TreeTableSelectionMode.d.ts.map +1 -0
- package/dist/esm/data/TreeTableSelectionMode.js +12 -0
- package/dist/esm/data/TreeTableSelectionMode.js.map +1 -0
- package/dist/esm/data/checkboxSelection.d.ts +9 -0
- package/dist/esm/data/checkboxSelection.d.ts.map +1 -0
- package/dist/esm/data/checkboxSelection.js +85 -0
- package/dist/esm/data/checkboxSelection.js.map +1 -0
- package/dist/esm/data/index.d.ts +4 -0
- package/dist/esm/data/index.d.ts.map +1 -1
- package/dist/esm/data/index.js +4 -0
- package/dist/esm/data/index.js.map +1 -1
- package/dist/esm/data/isSerializedLegacyElement.d.ts +3 -0
- package/dist/esm/data/isSerializedLegacyElement.d.ts.map +1 -0
- package/dist/esm/data/isSerializedLegacyElement.js +27 -0
- package/dist/esm/data/isSerializedLegacyElement.js.map +1 -0
- package/dist/esm/data/readableValue.d.ts +2 -0
- package/dist/esm/data/readableValue.d.ts.map +1 -0
- package/dist/esm/data/readableValue.js +29 -0
- package/dist/esm/data/readableValue.js.map +1 -0
- package/dist/esm/data/selectionKeysOf.d.ts +2 -0
- package/dist/esm/data/selectionKeysOf.d.ts.map +1 -0
- package/dist/esm/data/selectionKeysOf.js +38 -0
- package/dist/esm/data/selectionKeysOf.js.map +1 -0
- package/dist/esm/data/treeTableRows.d.ts +6 -0
- package/dist/esm/data/treeTableRows.d.ts.map +1 -0
- package/dist/esm/data/treeTableRows.js +38 -0
- package/dist/esm/data/treeTableRows.js.map +1 -0
- package/dist/esm/data/useIncrementalLoad.d.ts +6 -0
- package/dist/esm/data/useIncrementalLoad.d.ts.map +1 -0
- package/dist/esm/data/useIncrementalLoad.js +44 -0
- package/dist/esm/data/useIncrementalLoad.js.map +1 -0
- package/dist/esm/data/useLoadOnReach.d.ts +3 -0
- package/dist/esm/data/useLoadOnReach.d.ts.map +1 -0
- package/dist/esm/data/useLoadOnReach.js +40 -0
- package/dist/esm/data/useLoadOnReach.js.map +1 -0
- package/dist/esm/data/useTreeGridNavigation.d.ts +17 -0
- package/dist/esm/data/useTreeGridNavigation.d.ts.map +1 -0
- package/dist/esm/data/useTreeGridNavigation.js +73 -0
- package/dist/esm/data/useTreeGridNavigation.js.map +1 -0
- package/dist/esm/editor/PrimeEditor.d.ts +3 -0
- package/dist/esm/editor/PrimeEditor.d.ts.map +1 -0
- package/dist/esm/editor/PrimeEditor.js +94 -0
- package/dist/esm/editor/PrimeEditor.js.map +1 -0
- package/dist/esm/editor/QuillConstructor.d.ts +4 -0
- package/dist/esm/editor/QuillConstructor.d.ts.map +1 -0
- package/dist/esm/editor/QuillConstructor.js +2 -0
- package/dist/esm/editor/QuillConstructor.js.map +1 -0
- package/dist/esm/editor/QuillEditorOptions.d.ts +13 -0
- package/dist/esm/editor/QuillEditorOptions.d.ts.map +1 -0
- package/dist/esm/editor/QuillEditorOptions.js +2 -0
- package/dist/esm/editor/QuillEditorOptions.js.map +1 -0
- package/dist/esm/editor/QuillEditorState.d.ts +6 -0
- package/dist/esm/editor/QuillEditorState.d.ts.map +1 -0
- package/dist/esm/editor/QuillEditorState.js +2 -0
- package/dist/esm/editor/QuillEditorState.js.map +1 -0
- package/dist/esm/editor/QuillInstance.d.ts +16 -0
- package/dist/esm/editor/QuillInstance.d.ts.map +1 -0
- package/dist/esm/editor/QuillInstance.js +2 -0
- package/dist/esm/editor/QuillInstance.js.map +1 -0
- package/dist/esm/editor/QuillLoader.d.ts +5 -0
- package/dist/esm/editor/QuillLoader.d.ts.map +1 -0
- package/dist/esm/editor/QuillLoader.js +2 -0
- package/dist/esm/editor/QuillLoader.js.map +1 -0
- package/dist/esm/editor/QuillLoaderContext.d.ts +3 -0
- package/dist/esm/editor/QuillLoaderContext.d.ts.map +1 -0
- package/dist/esm/editor/QuillLoaderContext.js +8 -0
- package/dist/esm/editor/QuillLoaderContext.js.map +1 -0
- package/dist/esm/editor/QuillLoaderProvider.d.ts +8 -0
- package/dist/esm/editor/QuillLoaderProvider.d.ts.map +1 -0
- package/dist/esm/editor/QuillLoaderProvider.js +19 -0
- package/dist/esm/editor/QuillLoaderProvider.js.map +1 -0
- package/dist/esm/editor/QuillOptions.d.ts +9 -0
- package/dist/esm/editor/QuillOptions.d.ts.map +1 -0
- package/dist/esm/editor/QuillOptions.js +2 -0
- package/dist/esm/editor/QuillOptions.js.map +1 -0
- package/dist/esm/editor/SafeElement.d.ts +7 -0
- package/dist/esm/editor/SafeElement.d.ts.map +1 -0
- package/dist/esm/editor/SafeElement.js +2 -0
- package/dist/esm/editor/SafeElement.js.map +1 -0
- package/dist/esm/editor/SafeNode.d.ts +3 -0
- package/dist/esm/editor/SafeNode.d.ts.map +1 -0
- package/dist/esm/editor/SafeNode.js +2 -0
- package/dist/esm/editor/SafeNode.js.map +1 -0
- package/dist/esm/editor/applyHtml.d.ts +3 -0
- package/dist/esm/editor/applyHtml.d.ts.map +1 -0
- package/dist/esm/editor/applyHtml.js +23 -0
- package/dist/esm/editor/applyHtml.js.map +1 -0
- package/dist/esm/editor/characterCount.d.ts +3 -0
- package/dist/esm/editor/characterCount.d.ts.map +1 -0
- package/dist/esm/editor/characterCount.js +8 -0
- package/dist/esm/editor/characterCount.js.map +1 -0
- package/dist/esm/editor/htmlToPlainText.d.ts +2 -0
- package/dist/esm/editor/htmlToPlainText.d.ts.map +1 -0
- package/dist/esm/editor/htmlToPlainText.js +99 -0
- package/dist/esm/editor/htmlToPlainText.js.map +1 -0
- package/dist/esm/editor/index.d.ts +14 -0
- package/dist/esm/editor/index.d.ts.map +1 -0
- package/dist/esm/editor/index.js +14 -0
- package/dist/esm/editor/index.js.map +1 -0
- package/dist/esm/editor/renderSafeNodes.d.ts +4 -0
- package/dist/esm/editor/renderSafeNodes.d.ts.map +1 -0
- package/dist/esm/editor/renderSafeNodes.js +33 -0
- package/dist/esm/editor/renderSafeNodes.js.map +1 -0
- package/dist/esm/editor/safeUrls.d.ts +3 -0
- package/dist/esm/editor/safeUrls.d.ts.map +1 -0
- package/dist/esm/editor/safeUrls.js +41 -0
- package/dist/esm/editor/safeUrls.js.map +1 -0
- package/dist/esm/editor/sanitizeHtml.d.ts +3 -0
- package/dist/esm/editor/sanitizeHtml.d.ts.map +1 -0
- package/dist/esm/editor/sanitizeHtml.js +138 -0
- package/dist/esm/editor/sanitizeHtml.js.map +1 -0
- package/dist/esm/editor/serializeSafeNodes.d.ts +3 -0
- package/dist/esm/editor/serializeSafeNodes.d.ts.map +1 -0
- package/dist/esm/editor/serializeSafeNodes.js +31 -0
- package/dist/esm/editor/serializeSafeNodes.js.map +1 -0
- package/dist/esm/editor/toolbarLayout.d.ts +2 -0
- package/dist/esm/editor/toolbarLayout.d.ts.map +1 -0
- package/dist/esm/editor/toolbarLayout.js +33 -0
- package/dist/esm/editor/toolbarLayout.js.map +1 -0
- package/dist/esm/editor/useQuillEditor.d.ts +4 -0
- package/dist/esm/editor/useQuillEditor.d.ts.map +1 -0
- package/dist/esm/editor/useQuillEditor.js +135 -0
- package/dist/esm/editor/useQuillEditor.js.map +1 -0
- package/dist/esm/file/FileGallery.stories.d.ts +19 -0
- package/dist/esm/file/FileGallery.stories.d.ts.map +1 -0
- package/dist/esm/file/FileGallery.stories.js +23 -0
- package/dist/esm/file/FileGallery.stories.js.map +1 -0
- package/dist/esm/file/FileUploadContext.d.ts +7 -0
- package/dist/esm/file/FileUploadContext.d.ts.map +1 -0
- package/dist/esm/file/FileUploadContext.js +11 -0
- package/dist/esm/file/FileUploadContext.js.map +1 -0
- package/dist/esm/file/FileUploadHandler.d.ts +4 -0
- package/dist/esm/file/FileUploadHandler.d.ts.map +1 -0
- package/dist/esm/file/FileUploadHandler.js +2 -0
- package/dist/esm/file/FileUploadHandler.js.map +1 -0
- package/dist/esm/file/FileUploadHandlerProvider.d.ts +9 -0
- package/dist/esm/file/FileUploadHandlerProvider.d.ts.map +1 -0
- package/dist/esm/file/FileUploadHandlerProvider.js +23 -0
- package/dist/esm/file/FileUploadHandlerProvider.js.map +1 -0
- package/dist/esm/file/FileUploadMode.d.ts +6 -0
- package/dist/esm/file/FileUploadMode.d.ts.map +1 -0
- package/dist/esm/file/FileUploadMode.js +11 -0
- package/dist/esm/file/FileUploadMode.js.map +1 -0
- package/dist/esm/file/PrimeFileUpload.d.ts +3 -0
- package/dist/esm/file/PrimeFileUpload.d.ts.map +1 -0
- package/dist/esm/file/PrimeFileUpload.js +159 -0
- package/dist/esm/file/PrimeFileUpload.js.map +1 -0
- package/dist/esm/file/UploadStatus.d.ts +5 -0
- package/dist/esm/file/UploadStatus.d.ts.map +1 -0
- package/dist/esm/file/UploadStatus.js +2 -0
- package/dist/esm/file/UploadStatus.js.map +1 -0
- package/dist/esm/file/UploadTarget.d.ts +8 -0
- package/dist/esm/file/UploadTarget.d.ts.map +1 -0
- package/dist/esm/file/UploadTarget.js +2 -0
- package/dist/esm/file/UploadTarget.js.map +1 -0
- package/dist/esm/file/index.d.ts +7 -0
- package/dist/esm/file/index.d.ts.map +1 -0
- package/dist/esm/file/index.js +7 -0
- package/dist/esm/file/index.js.map +1 -0
- package/dist/esm/file/postFiles.d.ts +2 -0
- package/dist/esm/file/postFiles.d.ts.map +1 -0
- package/dist/esm/file/postFiles.js +29 -0
- package/dist/esm/file/postFiles.js.map +1 -0
- package/dist/esm/file/resolveUploadTarget.d.ts +3 -0
- package/dist/esm/file/resolveUploadTarget.d.ts.map +1 -0
- package/dist/esm/file/resolveUploadTarget.js +66 -0
- package/dist/esm/file/resolveUploadTarget.js.map +1 -0
- package/dist/esm/file/useUploadSession.d.ts +10 -0
- package/dist/esm/file/useUploadSession.d.ts.map +1 -0
- package/dist/esm/file/useUploadSession.js +60 -0
- package/dist/esm/file/useUploadSession.js.map +1 -0
- package/dist/esm/form/FormGallery.stories.d.ts.map +1 -1
- package/dist/esm/form/FormGallery.stories.js +9 -0
- package/dist/esm/form/FormGallery.stories.js.map +1 -1
- package/dist/esm/form/MultiStateGlyph.d.ts +8 -0
- package/dist/esm/form/MultiStateGlyph.d.ts.map +1 -0
- package/dist/esm/form/MultiStateGlyph.js +57 -0
- package/dist/esm/form/MultiStateGlyph.js.map +1 -0
- package/dist/esm/form/MultiStateOption.d.ts +7 -0
- package/dist/esm/form/MultiStateOption.d.ts.map +1 -0
- package/dist/esm/form/MultiStateOption.js +2 -0
- package/dist/esm/form/MultiStateOption.js.map +1 -0
- package/dist/esm/form/PrimeMultiStateCheckbox.d.ts +3 -0
- package/dist/esm/form/PrimeMultiStateCheckbox.d.ts.map +1 -0
- package/dist/esm/form/PrimeMultiStateCheckbox.js +121 -0
- package/dist/esm/form/PrimeMultiStateCheckbox.js.map +1 -0
- package/dist/esm/form/index.d.ts +1 -0
- package/dist/esm/form/index.d.ts.map +1 -1
- package/dist/esm/form/index.js +1 -0
- package/dist/esm/form/index.js.map +1 -1
- package/dist/esm/form/multiStateOptions.d.ts +4 -0
- package/dist/esm/form/multiStateOptions.d.ts.map +1 -0
- package/dist/esm/form/multiStateOptions.js +75 -0
- package/dist/esm/form/multiStateOptions.js.map +1 -0
- package/dist/esm/form/multiStateStates.d.ts +4 -0
- package/dist/esm/form/multiStateStates.d.ts.map +1 -0
- package/dist/esm/form/multiStateStates.js +30 -0
- package/dist/esm/form/multiStateStates.js.map +1 -0
- package/dist/esm/form/useMultiStateSelection.d.ts +6 -0
- package/dist/esm/form/useMultiStateSelection.d.ts.map +1 -0
- package/dist/esm/form/useMultiStateSelection.js +49 -0
- package/dist/esm/form/useMultiStateSelection.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +22 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/legacyEnumerationChoices.d.ts +3 -0
- package/dist/esm/legacyEnumerationChoices.d.ts.map +1 -0
- package/dist/esm/legacyEnumerationChoices.js +37 -0
- package/dist/esm/legacyEnumerationChoices.js.map +1 -0
- package/dist/esm/loadQuill/index.d.ts +3 -0
- package/dist/esm/loadQuill/index.d.ts.map +1 -0
- package/dist/esm/loadQuill/index.js +20 -0
- package/dist/esm/loadQuill/index.js.map +1 -0
- package/dist/esm/primeReactComponents.d.ts.map +1 -1
- package/dist/esm/primeReactComponents.js +15 -0
- package/dist/esm/primeReactComponents.js.map +1 -1
- package/dist/esm/primeReactDescriptors.d.ts +3 -0
- package/dist/esm/primeReactDescriptors.d.ts.map +1 -0
- package/dist/esm/primeReactDescriptors.js +398 -0
- package/dist/esm/primeReactDescriptors.js.map +1 -0
- package/dist/esm/primeReactPackage.d.ts.map +1 -1
- package/dist/esm/primeReactPackage.js +8 -0
- package/dist/esm/primeReactPackage.js.map +1 -1
- package/dist/esm/properties.d.ts +1 -0
- package/dist/esm/properties.d.ts.map +1 -1
- package/dist/esm/properties.js +11 -1
- package/dist/esm/properties.js.map +1 -1
- package/dist/esm/resolveEnumeration.d.ts +3 -0
- package/dist/esm/resolveEnumeration.d.ts.map +1 -0
- package/dist/esm/resolveEnumeration.js +53 -0
- package/dist/esm/resolveEnumeration.js.map +1 -0
- package/dist/esm/structurallyEqual.d.ts +2 -0
- package/dist/esm/structurallyEqual.d.ts.map +1 -0
- package/dist/esm/structurallyEqual.js +25 -0
- package/dist/esm/structurallyEqual.js.map +1 -0
- package/dist/esm/treeNodes.d.ts +2 -1
- package/dist/esm/treeNodes.d.ts.map +1 -1
- package/dist/esm/treeNodes.js +42 -8
- package/dist/esm/treeNodes.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/useResettingState.d.ts +3 -0
- package/dist/esm/useResettingState.d.ts.map +1 -0
- package/dist/esm/useResettingState.js +45 -0
- package/dist/esm/useResettingState.js.map +1 -0
- package/dist/esm/useStructuralValue.d.ts +2 -0
- package/dist/esm/useStructuralValue.d.ts.map +1 -0
- package/dist/esm/useStructuralValue.js +18 -0
- package/dist/esm/useStructuralValue.js.map +1 -0
- package/editor/PrimeEditor.tsx +83 -0
- package/editor/QuillConstructor.ts +8 -0
- package/editor/QuillEditorOptions.ts +29 -0
- package/editor/QuillEditorState.ts +14 -0
- package/editor/QuillInstance.ts +27 -0
- package/editor/QuillLoader.ts +13 -0
- package/editor/QuillLoaderContext.ts +8 -0
- package/editor/QuillLoaderProvider.tsx +24 -0
- package/editor/QuillOptions.ts +10 -0
- package/editor/SafeElement.ts +15 -0
- package/editor/SafeNode.ts +7 -0
- package/editor/applyHtml.ts +21 -0
- package/editor/characterCount.ts +9 -0
- package/editor/htmlToPlainText.ts +74 -0
- package/editor/index.ts +16 -0
- package/editor/renderSafeNodes.tsx +26 -0
- package/editor/safeUrls.ts +43 -0
- package/editor/sanitizeHtml.ts +94 -0
- package/editor/serializeSafeNodes.ts +26 -0
- package/editor/toolbarLayout.ts +10 -0
- package/editor/useQuillEditor.ts +114 -0
- package/file/FileGallery.stories.tsx +32 -0
- package/file/FileUploadContext.ts +17 -0
- package/file/FileUploadHandler.ts +15 -0
- package/file/FileUploadHandlerProvider.tsx +33 -0
- package/file/FileUploadMode.ts +9 -0
- package/file/PrimeFileUpload.tsx +129 -0
- package/file/UploadStatus.ts +9 -0
- package/file/UploadTarget.ts +12 -0
- package/file/index.ts +9 -0
- package/file/postFiles.ts +23 -0
- package/file/resolveUploadTarget.ts +61 -0
- package/file/useUploadSession.ts +67 -0
- package/form/FormGallery.stories.tsx +9 -0
- package/form/MultiStateGlyph.tsx +55 -0
- package/form/MultiStateOption.ts +17 -0
- package/form/PrimeMultiStateCheckbox.tsx +101 -0
- package/form/index.ts +1 -0
- package/form/multiStateOptions.ts +78 -0
- package/form/multiStateStates.ts +23 -0
- package/form/useMultiStateSelection.ts +48 -0
- package/index.ts +4 -0
- package/legacyEnumerationChoices.ts +35 -0
- package/loadQuill/index.ts +21 -0
- package/package.json +27 -7
- package/primeReactComponents.ts +12 -0
- package/primeReactDescriptors.ts +111 -0
- package/primeReactPackage.ts +5 -3
- package/primeReactTheme.css +69 -0
- package/properties.ts +14 -0
- package/resolveEnumeration.ts +50 -0
- package/structurallyEqual.ts +29 -0
- package/treeNodes.ts +44 -6
- package/useResettingState.ts +34 -0
- package/useStructuralValue.ts +17 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { SafeNode } from './SafeNode';
|
|
5
|
+
|
|
6
|
+
const voidTags = new Set(['br', 'hr', 'img']);
|
|
7
|
+
|
|
8
|
+
const escapes: Record<string, string> = { '&': '&', '<': '<', '>': '>', '"': '"' };
|
|
9
|
+
|
|
10
|
+
const escape = (text: string) => text.replace(/[&<>"]/g, character => escapes[character]);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Writes safe nodes back out as an HTML string, for an editor that reads HTML.
|
|
14
|
+
*
|
|
15
|
+
* Text and attribute values are escaped, and only tags and attributes the allowlist produced are written, so
|
|
16
|
+
* the string can contain nothing the allowlist did not already accept.
|
|
17
|
+
*
|
|
18
|
+
* @param nodes The nodes `sanitizeHtml` returned.
|
|
19
|
+
*/
|
|
20
|
+
export function serializeSafeNodes(nodes: SafeNode[]): string {
|
|
21
|
+
return nodes.map(node => {
|
|
22
|
+
if (typeof node === 'string') return escape(node);
|
|
23
|
+
const attributes = Object.entries(node.attributes).map(([name, value]) => ` ${name}="${escape(value)}"`).join('');
|
|
24
|
+
return voidTags.has(node.tag) ? `<${node.tag}${attributes}>` : `<${node.tag}${attributes}>${serializeSafeNodes(node.children)}</${node.tag}>`;
|
|
25
|
+
}).join('');
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** The formats the toolbar offers. Content in other formats is still shown, but cannot be produced here. */
|
|
5
|
+
export const toolbarLayout: (string | Record<string, unknown>)[][] = [
|
|
6
|
+
['bold', 'italic', 'underline'],
|
|
7
|
+
[{ header: [1, 2, 3, false] }],
|
|
8
|
+
[{ list: 'ordered' }, { list: 'bullet' }],
|
|
9
|
+
['link'],
|
|
10
|
+
];
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { QuillConstructor } from './QuillConstructor';
|
|
6
|
+
import { QuillEditorOptions } from './QuillEditorOptions';
|
|
7
|
+
import { QuillEditorState } from './QuillEditorState';
|
|
8
|
+
import { QuillInstance } from './QuillInstance';
|
|
9
|
+
import { applyHtml } from './applyHtml';
|
|
10
|
+
import { characterCount } from './characterCount';
|
|
11
|
+
import { toolbarLayout } from './toolbarLayout';
|
|
12
|
+
|
|
13
|
+
const idle: QuillEditorState = { ready: false, failure: undefined, characters: 0 };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Builds and owns a Quill editor inside an element React leaves alone.
|
|
17
|
+
*
|
|
18
|
+
* The editor is constructed once per loader and toolbar setting, in a container this hook creates and
|
|
19
|
+
* removes itself. React renders the host with no children, so a re-render can never wipe what Quill built -
|
|
20
|
+
* the failure of rendering the authored HTML as the host's children and then letting Quill take it over.
|
|
21
|
+
*
|
|
22
|
+
* Everything else is applied to the live editor without rebuilding it:
|
|
23
|
+
* - a changed `html` replaces the content silently, and only when the authored value really changed, so a
|
|
24
|
+
* user's edit survives an unrelated property change;
|
|
25
|
+
* - `editable`, the placeholder and the accessible name update in place;
|
|
26
|
+
* - the `text-change` listener is removed, and the toolbar and surface with the container, when the editor
|
|
27
|
+
* is rebuilt or the control unmounts.
|
|
28
|
+
*
|
|
29
|
+
* Only a user edit calls `onUserChange`.
|
|
30
|
+
*/
|
|
31
|
+
export function useQuillEditor(options: QuillEditorOptions): QuillEditorState {
|
|
32
|
+
const { loader, host, html, showHeader, editable, placeholder, ariaLabel } = options;
|
|
33
|
+
const [state, setState] = useState<QuillEditorState>(idle);
|
|
34
|
+
const quillReference = useRef<QuillInstance | undefined>(undefined);
|
|
35
|
+
const appliedHtml = useRef(html);
|
|
36
|
+
const latest = useRef(options);
|
|
37
|
+
latest.current = options;
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const container = host.current;
|
|
41
|
+
if (loader === undefined || container === null) return undefined;
|
|
42
|
+
|
|
43
|
+
let disposed = false;
|
|
44
|
+
let dispose: (() => void) | undefined;
|
|
45
|
+
|
|
46
|
+
loader().then(loaded => {
|
|
47
|
+
if (disposed) return;
|
|
48
|
+
const Quill: QuillConstructor = typeof loaded === 'function' ? loaded : loaded.default;
|
|
49
|
+
const surface = container.ownerDocument.createElement('div');
|
|
50
|
+
container.appendChild(surface);
|
|
51
|
+
const quill = new Quill(surface, {
|
|
52
|
+
theme: 'snow',
|
|
53
|
+
modules: { toolbar: showHeader ? toolbarLayout : false },
|
|
54
|
+
placeholder: latest.current.placeholder,
|
|
55
|
+
readOnly: !latest.current.editable,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
applyHtml(quill, latest.current.html);
|
|
59
|
+
appliedHtml.current = latest.current.html;
|
|
60
|
+
const changed = (_delta: unknown, _previous: unknown, source: string) => {
|
|
61
|
+
if (source === 'silent') return;
|
|
62
|
+
setState(current => ({ ...current, characters: characterCount(quill) }));
|
|
63
|
+
if (source === 'user') latest.current.onUserChange();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
quill.on('text-change', changed);
|
|
67
|
+
quillReference.current = quill;
|
|
68
|
+
dispose = () => {
|
|
69
|
+
quill.off('text-change', changed);
|
|
70
|
+
quillReference.current = undefined;
|
|
71
|
+
container.replaceChildren();
|
|
72
|
+
};
|
|
73
|
+
setState({ ready: true, failure: undefined, characters: characterCount(quill) });
|
|
74
|
+
}).catch((error: unknown) => {
|
|
75
|
+
if (!disposed) setState({ ...idle, failure: error instanceof Error ? error.message : String(error) });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return () => {
|
|
79
|
+
disposed = true;
|
|
80
|
+
dispose?.();
|
|
81
|
+
setState(idle);
|
|
82
|
+
};
|
|
83
|
+
}, [loader, host, showHeader]);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const quill = quillReference.current;
|
|
87
|
+
if (!state.ready || quill === undefined || html === appliedHtml.current) return;
|
|
88
|
+
appliedHtml.current = html;
|
|
89
|
+
applyHtml(quill, html);
|
|
90
|
+
setState(current => ({ ...current, characters: characterCount(quill) }));
|
|
91
|
+
}, [html, state.ready]);
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const quill = quillReference.current;
|
|
95
|
+
if (!state.ready || quill === undefined) return;
|
|
96
|
+
quill.enable(editable);
|
|
97
|
+
quill.root.setAttribute('aria-readonly', String(!editable));
|
|
98
|
+
const toolbar = quill.getModule('toolbar') as { container?: HTMLElement } | undefined;
|
|
99
|
+
toolbar?.container?.querySelectorAll('button, select').forEach(control => {
|
|
100
|
+
if (editable) control.removeAttribute('disabled'); else control.setAttribute('disabled', '');
|
|
101
|
+
});
|
|
102
|
+
}, [editable, state.ready]);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
const quill = quillReference.current;
|
|
106
|
+
if (!state.ready || quill === undefined) return;
|
|
107
|
+
quill.root.setAttribute('role', 'textbox');
|
|
108
|
+
quill.root.setAttribute('aria-multiline', 'true');
|
|
109
|
+
quill.root.setAttribute('aria-label', ariaLabel);
|
|
110
|
+
if (placeholder === undefined) quill.root.removeAttribute('data-placeholder'); else quill.root.setAttribute('data-placeholder', placeholder);
|
|
111
|
+
}, [ariaLabel, placeholder, state.ready]);
|
|
112
|
+
|
|
113
|
+
return state;
|
|
114
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
5
|
+
import { SceneElementView } from '@cratis/scene.react';
|
|
6
|
+
import { primeReactComponents } from '../primeReactComponents';
|
|
7
|
+
import { sceneComponent, sceneGallery } from '../storyElements';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'PrimeReact/File',
|
|
11
|
+
component: SceneElementView,
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
parameters: {
|
|
14
|
+
layout: 'padded',
|
|
15
|
+
docs: { description: { component: 'File-upload controls through the Scene registry. A host supplies an upload handler or server URL when it wants files sent.' } },
|
|
16
|
+
},
|
|
17
|
+
} satisfies Meta<typeof SceneElementView>;
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
|
|
21
|
+
type Story = StoryObj<typeof meta>;
|
|
22
|
+
|
|
23
|
+
/** Every file component in this package, rendered through the real Scene registry. */
|
|
24
|
+
export const Gallery: Story = {
|
|
25
|
+
args: {
|
|
26
|
+
element: sceneGallery('gallery', [
|
|
27
|
+
sceneComponent('fileUpload', 'fileUpload', { mode: 'advanced', accept: 'image/*', multiple: true }),
|
|
28
|
+
]),
|
|
29
|
+
registry: primeReactComponents,
|
|
30
|
+
resolveBinding: () => undefined,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { createContext } from 'react';
|
|
5
|
+
import { FileUploadHandler } from './FileUploadHandler';
|
|
6
|
+
|
|
7
|
+
/** What a host provides to the file uploads inside it. */
|
|
8
|
+
export interface FileUploadContextValue {
|
|
9
|
+
/** The host-owned upload effect, when it owns the effect. */
|
|
10
|
+
handler: FileUploadHandler | undefined;
|
|
11
|
+
|
|
12
|
+
/** The origins, besides the page's own, an upload may be sent to. */
|
|
13
|
+
allowedOrigins: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Without a provider an upload goes only to the page's own origin and the control makes the request itself. */
|
|
17
|
+
export const FileUploadContext = createContext<FileUploadContextValue>({ handler: undefined, allowedOrigins: [] });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** A host-owned effect that sends selected files to the server the screen specifies. */
|
|
5
|
+
export interface FileUploadHandler {
|
|
6
|
+
/**
|
|
7
|
+
* Uploads a selection without coupling the portable Scene package to a host's server client.
|
|
8
|
+
*
|
|
9
|
+
* @param files The files selected by the user.
|
|
10
|
+
* @param serverUrl The upload address authored on the Scene element, after the origin policy accepted it
|
|
11
|
+
* (same origin as the page, or one the host allowed). `undefined` when none was authored; an address the
|
|
12
|
+
* policy refused never reaches the handler.
|
|
13
|
+
*/
|
|
14
|
+
(files: File[], serverUrl?: string): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { ReactNode, useMemo } from 'react';
|
|
5
|
+
import { FileUploadContext } from './FileUploadContext';
|
|
6
|
+
import { FileUploadHandler } from './FileUploadHandler';
|
|
7
|
+
|
|
8
|
+
export interface FileUploadHandlerProviderProps {
|
|
9
|
+
/**
|
|
10
|
+
* The host-owned upload effect available to the enclosed Scene surface. When omitted the control posts the
|
|
11
|
+
* files itself, to the authored address, under the origin policy.
|
|
12
|
+
*/
|
|
13
|
+
handler?: FileUploadHandler;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Origins besides the page's own that an authored upload address may name, for example
|
|
17
|
+
* `['https://files.example.com']`. An address on any other origin is refused before anything is sent - to
|
|
18
|
+
* the handler as well. Cookies are never sent to these origins.
|
|
19
|
+
*/
|
|
20
|
+
allowedOrigins?: readonly string[];
|
|
21
|
+
|
|
22
|
+
/** The surface whose file uploads the host configures. */
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Makes a host's upload effect and origin policy available without coupling the Scene package to that host's
|
|
28
|
+
* server client.
|
|
29
|
+
*/
|
|
30
|
+
export function FileUploadHandlerProvider({ handler, allowedOrigins = [], children }: FileUploadHandlerProviderProps) {
|
|
31
|
+
const value = useMemo(() => ({ handler, allowedOrigins }), [handler, allowedOrigins]);
|
|
32
|
+
return <FileUploadContext.Provider value={value}>{children}</FileUploadContext.Provider>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** The canonical operating modes for a file upload control. */
|
|
5
|
+
export enum FileUploadMode {
|
|
6
|
+
Advanced = 'advanced',
|
|
7
|
+
Basic = 'basic',
|
|
8
|
+
Auto = 'auto',
|
|
9
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { ChangeEvent, DragEvent, useContext, useMemo, useSyncExternalStore } from 'react';
|
|
5
|
+
import { FileUpload, type FileUploadChangeEvent, type FileUploadHandlerEvent } from 'primereact/fileupload';
|
|
6
|
+
import { RegisteredComponentProps } from '@cratis/scene.react';
|
|
7
|
+
import { booleanProperty, numberProperty, stringProperty } from '../properties';
|
|
8
|
+
import { resolveEnumeration } from '../resolveEnumeration';
|
|
9
|
+
import { FileUploadContext } from './FileUploadContext';
|
|
10
|
+
import { FileUploadMode } from './FileUploadMode';
|
|
11
|
+
import { resolveUploadTarget } from './resolveUploadTarget';
|
|
12
|
+
import { useUploadSession } from './useUploadSession';
|
|
13
|
+
|
|
14
|
+
const subscribeToNothing = () => () => undefined;
|
|
15
|
+
const pageOrigin = () => window.location.origin;
|
|
16
|
+
const noPageOrigin = () => undefined;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The `PrimeReact:fileUpload` component.
|
|
20
|
+
*
|
|
21
|
+
* PrimeReact 11 provides the accessible file picker and drop zone; this adapter owns everything that has an
|
|
22
|
+
* effect or a policy:
|
|
23
|
+
* - The authored `url` is untrusted data. It is checked by {@link resolveUploadTarget} - same origin as the
|
|
24
|
+
* page unless the host allows more through {@link FileUploadHandlerProvider} - and a refused address is
|
|
25
|
+
* reported and blocks every upload, the host's handler included.
|
|
26
|
+
* - Nothing is requested on mount. A request starts only when the user selects or drops files (automatic and
|
|
27
|
+
* basic mode) or presses the upload button.
|
|
28
|
+
* - `multiple: false` is enforced before anything is sent, for the file input and for drag and drop.
|
|
29
|
+
* - One upload runs at a time (nothing can be selected or dropped while it runs), and a failure is shown
|
|
30
|
+
* rather than thrown.
|
|
31
|
+
* - A disabled element (`isEnabled: false`) disables the picker, the upload button and the drop zone.
|
|
32
|
+
*
|
|
33
|
+
* Without a host handler the control posts the files itself with `fetch`, sending cookies to the page's own
|
|
34
|
+
* origin only.
|
|
35
|
+
*/
|
|
36
|
+
export function PrimeFileUpload({ element, interactions }: RegisteredComponentProps) {
|
|
37
|
+
const { handler, allowedOrigins } = useContext(FileUploadContext);
|
|
38
|
+
const origin = useSyncExternalStore(subscribeToNothing, pageOrigin, noPageOrigin);
|
|
39
|
+
const modeResolution = resolveEnumeration('mode', element.properties.mode, FileUploadMode, FileUploadMode.Advanced);
|
|
40
|
+
const mode = modeResolution.isValid ? modeResolution.value : FileUploadMode.Advanced;
|
|
41
|
+
const authoredUrl = stringProperty(element, 'url');
|
|
42
|
+
const target = useMemo(() => resolveUploadTarget(authoredUrl, origin, allowedOrigins), [authoredUrl, origin, allowedOrigins]);
|
|
43
|
+
const serverUrl = target.isValid ? target.url : undefined;
|
|
44
|
+
const multiple = booleanProperty(element, 'multiple', false);
|
|
45
|
+
const enabled = element.isEnabled;
|
|
46
|
+
const canUpload = target.isValid && (handler !== undefined || serverUrl !== undefined);
|
|
47
|
+
const session = useUploadSession(handler, serverUrl, stringProperty(element, 'name', 'files'));
|
|
48
|
+
|
|
49
|
+
const uploaded = async (event: FileUploadHandlerEvent) => {
|
|
50
|
+
if (!enabled || !canUpload) return;
|
|
51
|
+
if (await session.upload(event.files)) event.options.clear();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const changed = (event: FileUploadChangeEvent) => {
|
|
55
|
+
// Clearing the selection after an upload reports an empty change; it is not a new selection, and must not
|
|
56
|
+
// wipe the result of the upload that caused it.
|
|
57
|
+
if (event.acceptedFiles.length === 0 && event.rejectedFiles.length === 0) return;
|
|
58
|
+
const rejection = event.rejectedFiles.flatMap(rejected => rejected.errors.map(error => error.message)).join(' ');
|
|
59
|
+
session.setStatus(rejection === '' ? undefined : { kind: 'error', text: rejection });
|
|
60
|
+
interactions?.onChange?.();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const refuseSeveralFromInput = (event: ChangeEvent<HTMLElement>) => {
|
|
64
|
+
const input = event.target as HTMLInputElement;
|
|
65
|
+
if (input.type !== 'file') return;
|
|
66
|
+
if (session.busy) {
|
|
67
|
+
event.stopPropagation();
|
|
68
|
+
input.value = '';
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (multiple || (input.files?.length ?? 0) < 2) return;
|
|
73
|
+
event.stopPropagation();
|
|
74
|
+
input.value = '';
|
|
75
|
+
session.setStatus({ kind: 'error', text: 'Only one file can be uploaded at a time.' });
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const refuseDrop = (event: DragEvent<HTMLElement>) => {
|
|
79
|
+
if (!enabled) {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (session.busy) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
event.stopPropagation();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (multiple || (event.dataTransfer?.files.length ?? 0) < 2) return;
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
event.stopPropagation();
|
|
92
|
+
session.setStatus({ kind: 'error', text: 'Only one file can be uploaded at a time.' });
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div
|
|
97
|
+
data-scene-id={element.id}
|
|
98
|
+
data-scene-component='fileUpload'
|
|
99
|
+
onClick={interactions?.onClick}
|
|
100
|
+
onDoubleClick={interactions?.onDoubleClick}
|
|
101
|
+
onChangeCapture={refuseSeveralFromInput}
|
|
102
|
+
onDropCapture={refuseDrop}
|
|
103
|
+
onDragOverCapture={event => { if (!enabled) event.preventDefault(); }}>
|
|
104
|
+
<FileUpload.Root
|
|
105
|
+
accept={stringProperty(element, 'accept')}
|
|
106
|
+
auto={(mode === FileUploadMode.Auto || mode === FileUploadMode.Basic) && canUpload}
|
|
107
|
+
customUpload
|
|
108
|
+
disabled={!enabled}
|
|
109
|
+
maxFileSize={numberProperty(element, 'maxFileSize')}
|
|
110
|
+
multiple={multiple}
|
|
111
|
+
name={stringProperty(element, 'name', 'files')}
|
|
112
|
+
uploadHandler={uploaded}
|
|
113
|
+
onChange={changed}>
|
|
114
|
+
<FileUpload.Content aria-label={stringProperty(element, 'ariaLabel', 'File upload drop zone')}>
|
|
115
|
+
<p>{mode === FileUploadMode.Basic ? 'Choose a file to upload.' : 'Drop files here or choose files to upload.'}</p>
|
|
116
|
+
<FileUpload.Trigger disabled={!enabled || session.busy}>{mode === FileUploadMode.Basic ? 'Choose file' : 'Choose files'}</FileUpload.Trigger>
|
|
117
|
+
{mode === FileUploadMode.Advanced && (
|
|
118
|
+
<FileUpload.Upload disabled={!enabled || !canUpload || session.busy}>Upload selected files</FileUpload.Upload>
|
|
119
|
+
)}
|
|
120
|
+
{!modeResolution.isValid && <p role='alert' data-scene-state='unsupported-mode'>{modeResolution.message}</p>}
|
|
121
|
+
{!target.isValid && <p role='alert' data-scene-state='refused-address'>{target.message}</p>}
|
|
122
|
+
{target.isValid && !canUpload && <p role='status'>Configure a server URL or provide an upload handler to upload files.</p>}
|
|
123
|
+
{session.busy && <p role='status'>Uploading…</p>}
|
|
124
|
+
{session.status !== undefined && <p role={session.status.kind === 'error' ? 'alert' : 'status'}>{session.status.text}</p>}
|
|
125
|
+
</FileUpload.Content>
|
|
126
|
+
</FileUpload.Root>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** What a file upload last reported to the person using it. */
|
|
5
|
+
export interface UploadStatus {
|
|
6
|
+
/** `error` is announced assertively; `success` politely. */
|
|
7
|
+
kind: 'success' | 'error';
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** The outcome of checking the `url` a file upload element names against the origin policy. */
|
|
5
|
+
export type UploadTarget =
|
|
6
|
+
| {
|
|
7
|
+
isValid: true;
|
|
8
|
+
|
|
9
|
+
/** The address to send to: a relative address as authored, or a normalized absolute one. `undefined` when none was authored. */
|
|
10
|
+
url: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
| { isValid: false; message: string };
|
package/file/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
export * from './FileUploadHandler';
|
|
5
|
+
export * from './FileUploadHandlerProvider';
|
|
6
|
+
export * from './FileUploadMode';
|
|
7
|
+
export * from './PrimeFileUpload';
|
|
8
|
+
export * from './resolveUploadTarget';
|
|
9
|
+
export * from './UploadTarget';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sends files to an address as a `multipart/form-data` POST.
|
|
6
|
+
*
|
|
7
|
+
* Cookies are sent only to the page's own origin (`credentials: 'same-origin'`), so an upload to another
|
|
8
|
+
* origin the host allowed carries no credentials. A redirect fails the upload rather than being followed,
|
|
9
|
+
* because following one would send the files to an address the origin policy never checked.
|
|
10
|
+
*
|
|
11
|
+
* @param url The address, already checked by `resolveUploadTarget`.
|
|
12
|
+
* @param field The multipart field name each file is appended under.
|
|
13
|
+
* @param files The files to send.
|
|
14
|
+
* @param signal Cancels the request.
|
|
15
|
+
* @throws When the request fails or the server does not answer with a success status.
|
|
16
|
+
*/
|
|
17
|
+
export async function postFiles(url: string, field: string, files: File[], signal: AbortSignal): Promise<void> {
|
|
18
|
+
const body = new FormData();
|
|
19
|
+
for (const file of files) body.append(field, file, file.name);
|
|
20
|
+
|
|
21
|
+
const response = await fetch(url, { method: 'POST', body, credentials: 'same-origin', redirect: 'error', signal });
|
|
22
|
+
if (!response.ok) throw new Error(`The server answered ${response.status}${response.statusText === '' ? '' : ` ${response.statusText}`}.`);
|
|
23
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { UploadTarget } from './UploadTarget';
|
|
5
|
+
|
|
6
|
+
/** A scheme (`https:`) or a protocol-relative prefix (`//host`): the address names its own host. */
|
|
7
|
+
const namesItsOwnHost = /^([a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
8
|
+
|
|
9
|
+
/** Whitespace, control characters and backslashes, which browsers normalize in ways that disguise another host. */
|
|
10
|
+
const disguisesAHost = (address: string) => Array.from(address).some(character => character <= ' ' || character === '\u007f' || character === '\\');
|
|
11
|
+
|
|
12
|
+
/** Resolved against, to read an address's own parts without depending on a page. It is never sent to. */
|
|
13
|
+
const placeholderOrigin = 'https://upload-target.invalid';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Checks the address an authored upload sends files to.
|
|
17
|
+
*
|
|
18
|
+
* The address comes from authored model data, so it is not trusted. By default an upload may only go to the
|
|
19
|
+
* origin the page was served from - a relative address, or an absolute one with the same origin. A host that
|
|
20
|
+
* needs another origin names it in `allowedOrigins` when it provides the upload handler. Whatever is accepted
|
|
21
|
+
* has the scheme `http:` or `https:`, carries no user information, and contains no whitespace, control
|
|
22
|
+
* characters or backslashes, because those are how an address is made to point somewhere other than where it
|
|
23
|
+
* reads.
|
|
24
|
+
*
|
|
25
|
+
* @param url The authored address, if any.
|
|
26
|
+
* @param pageOrigin The origin of the page, `undefined` where there is no page (server rendering).
|
|
27
|
+
* @param allowedOrigins The additional origins the host permits.
|
|
28
|
+
* @returns The address to send to, or why it cannot be used.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveUploadTarget(url: string | undefined, pageOrigin: string | undefined, allowedOrigins: readonly string[]): UploadTarget {
|
|
31
|
+
if (url === undefined || url === '') return { isValid: true, url: undefined };
|
|
32
|
+
if (disguisesAHost(url)) return refused('it contains whitespace, control characters or a backslash');
|
|
33
|
+
|
|
34
|
+
let resolved: URL;
|
|
35
|
+
try {
|
|
36
|
+
resolved = new URL(url, placeholderOrigin);
|
|
37
|
+
} catch {
|
|
38
|
+
return refused('it is not a valid address');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (resolved.protocol !== 'http:' && resolved.protocol !== 'https:') return refused(`the ${resolved.protocol} scheme is not allowed`);
|
|
42
|
+
if (resolved.username !== '' || resolved.password !== '') return refused('it carries user information');
|
|
43
|
+
if (!namesItsOwnHost.test(url)) return { isValid: true, url };
|
|
44
|
+
|
|
45
|
+
const permitted = [...(pageOrigin === undefined ? [] : [pageOrigin]), ...allowedOrigins.flatMap(originOf)];
|
|
46
|
+
return permitted.includes(resolved.origin)
|
|
47
|
+
? { isValid: true, url: resolved.href }
|
|
48
|
+
: refused(`${resolved.origin} is not the origin of this page or one the host allows`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function originOf(address: string): string[] {
|
|
52
|
+
try {
|
|
53
|
+
return [new URL(address).origin];
|
|
54
|
+
} catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function refused(reason: string): UploadTarget {
|
|
60
|
+
return { isValid: false, message: `The upload address is not allowed: ${reason}.` };
|
|
61
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { FileUploadHandler } from './FileUploadHandler';
|
|
6
|
+
import { UploadStatus } from './UploadStatus';
|
|
7
|
+
import { postFiles } from './postFiles';
|
|
8
|
+
|
|
9
|
+
/** The state of the upload in flight, if any, and the action that starts one. */
|
|
10
|
+
export interface UploadSession {
|
|
11
|
+
/** Whether an upload is running. */
|
|
12
|
+
busy: boolean;
|
|
13
|
+
|
|
14
|
+
/** The last result, or `undefined` before the first upload and after a new selection. */
|
|
15
|
+
status: UploadStatus | undefined;
|
|
16
|
+
setStatus(status: UploadStatus | undefined): void;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sends the files, unless an upload is already running.
|
|
20
|
+
*
|
|
21
|
+
* @returns Whether the files were sent, so the caller knows to clear its selection. A failure is reported
|
|
22
|
+
* through `status` and returns `false`: the selection stays so it can be retried.
|
|
23
|
+
*/
|
|
24
|
+
upload(files: File[]): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Runs one upload at a time for a control, through the host's handler or a POST to the checked address.
|
|
29
|
+
*
|
|
30
|
+
* Starting an upload while one runs is ignored rather than queued, so a double click cannot send the same
|
|
31
|
+
* files twice. Every failure ends in a visible `status`; none escapes as an unhandled rejection. An upload
|
|
32
|
+
* still running when the control unmounts is cancelled.
|
|
33
|
+
*
|
|
34
|
+
* @param handler The host's upload effect, if it owns the effect.
|
|
35
|
+
* @param url The checked address, if one was authored.
|
|
36
|
+
* @param field The multipart field name used when the control posts the files itself.
|
|
37
|
+
*/
|
|
38
|
+
export function useUploadSession(handler: FileUploadHandler | undefined, url: string | undefined, field: string): UploadSession {
|
|
39
|
+
const running = useRef<AbortController | undefined>(undefined);
|
|
40
|
+
const [busy, setBusy] = useState(false);
|
|
41
|
+
const [status, setStatus] = useState<UploadStatus | undefined>(undefined);
|
|
42
|
+
|
|
43
|
+
useEffect(() => () => running.current?.abort(), []);
|
|
44
|
+
|
|
45
|
+
const upload = useCallback(async (files: File[]): Promise<boolean> => {
|
|
46
|
+
if (running.current !== undefined) return false;
|
|
47
|
+
const controller = new AbortController();
|
|
48
|
+
running.current = controller;
|
|
49
|
+
setBusy(true);
|
|
50
|
+
setStatus(undefined);
|
|
51
|
+
try {
|
|
52
|
+
if (handler !== undefined) await handler(files, url);
|
|
53
|
+
else if (url !== undefined) await postFiles(url, field, files, controller.signal);
|
|
54
|
+
else throw new Error('There is nowhere to send the files.');
|
|
55
|
+
setStatus({ kind: 'success', text: `${files.length} file${files.length === 1 ? '' : 's'} uploaded.` });
|
|
56
|
+
return true;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (!controller.signal.aborted) setStatus({ kind: 'error', text: `Upload failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
59
|
+
return false;
|
|
60
|
+
} finally {
|
|
61
|
+
running.current = undefined;
|
|
62
|
+
setBusy(false);
|
|
63
|
+
}
|
|
64
|
+
}, [handler, url, field]);
|
|
65
|
+
|
|
66
|
+
return { busy, status, setStatus, upload };
|
|
67
|
+
}
|
|
@@ -26,6 +26,7 @@ export const Gallery: Story = {
|
|
|
26
26
|
element: sceneGallery('gallery', [
|
|
27
27
|
sceneComponent('inputText', 'inputText', { placeholder: 'Name' }),
|
|
28
28
|
sceneComponent('inputTextarea', 'inputTextarea', { placeholder: 'Notes', rows: 3 }),
|
|
29
|
+
sceneComponent('editor', 'editor', { value: '<p>Write a <strong>rich</strong> note.</p>' }),
|
|
29
30
|
sceneComponent('inputNumber', 'inputNumber', { value: 42, showButtons: true }),
|
|
30
31
|
sceneComponent('password', 'password', { placeholder: 'Password' }),
|
|
31
32
|
sceneComponent('floatLabel', 'floatLabel', { label: 'Project name' }),
|
|
@@ -35,6 +36,14 @@ export const Gallery: Story = {
|
|
|
35
36
|
sceneComponent('listBox', 'listBox', { options: ['Daily', 'Weekly', 'Monthly'] }),
|
|
36
37
|
sceneComponent('selectButton', 'selectButton', { options: ['Day', 'Week', 'Month'], value: 'Week' }),
|
|
37
38
|
sceneComponent('checkbox', 'checkbox', { label: 'Notify me', checked: true }),
|
|
39
|
+
sceneComponent('multiStateCheckbox', 'multiStateCheckbox', {
|
|
40
|
+
value: null,
|
|
41
|
+
options: [
|
|
42
|
+
{ label: 'Approved', value: 'approved', icon: 'pi pi-check' },
|
|
43
|
+
{ label: 'Rejected', value: 'rejected', icon: 'pi pi-times' },
|
|
44
|
+
],
|
|
45
|
+
ariaLabel: 'Review state',
|
|
46
|
+
}),
|
|
38
47
|
sceneComponent('radioButton', 'radioButton', { options: ['Email', 'SMS'], value: 'Email' }),
|
|
39
48
|
sceneComponent('toggleSwitch', 'toggleSwitch', { label: 'Enabled', checked: true }),
|
|
40
49
|
sceneComponent('slider', 'slider', { value: 35 }),
|