@phis/ui 0.4.2 → 0.4.4

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 (335) hide show
  1. package/components/cms/phi-cms-layout-renderer.js +4 -3
  2. package/components/cms/phi-cms-page-render-scope.d.ts +4 -5
  3. package/components/cms/phi-cms-page-render-scope.js +43 -22
  4. package/components/cms/phi-cms-render-diagnostic-client.js +7 -3
  5. package/components/cms/phi-cms-render-error-boundary.js +2 -1
  6. package/components/controls/phi-anchor-nav-control.js +4 -1
  7. package/components/controls/phi-background-control.js +21 -6
  8. package/components/controls/phi-bullet-list-control.d.ts +7 -2
  9. package/components/controls/phi-bullet-list-control.js +35 -5
  10. package/components/controls/phi-color-control.js +4 -2
  11. package/components/controls/phi-drawer-control-adapter.js +3 -2
  12. package/components/controls/phi-inline-text-editor.js +7 -1
  13. package/components/controls/phi-length-control.js +1 -1
  14. package/components/controls/phi-ribbon-control.d.ts +38 -0
  15. package/components/controls/phi-ribbon-control.js +57 -0
  16. package/components/controls/phi-ribbon-wrap-control.d.ts +26 -0
  17. package/components/controls/phi-ribbon-wrap-control.js +25 -0
  18. package/components/controls/phi-select-control.js +5 -2
  19. package/components/controls/phi-series-chart-control.d.ts +48 -0
  20. package/components/controls/phi-series-chart-control.js +82 -0
  21. package/components/controls/phi-table-control.d.ts +3 -0
  22. package/components/controls/phi-table-control.js +13 -1
  23. package/components/controls/phi-text-control.js +57 -3
  24. package/components/controls/phi-tree-control.js +4 -4
  25. package/components/controls/phi-truncated-hover-text.d.ts +15 -0
  26. package/components/controls/phi-truncated-hover-text.js +25 -0
  27. package/components/forms/link-target-form-control.d.ts +1 -1
  28. package/components/forms/link-target-form-control.js +2 -45
  29. package/components/forms/phi-form-widget-frame.js +4 -2
  30. package/components/forms/shared-form-provider-registry.js +1 -4
  31. package/components/layouts/clients/phi-three-column-layout-client.js +10 -1
  32. package/components/media/asset-collection-runtime.js +6 -16
  33. package/components/media/media-font-metrics.d.ts +3 -0
  34. package/components/media/media-font-metrics.js +43 -0
  35. package/components/media/phi-area-upload-widget.js +53 -97
  36. package/components/media/phi-image-preview-data.d.ts +13 -0
  37. package/components/media/phi-image-preview-data.js +184 -9
  38. package/components/media/phi-media-picker-binding.js +16 -13
  39. package/components/media/phi-media-upload.d.ts +15 -8
  40. package/components/media/phi-media-upload.js +18 -9
  41. package/components/media/phi-site-font-assets.js +7 -8
  42. package/components/navigation/phi-link.js +6 -1
  43. package/components/overlays/phi-overlay-container-client.js +16 -1
  44. package/components/regions/clients/cms-region-container-client.js +6 -1
  45. package/components/regions/phi-cms-region-container.js +3 -0
  46. package/components/regions/phi-cms-region-shell.d.ts +12 -1
  47. package/components/regions/phi-cms-region-shell.js +81 -19
  48. package/components/regions/presets/phi-settings-page-shell-tree.js +48 -1
  49. package/components/root/phi-config-provider.d.ts +13 -0
  50. package/components/root/phi-config-provider.js +12 -1
  51. package/components/root/phi-root-background.d.ts +7 -0
  52. package/components/root/phi-root-background.js +20 -1
  53. package/components/root/phi-root-theme-resolver.js +3 -0
  54. package/components/root/phi-shell-chrome-overlay.d.ts +12 -1
  55. package/components/root/phi-shell-chrome-overlay.js +19 -3
  56. package/components/runtime/runtime-controller-materialization.js +12 -1
  57. package/components/shell/phi-antd-icon.d.ts +1 -0
  58. package/components/shell/phi-antd-icon.js +3 -0
  59. package/components/shell/use-phi-shell-sticky-top.js +61 -16
  60. package/components/state/scoped-state-store.js +20 -3
  61. package/components/surface/phi-surface-tone.d.ts +13 -0
  62. package/components/surface/phi-surface-tone.js +21 -0
  63. package/components/tables/client/phi-table-binding.d.ts +2 -1
  64. package/components/tables/client/phi-table-binding.js +45 -21
  65. package/components/trees/client/phi-tree-binding.js +19 -11
  66. package/components/widgets/client/bullet-list-entries-editor.d.ts +26 -0
  67. package/components/widgets/client/bullet-list-entries-editor.js +109 -0
  68. package/components/widgets/client/html-editor.js +1 -1
  69. package/components/widgets/client/shared/use-page-reference-options.d.ts +17 -0
  70. package/components/widgets/client/shared/use-page-reference-options.js +57 -0
  71. package/components/widgets/config/background.d.ts +32 -1
  72. package/components/widgets/config/background.js +41 -2
  73. package/components/widgets/config/choice-shared.js +12 -13
  74. package/components/widgets/config/command-buttons.d.ts +5 -1
  75. package/components/widgets/config/command-buttons.js +38 -81
  76. package/components/widgets/config/control-signal-config.d.ts +3 -1
  77. package/components/widgets/config/control-signal-config.js +19 -17
  78. package/components/widgets/config/image-source-parser.d.ts +1 -0
  79. package/components/widgets/config/image-source-parser.js +7 -8
  80. package/components/widgets/config/mask.d.ts +6 -1
  81. package/components/widgets/config/mask.js +24 -1
  82. package/components/widgets/config/parser-primitives.js +72 -0
  83. package/components/widgets/config/stack-tabs.d.ts +1 -1
  84. package/components/widgets/config/stack-tabs.js +2 -3
  85. package/components/widgets/config/strict-config-reader.d.ts +40 -0
  86. package/components/widgets/config/strict-config-reader.js +174 -0
  87. package/components/widgets/label-sets/background.js +2 -0
  88. package/components/widgets/label-sets/effects.js +2 -0
  89. package/components/widgets/label-types/background.d.ts +1 -0
  90. package/components/widgets/label-types/background.js +1 -0
  91. package/components/widgets/label-types/effects.d.ts +1 -0
  92. package/components/widgets/label-types/effects.js +1 -0
  93. package/components/widgets/navigation-descriptor-resolver.server.js +20 -8
  94. package/components/widgets/shared/bullet-list-vocabulary.d.ts +27 -1
  95. package/components/widgets/shared/bullet-list-vocabulary.js +24 -4
  96. package/components/widgets/shared/card-body-client.d.ts +18 -4
  97. package/components/widgets/shared/card-body-client.js +126 -26
  98. package/components/widgets/shared/card-vocabulary.d.ts +31 -4
  99. package/components/widgets/shared/card-vocabulary.js +35 -2
  100. package/components/widgets/shared/markdown-body-client.js +39 -10
  101. package/components/widgets/signals/control-signal-capabilities.d.ts +0 -18
  102. package/components/widgets/signals/control-signal-capabilities.js +10 -22
  103. package/components/workspace/catalog-store.d.ts +4 -3
  104. package/components/workspace/catalog-store.js +6 -5
  105. package/gateway/dashboard-cards-route.js +1 -0
  106. package/gateway/internal-references.d.ts +6 -1
  107. package/gateway/internal-references.js +48 -23
  108. package/gateway/site-cms-write-validation.d.ts +9 -0
  109. package/gateway/site-cms-write-validation.js +78 -0
  110. package/gateway/site-proxy.js +19 -3
  111. package/helpers/cms-container-chrome.js +6 -1
  112. package/helpers/locale.d.ts +8 -0
  113. package/helpers/locale.js +18 -0
  114. package/helpers/renderable-block-serialization.js +1 -1
  115. package/helpers/shell-region-style.js +2 -1
  116. package/helpers/surface-style.d.ts +12 -1
  117. package/helpers/surface-style.js +28 -2
  118. package/module-dashboard-cards.d.ts +1 -1
  119. package/module-dashboard-cards.js +1 -1
  120. package/next/config.d.mts +3 -0
  121. package/next/config.mjs +28 -1
  122. package/package.json +2 -2
  123. package/plugins/runtime/phi-slot-child-viewport-effects-observer.js +5 -1
  124. package/plugins/runtime-modules/accounting/presets.d.ts +9 -0
  125. package/plugins/runtime-modules/accounting/presets.js +37 -0
  126. package/plugins/runtime-modules/accounting/server.d.ts +1 -4
  127. package/plugins/runtime-modules/accounting/server.js +4 -7
  128. package/plugins/runtime-modules/app/presets.d.ts +18 -0
  129. package/plugins/runtime-modules/app/presets.js +67 -0
  130. package/plugins/runtime-modules/app/server.d.ts +1 -4
  131. package/plugins/runtime-modules/app/server.js +4 -7
  132. package/plugins/runtime-modules/area-definitions.d.ts +0 -42
  133. package/plugins/runtime-modules/area-definitions.js +3 -8
  134. package/plugins/runtime-modules/asset/controller/runtime.js +2 -2
  135. package/plugins/runtime-modules/asset/services/collection.js +9 -1
  136. package/plugins/runtime-modules/asset/services/options.js +2 -2
  137. package/plugins/runtime-modules/builder/area-settings-answer.js +2 -1
  138. package/plugins/runtime-modules/builder/authoring-canvas.js +6 -6
  139. package/plugins/runtime-modules/builder/cards.js +3 -5
  140. package/plugins/runtime-modules/builder/clients/builder-region-overrides.js +3 -3
  141. package/plugins/runtime-modules/builder/clients/canvas-widget.js +15 -54
  142. package/plugins/runtime-modules/builder/clients/inspector-config-field.d.ts +4 -1
  143. package/plugins/runtime-modules/builder/clients/inspector-config-field.js +25 -35
  144. package/plugins/runtime-modules/builder/clients/inspector-link-target-field.js +1 -1
  145. package/plugins/runtime-modules/builder/clients/inspector-section-widget.js +8 -8
  146. package/plugins/runtime-modules/builder/clients/inspector-title.js +6 -6
  147. package/plugins/runtime-modules/builder/clients/layout-inspector.d.ts +1 -1
  148. package/plugins/runtime-modules/builder/clients/layout-inspector.js +11 -25
  149. package/plugins/runtime-modules/builder/clients/layout-scaffold-overlays.js +10 -5
  150. package/plugins/runtime-modules/builder/clients/mode-switch.js +1 -1
  151. package/plugins/runtime-modules/builder/clients/pages-header.js +11 -11
  152. package/plugins/runtime-modules/builder/clients/pages-workspace.js +1 -1
  153. package/plugins/runtime-modules/builder/clients/region-visibility-fields.js +1 -1
  154. package/plugins/runtime-modules/builder/clients/runtime-module-authoring-boundary.js +1 -1
  155. package/plugins/runtime-modules/builder/clients/shells-workspace.js +15 -31
  156. package/plugins/runtime-modules/builder/clients/structure-canvas.js +1 -1
  157. package/plugins/runtime-modules/builder/clients/use-region-draft-hydration.d.ts +26 -0
  158. package/plugins/runtime-modules/builder/clients/use-region-draft-hydration.js +36 -0
  159. package/plugins/runtime-modules/builder/clients/widget-effects-editor.js +1 -1
  160. package/plugins/runtime-modules/builder/clients/widget-inspector.js +1 -0
  161. package/plugins/runtime-modules/builder/clients/workspace-scope-boundary.js +2 -2
  162. package/plugins/runtime-modules/builder/cms-instance-allocator.js +8 -28
  163. package/plugins/runtime-modules/builder/controller/client.d.ts +0 -15
  164. package/plugins/runtime-modules/builder/controller/definition.d.ts +0 -15
  165. package/plugins/runtime-modules/builder/controller/definition.js +0 -8
  166. package/plugins/runtime-modules/builder/controller/draft-command-controller.d.ts +1 -2
  167. package/plugins/runtime-modules/builder/controller/draft-command-controller.js +11 -30
  168. package/plugins/runtime-modules/builder/controller/draft-status-controller.js +15 -2
  169. package/plugins/runtime-modules/builder/controller/inspector-controller.d.ts +1 -2
  170. package/plugins/runtime-modules/builder/controller/inspector-controller.js +4 -7
  171. package/plugins/runtime-modules/builder/controller/page-controller.d.ts +1 -2
  172. package/plugins/runtime-modules/builder/controller/page-controller.js +16 -68
  173. package/plugins/runtime-modules/builder/controller/server-definition.d.ts +0 -15
  174. package/plugins/runtime-modules/builder/controller/signal-wiring-controller.d.ts +2 -5
  175. package/plugins/runtime-modules/builder/controller/signal-wiring-controller.js +6 -50
  176. package/plugins/runtime-modules/builder/controller/workspace-controller.d.ts +2 -3
  177. package/plugins/runtime-modules/builder/controller/workspace-controller.js +89 -153
  178. package/plugins/runtime-modules/builder/data-providers.d.ts +1 -0
  179. package/plugins/runtime-modules/builder/data-providers.js +1 -0
  180. package/plugins/runtime-modules/builder/definition.d.ts +1 -0
  181. package/plugins/runtime-modules/builder/developer-workspace-store.d.ts +64 -30
  182. package/plugins/runtime-modules/builder/developer-workspace-store.js +255 -61
  183. package/plugins/runtime-modules/builder/edit-scaffold-drawer.js +4 -3
  184. package/plugins/runtime-modules/builder/history.d.ts +3 -1
  185. package/plugins/runtime-modules/builder/options-providers.js +3 -2
  186. package/plugins/runtime-modules/builder/page-catalog-client.js +19 -7
  187. package/plugins/runtime-modules/builder/page-meta-form.js +1 -0
  188. package/plugins/runtime-modules/builder/page-references-provider.js +1 -1
  189. package/plugins/runtime-modules/builder/persistence.js +33 -69
  190. package/plugins/runtime-modules/builder/public-route-collisions.d.ts +2 -2
  191. package/plugins/runtime-modules/builder/public-route-collisions.js +6 -19
  192. package/plugins/runtime-modules/builder/render-root-node-preview.server.js +20 -3
  193. package/plugins/runtime-modules/builder/render-root-node-scaffold.js +28 -18
  194. package/plugins/runtime-modules/builder/runtime-module-selection.d.ts +2 -12
  195. package/plugins/runtime-modules/builder/runtime-module-selection.js +16 -42
  196. package/plugins/runtime-modules/builder/services/navigation-table.js +9 -15
  197. package/plugins/runtime-modules/builder/services/page-source-tree.js +8 -8
  198. package/plugins/runtime-modules/builder/services/runtime-modules-table.js +10 -10
  199. package/plugins/runtime-modules/builder/services/signal-routes-table.js +3 -3
  200. package/plugins/runtime-modules/builder/signal-route-selection.d.ts +1 -2
  201. package/plugins/runtime-modules/builder/signal-route-selection.js +2 -2
  202. package/plugins/runtime-modules/builder/signal-wiring-options.js +3 -2
  203. package/plugins/runtime-modules/builder/structure-dnd.js +8 -61
  204. package/plugins/runtime-modules/builder/trees/phi-default-builder-area-preset-tree.js +17 -10
  205. package/plugins/runtime-modules/builder/widgets/structure-region/built-in.js +27 -22
  206. package/plugins/runtime-modules/builder/workspace-catalog-actions.d.ts +55 -0
  207. package/plugins/runtime-modules/builder/workspace-catalog-actions.js +142 -0
  208. package/plugins/runtime-modules/client-authoring-module.d.ts +8 -1
  209. package/plugins/runtime-modules/client-authoring-module.js +13 -3
  210. package/plugins/runtime-modules/client-manifests/common.js +1 -1
  211. package/plugins/runtime-modules/{runtime-calendar-adapters.js → core/calendar-adapters.js} +2 -2
  212. package/plugins/runtime-modules/{client-calendar-adapters/core.js → core/client-calendar-adapters.js} +1 -1
  213. package/plugins/runtime-modules/core/content-entries/table.js +1 -1
  214. package/plugins/runtime-modules/core/data-providers.d.ts +3 -0
  215. package/plugins/runtime-modules/core/data-providers.js +2 -1
  216. package/plugins/runtime-modules/core/definition.d.ts +3 -0
  217. package/plugins/runtime-modules/core/definition.js +1 -1
  218. package/plugins/runtime-modules/core/messaging/data-providers.d.ts +1 -0
  219. package/plugins/runtime-modules/core/messaging/data-providers.js +1 -0
  220. package/plugins/runtime-modules/core/messaging/thread-inbox-table.js +1 -1
  221. package/plugins/runtime-modules/core/server.js +1 -1
  222. package/plugins/runtime-modules/core/themes.d.ts +44 -0
  223. package/plugins/runtime-modules/core/themes.js +12 -0
  224. package/plugins/runtime-modules/core/widgets/brand/client.d.ts +12 -3
  225. package/plugins/runtime-modules/core/widgets/brand/client.js +3 -11
  226. package/plugins/runtime-modules/core/widgets/brand/home.d.ts +15 -0
  227. package/plugins/runtime-modules/core/widgets/brand/home.js +18 -0
  228. package/plugins/runtime-modules/core/widgets/brand/plugin.js +3 -2
  229. package/plugins/runtime-modules/core/widgets/brand/server.d.ts +2 -2
  230. package/plugins/runtime-modules/core/widgets/brand/server.js +2 -2
  231. package/plugins/runtime-modules/core/widgets/card/collection-view-binding.js +25 -9
  232. package/plugins/runtime-modules/core/widgets/card/config.d.ts +301 -254
  233. package/plugins/runtime-modules/core/widgets/card/config.js +68 -3
  234. package/plugins/runtime-modules/core/widgets/card/editor.js +41 -4
  235. package/plugins/runtime-modules/core/widgets/card/server.d.ts +3 -0
  236. package/plugins/runtime-modules/core/widgets/card/server.js +32 -3
  237. package/plugins/runtime-modules/core/widgets/collection-view/asset-binding.js +7 -6
  238. package/plugins/runtime-modules/core/widgets/collection-view/config.js +9 -2
  239. package/plugins/runtime-modules/core/widgets/description/client.js +3 -3
  240. package/plugins/runtime-modules/core/widgets/description/config.d.ts +2 -25
  241. package/plugins/runtime-modules/core/widgets/description/editor.js +7 -63
  242. package/plugins/runtime-modules/core/widgets/description/server.js +3 -2
  243. package/plugins/runtime-modules/core/widgets/entry-list/config.d.ts +2 -25
  244. package/plugins/runtime-modules/core/widgets/gallery/config.d.ts +2 -25
  245. package/plugins/runtime-modules/core/widgets/header-navigation/client.js +2 -2
  246. package/plugins/runtime-modules/core/widgets/html/client.js +1 -1
  247. package/plugins/runtime-modules/core/widgets/image/config.js +2 -2
  248. package/plugins/runtime-modules/core/widgets/markdown/config.d.ts +320 -2
  249. package/plugins/runtime-modules/core/widgets/markdown/config.js +18 -4
  250. package/plugins/runtime-modules/core/widgets/markdown/editor.js +11 -4
  251. package/plugins/runtime-modules/core/widgets/markdown-toc/client.js +6 -1
  252. package/plugins/runtime-modules/core/widgets/markdown-toc/config.d.ts +14 -0
  253. package/plugins/runtime-modules/core/widgets/markdown-toc/config.js +14 -2
  254. package/plugins/runtime-modules/core/widgets/quick-links/authoring.js +7 -2
  255. package/plugins/runtime-modules/core/widgets/quick-links/client.js +2 -2
  256. package/plugins/runtime-modules/core/widgets/quick-links/items.d.ts +0 -2
  257. package/plugins/runtime-modules/core/widgets/quick-links/items.js +0 -15
  258. package/plugins/runtime-modules/core/widgets/result/config.d.ts +5 -25
  259. package/plugins/runtime-modules/core/widgets/result/config.js +2 -0
  260. package/plugins/runtime-modules/core/widgets/result/server.js +25 -19
  261. package/plugins/runtime-modules/core/widgets/sidebar-navigation/authoring.js +1 -1
  262. package/plugins/runtime-modules/core/widgets/sidebar-navigation/client.js +1 -1
  263. package/plugins/runtime-modules/core/widgets/simple-text/client.js +8 -3
  264. package/plugins/runtime-modules/core/widgets/simple-text/config.d.ts +2 -25
  265. package/plugins/runtime-modules/core/widgets/simple-text/editor.js +2 -2
  266. package/plugins/runtime-modules/core/widgets/slot-upload/client.js +11 -2
  267. package/plugins/runtime-modules/core/widgets/table/client.js +8 -4
  268. package/plugins/runtime-modules/core/widgets/table/config.d.ts +6 -83
  269. package/plugins/runtime-modules/core/widgets/table/config.js +302 -452
  270. package/plugins/runtime-modules/core/widgets/tree/config.js +5 -2
  271. package/plugins/runtime-modules/dashboard/card-rows.d.ts +7 -0
  272. package/plugins/runtime-modules/dashboard/card-rows.js +87 -0
  273. package/plugins/runtime-modules/dashboard/clients/dashboard-card-view.js +32 -12
  274. package/plugins/runtime-modules/dashboard/services/collection.js +17 -7
  275. package/plugins/runtime-modules/dashboard/trees/phi-default-builder-dashboard-page-tree.js +5 -40
  276. package/plugins/runtime-modules/groups/data-providers.d.ts +2 -0
  277. package/plugins/runtime-modules/groups/data-providers.js +3 -3
  278. package/plugins/runtime-modules/groups/definition.d.ts +2 -0
  279. package/plugins/runtime-modules/localization/data-providers.d.ts +3 -0
  280. package/plugins/runtime-modules/localization/data-providers.js +2 -0
  281. package/plugins/runtime-modules/localization/definition.d.ts +3 -0
  282. package/plugins/runtime-modules/localization/services/table.js +3 -7
  283. package/plugins/runtime-modules/observability/data-providers.d.ts +1 -0
  284. package/plugins/runtime-modules/observability/data-providers.js +1 -0
  285. package/plugins/runtime-modules/observability/definition.d.ts +1 -0
  286. package/plugins/runtime-modules/observability/module.d.ts +1 -0
  287. package/plugins/runtime-modules/observability/services/table.js +1 -1
  288. package/plugins/runtime-modules/phi-default-site-area-preset-tree.js +2 -105
  289. package/plugins/runtime-modules/public/presets.d.ts +115 -0
  290. package/plugins/runtime-modules/{area-base-presets.js → public/presets.js} +19 -119
  291. package/plugins/runtime-modules/public/server.d.ts +1 -4
  292. package/plugins/runtime-modules/public/server.js +4 -7
  293. package/plugins/runtime-modules/public/trees/phi-default-pub-error-page-tree.js +7 -8
  294. package/plugins/runtime-modules/public-base-page-references.d.ts +1 -1
  295. package/plugins/runtime-modules/public-base-page-references.js +1 -1
  296. package/plugins/runtime-modules/revisions/controller/client.js +11 -11
  297. package/plugins/runtime-modules/revisions/services/table.js +3 -3
  298. package/plugins/runtime-modules/theme/trees/phi-default-builder-theme-page-tree.js +22 -0
  299. package/plugins/runtime-modules/theme/widgets/brand-controls/client.d.ts +2 -1
  300. package/plugins/runtime-modules/theme/widgets/brand-controls/client.js +111 -72
  301. package/plugins/runtime-modules/theme/widgets/brand-controls/config.d.ts +2 -1
  302. package/plugins/runtime-modules/theme/widgets/brand-controls/config.js +28 -31
  303. package/plugins/runtime-modules/theme/widgets/brand-controls/plugin.js +1 -1
  304. package/plugins/runtime-modules/theme-descriptors.js +1 -1
  305. package/plugins/runtime-modules/user-management/data-providers.d.ts +2 -0
  306. package/plugins/runtime-modules/user-management/data-providers.js +1 -0
  307. package/plugins/runtime-modules/user-management/definition.d.ts +2 -0
  308. package/server-helpers/cms-link-targets.js +6 -0
  309. package/styles/control-shape.css +11 -11
  310. package/styles/controls.css +67 -5
  311. package/styles/layout.css +61 -9
  312. package/styles/shell.css +73 -0
  313. package/theme/phi-antd-token-resolver.js +20 -1
  314. package/theme/phi-theme-tokens.d.ts +1 -1
  315. package/theme/phi-theme-tokens.js +1 -0
  316. package/theme/phi-theme.d.ts +5 -0
  317. package/theme/phi-theme.js +21 -0
  318. package/theme/phi-tokens.d.ts +16 -15
  319. package/theme/phi-tokens.js +16 -15
  320. package/types/cms-config.js +142 -185
  321. package/types/cms-plugins.d.ts +6 -11
  322. package/types/core-widget-placements.d.ts +14 -3
  323. package/types/dashboard-cards.d.ts +48 -4
  324. package/types/dashboard-cards.js +6 -0
  325. package/types/media.d.ts +0 -3
  326. package/types/renderable-block.d.ts +6 -1
  327. package/types/site-theme.d.ts +10 -2
  328. package/types/table-widget.d.ts +9 -0
  329. package/types/table-widget.js +20 -0
  330. package/types.d.ts +1 -1
  331. package/widget-config.d.ts +2 -0
  332. package/widget-config.js +6 -0
  333. package/plugins/runtime-modules/area-base-presets.d.ts +0 -139
  334. /package/plugins/runtime-modules/{runtime-calendar-adapters.d.ts → core/calendar-adapters.d.ts} +0 -0
  335. /package/plugins/runtime-modules/{client-calendar-adapters/core.d.ts → core/client-calendar-adapters.d.ts} +0 -0
