@cratis/scene.primereact 4.6.0 → 4.8.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.
Files changed (388) hide show
  1. package/.storybook/preview.tsx +8 -1
  2. package/EnumerationResolution.ts +7 -0
  3. package/README.md +22 -12
  4. package/data/DataGallery.stories.tsx +6 -0
  5. package/data/DataScrollerRow.tsx +35 -0
  6. package/data/PrimeDataScroller.tsx +84 -0
  7. package/data/PrimeTreeTable.tsx +145 -0
  8. package/data/TreeTableCheckState.ts +9 -0
  9. package/data/TreeTableRow.ts +22 -0
  10. package/data/TreeTableRowView.tsx +86 -0
  11. package/data/TreeTableSelectionMode.ts +10 -0
  12. package/data/checkboxSelection.ts +92 -0
  13. package/data/index.ts +4 -0
  14. package/data/isSerializedLegacyElement.ts +30 -0
  15. package/data/readableValue.ts +32 -0
  16. package/data/selectionKeysOf.ts +40 -0
  17. package/data/treeTableRows.ts +32 -0
  18. package/data/useIncrementalLoad.ts +36 -0
  19. package/data/useLoadOnReach.ts +31 -0
  20. package/data/useTreeGridNavigation.ts +81 -0
  21. package/dist/cjs/data/DataScrollerRow.js +50 -0
  22. package/dist/cjs/data/DataScrollerRow.js.map +1 -0
  23. package/dist/cjs/data/PrimeDataScroller.js +120 -0
  24. package/dist/cjs/data/PrimeDataScroller.js.map +1 -0
  25. package/dist/cjs/data/PrimeTreeTable.js +217 -0
  26. package/dist/cjs/data/PrimeTreeTable.js.map +1 -0
  27. package/dist/cjs/data/TreeTableCheckState.js +13 -0
  28. package/dist/cjs/data/TreeTableCheckState.js.map +1 -0
  29. package/dist/cjs/data/TreeTableRowView.js +76 -0
  30. package/dist/cjs/data/TreeTableRowView.js.map +1 -0
  31. package/dist/cjs/data/TreeTableSelectionMode.js +14 -0
  32. package/dist/cjs/data/TreeTableSelectionMode.js.map +1 -0
  33. package/dist/cjs/data/checkboxSelection.js +88 -0
  34. package/dist/cjs/data/checkboxSelection.js.map +1 -0
  35. package/dist/cjs/data/isSerializedLegacyElement.js +30 -0
  36. package/dist/cjs/data/isSerializedLegacyElement.js.map +1 -0
  37. package/dist/cjs/data/readableValue.js +31 -0
  38. package/dist/cjs/data/readableValue.js.map +1 -0
  39. package/dist/cjs/data/selectionKeysOf.js +40 -0
  40. package/dist/cjs/data/selectionKeysOf.js.map +1 -0
  41. package/dist/cjs/data/treeTableRows.js +42 -0
  42. package/dist/cjs/data/treeTableRows.js.map +1 -0
  43. package/dist/cjs/data/useIncrementalLoad.js +46 -0
  44. package/dist/cjs/data/useIncrementalLoad.js.map +1 -0
  45. package/dist/cjs/data/useLoadOnReach.js +42 -0
  46. package/dist/cjs/data/useLoadOnReach.js.map +1 -0
  47. package/dist/cjs/data/useTreeGridNavigation.js +75 -0
  48. package/dist/cjs/data/useTreeGridNavigation.js.map +1 -0
  49. package/dist/cjs/editor/PrimeEditor.js +96 -0
  50. package/dist/cjs/editor/PrimeEditor.js.map +1 -0
  51. package/dist/cjs/editor/QuillLoaderContext.js +10 -0
  52. package/dist/cjs/editor/QuillLoaderContext.js.map +1 -0
  53. package/dist/cjs/editor/QuillLoaderProvider.js +21 -0
  54. package/dist/cjs/editor/QuillLoaderProvider.js.map +1 -0
  55. package/dist/cjs/editor/applyHtml.js +25 -0
  56. package/dist/cjs/editor/applyHtml.js.map +1 -0
  57. package/dist/cjs/editor/characterCount.js +10 -0
  58. package/dist/cjs/editor/characterCount.js.map +1 -0
  59. package/dist/cjs/editor/htmlToPlainText.js +101 -0
  60. package/dist/cjs/editor/htmlToPlainText.js.map +1 -0
  61. package/dist/cjs/editor/renderSafeNodes.js +35 -0
  62. package/dist/cjs/editor/renderSafeNodes.js.map +1 -0
  63. package/dist/cjs/editor/safeUrls.js +44 -0
  64. package/dist/cjs/editor/safeUrls.js.map +1 -0
  65. package/dist/cjs/editor/sanitizeHtml.js +140 -0
  66. package/dist/cjs/editor/sanitizeHtml.js.map +1 -0
  67. package/dist/cjs/editor/serializeSafeNodes.js +33 -0
  68. package/dist/cjs/editor/serializeSafeNodes.js.map +1 -0
  69. package/dist/cjs/editor/toolbarLayout.js +35 -0
  70. package/dist/cjs/editor/toolbarLayout.js.map +1 -0
  71. package/dist/cjs/editor/useQuillEditor.js +137 -0
  72. package/dist/cjs/editor/useQuillEditor.js.map +1 -0
  73. package/dist/cjs/file/FileUploadContext.js +13 -0
  74. package/dist/cjs/file/FileUploadContext.js.map +1 -0
  75. package/dist/cjs/file/FileUploadHandlerProvider.js +25 -0
  76. package/dist/cjs/file/FileUploadHandlerProvider.js.map +1 -0
  77. package/dist/cjs/file/FileUploadMode.js +13 -0
  78. package/dist/cjs/file/FileUploadMode.js.map +1 -0
  79. package/dist/cjs/file/PrimeFileUpload.js +161 -0
  80. package/dist/cjs/file/PrimeFileUpload.js.map +1 -0
  81. package/dist/cjs/file/postFiles.js +31 -0
  82. package/dist/cjs/file/postFiles.js.map +1 -0
  83. package/dist/cjs/file/resolveUploadTarget.js +68 -0
  84. package/dist/cjs/file/resolveUploadTarget.js.map +1 -0
  85. package/dist/cjs/file/useUploadSession.js +62 -0
  86. package/dist/cjs/file/useUploadSession.js.map +1 -0
  87. package/dist/cjs/index.js +32 -0
  88. package/dist/cjs/index.js.map +1 -1
  89. package/dist/cjs/legacyEnumerationChoices.js +39 -0
  90. package/dist/cjs/legacyEnumerationChoices.js.map +1 -0
  91. package/dist/cjs/loadQuill/index.js +22 -0
  92. package/dist/cjs/loadQuill/index.js.map +1 -0
  93. package/dist/cjs/primeReactComponents.js +10 -0
  94. package/dist/cjs/primeReactComponents.js.map +1 -1
  95. package/dist/cjs/primeReactDescriptors.js +243 -0
  96. package/dist/cjs/primeReactDescriptors.js.map +1 -1
  97. package/dist/cjs/primeReactPackage.js +4 -0
  98. package/dist/cjs/primeReactPackage.js.map +1 -1
  99. package/dist/cjs/resolveEnumeration.js +55 -0
  100. package/dist/cjs/resolveEnumeration.js.map +1 -0
  101. package/dist/cjs/treeNodes.js +42 -7
  102. package/dist/cjs/treeNodes.js.map +1 -1
  103. package/dist/cjs/useResettingState.js +47 -0
  104. package/dist/cjs/useResettingState.js.map +1 -0
  105. package/dist/esm/EnumerationResolution.d.ts +9 -0
  106. package/dist/esm/EnumerationResolution.d.ts.map +1 -0
  107. package/dist/esm/EnumerationResolution.js +2 -0
  108. package/dist/esm/EnumerationResolution.js.map +1 -0
  109. package/dist/esm/data/DataGallery.stories.d.ts.map +1 -1
  110. package/dist/esm/data/DataGallery.stories.js +6 -0
  111. package/dist/esm/data/DataGallery.stories.js.map +1 -1
  112. package/dist/esm/data/DataScrollerRow.d.ts +4 -0
  113. package/dist/esm/data/DataScrollerRow.d.ts.map +1 -0
  114. package/dist/esm/data/DataScrollerRow.js +48 -0
  115. package/dist/esm/data/DataScrollerRow.js.map +1 -0
  116. package/dist/esm/data/PrimeDataScroller.d.ts +3 -0
  117. package/dist/esm/data/PrimeDataScroller.d.ts.map +1 -0
  118. package/dist/esm/data/PrimeDataScroller.js +118 -0
  119. package/dist/esm/data/PrimeDataScroller.js.map +1 -0
  120. package/dist/esm/data/PrimeTreeTable.d.ts +3 -0
  121. package/dist/esm/data/PrimeTreeTable.d.ts.map +1 -0
  122. package/dist/esm/data/PrimeTreeTable.js +215 -0
  123. package/dist/esm/data/PrimeTreeTable.js.map +1 -0
  124. package/dist/esm/data/TreeTableCheckState.d.ts +6 -0
  125. package/dist/esm/data/TreeTableCheckState.d.ts.map +1 -0
  126. package/dist/esm/data/TreeTableCheckState.js +11 -0
  127. package/dist/esm/data/TreeTableCheckState.js.map +1 -0
  128. package/dist/esm/data/TreeTableRow.d.ts +10 -0
  129. package/dist/esm/data/TreeTableRow.d.ts.map +1 -0
  130. package/dist/esm/data/TreeTableRow.js +2 -0
  131. package/dist/esm/data/TreeTableRow.js.map +1 -0
  132. package/dist/esm/data/TreeTableRowView.d.ts +21 -0
  133. package/dist/esm/data/TreeTableRowView.d.ts.map +1 -0
  134. package/dist/esm/data/TreeTableRowView.js +74 -0
  135. package/dist/esm/data/TreeTableRowView.js.map +1 -0
  136. package/dist/esm/data/TreeTableSelectionMode.d.ts +7 -0
  137. package/dist/esm/data/TreeTableSelectionMode.d.ts.map +1 -0
  138. package/dist/esm/data/TreeTableSelectionMode.js +12 -0
  139. package/dist/esm/data/TreeTableSelectionMode.js.map +1 -0
  140. package/dist/esm/data/checkboxSelection.d.ts +9 -0
  141. package/dist/esm/data/checkboxSelection.d.ts.map +1 -0
  142. package/dist/esm/data/checkboxSelection.js +85 -0
  143. package/dist/esm/data/checkboxSelection.js.map +1 -0
  144. package/dist/esm/data/index.d.ts +4 -0
  145. package/dist/esm/data/index.d.ts.map +1 -1
  146. package/dist/esm/data/index.js +4 -0
  147. package/dist/esm/data/index.js.map +1 -1
  148. package/dist/esm/data/isSerializedLegacyElement.d.ts +3 -0
  149. package/dist/esm/data/isSerializedLegacyElement.d.ts.map +1 -0
  150. package/dist/esm/data/isSerializedLegacyElement.js +27 -0
  151. package/dist/esm/data/isSerializedLegacyElement.js.map +1 -0
  152. package/dist/esm/data/readableValue.d.ts +2 -0
  153. package/dist/esm/data/readableValue.d.ts.map +1 -0
  154. package/dist/esm/data/readableValue.js +29 -0
  155. package/dist/esm/data/readableValue.js.map +1 -0
  156. package/dist/esm/data/selectionKeysOf.d.ts +2 -0
  157. package/dist/esm/data/selectionKeysOf.d.ts.map +1 -0
  158. package/dist/esm/data/selectionKeysOf.js +38 -0
  159. package/dist/esm/data/selectionKeysOf.js.map +1 -0
  160. package/dist/esm/data/treeTableRows.d.ts +6 -0
  161. package/dist/esm/data/treeTableRows.d.ts.map +1 -0
  162. package/dist/esm/data/treeTableRows.js +38 -0
  163. package/dist/esm/data/treeTableRows.js.map +1 -0
  164. package/dist/esm/data/useIncrementalLoad.d.ts +6 -0
  165. package/dist/esm/data/useIncrementalLoad.d.ts.map +1 -0
  166. package/dist/esm/data/useIncrementalLoad.js +44 -0
  167. package/dist/esm/data/useIncrementalLoad.js.map +1 -0
  168. package/dist/esm/data/useLoadOnReach.d.ts +3 -0
  169. package/dist/esm/data/useLoadOnReach.d.ts.map +1 -0
  170. package/dist/esm/data/useLoadOnReach.js +40 -0
  171. package/dist/esm/data/useLoadOnReach.js.map +1 -0
  172. package/dist/esm/data/useTreeGridNavigation.d.ts +17 -0
  173. package/dist/esm/data/useTreeGridNavigation.d.ts.map +1 -0
  174. package/dist/esm/data/useTreeGridNavigation.js +73 -0
  175. package/dist/esm/data/useTreeGridNavigation.js.map +1 -0
  176. package/dist/esm/editor/PrimeEditor.d.ts +3 -0
  177. package/dist/esm/editor/PrimeEditor.d.ts.map +1 -0
  178. package/dist/esm/editor/PrimeEditor.js +94 -0
  179. package/dist/esm/editor/PrimeEditor.js.map +1 -0
  180. package/dist/esm/editor/QuillConstructor.d.ts +4 -0
  181. package/dist/esm/editor/QuillConstructor.d.ts.map +1 -0
  182. package/dist/esm/editor/QuillConstructor.js +2 -0
  183. package/dist/esm/editor/QuillConstructor.js.map +1 -0
  184. package/dist/esm/editor/QuillEditorOptions.d.ts +13 -0
  185. package/dist/esm/editor/QuillEditorOptions.d.ts.map +1 -0
  186. package/dist/esm/editor/QuillEditorOptions.js +2 -0
  187. package/dist/esm/editor/QuillEditorOptions.js.map +1 -0
  188. package/dist/esm/editor/QuillEditorState.d.ts +6 -0
  189. package/dist/esm/editor/QuillEditorState.d.ts.map +1 -0
  190. package/dist/esm/editor/QuillEditorState.js +2 -0
  191. package/dist/esm/editor/QuillEditorState.js.map +1 -0
  192. package/dist/esm/editor/QuillInstance.d.ts +16 -0
  193. package/dist/esm/editor/QuillInstance.d.ts.map +1 -0
  194. package/dist/esm/editor/QuillInstance.js +2 -0
  195. package/dist/esm/editor/QuillInstance.js.map +1 -0
  196. package/dist/esm/editor/QuillLoader.d.ts +5 -0
  197. package/dist/esm/editor/QuillLoader.d.ts.map +1 -0
  198. package/dist/esm/editor/QuillLoader.js +2 -0
  199. package/dist/esm/editor/QuillLoader.js.map +1 -0
  200. package/dist/esm/editor/QuillLoaderContext.d.ts +3 -0
  201. package/dist/esm/editor/QuillLoaderContext.d.ts.map +1 -0
  202. package/dist/esm/editor/QuillLoaderContext.js +8 -0
  203. package/dist/esm/editor/QuillLoaderContext.js.map +1 -0
  204. package/dist/esm/editor/QuillLoaderProvider.d.ts +8 -0
  205. package/dist/esm/editor/QuillLoaderProvider.d.ts.map +1 -0
  206. package/dist/esm/editor/QuillLoaderProvider.js +19 -0
  207. package/dist/esm/editor/QuillLoaderProvider.js.map +1 -0
  208. package/dist/esm/editor/QuillOptions.d.ts +9 -0
  209. package/dist/esm/editor/QuillOptions.d.ts.map +1 -0
  210. package/dist/esm/editor/QuillOptions.js +2 -0
  211. package/dist/esm/editor/QuillOptions.js.map +1 -0
  212. package/dist/esm/editor/SafeElement.d.ts +7 -0
  213. package/dist/esm/editor/SafeElement.d.ts.map +1 -0
  214. package/dist/esm/editor/SafeElement.js +2 -0
  215. package/dist/esm/editor/SafeElement.js.map +1 -0
  216. package/dist/esm/editor/SafeNode.d.ts +3 -0
  217. package/dist/esm/editor/SafeNode.d.ts.map +1 -0
  218. package/dist/esm/editor/SafeNode.js +2 -0
  219. package/dist/esm/editor/SafeNode.js.map +1 -0
  220. package/dist/esm/editor/applyHtml.d.ts +3 -0
  221. package/dist/esm/editor/applyHtml.d.ts.map +1 -0
  222. package/dist/esm/editor/applyHtml.js +23 -0
  223. package/dist/esm/editor/applyHtml.js.map +1 -0
  224. package/dist/esm/editor/characterCount.d.ts +3 -0
  225. package/dist/esm/editor/characterCount.d.ts.map +1 -0
  226. package/dist/esm/editor/characterCount.js +8 -0
  227. package/dist/esm/editor/characterCount.js.map +1 -0
  228. package/dist/esm/editor/htmlToPlainText.d.ts +2 -0
  229. package/dist/esm/editor/htmlToPlainText.d.ts.map +1 -0
  230. package/dist/esm/editor/htmlToPlainText.js +99 -0
  231. package/dist/esm/editor/htmlToPlainText.js.map +1 -0
  232. package/dist/esm/editor/index.d.ts +14 -0
  233. package/dist/esm/editor/index.d.ts.map +1 -0
  234. package/dist/esm/editor/index.js +14 -0
  235. package/dist/esm/editor/index.js.map +1 -0
  236. package/dist/esm/editor/renderSafeNodes.d.ts +4 -0
  237. package/dist/esm/editor/renderSafeNodes.d.ts.map +1 -0
  238. package/dist/esm/editor/renderSafeNodes.js +33 -0
  239. package/dist/esm/editor/renderSafeNodes.js.map +1 -0
  240. package/dist/esm/editor/safeUrls.d.ts +3 -0
  241. package/dist/esm/editor/safeUrls.d.ts.map +1 -0
  242. package/dist/esm/editor/safeUrls.js +41 -0
  243. package/dist/esm/editor/safeUrls.js.map +1 -0
  244. package/dist/esm/editor/sanitizeHtml.d.ts +3 -0
  245. package/dist/esm/editor/sanitizeHtml.d.ts.map +1 -0
  246. package/dist/esm/editor/sanitizeHtml.js +138 -0
  247. package/dist/esm/editor/sanitizeHtml.js.map +1 -0
  248. package/dist/esm/editor/serializeSafeNodes.d.ts +3 -0
  249. package/dist/esm/editor/serializeSafeNodes.d.ts.map +1 -0
  250. package/dist/esm/editor/serializeSafeNodes.js +31 -0
  251. package/dist/esm/editor/serializeSafeNodes.js.map +1 -0
  252. package/dist/esm/editor/toolbarLayout.d.ts +2 -0
  253. package/dist/esm/editor/toolbarLayout.d.ts.map +1 -0
  254. package/dist/esm/editor/toolbarLayout.js +33 -0
  255. package/dist/esm/editor/toolbarLayout.js.map +1 -0
  256. package/dist/esm/editor/useQuillEditor.d.ts +4 -0
  257. package/dist/esm/editor/useQuillEditor.d.ts.map +1 -0
  258. package/dist/esm/editor/useQuillEditor.js +135 -0
  259. package/dist/esm/editor/useQuillEditor.js.map +1 -0
  260. package/dist/esm/file/FileGallery.stories.d.ts +19 -0
  261. package/dist/esm/file/FileGallery.stories.d.ts.map +1 -0
  262. package/dist/esm/file/FileGallery.stories.js +23 -0
  263. package/dist/esm/file/FileGallery.stories.js.map +1 -0
  264. package/dist/esm/file/FileUploadContext.d.ts +7 -0
  265. package/dist/esm/file/FileUploadContext.d.ts.map +1 -0
  266. package/dist/esm/file/FileUploadContext.js +11 -0
  267. package/dist/esm/file/FileUploadContext.js.map +1 -0
  268. package/dist/esm/file/FileUploadHandler.d.ts +4 -0
  269. package/dist/esm/file/FileUploadHandler.d.ts.map +1 -0
  270. package/dist/esm/file/FileUploadHandler.js +2 -0
  271. package/dist/esm/file/FileUploadHandler.js.map +1 -0
  272. package/dist/esm/file/FileUploadHandlerProvider.d.ts +9 -0
  273. package/dist/esm/file/FileUploadHandlerProvider.d.ts.map +1 -0
  274. package/dist/esm/file/FileUploadHandlerProvider.js +23 -0
  275. package/dist/esm/file/FileUploadHandlerProvider.js.map +1 -0
  276. package/dist/esm/file/FileUploadMode.d.ts +6 -0
  277. package/dist/esm/file/FileUploadMode.d.ts.map +1 -0
  278. package/dist/esm/file/FileUploadMode.js +11 -0
  279. package/dist/esm/file/FileUploadMode.js.map +1 -0
  280. package/dist/esm/file/PrimeFileUpload.d.ts +3 -0
  281. package/dist/esm/file/PrimeFileUpload.d.ts.map +1 -0
  282. package/dist/esm/file/PrimeFileUpload.js +159 -0
  283. package/dist/esm/file/PrimeFileUpload.js.map +1 -0
  284. package/dist/esm/file/UploadStatus.d.ts +5 -0
  285. package/dist/esm/file/UploadStatus.d.ts.map +1 -0
  286. package/dist/esm/file/UploadStatus.js +2 -0
  287. package/dist/esm/file/UploadStatus.js.map +1 -0
  288. package/dist/esm/file/UploadTarget.d.ts +8 -0
  289. package/dist/esm/file/UploadTarget.d.ts.map +1 -0
  290. package/dist/esm/file/UploadTarget.js +2 -0
  291. package/dist/esm/file/UploadTarget.js.map +1 -0
  292. package/dist/esm/file/index.d.ts +7 -0
  293. package/dist/esm/file/index.d.ts.map +1 -0
  294. package/dist/esm/file/index.js +7 -0
  295. package/dist/esm/file/index.js.map +1 -0
  296. package/dist/esm/file/postFiles.d.ts +2 -0
  297. package/dist/esm/file/postFiles.d.ts.map +1 -0
  298. package/dist/esm/file/postFiles.js +29 -0
  299. package/dist/esm/file/postFiles.js.map +1 -0
  300. package/dist/esm/file/resolveUploadTarget.d.ts +3 -0
  301. package/dist/esm/file/resolveUploadTarget.d.ts.map +1 -0
  302. package/dist/esm/file/resolveUploadTarget.js +66 -0
  303. package/dist/esm/file/resolveUploadTarget.js.map +1 -0
  304. package/dist/esm/file/useUploadSession.d.ts +10 -0
  305. package/dist/esm/file/useUploadSession.d.ts.map +1 -0
  306. package/dist/esm/file/useUploadSession.js +60 -0
  307. package/dist/esm/file/useUploadSession.js.map +1 -0
  308. package/dist/esm/form/FormGallery.stories.d.ts.map +1 -1
  309. package/dist/esm/form/FormGallery.stories.js +1 -0
  310. package/dist/esm/form/FormGallery.stories.js.map +1 -1
  311. package/dist/esm/index.d.ts +2 -0
  312. package/dist/esm/index.d.ts.map +1 -1
  313. package/dist/esm/index.js +16 -1
  314. package/dist/esm/index.js.map +1 -1
  315. package/dist/esm/legacyEnumerationChoices.d.ts +3 -0
  316. package/dist/esm/legacyEnumerationChoices.d.ts.map +1 -0
  317. package/dist/esm/legacyEnumerationChoices.js +37 -0
  318. package/dist/esm/legacyEnumerationChoices.js.map +1 -0
  319. package/dist/esm/loadQuill/index.d.ts +3 -0
  320. package/dist/esm/loadQuill/index.d.ts.map +1 -0
  321. package/dist/esm/loadQuill/index.js +20 -0
  322. package/dist/esm/loadQuill/index.js.map +1 -0
  323. package/dist/esm/primeReactComponents.d.ts.map +1 -1
  324. package/dist/esm/primeReactComponents.js +10 -0
  325. package/dist/esm/primeReactComponents.js.map +1 -1
  326. package/dist/esm/primeReactDescriptors.d.ts.map +1 -1
  327. package/dist/esm/primeReactDescriptors.js +243 -0
  328. package/dist/esm/primeReactDescriptors.js.map +1 -1
  329. package/dist/esm/primeReactPackage.js +4 -0
  330. package/dist/esm/primeReactPackage.js.map +1 -1
  331. package/dist/esm/resolveEnumeration.d.ts +3 -0
  332. package/dist/esm/resolveEnumeration.d.ts.map +1 -0
  333. package/dist/esm/resolveEnumeration.js +53 -0
  334. package/dist/esm/resolveEnumeration.js.map +1 -0
  335. package/dist/esm/treeNodes.d.ts +2 -1
  336. package/dist/esm/treeNodes.d.ts.map +1 -1
  337. package/dist/esm/treeNodes.js +42 -8
  338. package/dist/esm/treeNodes.js.map +1 -1
  339. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  340. package/dist/esm/useResettingState.d.ts +3 -0
  341. package/dist/esm/useResettingState.d.ts.map +1 -0
  342. package/dist/esm/useResettingState.js +45 -0
  343. package/dist/esm/useResettingState.js.map +1 -0
  344. package/editor/PrimeEditor.tsx +83 -0
  345. package/editor/QuillConstructor.ts +8 -0
  346. package/editor/QuillEditorOptions.ts +29 -0
  347. package/editor/QuillEditorState.ts +14 -0
  348. package/editor/QuillInstance.ts +27 -0
  349. package/editor/QuillLoader.ts +13 -0
  350. package/editor/QuillLoaderContext.ts +8 -0
  351. package/editor/QuillLoaderProvider.tsx +24 -0
  352. package/editor/QuillOptions.ts +10 -0
  353. package/editor/SafeElement.ts +15 -0
  354. package/editor/SafeNode.ts +7 -0
  355. package/editor/applyHtml.ts +21 -0
  356. package/editor/characterCount.ts +9 -0
  357. package/editor/htmlToPlainText.ts +74 -0
  358. package/editor/index.ts +16 -0
  359. package/editor/renderSafeNodes.tsx +26 -0
  360. package/editor/safeUrls.ts +43 -0
  361. package/editor/sanitizeHtml.ts +94 -0
  362. package/editor/serializeSafeNodes.ts +26 -0
  363. package/editor/toolbarLayout.ts +10 -0
  364. package/editor/useQuillEditor.ts +114 -0
  365. package/file/FileGallery.stories.tsx +32 -0
  366. package/file/FileUploadContext.ts +17 -0
  367. package/file/FileUploadHandler.ts +15 -0
  368. package/file/FileUploadHandlerProvider.tsx +33 -0
  369. package/file/FileUploadMode.ts +9 -0
  370. package/file/PrimeFileUpload.tsx +129 -0
  371. package/file/UploadStatus.ts +9 -0
  372. package/file/UploadTarget.ts +12 -0
  373. package/file/index.ts +9 -0
  374. package/file/postFiles.ts +23 -0
  375. package/file/resolveUploadTarget.ts +61 -0
  376. package/file/useUploadSession.ts +67 -0
  377. package/form/FormGallery.stories.tsx +1 -0
  378. package/index.ts +2 -0
  379. package/legacyEnumerationChoices.ts +35 -0
  380. package/loadQuill/index.ts +21 -0
  381. package/package.json +17 -7
  382. package/primeReactComponents.ts +8 -0
  383. package/primeReactDescriptors.ts +63 -0
  384. package/primeReactPackage.ts +2 -2
  385. package/primeReactTheme.css +69 -0
  386. package/resolveEnumeration.ts +50 -0
  387. package/treeNodes.ts +44 -6
  388. package/useResettingState.ts +34 -0
