@delmaredigital/payload-experiments 0.3.0-rc.4 → 0.4.0-rc.2

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 (299) hide show
  1. package/README.md +122 -27
  2. package/dist/admin/MetricSourceSelect.js +3 -3
  3. package/dist/admin/PlacementStatus.d.ts +3 -0
  4. package/dist/admin/PlacementStatus.js +54 -0
  5. package/dist/admin/PreviewLinks.d.ts +2 -1
  6. package/dist/admin/PreviewLinks.js +2 -2
  7. package/dist/admin/TestsDashboardCards.d.ts +1 -0
  8. package/dist/admin/TestsDashboardCards.js +4 -8
  9. package/dist/admin/TestsNavLink.d.ts +1 -0
  10. package/dist/admin/TestsNavLink.js +4 -10
  11. package/dist/admin/actions/ConfirmDialog.d.ts +2 -1
  12. package/dist/admin/actions/ConfirmDialog.js +2 -2
  13. package/dist/admin/actions/DecideDialog.d.ts +2 -1
  14. package/dist/admin/actions/DecideDialog.js +2 -2
  15. package/dist/admin/api.d.ts +18 -2
  16. package/dist/admin/api.js +47 -2
  17. package/dist/admin/blocks/BlockPanel.d.ts +4 -0
  18. package/dist/admin/blocks/BlockPanel.js +199 -0
  19. package/dist/admin/blocks/TestBlockField.d.ts +5 -0
  20. package/dist/admin/blocks/TestBlockField.js +163 -0
  21. package/dist/admin/blocks/formApi.d.ts +46 -0
  22. package/dist/admin/blocks/formApi.js +159 -0
  23. package/dist/admin/blocks/inject.d.ts +7 -0
  24. package/dist/admin/blocks/inject.js +41 -0
  25. package/dist/admin/blocks/model.d.ts +17 -0
  26. package/dist/admin/blocks/model.js +45 -0
  27. package/dist/admin/blocks/statusCache.d.ts +9 -0
  28. package/dist/admin/blocks/statusCache.js +92 -0
  29. package/dist/admin/blocks/useBlockForm.d.ts +17 -0
  30. package/dist/admin/blocks/useBlockForm.js +46 -0
  31. package/dist/admin/copy/actions.d.ts +2 -0
  32. package/dist/admin/copy/actions.js +9 -0
  33. package/dist/admin/copy/blocks.d.ts +43 -0
  34. package/dist/admin/copy/blocks.js +64 -0
  35. package/dist/admin/copy/common.d.ts +1 -0
  36. package/dist/admin/copy/common.js +1 -1
  37. package/dist/admin/copy/goals.d.ts +2 -0
  38. package/dist/admin/copy/goals.js +4 -1
  39. package/dist/admin/copy/newTest.d.ts +10 -0
  40. package/dist/admin/copy/newTest.js +10 -0
  41. package/dist/admin/copy/pages.d.ts +24 -0
  42. package/dist/admin/copy/pages.js +24 -0
  43. package/dist/admin/copy/readiness.d.ts +23 -3
  44. package/dist/admin/copy/readiness.js +24 -3
  45. package/dist/admin/copy/results.d.ts +1 -0
  46. package/dist/admin/copy/results.js +1 -0
  47. package/dist/admin/copy/testPage.d.ts +4 -0
  48. package/dist/admin/copy/testPage.js +4 -0
  49. package/dist/admin/goals/XpGoalField.d.ts +4 -0
  50. package/dist/admin/goals/XpGoalField.js +168 -0
  51. package/dist/admin/goals/xpGoalField.css +48 -0
  52. package/dist/admin/metricSource.model.d.ts +2 -0
  53. package/dist/admin/metricSource.model.js +4 -0
  54. package/dist/admin/pageCollections.d.ts +10 -0
  55. package/dist/admin/pageCollections.js +20 -0
  56. package/dist/admin/paths.d.ts +9 -0
  57. package/dist/admin/paths.js +9 -0
  58. package/dist/admin/placementStatus.model.d.ts +2 -0
  59. package/dist/admin/placementStatus.model.js +19 -0
  60. package/dist/admin/placements/placementLabels.d.ts +11 -0
  61. package/dist/admin/placements/placementLabels.js +21 -0
  62. package/dist/admin/placements/placementLabels.model.d.ts +13 -0
  63. package/dist/admin/placements/placementLabels.model.js +12 -0
  64. package/dist/admin/placements/usePlacementLabels.d.ts +12 -0
  65. package/dist/admin/placements/usePlacementLabels.js +102 -0
  66. package/dist/admin/results/DataQualityPanel.js +4 -1
  67. package/dist/admin/results/results.model.d.ts +3 -0
  68. package/dist/admin/results/results.model.js +8 -4
  69. package/dist/admin/surface/TestsHome.js +39 -10
  70. package/dist/admin/surface/TestsSurface.js +7 -6
  71. package/dist/admin/surface/contracts.d.ts +6 -5
  72. package/dist/admin/surface/goals/GoalForm.js +71 -44
  73. package/dist/admin/surface/goals/GoalsList.js +5 -4
  74. package/dist/admin/surface/goals/data.js +4 -1
  75. package/dist/admin/surface/goals/goals.model.d.ts +1 -2
  76. package/dist/admin/surface/goals/goals.model.js +1 -3
  77. package/dist/admin/surface/home.model.d.ts +3 -3
  78. package/dist/admin/surface/home.model.js +8 -4
  79. package/dist/admin/surface/newTest/NewTest.js +56 -17
  80. package/dist/admin/surface/newTest/PageWizard.d.ts +7 -0
  81. package/dist/admin/surface/newTest/PageWizard.js +214 -0
  82. package/dist/admin/surface/pagePicker/CopyRoute.d.ts +6 -0
  83. package/dist/admin/surface/pagePicker/CopyRoute.js +53 -0
  84. package/dist/admin/surface/pagePicker/PageCombobox.d.ts +10 -0
  85. package/dist/admin/surface/pagePicker/PageCombobox.js +395 -0
  86. package/dist/admin/surface/pagePicker/pagePicker.css +10 -0
  87. package/dist/admin/surface/pagePicker/pagePicker.model.d.ts +27 -0
  88. package/dist/admin/surface/pagePicker/pagePicker.model.js +25 -0
  89. package/dist/admin/surface/readiness/ActionDialogs.js +99 -64
  90. package/dist/admin/surface/readiness/Banner.js +30 -6
  91. package/dist/admin/surface/readiness/readiness.model.d.ts +8 -1
  92. package/dist/admin/surface/readiness/readiness.model.js +92 -9
  93. package/dist/admin/surface/results/ReleasePages.d.ts +3 -1
  94. package/dist/admin/surface/results/ReleasePages.js +3 -7
  95. package/dist/admin/surface/results/ResultsBody.js +21 -2
  96. package/dist/admin/surface/results/ResultsHeader.js +5 -13
  97. package/dist/admin/surface/results/useResults.d.ts +20 -1
  98. package/dist/admin/surface/test/AdvancedSection.js +111 -104
  99. package/dist/admin/surface/test/GoalSection.js +94 -14
  100. package/dist/admin/surface/test/LengthSection.js +1 -1
  101. package/dist/admin/surface/test/PageHoldout.d.ts +3 -0
  102. package/dist/admin/surface/test/PageHoldout.js +67 -0
  103. package/dist/admin/surface/test/TestPage.js +5 -9
  104. package/dist/admin/surface/test/VersionsSection.js +12 -5
  105. package/dist/admin/surface/test/fields.d.ts +13 -4
  106. package/dist/admin/surface/test/fields.js +46 -14
  107. package/dist/admin/surface/test/jumpToSection.d.ts +1 -1
  108. package/dist/admin/surface/test/jumpToSection.js +4 -3
  109. package/dist/admin/surface/test/pageParts.d.ts +4 -1
  110. package/dist/admin/surface/test/pageParts.js +49 -1
  111. package/dist/admin/surface/test/testPage.css +3 -1
  112. package/dist/admin/surface/testDocument.model.d.ts +1 -0
  113. package/dist/admin/surface/testDocument.model.js +2 -1
  114. package/dist/admin/surface/useTestDocument.d.ts +15 -2
  115. package/dist/admin/surface/useTestDocument.js +53 -15
  116. package/dist/admin/surfaceUrls.d.ts +7 -0
  117. package/dist/admin/surfaceUrls.js +19 -0
  118. package/dist/admin/theme.css +2 -0
  119. package/dist/admin/useApi.d.ts +21 -2
  120. package/dist/admin/useApi.js +3 -5
  121. package/dist/admin/views/CollectionListRedirect.d.ts +1 -0
  122. package/dist/admin/views/CollectionListRedirect.js +1 -1
  123. package/dist/admin/views/ExperimentsRootView.js +28 -133
  124. package/dist/admin/views/GoalEditRedirect.d.ts +4 -0
  125. package/dist/admin/views/GoalEditRedirect.js +14 -0
  126. package/dist/admin/views/GoalsCollectionListView.d.ts +2 -0
  127. package/dist/admin/views/GoalsCollectionListView.js +4 -0
  128. package/dist/admin/views/TestsCollectionListView.d.ts +2 -0
  129. package/dist/admin/views/TestsCollectionListView.js +4 -0
  130. package/dist/admin/views/TestsNavLinkServer.d.ts +1 -0
  131. package/dist/admin/views/TestsNavLinkServer.js +1 -1
  132. package/dist/admin/views/buildSurfaceProps.d.ts +8 -0
  133. package/dist/admin/views/buildSurfaceProps.js +143 -0
  134. package/dist/admin/views/renderCollectionList.d.ts +2 -0
  135. package/dist/admin/views/renderCollectionList.js +34 -0
  136. package/dist/admin/views/renderTestsSurface.d.ts +3 -0
  137. package/dist/admin/views/renderTestsSurface.js +34 -0
  138. package/dist/blocks/bindings.d.ts +3 -0
  139. package/dist/blocks/bindings.js +34 -0
  140. package/dist/blocks/createExperimentBlock.d.ts +6 -0
  141. package/dist/blocks/createExperimentBlock.js +77 -0
  142. package/dist/blocks/formState.d.ts +31 -0
  143. package/dist/blocks/formState.js +146 -0
  144. package/dist/blocks/model.d.ts +41 -0
  145. package/dist/blocks/model.js +34 -0
  146. package/dist/client/ExperimentGroup.d.ts +6 -0
  147. package/dist/client/ExperimentGroup.js +9 -0
  148. package/dist/client/ExperimentsProvider.js +20 -4
  149. package/dist/client/clickGoals.d.ts +4 -0
  150. package/dist/client/clickGoals.js +43 -0
  151. package/dist/collections/experiments/config.js +21 -7
  152. package/dist/collections/experiments/deleteGuard.d.ts +2 -1
  153. package/dist/collections/experiments/deleteGuard.js +12 -1
  154. package/dist/collections/experiments/editability.js +4 -2
  155. package/dist/collections/experiments/pickSlug.d.ts +2 -0
  156. package/dist/collections/experiments/pickSlug.js +8 -0
  157. package/dist/collections/labels.d.ts +3 -0
  158. package/dist/collections/labels.js +20 -0
  159. package/dist/collections/metrics.js +28 -6
  160. package/dist/collections/phases.js +1 -0
  161. package/dist/collections/serialize.js +7 -0
  162. package/dist/db/clickGoals.d.ts +9 -0
  163. package/dist/db/clickGoals.js +22 -0
  164. package/dist/db/delete.d.ts +1 -0
  165. package/dist/db/delete.js +1 -0
  166. package/dist/db/locks.d.ts +8 -0
  167. package/dist/db/locks.js +27 -4
  168. package/dist/db/names.d.ts +4 -0
  169. package/dist/db/names.js +4 -0
  170. package/dist/db/pageBindings.d.ts +4 -0
  171. package/dist/db/pageBindings.js +17 -0
  172. package/dist/db/pageBusy.d.ts +2 -0
  173. package/dist/db/pageBusy.js +10 -0
  174. package/dist/db/pageContent.d.ts +10 -0
  175. package/dist/db/pageContent.js +79 -0
  176. package/dist/db/placements.d.ts +33 -0
  177. package/dist/db/placements.js +54 -0
  178. package/dist/db/slugs.d.ts +8 -0
  179. package/dist/db/slugs.js +40 -0
  180. package/dist/db/snapshots.d.ts +6 -0
  181. package/dist/db/snapshots.js +8 -5
  182. package/dist/endpoints/clickKeys.d.ts +5 -0
  183. package/dist/endpoints/clickKeys.js +17 -0
  184. package/dist/endpoints/events.js +4 -1
  185. package/dist/endpoints/index.js +28 -0
  186. package/dist/endpoints/pages.d.ts +5 -0
  187. package/dist/endpoints/pages.js +120 -0
  188. package/dist/endpoints/placementStatus.d.ts +6 -0
  189. package/dist/endpoints/placementStatus.js +238 -0
  190. package/dist/endpoints/readBody.d.ts +2 -0
  191. package/dist/endpoints/readBody.js +24 -0
  192. package/dist/endpoints/results.js +5 -1
  193. package/dist/endpoints/summary.js +11 -4
  194. package/dist/endpoints/wrapBlock.d.ts +6 -0
  195. package/dist/endpoints/wrapBlock.js +76 -0
  196. package/dist/errors.d.ts +3 -3
  197. package/dist/errors.js +4 -4
  198. package/dist/events/rows.d.ts +1 -1
  199. package/dist/events/rows.js +5 -3
  200. package/dist/exports/adminClient.d.ts +5 -0
  201. package/dist/exports/adminClient.js +5 -0
  202. package/dist/exports/adminRsc.d.ts +3 -0
  203. package/dist/exports/adminRsc.js +3 -0
  204. package/dist/exports/blocks.d.ts +2 -0
  205. package/dist/exports/blocks.js +1 -0
  206. package/dist/exports/client.d.ts +1 -0
  207. package/dist/exports/client.js +1 -0
  208. package/dist/exports/index.d.ts +6 -1
  209. package/dist/exports/index.js +3 -0
  210. package/dist/exports/puck.d.ts +3 -1
  211. package/dist/exports/puck.js +2 -0
  212. package/dist/exports/puckEditor.d.ts +3 -0
  213. package/dist/exports/puckEditor.js +2 -0
  214. package/dist/exports/rsc.d.ts +3 -0
  215. package/dist/exports/rsc.js +2 -0
  216. package/dist/fields/xpGoal.d.ts +3 -0
  217. package/dist/fields/xpGoal.js +16 -0
  218. package/dist/globals/state.js +10 -0
  219. package/dist/goals/cache.d.ts +3 -0
  220. package/dist/goals/cache.js +35 -0
  221. package/dist/goals/fairness.d.ts +40 -0
  222. package/dist/goals/fairness.js +200 -0
  223. package/dist/goals/key.d.ts +4 -0
  224. package/dist/goals/key.js +3 -0
  225. package/dist/goals/picker/clickGoalPicker.model.d.ts +29 -0
  226. package/dist/goals/picker/clickGoalPicker.model.js +45 -0
  227. package/dist/goals/picker/copy.d.ts +16 -0
  228. package/dist/goals/picker/copy.js +15 -0
  229. package/dist/goals/picker/useClickGoalPicker.d.ts +30 -0
  230. package/dist/goals/picker/useClickGoalPicker.js +220 -0
  231. package/dist/goals/validate.d.ts +3 -0
  232. package/dist/goals/validate.js +14 -0
  233. package/dist/hooks/pageGuard.d.ts +5 -1
  234. package/dist/hooks/pageGuard.js +157 -95
  235. package/dist/hooks/puckGoals.d.ts +3 -0
  236. package/dist/hooks/puckGoals.js +38 -0
  237. package/dist/onInit.js +29 -0
  238. package/dist/options.d.ts +2 -0
  239. package/dist/options.js +41 -1
  240. package/dist/plugin.js +50 -43
  241. package/dist/puck/copy.d.ts +1 -0
  242. package/dist/puck/copy.js +1 -0
  243. package/dist/puck/editor/editor.chrome.css +14 -0
  244. package/dist/puck/editor/editor.css +106 -0
  245. package/dist/puck/editor/editor.model.d.ts +4 -2
  246. package/dist/puck/editor/editor.model.js +6 -3
  247. package/dist/puck/editor/integration.d.ts +10 -0
  248. package/dist/puck/editor/integration.js +30 -0
  249. package/dist/puck/editor/xpGoalPuckEditorField.d.ts +7 -0
  250. package/dist/puck/editor/xpGoalPuckEditorField.js +142 -0
  251. package/dist/puck/integration.d.ts +6 -0
  252. package/dist/puck/integration.js +21 -0
  253. package/dist/puck/xpGoalField.d.ts +3 -0
  254. package/dist/puck/xpGoalField.js +6 -0
  255. package/dist/results/health.d.ts +4 -0
  256. package/dist/results/health.js +27 -0
  257. package/dist/results/snapshot.js +15 -2
  258. package/dist/sanitizeReadinessDetail.js +43 -0
  259. package/dist/server/pageKindSeoContract.d.ts +27 -0
  260. package/dist/server/pageKindSeoContract.js +80 -0
  261. package/dist/server/recordContentChange.js +58 -8
  262. package/dist/server/resolveExperimentBlocks.d.ts +5 -0
  263. package/dist/server/resolveExperimentBlocks.js +54 -0
  264. package/dist/server/runResults.js +56 -15
  265. package/dist/server/toClientAssignments.js +2 -1
  266. package/dist/transitions/addMetric.d.ts +2 -1
  267. package/dist/transitions/addMetric.js +28 -1
  268. package/dist/transitions/checkStart.d.ts +14 -1
  269. package/dist/transitions/checkStart.js +92 -8
  270. package/dist/transitions/decide.js +25 -0
  271. package/dist/transitions/deleteExperiment.d.ts +3 -1
  272. package/dist/transitions/deleteExperiment.js +27 -1
  273. package/dist/transitions/kernel.js +4 -0
  274. package/dist/transitions/makeContent.d.ts +5 -0
  275. package/dist/transitions/makeContent.js +39 -11
  276. package/dist/transitions/pageCollection.d.ts +10 -0
  277. package/dist/transitions/pageCollection.js +20 -0
  278. package/dist/transitions/pageVersions.d.ts +10 -0
  279. package/dist/transitions/pageVersions.js +44 -0
  280. package/dist/transitions/phaseRecord.d.ts +1 -0
  281. package/dist/transitions/placementInteractions.d.ts +40 -0
  282. package/dist/transitions/placementInteractions.js +94 -0
  283. package/dist/transitions/placements.d.ts +8 -0
  284. package/dist/transitions/placements.js +148 -1
  285. package/dist/transitions/preflightData.d.ts +2 -2
  286. package/dist/transitions/preflightData.js +137 -5
  287. package/dist/transitions/resolveCaps.d.ts +1 -0
  288. package/dist/transitions/resolveCaps.js +6 -2
  289. package/dist/transitions/start.js +46 -1
  290. package/dist/types/admin.d.ts +1 -1
  291. package/dist/types/assignments.d.ts +1 -0
  292. package/dist/types/contracts.d.ts +15 -0
  293. package/dist/types/endpoints.d.ts +92 -5
  294. package/dist/types/index.d.ts +3 -0
  295. package/dist/types/puck.d.ts +7 -1
  296. package/dist/types/snapshot.d.ts +17 -4
  297. package/dist/types/surface.d.ts +13 -0
  298. package/dist/ui/ui.css +4 -0
  299. package/package.json +8 -1