@@ -4,13 +4,13 @@ import { resolvePhiMediaAssetTypeLabel } from "./asset-type-label";
4
4
  import { PhiTagControl } from "../controls/phi-tag-control";
5
5
  import { PhiButtonControl } from "../controls/phi-button-control";
6
6
  import NextImage from "next/image";
7
- import { useEffect, useMemo, useRef, useState } from "react";
7
+ import { useMemo, useRef, useState } from "react";
8
8
  import { PhiCollectionLayoutControl } from "../controls/phi-collection-layout-control";
9
9
  import { resolvePhiMediaKindFromContentType, } from "../../constants/media";
10
10
  import { normalizePhiImagePreviewTile, resolvePhiMediaAssetDisplayDimensions, } from "./phi-image-preview-data";
11
11
  import { PhiMediaKindIcon } from "./phi-media-kind-icon";
12
- import { PhiMediaUploadError, isPhiMediaUploadCancelled, resolvePhiMediaUploadInitOptions, runPhiMediaUploadSession, } from "./media-upload-flow";
13
- import { PHI_MEDIA_UPLOAD_DEFAULT_LABELS, readPhiMediaUploadErrorMessage, } from "./phi-media-upload";
12
+ import { resolvePhiMediaUploadInitOptions, } from "./media-upload-flow";
13
+ import { PHI_MEDIA_UPLOAD_DEFAULT_LABELS, usePhiMediaUpload, } from "./phi-media-upload";
14
14
  import { usePhiImagePreviewStore } from "./phi-image-preview-store";
