@cohesivesystems/presentation-react 0.1.0-alpha.1

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 (252) hide show
  1. package/LICENSE +55 -0
  2. package/dist/collection-chrome-slot-renderer-registry.d.ts +12 -0
  3. package/dist/collection-chrome-slot-renderer-registry.d.ts.map +1 -0
  4. package/dist/collection-chrome-slot-renderer-registry.js +12 -0
  5. package/dist/collection-chrome-slot-renderer-registry.js.map +1 -0
  6. package/dist/collection-selection-state-context.d.ts +6 -0
  7. package/dist/collection-selection-state-context.d.ts.map +1 -0
  8. package/dist/collection-selection-state-context.js +25 -0
  9. package/dist/collection-selection-state-context.js.map +1 -0
  10. package/dist/collection-selection-state.d.ts +3 -0
  11. package/dist/collection-selection-state.d.ts.map +1 -0
  12. package/dist/collection-selection-state.js +52 -0
  13. package/dist/collection-selection-state.js.map +1 -0
  14. package/dist/document-workspace-runtime.d.ts +262 -0
  15. package/dist/document-workspace-runtime.d.ts.map +1 -0
  16. package/dist/document-workspace-runtime.js +699 -0
  17. package/dist/document-workspace-runtime.js.map +1 -0
  18. package/dist/index.d.ts +51 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +51 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/input-form-endpoint-runtime.d.ts +140 -0
  23. package/dist/input-form-endpoint-runtime.d.ts.map +1 -0
  24. package/dist/input-form-endpoint-runtime.js +280 -0
  25. package/dist/input-form-endpoint-runtime.js.map +1 -0
  26. package/dist/local-flow-state-data-source-projection.d.ts +21 -0
  27. package/dist/local-flow-state-data-source-projection.d.ts.map +1 -0
  28. package/dist/local-flow-state-data-source-projection.js +65 -0
  29. package/dist/local-flow-state-data-source-projection.js.map +1 -0
  30. package/dist/navigation-runtime-context.d.ts +22 -0
  31. package/dist/navigation-runtime-context.d.ts.map +1 -0
  32. package/dist/navigation-runtime-context.js +23 -0
  33. package/dist/navigation-runtime-context.js.map +1 -0
  34. package/dist/navigation-shell-frame-renderer-registry.d.ts +20 -0
  35. package/dist/navigation-shell-frame-renderer-registry.d.ts.map +1 -0
  36. package/dist/navigation-shell-frame-renderer-registry.js +9 -0
  37. package/dist/navigation-shell-frame-renderer-registry.js.map +1 -0
  38. package/dist/navigation-shell-region-component-registry.d.ts +50 -0
  39. package/dist/navigation-shell-region-component-registry.d.ts.map +1 -0
  40. package/dist/navigation-shell-region-component-registry.js +44 -0
  41. package/dist/navigation-shell-region-component-registry.js.map +1 -0
  42. package/dist/navigation-shell-slot-renderer-registry.d.ts +22 -0
  43. package/dist/navigation-shell-slot-renderer-registry.d.ts.map +1 -0
  44. package/dist/navigation-shell-slot-renderer-registry.js +9 -0
  45. package/dist/navigation-shell-slot-renderer-registry.js.map +1 -0
  46. package/dist/page-host-projection.d.ts +127 -0
  47. package/dist/page-host-projection.d.ts.map +1 -0
  48. package/dist/page-host-projection.js +59 -0
  49. package/dist/page-host-projection.js.map +1 -0
  50. package/dist/page-host-renderer-registry.d.ts +72 -0
  51. package/dist/page-host-renderer-registry.d.ts.map +1 -0
  52. package/dist/page-host-renderer-registry.js +63 -0
  53. package/dist/page-host-renderer-registry.js.map +1 -0
  54. package/dist/presentation-action-group-runtime-options.d.ts +36 -0
  55. package/dist/presentation-action-group-runtime-options.d.ts.map +1 -0
  56. package/dist/presentation-action-group-runtime-options.js +50 -0
  57. package/dist/presentation-action-group-runtime-options.js.map +1 -0
  58. package/dist/presentation-action-runtime.d.ts +52 -0
  59. package/dist/presentation-action-runtime.d.ts.map +1 -0
  60. package/dist/presentation-action-runtime.js +78 -0
  61. package/dist/presentation-action-runtime.js.map +1 -0
  62. package/dist/presentation-data-source-binding-runtime.d.ts +3 -0
  63. package/dist/presentation-data-source-binding-runtime.d.ts.map +1 -0
  64. package/dist/presentation-data-source-binding-runtime.js +65 -0
  65. package/dist/presentation-data-source-binding-runtime.js.map +1 -0
  66. package/dist/presentation-data-source-binding.d.ts +12 -0
  67. package/dist/presentation-data-source-binding.d.ts.map +1 -0
  68. package/dist/presentation-data-source-binding.js +13 -0
  69. package/dist/presentation-data-source-binding.js.map +1 -0
  70. package/dist/presentation-flow-runtime.d.ts +139 -0
  71. package/dist/presentation-flow-runtime.d.ts.map +1 -0
  72. package/dist/presentation-flow-runtime.js +514 -0
  73. package/dist/presentation-flow-runtime.js.map +1 -0
  74. package/dist/presentation-module-context.d.ts +4 -0
  75. package/dist/presentation-module-context.d.ts.map +1 -0
  76. package/dist/presentation-module-context.js +6 -0
  77. package/dist/presentation-module-context.js.map +1 -0
  78. package/dist/presentation-module-provider.d.ts +7 -0
  79. package/dist/presentation-module-provider.d.ts.map +1 -0
  80. package/dist/presentation-module-provider.js +7 -0
  81. package/dist/presentation-module-provider.js.map +1 -0
  82. package/dist/presentation-projection-diagnostics-context.d.ts +8 -0
  83. package/dist/presentation-projection-diagnostics-context.d.ts.map +1 -0
  84. package/dist/presentation-projection-diagnostics-context.js +3 -0
  85. package/dist/presentation-projection-diagnostics-context.js.map +1 -0
  86. package/dist/presentation-projection-diagnostics-hooks.d.ts +4 -0
  87. package/dist/presentation-projection-diagnostics-hooks.d.ts.map +1 -0
  88. package/dist/presentation-projection-diagnostics-hooks.js +17 -0
  89. package/dist/presentation-projection-diagnostics-hooks.js.map +1 -0
  90. package/dist/presentation-projection-diagnostics-runtime.d.ts +12 -0
  91. package/dist/presentation-projection-diagnostics-runtime.d.ts.map +1 -0
  92. package/dist/presentation-projection-diagnostics-runtime.js +54 -0
  93. package/dist/presentation-projection-diagnostics-runtime.js.map +1 -0
  94. package/dist/presentation-projection-trace.d.ts +27 -0
  95. package/dist/presentation-projection-trace.d.ts.map +1 -0
  96. package/dist/presentation-projection-trace.js +54 -0
  97. package/dist/presentation-projection-trace.js.map +1 -0
  98. package/dist/presentation-query-form-runtime.d.ts +30 -0
  99. package/dist/presentation-query-form-runtime.d.ts.map +1 -0
  100. package/dist/presentation-query-form-runtime.js +75 -0
  101. package/dist/presentation-query-form-runtime.js.map +1 -0
  102. package/dist/presentation-query-form-state-adapter-registry.d.ts +4 -0
  103. package/dist/presentation-query-form-state-adapter-registry.d.ts.map +1 -0
  104. package/dist/presentation-query-form-state-adapter-registry.js +13 -0
  105. package/dist/presentation-query-form-state-adapter-registry.js.map +1 -0
  106. package/dist/presentation-query-form-state-context.d.ts +20 -0
  107. package/dist/presentation-query-form-state-context.d.ts.map +1 -0
  108. package/dist/presentation-query-form-state-context.js +20 -0
  109. package/dist/presentation-query-form-state-context.js.map +1 -0
  110. package/dist/presentation-query-form-state.d.ts +29 -0
  111. package/dist/presentation-query-form-state.d.ts.map +1 -0
  112. package/dist/presentation-query-form-state.js +99 -0
  113. package/dist/presentation-query-form-state.js.map +1 -0
  114. package/dist/presentation-renderer-registry.d.ts +109 -0
  115. package/dist/presentation-renderer-registry.d.ts.map +1 -0
  116. package/dist/presentation-renderer-registry.js +25 -0
  117. package/dist/presentation-renderer-registry.js.map +1 -0
  118. package/dist/presentation-standards-context.d.ts +4 -0
  119. package/dist/presentation-standards-context.d.ts.map +1 -0
  120. package/dist/presentation-standards-context.js +12 -0
  121. package/dist/presentation-standards-context.js.map +1 -0
  122. package/dist/presentation-standards-provider.d.ts +14 -0
  123. package/dist/presentation-standards-provider.d.ts.map +1 -0
  124. package/dist/presentation-standards-provider.js +16 -0
  125. package/dist/presentation-standards-provider.js.map +1 -0
  126. package/dist/projected-document-action-runtime-bindings.d.ts +20 -0
  127. package/dist/projected-document-action-runtime-bindings.d.ts.map +1 -0
  128. package/dist/projected-document-action-runtime-bindings.js +88 -0
  129. package/dist/projected-document-action-runtime-bindings.js.map +1 -0
  130. package/dist/projected-document-action-runtime.d.ts +76 -0
  131. package/dist/projected-document-action-runtime.d.ts.map +1 -0
  132. package/dist/projected-document-action-runtime.js +224 -0
  133. package/dist/projected-document-action-runtime.js.map +1 -0
  134. package/dist/projected-document-process-preview-action-runtime-bindings.d.ts +26 -0
  135. package/dist/projected-document-process-preview-action-runtime-bindings.d.ts.map +1 -0
  136. package/dist/projected-document-process-preview-action-runtime-bindings.js +106 -0
  137. package/dist/projected-document-process-preview-action-runtime-bindings.js.map +1 -0
  138. package/dist/projected-document-process-preview-action-runtime.d.ts +321 -0
  139. package/dist/projected-document-process-preview-action-runtime.d.ts.map +1 -0
  140. package/dist/projected-document-process-preview-action-runtime.js +729 -0
  141. package/dist/projected-document-process-preview-action-runtime.js.map +1 -0
  142. package/dist/projected-document-process-preview-capability-runtime.d.ts +47 -0
  143. package/dist/projected-document-process-preview-capability-runtime.d.ts.map +1 -0
  144. package/dist/projected-document-process-preview-capability-runtime.js +49 -0
  145. package/dist/projected-document-process-preview-capability-runtime.js.map +1 -0
  146. package/dist/projected-document-workspace-action-runtime.d.ts +87 -0
  147. package/dist/projected-document-workspace-action-runtime.d.ts.map +1 -0
  148. package/dist/projected-document-workspace-action-runtime.js +150 -0
  149. package/dist/projected-document-workspace-action-runtime.js.map +1 -0
  150. package/dist/projected-document-workspace-projection-renderer-diagnostics.d.ts +18 -0
  151. package/dist/projected-document-workspace-projection-renderer-diagnostics.d.ts.map +1 -0
  152. package/dist/projected-document-workspace-projection-renderer-diagnostics.js +207 -0
  153. package/dist/projected-document-workspace-projection-renderer-diagnostics.js.map +1 -0
  154. package/dist/projected-document-workspace-runtime-binding-diagnostics.d.ts +19 -0
  155. package/dist/projected-document-workspace-runtime-binding-diagnostics.d.ts.map +1 -0
  156. package/dist/projected-document-workspace-runtime-binding-diagnostics.js +124 -0
  157. package/dist/projected-document-workspace-runtime-binding-diagnostics.js.map +1 -0
  158. package/dist/projected-navigation-provider.d.ts +16 -0
  159. package/dist/projected-navigation-provider.d.ts.map +1 -0
  160. package/dist/projected-navigation-provider.js +51 -0
  161. package/dist/projected-navigation-provider.js.map +1 -0
  162. package/dist/projected-presentation-flow-host.d.ts +16 -0
  163. package/dist/projected-presentation-flow-host.d.ts.map +1 -0
  164. package/dist/projected-presentation-flow-host.js +14 -0
  165. package/dist/projected-presentation-flow-host.js.map +1 -0
  166. package/dist/projected-query-form-state-adapter.d.ts +12 -0
  167. package/dist/projected-query-form-state-adapter.d.ts.map +1 -0
  168. package/dist/projected-query-form-state-adapter.js +361 -0
  169. package/dist/projected-query-form-state-adapter.js.map +1 -0
  170. package/dist/react-router-binding.d.ts +133 -0
  171. package/dist/react-router-binding.d.ts.map +1 -0
  172. package/dist/react-router-binding.js +76 -0
  173. package/dist/react-router-binding.js.map +1 -0
  174. package/dist/react-router-path.d.ts +4 -0
  175. package/dist/react-router-path.d.ts.map +1 -0
  176. package/dist/react-router-path.js +11 -0
  177. package/dist/react-router-path.js.map +1 -0
  178. package/dist/scope-selection-context.d.ts +28 -0
  179. package/dist/scope-selection-context.d.ts.map +1 -0
  180. package/dist/scope-selection-context.js +13 -0
  181. package/dist/scope-selection-context.js.map +1 -0
  182. package/dist/scope-selection-provider.d.ts +33 -0
  183. package/dist/scope-selection-provider.d.ts.map +1 -0
  184. package/dist/scope-selection-provider.js +162 -0
  185. package/dist/scope-selection-provider.js.map +1 -0
  186. package/dist/scope-selection-request.d.ts +6 -0
  187. package/dist/scope-selection-request.d.ts.map +1 -0
  188. package/dist/scope-selection-request.js +12 -0
  189. package/dist/scope-selection-request.js.map +1 -0
  190. package/dist/standard-navigation-shell-frame-renderers.d.ts +15 -0
  191. package/dist/standard-navigation-shell-frame-renderers.d.ts.map +1 -0
  192. package/dist/standard-navigation-shell-frame-renderers.js +22 -0
  193. package/dist/standard-navigation-shell-frame-renderers.js.map +1 -0
  194. package/dist/standard-navigation-shell-slot-renderers.d.ts +59 -0
  195. package/dist/standard-navigation-shell-slot-renderers.d.ts.map +1 -0
  196. package/dist/standard-navigation-shell-slot-renderers.js +67 -0
  197. package/dist/standard-navigation-shell-slot-renderers.js.map +1 -0
  198. package/dist/view-chrome-slot-renderer-registry.d.ts +12 -0
  199. package/dist/view-chrome-slot-renderer-registry.d.ts.map +1 -0
  200. package/dist/view-chrome-slot-renderer-registry.js +12 -0
  201. package/dist/view-chrome-slot-renderer-registry.js.map +1 -0
  202. package/package.json +60 -0
  203. package/src/collection-chrome-slot-renderer-registry.ts +66 -0
  204. package/src/collection-selection-state-context.ts +51 -0
  205. package/src/collection-selection-state.tsx +101 -0
  206. package/src/document-workspace-runtime.tsx +1505 -0
  207. package/src/index.ts +50 -0
  208. package/src/input-form-endpoint-runtime.ts +632 -0
  209. package/src/local-flow-state-data-source-projection.ts +133 -0
  210. package/src/navigation-runtime-context.ts +40 -0
  211. package/src/navigation-shell-frame-renderer-registry.ts +54 -0
  212. package/src/navigation-shell-region-component-registry.tsx +127 -0
  213. package/src/navigation-shell-slot-renderer-registry.ts +88 -0
  214. package/src/page-host-projection.ts +356 -0
  215. package/src/page-host-renderer-registry.tsx +353 -0
  216. package/src/presentation-action-group-runtime-options.ts +121 -0
  217. package/src/presentation-action-runtime.ts +190 -0
  218. package/src/presentation-data-source-binding-runtime.ts +109 -0
  219. package/src/presentation-data-source-binding.tsx +24 -0
  220. package/src/presentation-flow-runtime.tsx +835 -0
  221. package/src/presentation-module-context.ts +10 -0
  222. package/src/presentation-module-provider.tsx +19 -0
  223. package/src/presentation-projection-diagnostics-context.ts +17 -0
  224. package/src/presentation-projection-diagnostics-hooks.ts +29 -0
  225. package/src/presentation-projection-diagnostics-runtime.tsx +95 -0
  226. package/src/presentation-projection-trace.ts +117 -0
  227. package/src/presentation-query-form-runtime.ts +148 -0
  228. package/src/presentation-query-form-state-adapter-registry.ts +25 -0
  229. package/src/presentation-query-form-state-context.ts +56 -0
  230. package/src/presentation-query-form-state.tsx +250 -0
  231. package/src/presentation-renderer-registry.ts +214 -0
  232. package/src/presentation-standards-context.ts +21 -0
  233. package/src/presentation-standards-provider.tsx +44 -0
  234. package/src/projected-document-action-runtime-bindings.ts +132 -0
  235. package/src/projected-document-action-runtime.ts +414 -0
  236. package/src/projected-document-process-preview-action-runtime-bindings.ts +231 -0
  237. package/src/projected-document-process-preview-action-runtime.ts +1725 -0
  238. package/src/projected-document-process-preview-capability-runtime.ts +216 -0
  239. package/src/projected-document-workspace-action-runtime.ts +402 -0
  240. package/src/projected-document-workspace-projection-renderer-diagnostics.ts +342 -0
  241. package/src/projected-document-workspace-runtime-binding-diagnostics.ts +208 -0
  242. package/src/projected-navigation-provider.tsx +104 -0
  243. package/src/projected-presentation-flow-host.tsx +46 -0
  244. package/src/projected-query-form-state-adapter.ts +578 -0
  245. package/src/react-router-binding.tsx +417 -0
  246. package/src/react-router-path.ts +16 -0
  247. package/src/scope-selection-context.ts +58 -0
  248. package/src/scope-selection-provider.tsx +324 -0
  249. package/src/scope-selection-request.ts +21 -0
  250. package/src/standard-navigation-shell-frame-renderers.tsx +106 -0
  251. package/src/standard-navigation-shell-slot-renderers.tsx +289 -0
  252. package/src/view-chrome-slot-renderer-registry.ts +66 -0