package/dist/options.js CHANGED
@@ -49,7 +49,8 @@ export function resolveOptions(input) {
49
49
  if (![
50
50
  'block',
51
51
  'document',
52
- 'setting'
52
+ 'setting',
53
+ 'page'
53
54
  ].includes(key)) throw new Error(`Unknown experiments admin.descriptions option: ${key}`);
54
55
  if (value !== undefined && typeof value !== 'string') throw new Error(`Experiments admin.descriptions.${key} must be a string`);
55
56
  }
@@ -62,6 +63,31 @@ export function resolveOptions(input) {
62
63
  if (!entry || typeof entry.slug !== 'string' || !entry.slug.trim() || slugs.has(entry.slug) || typeof entry.pathOf !== 'function' || typeof entry.findByPath !== 'function') {
63
64
  throw new Error('Experiments pages.collections requires unique slugs, pathOf and findByPath functions');
64
65
  }
66
+ if (entry.contentOf !== undefined && typeof entry.contentOf !== 'function') throw new Error('Experiments contentOf must be a function');
67
+ for (const [key, max] of [
68
+ [
69
+ 'copyHint',
70
+ 200
71
+ ],
72
+ [
73
+ 'copyLabel',
74
+ 40
75
+ ]
76
+ ]){
77
+ const value = entry[key];
78
+ if (value !== undefined && (typeof value !== 'string' || !value.trim() || value.length > max)) throw new Error(`Experiments ${key} must be a non-empty string of at most ${max} characters`);
79
+ }
80
+ if (entry.copyUrl !== undefined && !validCopyUrl(entry.copyUrl)) throw new Error('Experiments copyUrl must be a root-relative path');
81
+ if (entry.copyLabel !== undefined && entry.copyUrl === undefined) throw new Error('Experiments copyLabel requires copyUrl');
82
+ slugs.add(entry.slug);
83
+ }
84
+ }
85
+ if (input.blocks !== undefined) {
86
+ if (!input.blocks || !Array.isArray(input.blocks.collections) || !input.blocks.collections.length) throw new Error('Experiments blocks.collections must be a non-empty array');
87
+ const slugs = new Set();
88
+ for (const entry of input.blocks.collections){
89
+ if (!entry || typeof entry.slug !== 'string' || !entry.slug.trim() || slugs.has(entry.slug) || typeof entry.field !== 'string' || !entry.field.trim() || entry.blockSlug !== undefined && (typeof entry.blockSlug !== 'string' || !entry.blockSlug.trim()) || entry.appliesTo !== undefined && typeof entry.appliesTo !== 'function') throw new Error('Invalid experiments blocks collection');
90
+ if (puckCollections(input).some((puck)=>puck.slug === entry.slug) && !entry.appliesTo) throw new Error('A collection registered in puck and blocks requires appliesTo');
65
91
  slugs.add(entry.slug);
66
92
  }
67
93
  }