15
15
  import { PHI_ASSET_CONTROLLER_STORE_KEY } from "./asset-controller-signals";
16
16
  import { applyPhiAssetCollectionData, buildPhiAssetCollectionQuery, PHI_ASSET_COLLECTION_DATA_SOURCE, } from "./asset-collection-runtime";
@@ -55,25 +55,37 @@ function resolveUploadStatusLabel(item, labels) {
55
55
  return labels.doneLabel;
56
56
  }
57
57
  /**
58
- * The Widget's own wording over the shared reading of every refusal.
59
- *
60
- * It used to know about duplicates and nothing else, and fell through to `error.message` -- which put a
61
- * control plane string in front of a person for every other refusal, including the two the Site itself
62
- * now states. Only the duplicate has a label of its own here; the rest come from one place.
58
+ * The Widget's own wording over the shared reading of every refusal: its duplicate, failure and storage
59
+ * texts, and the shared ones for the rest -- so no control plane string reaches a person.
63
60
  */
64
- function resolveUploadErrorMessage(error, labels) {
65
- const typedError = error;
66
- if ((error instanceof PhiMediaUploadError && error.code === "media_asset_exists") ||
67
- typedError?.code === "media_asset_exists" ||
68
- typedError?.message === "media_asset_exists") {
69
- return labels.assetExistsLabel;
70
- }
71
- return readPhiMediaUploadErrorMessage(error, {
61
+ function resolveAreaUploadLabels(labels) {
62
+ return {
72
63
  ...PHI_MEDIA_UPLOAD_DEFAULT_LABELS,
73
64
  errorGeneric: labels.uploadFailedText,
74
65
  errorDuplicate: labels.assetExistsLabel,
75
66
  errorStorageUnreachable: labels.uploadStorageUnreachableText,
76
- });
67
+ };
68
+ }
69
+ /** A file still on its way, or one that failed, drawn as a tile like the Assets that arrived. */
70
+ function buildPendingWallItem(item, presentationFlags) {
71
+ return {
72
+ localId: item.localId,
73
+ assetId: -1,
74
+ file: item.file,
75
+ title: item.file.name.replace(/\.[^.]+$/, "").trim() || item.file.name,
76
+ originalName: item.file.name,
77
+ thumbnailUrl: null,
78
+ previewUrl: null,
79
+ contentType: item.file.type,
80
+ kind: resolvePhiMediaKindFromContentType(item.file.type),
81
+ width: null,
82
+ height: null,
83
+ presentationFlags,
84
+ progress: item.progress,
85
+ status: item.status,
86
+ deleting: false,
87
+ error: item.error,
88
+ };
77
89
  }
78
90
  function resolveUploadWallDisplayDimensions(item) {
79
91
  return resolvePhiMediaAssetDisplayDimensions(item);
@@ -88,20 +100,6 @@ export function PhiAreaUploadBinding({ config, labels, onUploadComplete, collect
88
100
  const previewState = usePhiImagePreviewStore(PHI_ASSET_CONTROLLER_STORE_KEY);
89
101
  const activateCollectionAction = usePhiCollectionProviderAction(PHI_ASSET_COLLECTION_DATA_SOURCE);
90
102
  const dragDepth = useRef(0);
91
- /*
92
- * Uploads still on their way. The wall belongs to whatever shows it -- a Media picker in an Overlay
93
- * more often than not -- and closing that stops them, so no Asset is finalized that nobody sees
94
- * arrive. The Server is told `cancelled` and clears up what had arrived.
95
- */
96
- const runningUploads = useRef(new Set());
97
- useEffect(() => {
98
- const controllers = runningUploads.current;
99
- return () => {
100
- for (const controller of controllers)
101
- controller.abort();
102
- controllers.clear();
103
- };
104
- }, []);
105
103
  const [uploadWall, setUploadWall] = useState([]);
106
104
  const [isDragActive, setIsDragActive] = useState(false);
107
105
  const allowDelete = config?.allowDelete ?? true;
@@ -166,73 +164,31 @@ export function PhiAreaUploadBinding({ config, labels, onUploadComplete, collect
166
164
  showUploadMessage("error", error instanceof Error ? error.message : labels.deleteFailedText);
167
165
  }
168
166
  }
169
- function startUploadWallItem(file, localId) {
170
- const normalizedTitle = file.name.replace(/\.[^.]+$/, "").trim() || file.name;
171
- const nextLocalId = localId ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
172
- setUploadWall((current) => [
173
- {
174
- localId: nextLocalId,
175
- assetId: -1,
176
- file,
177
- title: normalizedTitle,
178
- originalName: file.name,
179
- thumbnailUrl: null,
180
- previewUrl: null,
181
- contentType: file.type,
182
- kind: resolvePhiMediaKindFromContentType(file.type),
183
- width: null,
184
- height: null,
185
- presentationFlags: uploadInitOptions.presentationFlags ?? 0,
186
- progress: 0,
187
- status: "uploading",
188
- deleting: false,
189
- error: null,
190
- },
191
- ...current.filter((item) => item.localId !== nextLocalId),
192
- ]);
193
- return nextLocalId;
194
- }
195
167
  /*
196
- * One file, transported by us.
197
- *
198
- * This was `customRequest`, whose `onProgress`, `onSuccess` and `onError` fed an Ant Design file
199
- * list that `showUploadList={false}` never rendered -- every visible part of the progress already
200
- * came from `uploadWall` below. Dropping the primitive dropped that second, invisible copy with it.
201
- * A retry from the wall comes through here as well, into the tile it already has (`localId`).
168
+ * The transport is the shared one (`usePhiMediaUpload`): progress, refusals and their wording, retry,
169
+ * and stopping what is still on its way when the wall goes away -- a Media picker in an Overlay more
170
+ * often than not -- so no Asset is finalized that nobody sees arrive. What is this Widget's own is the
171
+ * wall: an Asset that arrived becomes a tile, and the listing behind it hears of it.
202
172
  */
203
- async function uploadFileToWallItem(file, localId) {
204
- const nextLocalId = startUploadWallItem(file, localId);
205
- const controller = new AbortController();
206
- runningUploads.current.add(controller);
207
- try {
208
- const payload = await runPhiMediaUploadSession(file, (progress) => {
209
- setUploadWall((current) => current.map((item) => item.localId === nextLocalId ? { ...item, progress, status: "uploading" } : item));
210
- }, uploadInitOptions, controller.signal);
211
- if (controller.signal.aborted)
212
- return;
213
- const uploadedTile = normalizePhiImagePreviewTile(payload.asset, []);
214
- setUploadWall((current) => current.map((item) => item.localId === nextLocalId ? buildUploadWallItem(uploadedTile, nextLocalId) : item));
173
+ const areaUploadLabels = useMemo(() => resolveAreaUploadLabels(labels), [labels]);
174
+ const upload = usePhiMediaUpload({
175
+ labels: areaUploadLabels,
176
+ initOptions: uploadInitOptions,
177
+ onUploaded: (asset, file) => {
178
+ const uploadedTile = normalizePhiImagePreviewTile(asset, []);
179
+ setUploadWall((current) => [buildUploadWallItem(uploadedTile, `asset-${uploadedTile.id}`), ...current]);
215
180
  onUploadComplete?.();
216
181
  showUploadMessage("success", labels.uploadSuccessTemplate.replace("%1", file.name));
217
- }
218
- catch (error) {
219
- if (controller.signal.aborted || isPhiMediaUploadCancelled(error))
220
- return;
221
- const errorMessage = resolveUploadErrorMessage(error, labels);
222
- setUploadWall((current) => current.map((item) => item.localId === nextLocalId
223
- ? {
224
- ...item,
225
- progress: 100,
226
- status: "error",
227
- error: errorMessage,
228
- }
229
- : item));
230
- showUploadMessage("error", errorMessage);
231
- }
232
- finally {
233
- runningUploads.current.delete(controller);
234
- }
235
- }
182
+ },
183
+ onRejected: (message) => showUploadMessage("error", message),
184
+ });
185
+ // The shared items are on their way or failed; an arrived one is a tile in the wall instead.
186
+ const wallItems = [
187
+ ...upload.items
188
+ .filter((item) => item.status !== "done")
189
+ .map((item) => buildPendingWallItem(item, uploadInitOptions.presentationFlags ?? 0)),
190
+ ...uploadWall,
191
+ ];
236
192
  return (_jsxs(PhiFlexControl, { vertical: true, gap: 12, style: { width: "100%" }, children: [_jsx(PhiFileDropGuard, {}), _jsx("div", { "data-phi-media-dropzone": "true", onDragEnter: () => {
237
193
  dragDepth.current += 1;
238
194
  setIsDragActive(true);
@@ -248,7 +204,7 @@ export function PhiAreaUploadBinding({ config, labels, onUploadComplete, collect
248
204
  }, onDrop: () => {
249
205
  dragDepth.current = 0;
250
206
  setIsDragActive(false);
251
- }, style: { width: "100%" }, children: _jsx(PhiFileDropControl, { dropZone: true, multiple: multiple, accept: accept, onFile: (file) => void uploadFileToWallItem(file), style: {
207
+ }, style: { width: "100%" }, children: _jsx(PhiFileDropControl, { dropZone: true, multiple: multiple, accept: accept, onFile: (file) => void upload.upload(file), style: {
252
208
  padding: 0,
253
209
  width: "100%",
254
210
  borderColor: isDragActive ? token.colorPrimary : undefined,
@@ -258,7 +214,7 @@ export function PhiAreaUploadBinding({ config, labels, onUploadComplete, collect
258
214
  minHeight: uploadTileMinHeight,
259
215
  justifyContent: "center",
260
216
  cursor: isDragActive ? "copy" : "default",
261
- }, children: [_jsx(PhiIcon, { name: "upload", size: "inherit", style: { fontSize: uploadTileIconSize, color: token.colorTextSecondary } }), _jsx(PhiTypographyControl, { strong: true, children: labels.dropTitle }), _jsx(PhiTypographyControl, { type: "secondary", children: labels.dropHint })] }) }) }), _jsx(PhiCollectionLayoutControl, { mode: "grid", gap: token.paddingSM, minColumnWidth: uploadTileFrameSize, items: uploadWall.map((item) => {
217
+ }, children: [_jsx(PhiIcon, { name: "upload", size: "inherit", style: { fontSize: uploadTileIconSize, color: token.colorTextSecondary } }), _jsx(PhiTypographyControl, { strong: true, children: labels.dropTitle }), _jsx(PhiTypographyControl, { type: "secondary", children: labels.dropHint })] }) }) }), _jsx(PhiCollectionLayoutControl, { mode: "grid", gap: token.paddingSM, minColumnWidth: uploadTileFrameSize, items: wallItems.map((item) => {
262
218
  const displayDimensions = resolveUploadWallDisplayDimensions(item);
263
219
  return (_jsxs("div", { style: {
264
220
  border: resolvePhiThemeBorderLine(token.colorBorderSecondary),
@@ -316,7 +272,7 @@ export function PhiAreaUploadBinding({ config, labels, onUploadComplete, collect
316
272
  }, children: _jsx(PhiSpinControl, {}) })) : null] }), _jsxs(PhiFlexControl, { vertical: true, gap: 6, style: { padding: token.paddingSM }, children: [_jsx(PhiFlexControl, { align: "center", justify: "space-between", gap: 8, children: _jsx(PhiTypographyControl, { strong: true, ellipsis: true, title: item.title ?? item.originalName, style: { minWidth: 0 }, children: item.title ?? item.originalName }) }), _jsxs(PhiFlexControl, { align: "center", justify: "space-between", gap: 8, wrap: true, children: [_jsx(PhiTypographyControl, { type: "secondary", ellipsis: true, style: { display: "block", minWidth: 0 }, children: resolvePhiMediaAssetTypeLabel(item.kind, item.contentType, item.originalName) }), allowDelete && item.assetId > 0 ? (_jsx(PhiButtonControl, { ariaLabel: labels.deleteLabel, icon: _jsx(PhiIcon, { name: "delete", size: "inherit" }), type: "text", size: "small", danger: true, onClick: () => {
317
273
  void deleteUploadedAsset(item.localId, item.assetId);
318
274
  } })) : null] }), _jsxs(PhiFlexControl, { align: "center", gap: 6, wrap: true, children: [_jsx(PhiTagControl, { color: item.status === "error" ? "red" : item.status === "uploading" ? "blue" : "green", children: resolveUploadStatusLabel(item, labels) }), item.status === "error" && item.file ? (_jsx(PhiButtonControl, { type: "primary", size: "small", onClick: () => {
319
- void uploadFileToWallItem(item.file, item.localId);
275
+ void upload.retry(item.localId);
320
276
  }, label: labels.retryLabel })) : null] })] })] }, item.localId));
321
277
  }) })] }));