@@ -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' }),
package/index.ts CHANGED
@@ -10,6 +10,8 @@ export * from './menuItems';
10
10
  export * from './treeNodes';
11
11
  export * from './button';
12
12
  export * from './form';
13
+ export * from './editor';
14
+ export * from './file';
13
15
  export * from './chart';
14
16
  export * from './data';
15
17
  export * from './panel';
@@ -0,0 +1,35 @@
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 { PropertyChoice } from '@cratis/scene.model';
5
+
6
+ /**
7
+ * The choices for an enumeration property: the stable values new documents use, then the ordinals migrated
8
+ * documents carry, then the Pascal case member names a serialized .NET enum produces. These are the same forms
9
+ * the renderer accepts, so a stored value the control draws is never flagged invalid by descriptor validation.
10
+ *
11
+ * A migrated control stores the original numeric value. Offering those ordinals keeps the stored value valid
12
+ * against the descriptor (so an inspector shows it as the current choice) without rewriting it behind the
13
+ * author's back. The position of a member in `members` is its ordinal.
14
+ *
15
+ * @param members The enum object.
16
+ * @param labels The label of each member, by canonical value.
17
+ * @returns The canonical choices, then one legacy choice per ordinal, then one per Pascal case member name.
18
+ */
19
+ export function legacyEnumerationChoices<TMember extends string>(members: Record<string, TMember>, labels: Record<TMember, string>): PropertyChoice[] {
20
+ const canonical = Object.values(members);
21
+ const names = Object.keys(members);
22
+ return [
23
+ ...canonical.map(member => ({ value: member, label: labels[member] })),
24
+ ...canonical.map((member, ordinal) => ({
25
+ value: ordinal,
26
+ label: `${labels[member]} (legacy ${ordinal})`,
27
+ description: `The ordinal ${ordinal} of the original enum, as stored by migrated documents.`,
28
+ })),
29
+ ...names.map(name => ({
30
+ value: name,
31
+ label: `${labels[members[name]]} (legacy name ${name})`,
32
+ description: `The member name ${name} of the original enum, as a serialized enum stores it.`,
33
+ })),
34
+ ];
35
+ }
@@ -0,0 +1,21 @@
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 { QuillLoader } from '../editor/QuillLoader';
5
+
6
+ /**
7
+ * The ready-made Quill loader: `() => import('quill')`.
8
+ *
9
+ * This entry point is the only place the package names `quill`, and it is separate from the package root on
10
+ * purpose. A host that renders editable rich text imports it, installs the `quill` peer dependency and
11
+ * Quill's stylesheet, and passes it to `QuillLoaderProvider`; every other host never resolves `quill`.
12
+ *
13
+ * ```tsx
14
+ * import 'quill/dist/quill.snow.css';
15
+ * import { QuillLoaderProvider } from '@cratis/scene.primereact';
16
+ * import { loadQuill } from '@cratis/scene.primereact/quill';
17
+ *
18
+ * <QuillLoaderProvider loader={loadQuill}>{screen}</QuillLoaderProvider>
19
+ * ```
20
+ */
21
+ export const loadQuill: QuillLoader = () => import('quill') as unknown as ReturnType<QuillLoader>;