@@ -150,3 +176,17 @@ export function resolveOptions(input) {
150
176
  }
151
177
  };
152
178
  }
179
+ function validCopyUrl(value) {
180
+ if (typeof value !== 'string' || !/^\/(?![/\\])/.test(value) || /[\s\x00-\x1f\x7f]/.test(value)) return false;
181
+ try {
182
+ return new URL(value, 'http://x').origin === 'http://x';
183
+ } catch {
184
+ return false;
185
+ }
186
+ }
187
+ /** One published-row rule shared by scans, bindings, writes and editor links. */ export function documentEditor(options, collection, doc) {
188
+ const block = options.blocks?.collections.find((entry)=>entry.slug === collection);
189
+ const puck = options.puck?.enabled !== false && puckCollections(options).some((entry)=>entry.slug === collection);
190
+ if (block && (block.appliesTo ? block.appliesTo(doc) : true)) return 'blocks';
191
+ return puck ? 'puck' : 'page';
192
+ }
package/dist/plugin.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { buildCollections } from './collections/index.js';
2
2
  import { buildEndpoints } from './endpoints/index.js';
3
3
  import { contentChangesBeforeHook, contentChangesHook } from './hooks/contentChanges.js';
4
- import { pageGuardAfterOperation, pageGuardBeforeChange, pageGuardBeforeDelete, pageGuardBeforeOperation } from './hooks/pageGuard.js';
4
+ import { puckGoalsBeforeChange } from './hooks/puckGoals.js';
5
+ import { pageGuardAfterChange, pageGuardAfterOperation, pageGuardBeforeChange, pageGuardBeforeDelete, pageGuardBeforeOperation } from './hooks/pageGuard.js';
5
6
  import { createOnInit } from './onInit.js';