322
278
  }
@@ -3,6 +3,19 @@ export type PhiImagePreviewApiRecord = Omit<PhiMediaAsset, "kind"> & {
3
3
  thumbnailUrl?: string | null;
4
4
  previewUrl?: string | null;
5
5
  };
6
+ /** The media API's Folders, read strictly; `source` names who answered. */
7
+ export declare function readPhiMediaFolders(value: unknown, source?: string): PhiMediaAssetFolder[];
8
+ /** The media API's Spaces, read strictly. */
9
+ export declare function readPhiMediaSpaces(value: unknown, source?: string): PhiMediaSpaceOption[];
10
+ /** One Space, or `null` where none is reported. */
11
+ export declare function readPhiMediaSpace(value: unknown, source?: string): PhiMediaSpaceOption | null;
12
+ /** The paging the media API reports, or `null` where it reports none. */
13
+ export declare function readPhiMediaPagination(value: unknown, source?: string): PhiMediaPickerPagination | null;
14
+ /**
15
+ * The tiles a media Collection carries. The provider read them from the API already; a Collection
16
+ * holds items of no particular type, so whoever takes them out reads them again instead of casting.
17
+ */
18
+ export declare function readPhiMediaAssetTiles(value: unknown, source?: string): PhiMediaAssetTile[];
6
19
  export declare function resolvePhiMediaAssetDisplayDimensions(asset: Pick<PhiMediaAssetTile, "contentType" | "width" | "height">): {
7
20
  width: number;
8
21
  height: number;
@@ -1,4 +1,174 @@
1
- import { resolvePhiMediaKindFromContentType } from "../../constants/media";
1
+ import { PHI_MEDIA_SPACE_KINDS } from "@phis/contracts/media";
2
+ import { PhiImageAssetVariantKey, PhiMediaKind, resolvePhiMediaKindFromContentType } from "../../constants/media";
3
+ import { createPhiResponseReader, requirePhiField, } from "../widgets/config/strict-config-reader";
4
+ /*
5
+ * The media API's answer, read field by field.
6
+ *
7
+ * It was cast to its type on arrival, and every reader after it cast again from the Collection's untyped
8
+ * items. A field the server stopped sending, or sent under another name, came through as `undefined`
9
+ * under a type that said otherwise -- the tile type promised a `checksumSha256` the server has never
10
+ * sent. Now a wrong answer is an error naming the field, at the one place the answer arrives, and what a
11
+ * Collection carries afterwards is read with the same readers rather than cast.
12
+ */
13
+ const PHI_MEDIA_LISTING = "The media listing";
14
+ const PHI_MEDIA_VARIANT_KEYS = Object.values(PhiImageAssetVariantKey);
15
+ const PHI_MEDIA_KINDS = Object.values(PhiMediaKind);
16
+ const nullableInteger = (reader, key) => reader.integer(key) ?? null;
17
+ const nullableNumber = (reader, key) => reader.number(key) ?? null;
18
+ const nullableString = (reader, key) => reader.string(key) ?? null;
19
+ const requiredInteger = (reader, key) => requirePhiField(reader, key, "a whole number", reader.integer);
20
+ const requiredString = (reader, key) => requirePhiField(reader, key, "a text", reader.string);
21
+ function readPhiMediaFolderFields(reader) {
22
+ const createdAt = reader.string("createdAt");
23
+ const updatedAt = reader.string("updatedAt");
24
+ return {
25
+ id: requiredInteger(reader, "id"),
26
+ siteId: requiredInteger(reader, "siteId"),
27
+ spaceId: requiredInteger(reader, "spaceId"),
28
+ parentId: nullableInteger(reader, "parentId"),
29
+ name: requiredString(reader, "name"),
30
+ sortOrder: requiredInteger(reader, "sortOrder"),
31
+ flags: requiredInteger(reader, "flags"),
32
+ ...(createdAt ? { createdAt } : {}),
33
+ ...(updatedAt ? { updatedAt } : {}),
34
+ };
35
+ }
36
+ function readPhiMediaSpaceFields(reader) {
37
+ return {
38
+ id: nullableInteger(reader, "id"),
39
+ address: requiredString(reader, "address"),
40
+ kind: requirePhiField(reader, "kind", "a Media Space kind", (key) => reader.oneOf(key, PHI_MEDIA_SPACE_KINDS)),
41
+ groupId: nullableInteger(reader, "groupId"),
42
+ name: nullableString(reader, "name"),
43
+ quotaBytes: nullableInteger(reader, "quotaBytes"),
44
+ usedBytes: requiredInteger(reader, "usedBytes"),
45
+ reservedBytes: requiredInteger(reader, "reservedBytes"),
46
+ };
47
+ }
48
+ function readPhiMediaPaginationFields(reader) {
49
+ const total = reader.integer("total");
50
+ return {
51
+ page: requiredInteger(reader, "page"),
52
+ pageSize: requiredInteger(reader, "pageSize"),
53
+ ...(total === undefined ? {} : { total }),
54
+ hasMore: requirePhiField(reader, "hasMore", "true or false", reader.boolean),
55
+ };
56
+ }
57
+ function readPhiMediaVariantFields(reader) {
58
+ const createdAt = reader.string("createdAt");
59
+ const updatedAt = reader.string("updatedAt");
60
+ return {
61
+ assetId: requiredInteger(reader, "assetId"),
62
+ variantKey: requirePhiField(reader, "variantKey", "an image variant key", (key) => reader.oneOf(key, PHI_MEDIA_VARIANT_KEYS)),
63
+ deliveryUrl: requiredString(reader, "deliveryUrl"),
64
+ contentType: requiredString(reader, "contentType"),
65
+ bytes: nullableInteger(reader, "bytes"),
66
+ width: requiredInteger(reader, "width"),
67
+ height: requiredInteger(reader, "height"),
68
+ blurDataUrl: nullableString(reader, "blurDataUrl"),
69
+ flags: requiredInteger(reader, "flags"),
70
+ ...(createdAt ? { createdAt } : {}),
71
+ ...(updatedAt ? { updatedAt } : {}),
72
+ };
73
+ }
74
+ /** One Asset as the media API sends it; `kind` is derived from the content type, not taken. */
75
+ function readPhiMediaAssetFields(reader) {
76
+ const createdAt = reader.string("createdAt");
77
+ const updatedAt = reader.string("updatedAt");
78
+ const variantVersion = reader.integer("variantVersion");
79
+ const folder = reader.object("folder", readPhiMediaFolderFields);
80
+ const variants = reader.list("variants", readPhiMediaVariantFields);
81
+ return {
82
+ id: requiredInteger(reader, "id"),
83
+ siteId: requiredInteger(reader, "siteId"),
84
+ spaceId: requiredInteger(reader, "spaceId"),
85
+ folderId: nullableInteger(reader, "folderId"),
86
+ createdByUserId: nullableInteger(reader, "createdByUserId"),
87
+ updatedByUserId: nullableInteger(reader, "updatedByUserId"),
88
+ ...(variantVersion === undefined ? {} : { variantVersion }),
89
+ deliveryUrl: requiredString(reader, "deliveryUrl"),
90
+ originalName: requiredString(reader, "originalName"),
91
+ contentType: requiredString(reader, "contentType"),
92
+ bytes: nullableInteger(reader, "bytes"),
93
+ width: nullableNumber(reader, "width"),
94
+ height: nullableNumber(reader, "height"),
95
+ blurDataUrl: nullableString(reader, "blurDataUrl"),
96
+ thumbnailUrl: nullableString(reader, "thumbnailUrl"),
97
+ previewUrl: nullableString(reader, "previewUrl"),
98
+ meta: reader.value("meta", "an object", (value) => (isPlainRecord(value) ? value : undefined)) ?? null,
99
+ title: nullableString(reader, "title"),
100
+ altText: nullableString(reader, "altText"),
101
+ lifecycleStatus: requiredInteger(reader, "lifecycleStatus"),
102
+ deliveryPolicy: requiredInteger(reader, "deliveryPolicy"),
103
+ deliveryRevision: requiredInteger(reader, "deliveryRevision"),
104
+ presentationFlags: requiredInteger(reader, "presentationFlags"),
105
+ ...(createdAt ? { createdAt } : {}),
106
+ ...(updatedAt ? { updatedAt } : {}),
107
+ ...(folder ? { folder } : {}),
108
+ ...(variants ? { variants } : {}),
109
+ };
110
+ }
111
+ function isPlainRecord(value) {
112
+ return typeof value === "object" && value !== null && !Array.isArray(value);
113
+ }
114
+ /** The media API's Folders, read strictly; `source` names who answered. */
115
+ export function readPhiMediaFolders(value, source = PHI_MEDIA_LISTING) {
116
+ return createPhiResponseReader({ folders: value }, source).list("folders", readPhiMediaFolderFields) ?? [];
117
+ }
118
+ /** The media API's Spaces, read strictly. */
119
+ export function readPhiMediaSpaces(value, source = PHI_MEDIA_LISTING) {
120
+ return createPhiResponseReader({ spaces: value }, source).list("spaces", readPhiMediaSpaceFields) ?? [];
121
+ }
122
+ /** One Space, or `null` where none is reported. */
123
+ export function readPhiMediaSpace(value, source = PHI_MEDIA_LISTING) {
124
+ return createPhiResponseReader({ activeSpace: value }, source).object("activeSpace", readPhiMediaSpaceFields) ?? null;
125
+ }
126
+ /** The paging the media API reports, or `null` where it reports none. */
127
+ export function readPhiMediaPagination(value, source = PHI_MEDIA_LISTING) {
128
+ return createPhiResponseReader({ pagination: value }, source).object("pagination", readPhiMediaPaginationFields) ?? null;
129
+ }
130
+ /**
131
+ * The tiles a media Collection carries. The provider read them from the API already; a Collection
132
+ * holds items of no particular type, so whoever takes them out reads them again instead of casting.
133
+ */
134
+ export function readPhiMediaAssetTiles(value, source = PHI_MEDIA_LISTING) {
135
+ return createPhiResponseReader({ assets: value }, source).list("assets", (reader) => {
136
+ const variantVersion = reader.integer("variantVersion");
137
+ const meta = reader.value("meta", "an object", (entry) => (isPlainRecord(entry) ? entry : undefined));
138
+ const blurDataUrl = reader.string("blurDataUrl");
139
+ const folder = reader.object("folder", (folderReader) => ({
140
+ id: requiredInteger(folderReader, "id"),
141
+ name: requiredString(folderReader, "name"),
142
+ }));
143
+ return {
144
+ id: requiredInteger(reader, "id"),
145
+ siteId: requiredInteger(reader, "siteId"),
146
+ spaceId: requiredInteger(reader, "spaceId"),
147
+ folderId: nullableInteger(reader, "folderId"),
148
+ createdByUserId: nullableInteger(reader, "createdByUserId"),
149
+ updatedByUserId: nullableInteger(reader, "updatedByUserId"),
150
+ ...(variantVersion === undefined ? {} : { variantVersion }),
151
+ originalName: requiredString(reader, "originalName"),
152
+ title: nullableString(reader, "title"),
153
+ altText: nullableString(reader, "altText"),
154
+ deliveryUrl: requiredString(reader, "deliveryUrl"),
155
+ thumbnailUrl: nullableString(reader, "thumbnailUrl"),
156
+ previewUrl: nullableString(reader, "previewUrl"),
157
+ contentType: requiredString(reader, "contentType"),
158
+ kind: requirePhiField(reader, "kind", "a media kind", (key) => reader.oneOf(key, PHI_MEDIA_KINDS)),
159
+ width: nullableNumber(reader, "width"),
160
+ height: nullableNumber(reader, "height"),
161
+ bytes: nullableInteger(reader, "bytes"),
162
+ lifecycleStatus: requiredInteger(reader, "lifecycleStatus"),
163
+ deliveryPolicy: requiredInteger(reader, "deliveryPolicy"),
164
+ deliveryRevision: requiredInteger(reader, "deliveryRevision"),
165
+ presentationFlags: requiredInteger(reader, "presentationFlags"),
166
+ ...(blurDataUrl === undefined ? {} : { blurDataUrl }),
167
+ ...(meta === undefined ? {} : { meta }),
168
+ folder: folder ?? null,
169
+ };
170
+ }) ?? [];
171
+ }
2
172
  const PHI_SVG_MEDIA_MIN_DISPLAY_DIMENSION = 48;
3
173
  function normalizePhiImagePreviewAsset(asset) {
4
174
  return {
@@ -74,16 +244,21 @@ export function normalizePhiImagePreviewSelectionAsset(tile) {
74
244
  };
75
245
  }
76
246
  export async function readPhiImagePreviewResponse(response) {
77
- const payload = (await response.json());
247
+ const payload = await response.json();
78
248
  if (!response.ok) {
79
- throw new Error(payload.error ?? "Failed to load media assets.");
249
+ const error = isPlainRecord(payload) && typeof payload.error === "string" ? payload.error : null;
250
+ throw new Error(error ?? "Failed to load media assets.");
80
251
  }
252
+ const reader = createPhiResponseReader(payload, PHI_MEDIA_LISTING);
253
+ const folders = reader.list("folders", readPhiMediaFolderFields) ?? [];
254
+ const asset = reader.object("asset", readPhiMediaAssetFields);
81
255
  return {
82
- activeSpace: payload.activeSpace ?? null,
83
- spaces: payload.spaces ?? [],
84
- assets: (payload.assets ?? []).map((asset) => normalizePhiImagePreviewTile(asset, payload.folders ?? [])),
85
- folders: payload.folders ?? [],
86
- pagination: payload.pagination ?? null,
87
- asset: payload.asset ? normalizePhiImagePreviewAsset(payload.asset) : null,
256
+ activeSpace: reader.object("activeSpace", readPhiMediaSpaceFields) ?? null,
257
+ spaces: reader.list("spaces", readPhiMediaSpaceFields) ?? [],
258
+ assets: (reader.list("assets", readPhiMediaAssetFields) ?? [])
259
+ .map((entry) => normalizePhiImagePreviewTile(entry, folders)),
260
+ folders,
261
+ pagination: reader.object("pagination", readPhiMediaPaginationFields) ?? null,
262
+ asset: asset ? normalizePhiImagePreviewAsset(asset) : null,
88
263
  };
89
264
  }
@@ -10,6 +10,7 @@ import { PHI_ASSET_COLLECTION_DATA_SOURCE } from "./asset-collection-runtime";
10
10
  import { buildPhiMediaFolderCascaderOptions, buildPhiMediaFolderValueById, resolvePhiMediaFolderIdFromValue, } from "./media-folder-options";
11
11
  import { PHI_ASSET_CONTROLLER_STORE_KEY, PHI_ASSET_SIGNAL_CHANNELS, } from "./asset-controller-signals";
12
12
  import { setPhiImagePreviewSelection } from "./phi-image-preview-store";
13
+ import { readPhiMediaAssetTiles, readPhiMediaFolders, readPhiMediaSpace, readPhiMediaSpaces, } from "./phi-image-preview-data";
13
14
  import { buildPhiMediaSpaceOptions, usePhiMediaSpaceSelectionAllowed, } from "./media-space-selection";
14
15
  import { isPhiRecord } from "../../helpers/is-record";
15
16
  export function usePhiMediaPickerBinding({ config, labels, searchLabels, value: controlledValue, onAssetSelect, onAssetClear, open: controlledOpen, onOpenChange, onCommit, onDiscard, onPopupOpenChange, trigger, getPopupContainer, popupRootClassName, }) {
@@ -27,6 +28,13 @@ export function usePhiMediaPickerBinding({ config, labels, searchLabels, value:
27
28
  // Null asks for no particular Space, which the control plane answers with the Site Space. An
28
29
  // authoring surface never leaves this state, because it never renders the selector.
29
30
  const [spaceAddress, setSpaceAddress] = useState(null);
31
+ /*
32
+ * The Space the reader chose, which is what the query asks for -- kept apart from the Space the last
33
+ * answer served, which is what the selector shows. Writing the served Space back into the query made
34
+ * the first open ask twice: once for nothing in particular, and once more for the Space it had just
35
+ * been given.
36
+ */
37
+ const [requestedSpaceAddress, setRequestedSpaceAddress] = useState(null);
30
38
  const spaceSelectionAllowed = usePhiMediaSpaceSelectionAllowed();
31
39
  const [total, setTotal] = useState(0);
32
40
  const [loading, setLoading] = useState(false);
@@ -53,9 +61,9 @@ export function usePhiMediaPickerBinding({ config, labels, searchLabels, value:
53
61
  folderId,
54
62
  kind: mediaType,
55
63
  presentationFlags,
56
- ...(spaceSelectionAllowed && spaceAddress ? { spaceId: spaceAddress } : {}),
64
+ ...(spaceSelectionAllowed && requestedSpaceAddress ? { spaceId: requestedSpaceAddress } : {}),
57
65
  },
58
- }), [presentationFlags, folderId, mediaType, page, pageSize, searchQuery, spaceAddress, spaceSelectionAllowed]);
66
+ }), [presentationFlags, folderId, mediaType, page, pageSize, requestedSpaceAddress, searchQuery, spaceSelectionAllowed]);
59
67
  const querySignals = usePhiControlSignalController({ key: PHI_ASSET_SIGNAL_CHANNELS.query, signalRoutes: config?.signalRoutes, valueType: "string", typeKey: "media-picker-query", clearValue: "", onSetValue: (next) => { setSearchQuery(next); setPage(1); }, coerceValue: (next) => typeof next === "string" ? next : null });