@@ -0,0 +1,324 @@
1
+ import { useQuery, type QueryKey } from '@tanstack/react-query'
2
+ import {
3
+ useCallback,
4
+ useLayoutEffect,
5
+ useMemo,
6
+ useState,
7
+ type PropsWithChildren,
8
+ } from 'react'
9
+
10
+ import {
11
+ formatPresentationScopeSelectionQuerySuffix,
12
+ normalizeScopeIds,
13
+ type PresentationScopeAccess,
14
+ type PresentationScopeRequestStore,
15
+ } from '@cohesivesystems/presentation-core'
16
+ import {
17
+ PresentationScopeSelectionContext,
18
+ type PresentationScopeSelectionContextValue,
19
+ } from './scope-selection-context'
20
+
21
+ interface SelectedScopeOverride {
22
+ readonly identityKey: string
23
+ readonly scopeId: string
24
+ }
25
+
26
+ interface SelectedScopeIdsOverride {
27
+ readonly identityKey: string
28
+ readonly scopeIds: readonly string[]
29
+ }
30
+
31
+ /**
32
+ * Options for the generic presentation scope-selection provider.
33
+ */
34
+ export interface PresentationScopeSelectionProviderProps<
35
+ TScopeContext,
36
+ TScopeMetadata = unknown,
37
+ > extends PropsWithChildren {
38
+ /** Whether scope context may be loaded. */
39
+ readonly enabled: boolean
40
+
41
+ /** Identity/session partition key used for cache and persisted selection state. */
42
+ readonly identityKey: string
43
+
44
+ /** Query key used to load the source scope context. */
45
+ readonly queryKey: QueryKey
46
+
47
+ /** Loads the source scope context. */
48
+ readonly loadScopeContext: () => Promise<TScopeContext>
49
+
50
+ /** Projects accessible scopes from the source context. */
51
+ readonly getScopes: (
52
+ scopeContext: TScopeContext
53
+ ) => readonly PresentationScopeAccess<TScopeMetadata>[]
54
+
55
+ /** Resolves the default scope id from the source context and projected scopes. */
56
+ readonly getDefaultScopeId?: (
57
+ scopeContext: TScopeContext,
58
+ scopes: readonly PresentationScopeAccess<TScopeMetadata>[]
59
+ ) => string | null
60
+
61
+ /** Prefix used for persisted local selection keys. */
62
+ readonly storageKeyPrefix: string
63
+
64
+ /** Optional request-scope store to synchronize with the primary selected scope. */
65
+ readonly requestStore?: PresentationScopeRequestStore
66
+
67
+ /** Label used for single-scope query/cache suffixes. */
68
+ readonly singleScopeQueryLabel?: string
69
+
70
+ /** Label used for multi-scope query/cache suffixes. */
71
+ readonly multipleScopesQueryLabel?: string
72
+ }
73
+
74
+ /**
75
+ * Provides generic authorized-scope selection state for presentation hosts.
76
+ */
77
+ export function PresentationScopeSelectionProvider<
78
+ TScopeContext,
79
+ TScopeMetadata = unknown,
80
+ >({
81
+ children,
82
+ enabled,
83
+ getDefaultScopeId,
84
+ getScopes,
85
+ identityKey,
86
+ loadScopeContext,
87
+ multipleScopesQueryLabel,
88
+ queryKey,
89
+ requestStore,
90
+ singleScopeQueryLabel,
91
+ storageKeyPrefix,
92
+ }: PresentationScopeSelectionProviderProps<TScopeContext, TScopeMetadata>) {
93
+ const [selectedScopeOverride, setSelectedScopeOverride] =
94
+ useState<SelectedScopeOverride | null>(null)
95
+ const [selectedScopeIdsOverrides, setSelectedScopeIdsOverrides] =
96
+ useState<Record<string, SelectedScopeIdsOverride>>({})
97
+ const scopeContextQuery = useQuery<TScopeContext>({
98
+ enabled,
99
+ queryFn: loadScopeContext,
100
+ queryKey,
101
+ retry: false,
102
+ })
103
+ const scopeContext = scopeContextQuery.data ?? null
104
+ const scopes = useMemo<readonly PresentationScopeAccess<TScopeMetadata>[]>(
105
+ () => scopeContext ? getScopes(scopeContext) : [],
106
+ [getScopes, scopeContext],
107
+ )
108
+ const scopeIdSet = useMemo(
109
+ () => new Set(scopes.map((scope) => scope.id)),
110
+ [scopes],
111
+ )
112
+ const selectedScopeId = useMemo(() => {
113
+ if (!enabled || !scopeContext) {
114
+ return null
115
+ }
116
+
117
+ if (
118
+ selectedScopeOverride?.identityKey === identityKey &&
119
+ isAllowedScope(selectedScopeOverride.scopeId, scopeIdSet)
120
+ ) {
121
+ return selectedScopeOverride.scopeId
122
+ }
123
+
124
+ const storedScopeId = readStoredScopeId(storageKeyPrefix, identityKey, 'single')
125
+ if (isAllowedScope(storedScopeId, scopeIdSet)) {
126
+ return storedScopeId
127
+ }
128
+
129
+ return resolveDefaultScopeId(scopeContext, scopes, getDefaultScopeId)
130
+ }, [
131
+ enabled,
132
+ getDefaultScopeId,
133
+ identityKey,
134
+ scopeContext,
135
+ scopeIdSet,
136
+ scopes,
137
+ selectedScopeOverride,
138
+ storageKeyPrefix,
139
+ ])
140
+ const singleScopeQuerySuffix = formatPresentationScopeSelectionQuerySuffix(
141
+ {
142
+ mode: 'single',
143
+ scopeId: selectedScopeId,
144
+ },
145
+ {
146
+ multipleScopesLabel: multipleScopesQueryLabel,
147
+ singleScopeLabel: singleScopeQueryLabel,
148
+ },
149
+ )
150
+
151
+ useLayoutEffect(() => {
152
+ requestStore?.setSelection({
153
+ mode: 'single',
154
+ scopeId: selectedScopeId,
155
+ })
156
+ }, [requestStore, selectedScopeId])
157
+
158
+ const setSelectedScopeId = useCallback(
159
+ (scopeId: string) => {
160
+ if (!scopeIdSet.has(scopeId)) {
161
+ return
162
+ }
163
+
164
+ setSelectedScopeOverride({ identityKey, scopeId })
165
+ writeStoredScopeId(storageKeyPrefix, identityKey, 'single', scopeId)
166
+ },
167
+ [identityKey, scopeIdSet, storageKeyPrefix],
168
+ )
169
+ const getSelectedScopeIds = useCallback(
170
+ (purpose: string, fallbackScopeIds: readonly string[] = []) => {
171
+ const scopeIds =
172
+ selectedScopeIdsOverrides[purpose]?.identityKey === identityKey
173
+ ? selectedScopeIdsOverrides[purpose].scopeIds
174
+ : readStoredScopeIds(storageKeyPrefix, identityKey, purpose)
175
+
176
+ return normalizeSelectedScopeIds(scopeIds, scopeIdSet, fallbackScopeIds)
177
+ },
178
+ [
179
+ identityKey,
180
+ scopeIdSet,
181
+ selectedScopeIdsOverrides,
182
+ storageKeyPrefix,
183
+ ],
184
+ )
185
+ const setSelectedScopeIds = useCallback(
186
+ (purpose: string, scopeIds: readonly string[]) => {
187
+ const fallbackScopeIds = selectedScopeId ? [selectedScopeId] : []
188
+ const nextScopeIds = normalizeSelectedScopeIds(scopeIds, scopeIdSet, fallbackScopeIds)
189
+ setSelectedScopeIdsOverrides((current) => ({
190
+ ...current,
191
+ [purpose]: {
192
+ identityKey,
193
+ scopeIds: nextScopeIds,
194
+ },
195
+ }))
196
+ writeStoredScopeIds(storageKeyPrefix, identityKey, purpose, nextScopeIds)
197
+ },
198
+ [identityKey, scopeIdSet, selectedScopeId, storageKeyPrefix],
199
+ )
200
+ const value = useMemo<PresentationScopeSelectionContextValue<TScopeContext, TScopeMetadata>>(
201
+ () => ({
202
+ getSelectedScopeIds,
203
+ isLoading: scopeContextQuery.isLoading,
204
+ scopes,
205
+ scopeContext,
206
+ selectedScopeId,
207
+ setSelectedScopeId,
208
+ setSelectedScopeIds,
209
+ singleScopeQuerySuffix,
210
+ }),
211
+ [
212
+ getSelectedScopeIds,
213
+ scopeContext,
214
+ scopeContextQuery.isLoading,
215
+ scopes,
216
+ selectedScopeId,
217
+ setSelectedScopeId,
218
+ setSelectedScopeIds,
219
+ singleScopeQuerySuffix,
220
+ ],
221
+ )
222
+
223
+ return (
224
+ <PresentationScopeSelectionContext.Provider value={value}>
225
+ {children}
226
+ </PresentationScopeSelectionContext.Provider>
227
+ )
228
+ }
229
+
230
+ function resolveDefaultScopeId<TScopeContext, TScopeMetadata>(
231
+ scopeContext: TScopeContext,
232
+ scopes: readonly PresentationScopeAccess<TScopeMetadata>[],
233
+ getDefaultScopeId:
234
+ | ((
235
+ scopeContext: TScopeContext,
236
+ scopes: readonly PresentationScopeAccess<TScopeMetadata>[]
237
+ ) => string | null)
238
+ | undefined,
239
+ ) {
240
+ const scopeIds = new Set(scopes.map((scope) => scope.id))
241
+ const defaultScopeId = getDefaultScopeId?.(scopeContext, scopes)
242
+ if (defaultScopeId && scopeIds.has(defaultScopeId)) {
243
+ return defaultScopeId
244
+ }
245
+
246
+ return scopes.find((scope) => scope.isDefault)?.id ?? scopes[0]?.id ?? null
247
+ }
248
+
249
+ function normalizeSelectedScopeIds(
250
+ scopeIds: readonly string[],
251
+ allowedScopeIds: ReadonlySet<string>,
252
+ fallbackScopeIds: readonly string[],
253
+ ) {
254
+ const selected = normalizeScopeIds(scopeIds)
255
+ .filter((scopeId) => allowedScopeIds.has(scopeId))
256
+ if (selected.length > 0) {
257
+ return selected
258
+ }
259
+
260
+ return normalizeScopeIds(fallbackScopeIds)
261
+ .filter((scopeId) => allowedScopeIds.has(scopeId))
262
+ }
263
+
264
+ function isAllowedScope(
265
+ scopeId: string | null,
266
+ allowedScopeIds: ReadonlySet<string>,
267
+ ): scopeId is string {
268
+ return scopeId !== null && allowedScopeIds.has(scopeId)
269
+ }
270
+
271
+ function readStoredScopeId(storageKeyPrefix: string, identityKey: string, purpose: string) {
272
+ if (typeof window === 'undefined') {
273
+ return null
274
+ }
275
+
276
+ const value = window.localStorage.getItem(createStorageKey(storageKeyPrefix, identityKey, purpose))
277
+ return value && value.trim().length > 0 ? value : null
278
+ }
279
+
280
+ function writeStoredScopeId(
281
+ storageKeyPrefix: string,
282
+ identityKey: string,
283
+ purpose: string,
284
+ scopeId: string,
285
+ ) {
286
+ if (typeof window === 'undefined') {
287
+ return
288
+ }
289
+
290
+ window.localStorage.setItem(createStorageKey(storageKeyPrefix, identityKey, purpose), scopeId)
291
+ }
292
+
293
+ function readStoredScopeIds(storageKeyPrefix: string, identityKey: string, purpose: string) {
294
+ if (typeof window === 'undefined') {
295
+ return []
296
+ }
297
+
298
+ try {
299
+ const value = window.localStorage.getItem(createStorageKey(storageKeyPrefix, identityKey, purpose))
300
+ return value ? JSON.parse(value) as string[] : []
301
+ } catch {
302
+ return []
303
+ }
304
+ }
305
+
306
+ function writeStoredScopeIds(
307
+ storageKeyPrefix: string,
308
+ identityKey: string,
309
+ purpose: string,
310
+ scopeIds: readonly string[],
311
+ ) {
312
+ if (typeof window === 'undefined') {
313
+ return
314
+ }
315
+
316
+ window.localStorage.setItem(
317
+ createStorageKey(storageKeyPrefix, identityKey, purpose),
318
+ JSON.stringify(scopeIds),
319
+ )
320
+ }
321
+
322
+ function createStorageKey(storageKeyPrefix: string, identityKey: string, purpose: string) {
323
+ return `${storageKeyPrefix}.${purpose}.${identityKey}`
324
+ }
@@ -0,0 +1,21 @@
1
+ import { useLayoutEffect } from 'react'
2
+
3
+ import type {
4
+ PresentationScopeRequestSelection,
5
+ PresentationScopeRequestStore,
6
+ } from '@cohesivesystems/presentation-core'
7
+
8
+ /**
9
+ * Synchronizes a mounted route or surface with the current request-scope store.
10
+ */
11
+ export function usePresentationScopeRequestSelection(
12
+ store: PresentationScopeRequestStore,
13
+ selection: PresentationScopeRequestSelection,
14
+ ) {
15
+ const selectionKey = store.formatQuerySuffix(selection)
16
+
17
+ useLayoutEffect(() => {
18
+ store.setSelection(selection)
19
+ return () => store.setSelection({ mode: 'default' })
20
+ }, [selection, selectionKey, store])
21
+ }
@@ -0,0 +1,106 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import {
4
+ navigationShellKinds,
5
+ type NavigationShellSlotDefinition,
6
+ } from '@cohesivesystems/presentation-contracts'
7
+ import type {
8
+ ProjectedNavigationShellLayout,
9
+ } from '@cohesivesystems/presentation-core'
10
+ import {
11
+ createNavigationShellFrameRendererRegistry,
12
+ getNavigationShellFrameRendererRegistryKeys,
13
+ resolveNavigationShellFrameRenderer,
14
+ type ProjectedNavigationShellFrameRenderContext,
15
+ } from './navigation-shell-frame-renderer-registry'
16
+
17
+ export interface StandardNavigationShellFrameLayout {
18
+ readonly headerClassName: string
19
+ readonly headerContentClassName: string
20
+ readonly headerNavigationClassName: string
21
+ readonly rootClassName: string
22
+ }
23
+
24
+ export type StandardNavigationShellLayout<TSlotLayout = unknown> =
25
+ ProjectedNavigationShellLayout<
26
+ StandardNavigationShellFrameLayout,
27
+ TSlotLayout
28
+ >
29
+
30
+ export type StandardNavigationShellFrameRenderContext<TSlotLayout = unknown> =
31
+ ProjectedNavigationShellFrameRenderContext<
32
+ StandardNavigationShellLayout<TSlotLayout>
33
+ >
34
+
35
+ export const standardNavigationShellFrameRenderers =
36
+ createNavigationShellFrameRendererRegistry([
37
+ {
38
+ kind: navigationShellKinds.topNavigation,
39
+ render: renderStandardTopNavigationShellFrame,
40
+ },
41
+ ])
42
+
43
+ export const standardNavigationShellFrameRendererKeys =
44
+ getNavigationShellFrameRendererRegistryKeys(standardNavigationShellFrameRenderers)
45
+
46
+ export function renderStandardNavigationShellFrame(
47
+ context: StandardNavigationShellFrameRenderContext,
48
+ ) {
49
+ const renderer = resolveNavigationShellFrameRenderer(
50
+ standardNavigationShellFrameRenderers,
51
+ context.navigation.Shell.Kind,
52
+ )
53
+
54
+ return renderer?.(context) ?? context.children
55
+ }
56
+
57
+ function renderStandardTopNavigationShellFrame({
58
+ children,
59
+ layout,
60
+ renderSlot,
61
+ }: StandardNavigationShellFrameRenderContext) {
62
+ const frame = layout.frame
63
+
64
+ return (
65
+ <div className={frame.rootClassName}>
66
+ <header className={frame.headerClassName}>
67
+ <div className={frame.headerContentClassName}>
68
+ {layout.brandSlot ? (
69
+ <ShellSlot slot={layout.brandSlot}>
70
+ {renderSlot(layout.brandSlot)}
71
+ </ShellSlot>
72
+ ) : null}
73
+ <div className={frame.headerNavigationClassName}>
74
+ {layout.primaryNavigationSlot ? (
75
+ <ShellSlot slot={layout.primaryNavigationSlot}>
76
+ {renderSlot(layout.primaryNavigationSlot)}
77
+ </ShellSlot>
78
+ ) : null}
79
+ {layout.utilityActionSlots.map((slot) => (
80
+ <ShellSlot key={slot.Id} slot={slot}>
81
+ {renderSlot(slot)}
82
+ </ShellSlot>
83
+ ))}
84
+ </div>
85
+ </div>
86
+ </header>
87
+ {layout.systemNoticeSlots.map((slot) => (
88
+ <ShellSlot key={slot.Id} slot={slot}>
89
+ {renderSlot(slot)}
90
+ </ShellSlot>
91
+ ))}
92
+ <ShellSlot slot={layout.routedContentSlot}>
93
+ {layout.routedContentSlot ? renderSlot(layout.routedContentSlot) : children}
94
+ </ShellSlot>
95
+ </div>
96
+ )
97
+ }
98
+
99
+ function ShellSlot({
100
+ children,
101
+ }: {
102
+ readonly children: ReactNode
103
+ readonly slot: NavigationShellSlotDefinition | null
104
+ }) {
105
+ return <>{children}</>
106
+ }