6
7
  import { puckCollections, resolveOptions } from './options.js';
8
+ import { injectTestBlock } from './admin/blocks/inject.js';
7
9
  import * as paths from './admin/paths.js';
8
10
  export function createExperimentsPlugin(input) {
9
11
  const options = resolveOptions(input);
@@ -15,12 +17,13 @@ export function createExperimentsPlugin(input) {
15
17
  for (const { slug } of options.pages?.collections ?? []){
16
18
  if (!config.collections?.some((collection)=>collection.slug === slug)) throw new Error(`Unknown experiments page collection: ${slug}`);
17
19
  }
20
+ for (const { slug } of options.blocks?.collections ?? []){
21
+ if (!config.collections?.some((collection)=>collection.slug === slug)) throw new Error(`Unknown experiments blocks collection: ${slug}`);
22
+ }
18
23
  // The frozen factories consume registration/access metadata, never TUser callbacks.
19
24
  // Erase the user subtype only at this non-generic factory boundary.
20
25
  const { collections, globals } = buildCollections(options);
21
26
  const onInit = createOnInit(options);
22
- const beforeDashboard = config.admin?.components?.beforeDashboard ?? [];
23
- const beforeNavLinks = config.admin?.components?.beforeNavLinks ?? [];
24
27
  return {
25
28
  ...config,
26
29
  admin: {
@@ -30,10 +33,6 @@ export function createExperimentsPlugin(input) {
30
33
  xpTestsSurface: {
31
34
  path: paths.XP_TESTS_SURFACE,
32
35
  type: 'component'
33
- },
34
- xpTestsNav: {
35
- path: paths.XP_TESTS_NAV_CLIENT,
36
- type: 'component'
37
36
  }
38
37
  },
39
38
  components: {
@@ -44,59 +43,67 @@ export function createExperimentsPlugin(input) {
44
43
  Component: paths.XP_TESTS_VIEW,
45
44
  path: '/experiments/:segments*'
46
45
  }
47
- },
48
- beforeDashboard: beforeDashboard.includes(paths.XP_TESTS_DASHBOARD_CARDS) ? beforeDashboard : [
49
- ...beforeDashboard,
50
- paths.XP_TESTS_DASHBOARD_CARDS
51
- ],
52
- beforeNavLinks: beforeNavLinks.includes(paths.XP_TESTS_NAV_LINK) ? beforeNavLinks : [
53
- ...beforeNavLinks,
54
- paths.XP_TESTS_NAV_LINK
55
- ]
46
+ }
56
47
  }
57
48
  },
58
49
  collections: [
50
+ ...[
51
+ 'xp-experiments',
52
+ 'xp-metrics'
53
+ ].map((slug)=>collections.find((collection)=>collection.slug === slug)),
59
54
  ...(config.collections ?? []).map((collection)=>{
60
- const puck = options.puck?.enabled && registrations.some((entry)=>entry.slug === collection.slug);
55
+ const puck = options.puck?.enabled && registrations.find((entry)=>entry.slug === collection.slug);
61
56
  const page = options.pages?.collections.some((entry)=>entry.slug === collection.slug);
62
- if (!puck && !page) return collection;
57
+ const blocks = options.blocks?.collections.find((entry)=>entry.slug === collection.slug);
58
+ if (!puck && !page && !blocks) return collection;
63
59
  return {
64
60
  ...collection,
61
+ fields: [
62
+ ...page ? [
63
+ {
64
+ name: 'xpPlacementStatus',
65
+ type: 'ui',
66
+ admin: {
67
+ position: 'sidebar',
68
+ components: {
69
+ Field: paths.XP_PLACEMENT_STATUS
70
+ }
71
+ }
72
+ }
73
+ ] : [],
74
+ ...blocks ? injectTestBlock(collection.fields, blocks) : collection.fields
75
+ ],
65
76
  hooks: {
66
77
  ...collection.hooks,
67
78
  beforeChange: [
68
79
  ...collection.hooks?.beforeChange ?? [],
69
- ...page ? [
70
- pageGuardBeforeChange
71
- ] : [],
80
+ pageGuardBeforeChange,
81
+ contentChangesBeforeHook,
72
82
  ...puck ? [
73
- contentChangesBeforeHook
83
+ puckGoalsBeforeChange(puck.field)
74
84
  ] : []
75
85
  ],
76
- ...page ? {
77
- beforeOperation: [
78
- ...collection.hooks?.beforeOperation ?? [],
79
- pageGuardBeforeOperation
80
- ],
81
- beforeDelete: [
82
- ...collection.hooks?.beforeDelete ?? [],
83
- pageGuardBeforeDelete
84
- ],
85
- afterOperation: [
86
- pageGuardAfterOperation,
87
- ...collection.hooks?.afterOperation ?? []
88
- ]
89
- } : {},
90
- ...puck ? {
91
- afterChange: [
92
- ...collection.hooks?.afterChange ?? [],
93
- contentChangesHook
94
- ]
95
- } : {}
86
+ beforeOperation: [
87
+ ...collection.hooks?.beforeOperation ?? [],
88
+ pageGuardBeforeOperation
89
+ ],
90
+ beforeDelete: [
91
+ ...collection.hooks?.beforeDelete ?? [],
92
+ pageGuardBeforeDelete
93
+ ],
94
+ afterOperation: [
95
+ pageGuardAfterOperation,
96
+ ...collection.hooks?.afterOperation ?? []
97
+ ],
98
+ afterChange: [
99
+ ...collection.hooks?.afterChange ?? [],
100
+ pageGuardAfterChange,
101
+ contentChangesHook
102
+ ]
96
103
  }
97
104
  };
98
105
  }),
99
- ...collections
106
+ ...collections.filter((collection)=>collection.slug !== 'xp-experiments' && collection.slug !== 'xp-metrics')
100
107
  ],
101
108
  globals: [
102
109
  ...config.globals ?? [],
@@ -1,3 +1,4 @@
1
+ export declare const categoryTitle = "A/B tests";
1
2
  export declare const slotLabels: {
2
3
  readonly control: "Original";
3
4
  readonly variantA: "Variant A";
package/dist/puck/copy.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ARM_LABELS } from '../labels/arms.js';
2
+ export const categoryTitle = 'A/B tests';
2
3
  export const slotLabels = {
3
4
  control: ARM_LABELS.control,
4
5
  variantA: ARM_LABELS.variantA,
@@ -119,3 +119,17 @@
119
119
  .xp-slot-readout { display: grid; gap: var(--xp-space-2); }
120
120
  .xp-slot-readout > .xp-ui-button { justify-self: start; }
121
121
  .xp-slot-editor .xp-ui-tablist { flex: 1 1 auto; min-width: 0; flex-wrap: wrap; }
122
+ .xp-slot-root.xp-puck-field { display: grid; gap: var(--xp-space-3); min-width: 0; }
123
+
124
+ /* Puck's inline field sidebar stays light, including under Payload's dark-mode text overrides. */
125
+ [class*="_PuckFields"] .xp-slot-root .xp-ui-button--secondary { color: var(--xp-text); }
126
+ [class*="_PuckFields"] .xp-slot-root .xp-ui-button--primary { color: var(--xp-accent-fg); }
127
+ [class*="_PuckFields"] .xp-slot-root .xp-ui-error { color: var(--xp-error); }
128
+ [class*="_PuckFields"] .xp-slot-root .xp-ui-help { color: var(--xp-text-2); }
129
+ [class*="_PuckFields"] .xp-slot-root .xp-ui-field label { color: var(--xp-text); }
130
+ [class*="_PuckFields"] .xp-puck-field .xp-ui-button:disabled {
131
+ opacity: 1;
132
+ background: var(--xp-surface-3);
133
+ border-color: var(--xp-border-strong);
134
+ color: var(--xp-text-2);
135
+ }
@@ -55,6 +55,7 @@
55
55
  --xp-space-3: 12px;
56
56
  --xp-space-4: 16px;
57
57
  --xp-space-5: 20px;
58
+ --xp-field-gap: calc((20 / 13) * 1rem);
58
59
  --xp-space-6: 24px;
59
60
  --xp-space-7: 28px;
60
61
  --xp-space-8: 32px;
@@ -141,6 +142,7 @@
141
142
  --xp-space-3: 12px;
142
143
  --xp-space-4: 16px;
143
144
  --xp-space-5: 20px;
145
+ --xp-field-gap: calc((20 / 13) * 1rem);
144
146
  --xp-space-6: 24px;
145
147
  --xp-space-7: 28px;
146
148
  --xp-space-8: 32px;
@@ -170,6 +172,92 @@
170
172
  --xp-font-size-body: 15px;
171
173
  }
172
174
 
175
+ [class*="_PuckFields"] .xp-slot-root {
176
+ --xp-bg: rgb(245, 245, 245);
177
+ --xp-surface: rgb(255, 255, 255);
178
+ --xp-surface-2: rgb(235, 235, 235);
179
+ --xp-surface-3: rgb(221, 221, 221);
180
+ --xp-border: rgb(235, 235, 235);
181
+ --xp-border-strong: rgb(195, 195, 195);
182
+ --xp-text: rgb(34, 34, 34);
183
+ --xp-text-2: rgb(87, 87, 87);
184
+ --xp-text-3: rgb(141, 141, 141);
185
+ --xp-accent: rgb(47, 47, 47);
186
+ --xp-accent-hover: rgb(20, 20, 20);
187
+ --xp-accent-active: rgb(7, 7, 7);
188
+ --xp-accent-subtle: rgb(208, 208, 208);
189
+ --xp-accent-fg: rgb(255, 255, 255);
190
+ --xp-accent-text: rgb(34, 34, 34);
191
+ --xp-accent-text-decoration: none;
192
+ --xp-focus: rgb(47, 47, 47);
193
+ --xp-series-muted: rgb(181, 181, 181);
194
+ --xp-success: #1a6a3a;
195
+ --xp-success-bg: #e8f4ec;
196
+ --xp-success-border: #bddfc9;
197
+ --xp-warning: #8a5a0b;
198
+ --xp-warning-bg: #fbf3e1;
199
+ --xp-warning-border: #ecd6a2;
200
+ --xp-error: #a63030;
201
+ --xp-error-bg: #fbeaea;
202
+ --xp-error-border: #efc3c3;
203
+ --xp-series-a: #2a78d6;
204
+ --xp-series-b: #eb6834;
205
+ --xp-series-c: #8056b3;
206
+ --xp-radius-control: 8px;
207
+ --xp-radius-card: 12px;
208
+ --xp-radius-chip: 6px;
209
+ --xp-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
210
+ --xp-mono: 'SF Mono', Menlo, Consolas, Monaco, monospace;
211
+ --xp-text-display: 600 28px/32px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
212
+ --xp-text-title: 600 20px/28px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
213
+ --xp-text-body: 400 15px/22px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
214
+ --xp-text-small: 400 13px/18px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
215
+ --xp-motion: 120ms;
216
+ --xp-space-half: 2px;
217
+ --xp-space-one-half: 6px;
218
+ --xp-version-share-width: 96px;
219
+ --xp-version-share-width-mobile: 84px;
220
+ --xp-version-state-width: 192px;
221
+ --xp-version-remove-width: 128px;
222
+ --xp-targeting-attribute-width: 150px;
223
+ --xp-targeting-comparison-width: 140px;
224
+ --xp-legend-key-size: 10px;
225
+ --xp-legend-key-radius: 3px;
226
+ --xp-space-1: 4px;
227
+ --xp-space-2: 8px;
228
+ --xp-space-3: 12px;
229
+ --xp-space-4: 16px;
230
+ --xp-space-5: 20px;
231
+ --xp-field-gap: calc((20 / 13) * 1rem);
232
+ --xp-space-6: 24px;
233
+ --xp-space-7: 28px;
234
+ --xp-space-8: 32px;
235
+ --xp-surface-raised: rgb(235, 235, 235);
236
+ --xp-border-control: rgb(195, 195, 195);
237
+ --xp-input-border: rgb(181, 181, 181);
238
+ --xp-border-accent: rgb(154, 154, 154);
239
+ --xp-text-muted: rgb(87, 87, 87);
240
+ --xp-text-strong: rgb(34, 34, 34);
241
+ --xp-text-emphasis: rgb(34, 34, 34);
242
+ --xp-input-bg: rgb(255, 255, 255);
243
+ --xp-overlay: rgba(5, 5, 5, 0.5);
244
+ --xp-success-surface: #e8f4ec;
245
+ --xp-success-text: #1a6a3a;
246
+ --xp-success-text-strong: #1a6a3a;
247
+ --xp-warning-surface: #fbf3e1;
248
+ --xp-warning-text: #8a5a0b;
249
+ --xp-warning-text-strong: #8a5a0b;
250
+ --xp-error-surface: #fbeaea;
251
+ --xp-error-text: #a63030;
252
+ --xp-radius: 8px;
253
+ --xp-radius-large: 12px;
254
+ --xp-dash-card-gap: 12px;
255
+ --xp-dash-card-gap-small: 8px;
256
+ --xp-font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
257
+ --xp-font-size-small: 13px;
258
+ --xp-font-size-body: 15px;
259
+ }
260
+
173
261
  /* Shared primitives: no host palette references, and no global element resets. */
174
262
  .xp-ui-chips, .xp-ui-chips-control, .xp-ui-chips-item, .xp-ui-button, .xp-ui-input, .xp-ui-toggle, .xp-ui-tab, .xp-ui-dialog, .xp-ui-card { box-sizing: border-box; }
175
263
  .xp-ui-button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 36px; padding: 7px 14px; border-radius: var(--xp-radius-control); border: 1px solid transparent; font: 500 14px/20px var(--xp-font); color: var(--xp-text); background: transparent; cursor: pointer; white-space: normal; overflow-wrap: anywhere; max-width: 100%; min-width: 0; text-decoration: none; transition: background-color var(--xp-motion), border-color var(--xp-motion), color var(--xp-motion); }
@@ -246,6 +334,10 @@
246
334
  .xp-ui-chips p:not(.xp-ui-error) { margin: 0; color: var(--xp-text-muted); font-size: var(--xp-font-size-small); }
247
335
  /* Quiet actions and breadcrumb links stay neutral; only the selected tab takes the text accent. */
248
336
  .xp-ui-button-group { display: flex; flex-wrap: wrap; gap: 8px; min-width: 0; max-width: 100%; }
337
+ .xp-ui-inline-row { display: flex; flex-wrap: wrap; align-items: end; gap: var(--xp-space-3); min-width: 0; }
338
+ .xp-ui-inline-row > .xp-ui-field { flex: 1 1 auto; min-width: 0; }
339
+ .xp-ui-inline-row > .xp-ui-button { flex: 0 0 auto; }
340
+ .xp-ui-subpanel { display: grid; gap: var(--xp-space-3); border: 1px solid var(--xp-border); border-radius: var(--xp-radius-card); padding: var(--xp-space-4); }
249
341
  @media (prefers-reduced-motion: reduce) {
250
342
  .xp-ui-button, .xp-ui-input, .xp-ui-toggle, .xp-ui-toggle::before, .xp-ui-chips-control { transition: none; }
251
343
  }
@@ -384,3 +476,17 @@
384
476
  .xp-slot-readout { display: grid; gap: var(--xp-space-2); }
385
477
  .xp-slot-readout > .xp-ui-button { justify-self: start; }
386
478
  .xp-slot-editor .xp-ui-tablist { flex: 1 1 auto; min-width: 0; flex-wrap: wrap; }
479
+ .xp-slot-root.xp-puck-field { display: grid; gap: var(--xp-space-3); min-width: 0; }
480
+
481
+ /* Puck's inline field sidebar stays light, including under Payload's dark-mode text overrides. */
482
+ [class*="_PuckFields"] .xp-slot-root .xp-ui-button--secondary { color: var(--xp-text); }
483
+ [class*="_PuckFields"] .xp-slot-root .xp-ui-button--primary { color: var(--xp-accent-fg); }
484
+ [class*="_PuckFields"] .xp-slot-root .xp-ui-error { color: var(--xp-error); }
485
+ [class*="_PuckFields"] .xp-slot-root .xp-ui-help { color: var(--xp-text-2); }
486
+ [class*="_PuckFields"] .xp-slot-root .xp-ui-field label { color: var(--xp-text); }
487
+ [class*="_PuckFields"] .xp-puck-field .xp-ui-button:disabled {
488
+ opacity: 1;
489
+ background: var(--xp-surface-3);
490
+ border-color: var(--xp-border-strong);
491
+ color: var(--xp-text-2);
492
+ }
@@ -17,10 +17,11 @@ export declare function bannerFor(status?: Status | null, winner?: string): stri
17
17
  export declare function slugify(name: string): string;
18
18
  /** Complete draft defaults, shared by the page hand-off and setting creation. */
19
19
  export declare function newTestBody(input: {
20
- kind: 'block' | 'flag';
20
+ kind: 'block' | 'flag' | 'page';
21
21
  name: string;
22
22
  slug: string;
23
23
  unitType: UnitType;
24
+ pageCollection?: string;
24
25
  flagPoint?: string;
25
26
  layer?: DocId;
26
27
  hypothesis?: string;
@@ -28,9 +29,10 @@ export declare function newTestBody(input: {
28
29
  hypothesis?: string | undefined;
29
30
  layer?: DocId | undefined;
30
31
  flagPoint?: string | undefined;
32
+ pageCollection?: string | undefined;
31
33
  name: string;
32
34
  slug: string;
33
- kind: "block" | "flag";
35
+ kind: "block" | "page" | "flag";
34
36
  unitType: UnitType;
35
37
  exposurePolicy: string;
36
38
  arms: Arm[];
@@ -32,7 +32,7 @@ export function slugify(name) {
32
32
  return name.normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 64).replace(/-+$/g, '');
33
33
  }
34
34
  /** Complete draft defaults, shared by the page hand-off and setting creation. */ export function newTestBody(input) {
35
- const { kind, name, slug, unitType, flagPoint, layer, hypothesis } = input;
35
+ const { kind, name, slug, unitType, flagPoint, layer, hypothesis, pageCollection } = input;
36
36
  const arms = [
37
37
  {
38
38
  key: 'control',
@@ -57,13 +57,16 @@ export function slugify(name) {
57
57
  name,
58
58
  slug,
59
59
  kind,
60
- unitType,
61
- exposurePolicy: kind === 'block' ? 'render' : 'manual',
60
+ unitType: kind === 'page' ? 'visitor' : unitType,
61
+ exposurePolicy: kind === 'block' ? 'render' : kind === 'page' ? 'pageview' : 'manual',
62
62
  arms,
63
63
  allocation: 100,
64
64
  analysis: {
65
65
  plannedDurationDays: 28
66
66
  },
67
+ ...pageCollection === undefined ? {} : {
68
+ pageCollection
69
+ },
67
70
  ...flagPoint === undefined ? {} : {
68
71
  flagPoint
69
72
  },
@@ -0,0 +1,10 @@
1
+ import { type ExperimentsPuckIntegration } from '../../types/puck.js';
2
+ import type { PuckRoutes } from '../routes.js';
3
+ /**
4
+ * Call once at module scope in a 'use client' module. A new object per render
5
+ * rebuilds Puck's config and plugin list.
6
+ */
7
+ export declare function experimentsPuckEditor({ routes, accent }?: {
8
+ routes?: PuckRoutes;
9
+ accent?: string;
10
+ }): ExperimentsPuckIntegration;
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { EXPERIMENT_SLOT_TYPE } from '../../types/puck.js';
3
+ import { categoryTitle } from '../copy.js';
4
+ import { createExperimentSlotEditor } from './ExperimentSlotEditor.js';
5
+ import { experimentOutlinePlugin } from './experimentOutlinePlugin.js';
6
+ /**
7
+ * Call once at module scope in a 'use client' module. A new object per render
8
+ * rebuilds Puck's config and plugin list.
9
+ */ export function experimentsPuckEditor({ routes, accent } = {}) {
10
+ return {
11
+ name: 'experiments',
12
+ components: {
13
+ [EXPERIMENT_SLOT_TYPE]: createExperimentSlotEditor({
14
+ routes,
15
+ accent
16
+ })
17
+ },
18
+ categories: {
19
+ experiments: {
20
+ title: categoryTitle,
21
+ components: [
22
+ EXPERIMENT_SLOT_TYPE
23
+ ]
24
+ }
25
+ },
26
+ plugins: [
27
+ experimentOutlinePlugin()
28
+ ]
29
+ };
30
+ }
@@ -0,0 +1,7 @@
1
+ import type { CustomField } from '@puckeditor/core';
2
+ import { type PuckRoutes } from '../routes.js';
3
+ import './editor.css';
4
+ /** Editor config only. The render config keeps xpGoalPuckField() from /puck. */
5
+ export declare function xpGoalPuckEditorField(options?: {
6
+ routes?: PuckRoutes;
7
+ }): CustomField<string>;
@@ -0,0 +1,142 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useId } from 'react';
4
+ import { useClickGoalPicker } from '../../goals/picker/useClickGoalPicker.js';
5
+ import { CLICK_GOAL_COPY as C } from '../../goals/picker/copy.js';
6
+ import { TextInput, Select } from '../../ui/Inputs.js';
7
+ import { Button } from '../../ui/Button.js';
8
+ import { resolvePuckRoutes } from '../routes.js';
9
+ import './editor.css';
10
+ function XpGoalPuckEditor({ value, onChange, api, readOnly }) {
11
+ const picker = useClickGoalPicker(api, {
12
+ onSelect: onChange
13
+ });
14
+ const id = useId();
15
+ const stopEnter = (event)=>{
16
+ if (event.key === 'Enter') {
17
+ event.preventDefault();
18
+ event.stopPropagation();
19
+ }
20
+ };
21
+ return /*#__PURE__*/ _jsxs("div", {
22
+ className: "xp-slot-root xp-puck-field",
23
+ children: [
24
+ /*#__PURE__*/ _jsx(TextInput, {
25
+ id: `${id}-value`,
26
+ label: C.label,
27
+ value: value ?? '',
28
+ readOnly: readOnly,
29
+ onChange: (event)=>onChange(event.currentTarget.value)
30
+ }),
31
+ picker.canManage && !readOnly ? /*#__PURE__*/ _jsxs(_Fragment, {
32
+ children: [
33
+ /*#__PURE__*/ _jsxs("div", {
34
+ className: "xp-ui-inline-row",
35
+ children: [
36
+ /*#__PURE__*/ _jsxs(Select, {
37
+ id: `${id}-picker`,
38
+ label: C.choose,
39
+ value: picker.goals.some((goal)=>goal.slug === value) ? value : '',
40
+ onChange: (event)=>{
41
+ if (event.currentTarget.value) onChange(event.currentTarget.value);
42
+ },
43
+ children: [
44
+ /*#__PURE__*/ _jsx("option", {
45
+ value: "",
46
+ children: C.choose
47
+ }),
48
+ picker.goals.map((goal)=>/*#__PURE__*/ _jsx("option", {
49
+ value: goal.slug,
50
+ children: goal.name
51
+ }, goal.id))
52
+ ]
53
+ }),
54
+ !picker.panel.open ? /*#__PURE__*/ _jsx(Button, {
55
+ type: "button",
56
+ onClick: picker.open,
57
+ children: C.newGoal
58
+ }) : null
59
+ ]
60
+ }),
61
+ picker.undefinedKey(value ?? '') ? /*#__PURE__*/ _jsx("p", {
62
+ className: "xp-ui-help",
63
+ role: "status",
64
+ children: C.undefinedKey
65
+ }) : null,
66
+ picker.panel.open ? /*#__PURE__*/ _jsxs("section", {
67
+ className: "xp-ui-subpanel",
68
+ "aria-labelledby": `${id}-title`,
69
+ children: [
70
+ /*#__PURE__*/ _jsx("h3", {
71
+ id: `${id}-title`,
72
+ children: C.title
73
+ }),
74
+ /*#__PURE__*/ _jsx(TextInput, {
75
+ id: `${id}-name`,
76
+ label: C.name,
77
+ value: picker.panel.name,
78
+ disabled: picker.panel.pending,
79
+ onKeyDown: stopEnter,
80
+ onChange: (event)=>picker.setName(event.currentTarget.value)
81
+ }),
82
+ /*#__PURE__*/ _jsxs("details", {
83
+ open: picker.panel.advanced,
84
+ onToggle: (event)=>picker.setAdvanced(event.currentTarget.open),
85
+ children: [
86
+ /*#__PURE__*/ _jsx("summary", {
87
+ children: C.advanced
88
+ }),
89
+ /*#__PURE__*/ _jsx(TextInput, {
90
+ id: `${id}-slug`,
91
+ label: C.slug,
92
+ value: picker.panel.slug,
93
+ disabled: picker.panel.pending,
94
+ onKeyDown: stopEnter,
95
+ error: picker.panel.errors.slug,
96
+ onChange: (event)=>picker.setSlug(event.currentTarget.value)
97
+ })
98
+ ]
99
+ }),
100
+ picker.panel.errors.general ? /*#__PURE__*/ _jsx("p", {
101
+ className: "xp-ui-error",
102
+ role: "alert",
103
+ children: picker.panel.errors.general
104
+ }) : null,
105
+ /*#__PURE__*/ _jsxs("div", {
106
+ className: "xp-ui-button-group",
107
+ children: [
108
+ /*#__PURE__*/ _jsx(Button, {
109
+ type: "button",
110
+ variant: "primary",
111
+ disabled: picker.panel.pending || !picker.panel.name.trim(),
112
+ onClick: ()=>void picker.create(),
113
+ children: C.createGoal
114
+ }),
115
+ /*#__PURE__*/ _jsx(Button, {
116
+ type: "button",
117
+ disabled: picker.panel.pending,
118
+ onClick: picker.cancel,
119
+ children: C.cancel
120
+ })
121
+ ]
122
+ })
123
+ ]
124
+ }) : null
125
+ ]
126
+ }) : null
127
+ ]
128
+ });
129
+ }
130
+ /** Editor config only. The render config keeps xpGoalPuckField() from /puck. */ export function xpGoalPuckEditorField(options) {
131
+ const routes = resolvePuckRoutes(options?.routes);
132
+ return {
133
+ type: 'custom',
134
+ label: C.label,
135
+ render: ({ value, onChange, readOnly })=>/*#__PURE__*/ _jsx(XpGoalPuckEditor, {
136
+ value: value,
137
+ onChange: onChange,
138
+ api: routes.api,
139
+ readOnly: readOnly
140
+ })
141
+ };
142
+ }
@@ -0,0 +1,6 @@
1
+ import { type ExperimentsPuckIntegration } from '../types/puck.js';
2
+ import type { PuckRoutes } from './routes.js';
3
+ /** Bundle the test block and its sidebar category for a host render config. */
4
+ export declare function experimentsPuck({ routes }?: {
5
+ routes?: PuckRoutes;
6
+ }): ExperimentsPuckIntegration;
@@ -0,0 +1,21 @@
1
+ import { EXPERIMENT_SLOT_TYPE } from '../types/puck.js';
2
+ import { createExperimentSlot } from './ExperimentSlot.js';
3
+ import { categoryTitle } from './copy.js';
4
+ /** Bundle the test block and its sidebar category for a host render config. */ export function experimentsPuck({ routes } = {}) {
5
+ return {
6
+ name: 'experiments',
7
+ components: {
8
+ [EXPERIMENT_SLOT_TYPE]: createExperimentSlot({
9
+ routes
10
+ })
11
+ },
12
+ categories: {
13
+ experiments: {
14
+ title: categoryTitle,
15
+ components: [
16
+ EXPERIMENT_SLOT_TYPE
17
+ ]
18
+ }
19
+ }
20
+ };
21
+ }
@@ -0,0 +1,3 @@
1
+ import type { TextField } from '@puckeditor/core';
2
+ /** Prop config only. Components render xpGoal as data-xp-goal. */
3
+ export declare function xpGoalPuckField(): TextField;