60
68
  const pathSignals = usePhiControlSignalController({ key: PHI_ASSET_SIGNAL_CHANNELS.path, signalRoutes: config?.signalRoutes, valueType: "path", typeKey: "media-picker-path", clearValue: "/", onSetValue: (next) => { setFolderId(resolvePhiMediaFolderIdFromValue(folders, next)); setPage(1); }, coerceValue: (next) => typeof next === "string" ? next : null });
61
69
  const reloadSignals = usePhiControlSignalController({ key: PHI_ASSET_SIGNAL_CHANNELS.reload, signalRoutes: config?.signalRoutes, typeKey: "media-picker-reload", onReceiveCapability: (capabilityId) => { if (capabilityId !== "reload")
@@ -84,25 +92,19 @@ export function usePhiMediaPickerBinding({ config, labels, searchLabels, value:
84
92
  });
85
93
  if (abortController.signal.aborted)
86
94
  return;
87
- const nextFolders = Array.isArray(data.meta?.folders)
88
- ? data.meta.folders.filter(isPhiRecord)
89
- : [];
90
- const nextAssets = data.items.filter(isPhiRecord);
95
+ const nextFolders = readPhiMediaFolders(data.meta?.folders);
96
+ const nextAssets = readPhiMediaAssetTiles(data.items);
91
97
  setAssets(nextAssets);
92
98
  for (const asset of nextAssets)
93
99
  assetCacheRef.current.set(asset.id, asset);
94
100
  setFolders(nextFolders);
95
- setSpaces(Array.isArray(data.meta?.spaces)
96
- ? data.meta.spaces.filter(isPhiRecord)
97
- : []);
101
+ setSpaces(readPhiMediaSpaces(data.meta?.spaces));
98
102
  // The answer says which Space it actually served, so the selector follows the control plane
99
103
  // rather than the other way round -- including the first load, which asked for nothing.
100
- const activeAddress = isPhiRecord(data.meta?.activeSpace) && typeof data.meta.activeSpace.address === "string"
101
- ? data.meta.activeSpace.address
102
- : null;
104
+ const activeAddress = readPhiMediaSpace(data.meta?.activeSpace)?.address ?? null;
103
105
  if (activeAddress)
104
106
  setSpaceAddress(activeAddress);
105
- setTotal(typeof data.total === "number" ? data.total : data.items.length);
107
+ setTotal(data.total);
106
108
  }
107
109
  catch (error) {
108
110
  if (!abortController.signal.aborted) {
@@ -185,6 +187,7 @@ export function usePhiMediaPickerBinding({ config, labels, searchLabels, value:
185
187
  return;
186
188
  // Folders belong to the Space they were listed from, so switching Space starts at its root.
187
189
  setSpaceAddress(nextSpace);
190
+ setRequestedSpaceAddress(nextSpace);
188
191
  setFolderId(null);
189
192
  setPage(1);
190
193
  },
@@ -74,7 +74,11 @@ type PhiMediaUploadItem = {
74
74
  * upload settled -- a form field that receives the Asset id in `onUploaded` -- reads it to hold back
75
75
  * whatever would act on the incomplete value.
76
76
  */
77
- export declare function usePhiMediaUpload(input: {
77
+ export declare function usePhiMediaUpload<TAsset extends {
78
+ id: number;
79
+ } = {
80
+ id: number;
81
+ }>(input: {
78
82
  labels: PhiMediaUploadLabels;
79
83
  acceptance?: PhiMediaUploadAcceptance;
80
84
  initOptions?: PhiMediaUploadInitOptions;
@@ -85,18 +89,21 @@ export declare function usePhiMediaUpload(input: {
85
89
  * between, so it is this one thing that differs -- and everything a person sees while it happens is
86
90
  * shared rather than reimplemented alongside.
87
91
  */
88
- runSession?: PhiMediaUploadSessionRunner;
89
- onUploaded?: (asset: {
90
- id: number;
91
- }, file: File) => void | Promise<void>;
92
+ runSession?: (...args: Parameters<PhiMediaUploadSessionRunner>) => Promise<{
93
+ asset: TAsset;
94
+ }>;
95
+ /**
96
+ * The Asset as the session answered it. A surface that shows the new Asset at once -- a tile in the
97
+ * upload wall -- reads more of it than the id, and the default runner answers with the whole record.
98
+ */
99
+ onUploaded?: (asset: TAsset, file: File) => void | Promise<void>;
92
100
  onRejected?: (message: string, file: File) => void;
93
101
  }): {
94
102
  accept: string;
95
103
  items: readonly PhiMediaUploadItem[];
96
104
  uploading: boolean;
97
- upload: (file: File) => Promise<{
98
- id: number;
99
- } | null>;
105
+ upload: (file: File) => Promise<TAsset | null>;
106
+ retry: (localId: string) => Promise<TAsset | null>;
100
107
  cancel: () => void;
101
108
  reset: () => void;
102
109
  };
@@ -120,7 +120,8 @@ export function readPhiMediaUploadErrorMessage(error, labels) {
120
120
  */
121
121
  export function usePhiMediaUpload(input) {
122
122
  const { labels, acceptance, initOptions, onUploaded, onRejected } = input;
123
- const runSession = input.runSession ?? runPhiMediaUploadSession;
123
+ const runSession = input.runSession
124
+ ?? runPhiMediaUploadSession;
124
125
  const [items, setItems] = useState([]);
125
126
  const sequence = useRef(0);
126
127
  const running = useRef(new Set());
@@ -136,18 +137,20 @@ export function usePhiMediaUpload(input) {
136
137
  const patch = useCallback((localId, next) => {
137
138
  setItems((current) => current.map((item) => item.localId === localId ? { ...item, ...next } : item));
138
139
  }, []);
139
- const upload = useCallback(async (file) => {
140
+ /** One file into one item: a new item, or -- for a retry -- the failed one it had. */
141
+ const run = useCallback(async (file, retryLocalId) => {
140
142
  const rejection = readPhiMediaUploadRejection(file, acceptance, labels);
141
143
  if (rejection) {
142
144
  onRejected?.(rejection, file);
143
145
  return null;
144
146
  }
145
- sequence.current += 1;
146
- const localId = `upload-${sequence.current}`;
147
- setItems((current) => [
148
- { localId, file, progress: 0, status: "uploading", error: null, assetId: null },
149
- ...current,
150
- ]);
147
+ let localId = retryLocalId;
148
+ if (localId == null) {
149
+ sequence.current += 1;
150
+ localId = `upload-${sequence.current}`;
151
+ }
152
+ const startedItem = { localId, file, progress: 0, status: "uploading", error: null, assetId: null };
153
+ setItems((current) => [startedItem, ...current.filter((item) => item.localId !== localId)]);
151
154
  const controller = new AbortController();
152
155
  running.current.add(controller);
153
156
  let asset;
@@ -182,11 +185,17 @@ export function usePhiMediaUpload(input) {
182
185
  }
183
186
  return asset;
184
187
  }, [acceptance, initOptions, labels, onRejected, onUploaded, patch, runSession]);
188
+ const upload = useCallback((file) => run(file), [run]);
189
+ /** Starts a failed item's file again, in the place it had. */
190
+ const retry = useCallback((localId) => {
191
+ const failed = items.find((item) => item.localId === localId && item.status === "error");
192
+ return failed ? run(failed.file, localId) : Promise.resolve(null);
193
+ }, [items, run]);
185
194
  const cancel = useCallback(() => {
186
195
  for (const controller of running.current)
187
196
  controller.abort();
188
197
  }, []);
189
198
  const reset = useCallback(() => setItems([]), []);
190
199
  const uploading = items.some((item) => item.status === "uploading");
191
- return { accept, items, uploading, upload, cancel, reset };
200
+ return { accept, items, uploading, upload, retry, cancel, reset };
192
201
  }