@delmaredigital/payload-experiments 0.2.0-rc.2 → 0.2.0-rc.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 (73) hide show
  1. package/README.md +15 -0
  2. package/dist/admin/TestsNavLink.d.ts +1 -0
  3. package/dist/admin/TestsNavLink.js +1 -0
  4. package/dist/admin/actions/ConfirmDialog.d.ts +1 -0
  5. package/dist/admin/actions/ConfirmDialog.js +7 -5
  6. package/dist/admin/components/ChipsInput.d.ts +12 -0
  7. package/dist/admin/components/ChipsInput.js +96 -0
  8. package/dist/admin/components/RootViewMessage.d.ts +5 -0
  9. package/dist/admin/components/RootViewMessage.js +15 -0
  10. package/dist/admin/components/chipsInput.css +11 -0
  11. package/dist/admin/copy/audience.d.ts +4 -0
  12. package/dist/admin/copy/audience.js +4 -0
  13. package/dist/admin/copy/goals.d.ts +44 -0
  14. package/dist/admin/copy/goals.js +46 -0
  15. package/dist/admin/results/ArmTable.js +2 -1
  16. package/dist/admin/results/CappedCell.js +2 -1
  17. package/dist/admin/results/CiChart.js +8 -7
  18. package/dist/admin/results/DataQualityPanel.js +3 -2
  19. package/dist/admin/results/VerdictCard.js +3 -2
  20. package/dist/admin/styles.css +8 -8
  21. package/dist/admin/styles.d.ts +1 -0
  22. package/dist/admin/styles.js +1 -0
  23. package/dist/admin/surface/SurfaceShell.d.ts +1 -0
  24. package/dist/admin/surface/SurfaceShell.js +1 -0
  25. package/dist/admin/surface/TestsHome.js +1 -1
  26. package/dist/admin/surface/TestsSurface.js +10 -1
  27. package/dist/admin/surface/goals/GoalForm.d.ts +7 -0
  28. package/dist/admin/surface/goals/GoalForm.js +470 -0
  29. package/dist/admin/surface/goals/GoalsBreadcrumb.d.ts +5 -0
  30. package/dist/admin/surface/goals/GoalsBreadcrumb.js +47 -0
  31. package/dist/admin/surface/goals/GoalsList.d.ts +6 -0
  32. package/dist/admin/surface/goals/GoalsList.js +135 -0
  33. package/dist/admin/surface/goals/data.d.ts +3 -0
  34. package/dist/admin/surface/goals/data.js +24 -0
  35. package/dist/admin/surface/goals/goals.css +15 -0
  36. package/dist/admin/surface/goals/goals.model.d.ts +30 -0
  37. package/dist/admin/surface/goals/goals.model.js +26 -0
  38. package/dist/admin/surface/newTest/newTest.css +6 -6
  39. package/dist/admin/surface/readiness/Banner.js +5 -4
  40. package/dist/admin/surface/results/ResultsBody.js +3 -2
  41. package/dist/admin/surface/setting/setting.css +11 -11
  42. package/dist/admin/surface/surface.css +29 -29
  43. package/dist/admin/surface/test/GoalSection.js +32 -1
  44. package/dist/admin/surface/test/TargetingSection.js +27 -10
  45. package/dist/admin/surface/test/VersionsSection.js +6 -5
  46. package/dist/admin/surface/test/testPage.css +12 -12
  47. package/dist/admin/targeting/targeting.model.d.ts +6 -1
  48. package/dist/admin/targeting/targeting.model.js +18 -2
  49. package/dist/admin/theme.css +42 -0
  50. package/dist/admin/theme.d.ts +50 -0
  51. package/dist/admin/theme.js +49 -0
  52. package/dist/admin/views/ExperimentsRootView.js +18 -18
  53. package/dist/collections/metricValidation.d.ts +3 -0
  54. package/dist/collections/metricValidation.js +9 -0
  55. package/dist/collections/metrics.js +3 -5
  56. package/dist/endpoints/preflight.js +3 -2
  57. package/dist/endpoints/previewToken.js +3 -2
  58. package/dist/endpoints/recompute.js +3 -2
  59. package/dist/endpoints/results.js +3 -2
  60. package/dist/endpoints/routeId.d.ts +5 -0
  61. package/dist/endpoints/routeId.js +13 -0
  62. package/dist/endpoints/summary.js +32 -21
  63. package/dist/endpoints/transitions.js +3 -2
  64. package/dist/plugin.js +2 -2
  65. package/dist/puck/editor/CreateExperimentDrawer.d.ts +1 -0
  66. package/dist/puck/editor/CreateExperimentDrawer.js +34 -25
  67. package/dist/puck/editor/ExperimentSlotEditor.d.ts +1 -0
  68. package/dist/puck/editor/ExperimentSlotEditor.js +137 -78
  69. package/dist/puck/editor/copy.d.ts +3 -1
  70. package/dist/puck/editor/copy.js +3 -1
  71. package/dist/puck/editor/editor.css +302 -0
  72. package/dist/types/surface.d.ts +2 -1
  73. package/package.json +2 -1
@@ -8,6 +8,7 @@ import { bannerFor, tabsFor } from './editor.model.js';
8
8
  import { cloneSlot } from './cloneSlot.js';
9
9
  import { CreateExperimentDrawer } from './CreateExperimentDrawer.js';
10
10
  import { editorCopy } from './copy.js';
11
+ import './editor.css';
11
12
  const usePuckSelector = createUsePuck();
12
13
  // Public Puck registration enables native controls inside the editor canvas.
13
14
  const registerControls = (element)=>registerOverlayPortal(element, {
@@ -34,8 +35,8 @@ function EditorRender(props) {
34
35
  const pageTitle = usePuckSelector((state)=>typeof state.appState.data.root.props?.title === 'string' ? state.appState.data.root.props.title : '');
35
36
  const selected = usePuckSelector((state)=>state.selectedItem?.props.id === props.id);
36
37
  const originalEmpty = usePuckSelector((state)=>{
37
- const content = state.selectedItem?.props.control;
38
- return state.selectedItem?.props.id !== props.id || !Array.isArray(content) || content.length === 0;
38
+ const content = state.getItemById(props.id)?.props.control;
39
+ return !Array.isArray(content) || content.length === 0;
39
40
  });
40
41
  useEffect(()=>{
41
42
  if (experimentId === undefined) return;
@@ -74,6 +75,10 @@ function EditorRender(props) {
74
75
  const tabs = tabsFor(experiment);
75
76
  const activeSlot = active && active.id === experimentId ? active.slot : undefined;
76
77
  const slot = tabs.find((tab)=>tab.slot === activeSlot)?.slot ?? tabs[0]?.slot ?? 'control';
78
+ const slotEmpty = usePuckSelector((state)=>{
79
+ const content = state.getItemById(props.id)?.props[slot];
80
+ return !Array.isArray(content) || content.length === 0;
81
+ });
77
82
  const Slot = props[slot];
78
83
  const winnerKey = experiment?.decision?.winnerArm;
79
84
  const winner = tabs.find((tab)=>tab.slot === winnerKey)?.label;
@@ -120,105 +125,159 @@ function EditorRender(props) {
120
125
  });
121
126
  }
122
127
  return /*#__PURE__*/ _jsxs("section", {
123
- style: {
124
- border: '1px solid #aaa',
125
- padding: 12
126
- },
128
+ className: "xp-slot-root xp-slot-editor",
127
129
  children: [
128
130
  /*#__PURE__*/ _jsxs("div", {
131
+ className: "xp-slot-chrome",
129
132
  ref: registerControls,
130
133
  children: [
131
- /*#__PURE__*/ _jsx("p", {
132
- role: "status",
133
- children: experimentId === undefined ? bannerFor() : result?.error ? editorCopy.loadError : experiment ? bannerFor(experiment.status, winner) : editorCopy.loading
134
+ /*#__PURE__*/ _jsxs("header", {
135
+ className: "xp-slot-header",
136
+ children: [
137
+ /*#__PURE__*/ _jsx("h3", {
138
+ className: "xp-slot-title",
139
+ children: experiment?.name ?? pickerCopy.label
140
+ }),
141
+ experiment ? /*#__PURE__*/ _jsx("span", {
142
+ className: "xp-slot-status",
143
+ "data-status": experiment.status,
144
+ children: pickerCopy.statuses[experiment.status]
145
+ }) : null,
146
+ /*#__PURE__*/ _jsxs("div", {
147
+ className: "xp-slot-actions",
148
+ children: [
149
+ experimentId === undefined ? /*#__PURE__*/ _jsx("button", {
150
+ className: "xp-slot-button xp-slot-button-primary",
151
+ type: "button",
152
+ disabled: !selected,
153
+ onClick: ()=>setCreating(true),
154
+ children: editorCopy.createLabel
155
+ }) : null,
156
+ experiment ? /*#__PURE__*/ _jsx("a", {
157
+ className: "xp-slot-button xp-slot-button-primary",
158
+ href: `/admin/experiments/${encodeURIComponent(String(experimentId))}${experiment.status === 'draft' ? '' : '/results'}`,
159
+ children: experiment.status === 'draft' ? editorCopy.setup : editorCopy.viewResults
160
+ }) : null
161
+ ]
162
+ })
163
+ ]
134
164
  }),
135
- /*#__PURE__*/ _jsx("button", {
136
- type: "button",
137
- disabled: !selected,
138
- onClick: ()=>setCreating(true),
139
- children: editorCopy.createLabel
165
+ /*#__PURE__*/ _jsxs("p", {
166
+ className: experiment?.status === 'running' ? 'xp-slot-notice xp-slot-warning' : 'xp-slot-notice',
167
+ role: "status",
168
+ children: [
169
+ experiment?.status === 'running' ? /*#__PURE__*/ _jsxs("svg", {
170
+ className: "xp-slot-warning-icon",
171
+ "aria-hidden": "true",
172
+ viewBox: "0 0 24 24",
173
+ fill: "none",
174
+ stroke: "currentColor",
175
+ strokeWidth: "2",
176
+ children: [
177
+ /*#__PURE__*/ _jsx("path", {
178
+ d: "M12 3 2 21h20L12 3Z"
179
+ }),
180
+ /*#__PURE__*/ _jsx("path", {
181
+ d: "M12 9v5m0 3v1"
182
+ })
183
+ ]
184
+ }) : null,
185
+ experimentId === undefined ? bannerFor() : result?.error ? editorCopy.loadError : experiment ? bannerFor(experiment.status, winner) : editorCopy.loading
186
+ ]
140
187
  }),
141
188
  creating ? /*#__PURE__*/ _jsx(CreateExperimentDrawer, {
142
189
  initialName: pageTitle,
143
190
  onCreate: bind,
144
191
  onClose: ()=>setCreating(false)
145
192
  }) : null,
146
- experiment?.status === 'draft' ? /*#__PURE__*/ _jsx("a", {
147
- href: `/admin/experiments/${encodeURIComponent(String(experimentId))}`,
148
- children: editorCopy.setup
149
- }) : null,
150
- /*#__PURE__*/ _jsx("div", {
151
- style: {
152
- display: 'flex',
153
- flexWrap: 'wrap',
154
- gap: 8,
155
- marginBlock: 8
156
- },
157
- children: tabs.filter((tab)=>tab.slot !== 'control').map((tab)=>/*#__PURE__*/ _jsx("button", {
158
- type: "button",
159
- disabled: originalEmpty,
160
- onClick: ()=>requestCopy(tab.slot),
161
- children: editorCopy.copy(tab.label)
162
- }, tab.slot))
163
- }),
164
- confirming ? /*#__PURE__*/ _jsxs("p", {
193
+ confirming ? /*#__PURE__*/ _jsxs("div", {
194
+ className: "xp-slot-confirm",
165
195
  role: "alert",
166
196
  children: [
167
- editorCopy.replaceConfirm(tabs.find((tab)=>tab.slot === confirming)?.label ?? confirming),
168
- ' ',
169
- /*#__PURE__*/ _jsx("button", {
170
- type: "button",
171
- onClick: ()=>copyTo(confirming),
172
- children: editorCopy.replace
197
+ /*#__PURE__*/ _jsx("p", {
198
+ className: "xp-slot-notice",
199
+ children: editorCopy.replaceConfirm(tabs.find((tab)=>tab.slot === confirming)?.label ?? confirming)
173
200
  }),
174
- ' ',
175
- /*#__PURE__*/ _jsx("button", {
176
- type: "button",
177
- onClick: ()=>setConfirming(null),
178
- children: editorCopy.cancel
201
+ /*#__PURE__*/ _jsxs("div", {
202
+ className: "xp-slot-actions",
203
+ children: [
204
+ /*#__PURE__*/ _jsx("button", {
205
+ className: "xp-slot-button xp-slot-button-primary",
206
+ type: "button",
207
+ onClick: ()=>copyTo(confirming),
208
+ children: editorCopy.replace
209
+ }),
210
+ /*#__PURE__*/ _jsx("button", {
211
+ className: "xp-slot-button",
212
+ type: "button",
213
+ onClick: ()=>setConfirming(null),
214
+ children: editorCopy.cancel
215
+ })
216
+ ]
179
217
  })
180
218
  ]
181
219
  }) : null,
182
- /*#__PURE__*/ _jsx("div", {
183
- role: "tablist",
184
- "aria-label": editorCopy.tabs,
185
- style: {
186
- display: 'flex',
187
- gap: 8,
188
- marginBottom: 8
189
- },
190
- children: tabs.map((tab, index)=>/*#__PURE__*/ _jsx("button", {
191
- type: "button",
192
- role: "tab",
193
- id: `${tabId}-${tab.slot}`,
194
- "aria-selected": tab.slot === slot,
195
- tabIndex: tab.slot === slot ? 0 : -1,
196
- "aria-controls": `${tabId}-panel`,
197
- onClick: ()=>setActive({
198
- id: experimentId,
199
- slot: tab.slot
200
- }),
201
- onKeyDown: (event)=>{
202
- const next = event.key === 'ArrowRight' ? (index + 1) % tabs.length : event.key === 'ArrowLeft' ? (index + tabs.length - 1) % tabs.length : event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : undefined;
203
- if (next === undefined) return;
204
- event.preventDefault();
205
- setActive({
206
- id: experimentId,
207
- slot: tabs[next].slot
208
- });
209
- const buttons = event.currentTarget.parentElement?.querySelectorAll('[role="tab"]');
210
- buttons?.[next]?.focus();
211
- },
212
- children: tab.label
213
- }, tab.slot))
220
+ /*#__PURE__*/ _jsxs("div", {
221
+ className: "xp-slot-toolbar",
222
+ children: [
223
+ /*#__PURE__*/ _jsx("div", {
224
+ className: "xp-slot-tabs",
225
+ role: "tablist",
226
+ "aria-label": editorCopy.tabs,
227
+ children: tabs.map((tab, index)=>/*#__PURE__*/ _jsx("button", {
228
+ className: "xp-slot-tab",
229
+ type: "button",
230
+ role: "tab",
231
+ id: `${tabId}-${tab.slot}`,
232
+ "aria-selected": tab.slot === slot,
233
+ tabIndex: tab.slot === slot ? 0 : -1,
234
+ "aria-controls": `${tabId}-panel`,
235
+ onClick: ()=>setActive({
236
+ id: experimentId,
237
+ slot: tab.slot
238
+ }),
239
+ onKeyDown: (event)=>{
240
+ const next = event.key === 'ArrowRight' ? (index + 1) % tabs.length : event.key === 'ArrowLeft' ? (index + tabs.length - 1) % tabs.length : event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : undefined;
241
+ if (next === undefined) return;
242
+ event.preventDefault();
243
+ setActive({
244
+ id: experimentId,
245
+ slot: tabs[next].slot
246
+ });
247
+ const buttons = event.currentTarget.parentElement?.querySelectorAll('[role="tab"]');
248
+ buttons?.[next]?.focus();
249
+ },
250
+ children: tab.label
251
+ }, tab.slot))
252
+ }),
253
+ slot !== 'control' ? /*#__PURE__*/ _jsx("div", {
254
+ className: "xp-slot-actions",
255
+ children: /*#__PURE__*/ _jsx("button", {
256
+ className: "xp-slot-button",
257
+ type: "button",
258
+ disabled: !selected || originalEmpty,
259
+ onClick: ()=>requestCopy(slot),
260
+ children: editorCopy.copy
261
+ })
262
+ }) : null
263
+ ]
214
264
  })
215
265
  ]
216
266
  }),
217
- /*#__PURE__*/ _jsx("div", {
267
+ /*#__PURE__*/ _jsxs("div", {
268
+ className: slotEmpty ? 'xp-slot-panel xp-slot-panel-empty' : 'xp-slot-panel',
218
269
  role: "tabpanel",
219
270
  id: `${tabId}-panel`,
220
271
  "aria-labelledby": `${tabId}-${slot}`,
221
- children: /*#__PURE__*/ _jsx(Slot, {})
272
+ children: [
273
+ /*#__PURE__*/ _jsx(Slot, {
274
+ className: "xp-slot-drop-target"
275
+ }),
276
+ slotEmpty ? /*#__PURE__*/ _jsx("span", {
277
+ className: "xp-slot-drop-label",
278
+ children: editorCopy.dropZone(slot)
279
+ }) : null
280
+ ]
222
281
  })
223
282
  ]
224
283
  });
@@ -10,7 +10,8 @@ export declare const editorCopy: {
10
10
  readonly running: "Changing this content mid-test mixes two versions in one result";
11
11
  readonly decided: (winner: string) => string;
12
12
  readonly archived: "This test is archived; the winning content is shown to everyone.";
13
- readonly copy: (label: string) => string;
13
+ readonly copy: "Copy Original to this version";
14
+ readonly dropZone: (slot: "control" | "variantA" | "variantB" | "variantC") => string;
14
15
  readonly replaceConfirm: (label: string) => string;
15
16
  readonly replace: "Replace";
16
17
  readonly createLabel: "Test this block";
@@ -18,6 +19,7 @@ export declare const editorCopy: {
18
19
  readonly cancel: "Cancel";
19
20
  readonly name: "Name";
20
21
  readonly setup: "Set up the test";
22
+ readonly viewResults: "View results";
21
23
  readonly nameExists: "This name is too close to an existing test. Choose a different name.";
22
24
  readonly createError: "Couldn't create the test. Try again.";
23
25
  readonly loading: "Loading test…";
@@ -10,7 +10,8 @@ export const editorCopy = {
10
10
  running: 'Changing this content mid-test mixes two versions in one result',
11
11
  decided: (winner)=>`Decided: ${winner}. Use 'Make it the content' on the test to finish.`,
12
12
  archived: 'This test is archived; the winning content is shown to everyone.',
13
- copy: (label)=>`Copy Original → ${label}`,
13
+ copy: 'Copy Original to this version',
14
+ dropZone: (slot)=>slot === 'control' ? 'Drop the original content here' : `Drop ${editorCopy.labels[slot]} content here`,
14
15
  replaceConfirm: (label)=>`${label} already has content. Replace it with a copy of the Original?`,
15
16
  replace: 'Replace',
16
17
  createLabel: 'Test this block',
@@ -18,6 +19,7 @@ export const editorCopy = {
18
19
  cancel: 'Cancel',
19
20
  name: 'Name',
20
21
  setup: 'Set up the test',
22
+ viewResults: 'View results',
21
23
  nameExists: "This name is too close to an existing test. Choose a different name.",
22
24
  createError: "Couldn't create the test. Try again.",
23
25
  loading: 'Loading test…',
@@ -0,0 +1,302 @@
1
+ /* Payload 3.90 defaults from @payloadcms/ui dist/scss/colors.scss and app.scss,
2
+ mapped as in admin/theme.css; radii from scss/vars.scss. Literal values keep
3
+ the editor independent of the parent admin's styles and sizing. */
4
+ .xp-slot-root {
5
+ --xp-bg: rgb(255, 255, 255);
6
+ --xp-text: rgb(47, 47, 47);
7
+ --xp-surface: rgb(245, 245, 245);
8
+ --xp-surface-raised: rgb(235, 235, 235);
9
+ --xp-border: rgb(221, 221, 221);
10
+ --xp-border-strong: rgb(208, 208, 208);
11
+ --xp-input-border: rgb(181, 181, 181);
12
+ --xp-border-accent: rgb(154, 154, 154);
13
+ --xp-text-muted: rgb(101, 101, 101);
14
+ --xp-text-strong: rgb(47, 47, 47);
15
+ --xp-text-emphasis: rgb(20, 20, 20);
16
+ --xp-input-bg: rgb(255, 255, 255);
17
+ --xp-success-surface: rgb(218, 237, 248);
18
+ --xp-success: rgb(21, 135, 186);
19
+ --xp-success-text-strong: rgb(18, 56, 76);
20
+ --xp-warning-surface: rgb(248, 232, 219);
21
+ --xp-warning: rgb(185, 108, 13);
22
+ --xp-warning-text-strong: rgb(73, 47, 21);
23
+ --xp-error-text: rgb(182, 54, 54);
24
+ --xp-radius: 3px;
25
+ --xp-radius-large: 4px;
26
+ --xp-space-1: 5px;
27
+ --xp-space-2: 10px;
28
+ --xp-space-3: 15px;
29
+ --xp-space-4: 20px;
30
+ --xp-font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
31
+ --xp-font-size-small: 13px;
32
+ --xp-font-size-body: 15px;
33
+ }
34
+
35
+ /* payload-puck's IframeWrapper sets both class="dark" and data-theme="dark" on the canvas <html>. */
36
+ [data-theme='dark'] .xp-slot-root,
37
+ .dark .xp-slot-root {
38
+ --xp-bg: rgb(20, 20, 20);
39
+ --xp-text: rgb(255, 255, 255);
40
+ --xp-surface: rgb(34, 34, 34);
41
+ --xp-surface-raised: rgb(47, 47, 47);
42
+ --xp-border: rgb(60, 60, 60);
43
+ --xp-border-strong: rgb(74, 74, 74);
44
+ --xp-input-border: rgb(101, 101, 101);
45
+ --xp-border-accent: rgb(141, 141, 141);
46
+ --xp-text-muted: rgb(181, 181, 181);
47
+ --xp-text-strong: rgb(235, 235, 235);
48
+ --xp-text-emphasis: rgb(255, 255, 255);
49
+ --xp-input-bg: rgb(34, 34, 34);
50
+ --xp-success-surface: rgb(19, 44, 58);
51
+ --xp-success: rgb(21, 135, 186);
52
+ --xp-success-text-strong: rgb(188, 225, 248);
53
+ --xp-warning-surface: rgb(56, 38, 20);
54
+ --xp-warning: rgb(185, 108, 13);
55
+ --xp-warning-text-strong: rgb(243, 212, 186);
56
+ --xp-error-text: rgb(246, 109, 103);
57
+ --xp-radius: 3px;
58
+ --xp-radius-large: 4px;
59
+ --xp-space-1: 5px;
60
+ --xp-space-2: 10px;
61
+ --xp-space-3: 15px;
62
+ --xp-space-4: 20px;
63
+ --xp-font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
64
+ --xp-font-size-small: 13px;
65
+ --xp-font-size-body: 15px;
66
+ }
67
+
68
+ /* These controls also render in the host's preflight-reset editor iframe. */
69
+ .xp-slot-editor {
70
+ min-width: 0;
71
+ }
72
+
73
+ .xp-slot-chrome,
74
+ .xp-slot-drawer {
75
+ box-sizing: border-box;
76
+ background: var(--xp-bg);
77
+ color: var(--xp-text);
78
+ border: 1px solid var(--xp-border);
79
+ border-radius: var(--xp-radius-large);
80
+ padding: var(--xp-space-3);
81
+ font-family: var(--xp-font-body);
82
+ font-size: var(--xp-font-size-body);
83
+ line-height: 1.5;
84
+ }
85
+
86
+ .xp-slot-header,
87
+ .xp-slot-toolbar,
88
+ .xp-slot-actions {
89
+ display: flex;
90
+ flex-wrap: wrap;
91
+ align-items: center;
92
+ gap: var(--xp-space-2);
93
+ }
94
+
95
+ .xp-slot-header > .xp-slot-actions,
96
+ .xp-slot-toolbar > .xp-slot-actions {
97
+ margin-inline-start: auto;
98
+ }
99
+
100
+ .xp-slot-title {
101
+ margin: 0;
102
+ color: var(--xp-text);
103
+ font: inherit;
104
+ font-weight: 600;
105
+ overflow-wrap: anywhere;
106
+ }
107
+
108
+ .xp-slot-status {
109
+ display: inline-flex;
110
+ padding: var(--xp-space-1) var(--xp-space-2);
111
+ border: 1px solid var(--xp-border-strong);
112
+ border-radius: var(--xp-radius-large);
113
+ background: var(--xp-surface);
114
+ color: var(--xp-text-strong);
115
+ font-size: var(--xp-font-size-small);
116
+ line-height: 1.2;
117
+ }
118
+
119
+ .xp-slot-status[data-status='running'] {
120
+ background: var(--xp-success-surface);
121
+ border-color: var(--xp-success);
122
+ color: var(--xp-success-text-strong);
123
+ }
124
+
125
+ .xp-slot-notice {
126
+ margin: var(--xp-space-3) 0;
127
+ color: var(--xp-text-muted);
128
+ }
129
+
130
+ .xp-slot-warning {
131
+ display: flex;
132
+ align-items: flex-start;
133
+ gap: var(--xp-space-2);
134
+ border: 1px solid var(--xp-warning);
135
+ border-radius: var(--xp-radius);
136
+ padding: var(--xp-space-2) var(--xp-space-3);
137
+ background: var(--xp-warning-surface);
138
+ color: var(--xp-warning-text-strong);
139
+ }
140
+
141
+ .xp-slot-warning-icon {
142
+ flex: 0 0 auto;
143
+ width: var(--xp-space-4);
144
+ height: var(--xp-space-4);
145
+ }
146
+
147
+ .xp-slot-button,
148
+ .xp-slot-tab,
149
+ .xp-slot-input {
150
+ box-sizing: border-box;
151
+ appearance: none;
152
+ background: var(--xp-bg);
153
+ border: 1px solid var(--xp-input-border);
154
+ padding: var(--xp-space-2) var(--xp-space-3);
155
+ border-radius: var(--xp-radius);
156
+ font: inherit;
157
+ font-size: var(--xp-font-size-body);
158
+ color: var(--xp-text);
159
+ cursor: pointer;
160
+ line-height: 1.4;
161
+ }
162
+
163
+ .xp-slot-button {
164
+ display: inline-flex;
165
+ align-items: center;
166
+ justify-content: center;
167
+ gap: var(--xp-space-1);
168
+ text-decoration: none;
169
+ text-align: center;
170
+ }
171
+
172
+ .xp-slot-button:hover:not(:disabled),
173
+ .xp-slot-tab:hover:not(:disabled) {
174
+ background: var(--xp-surface-raised);
175
+ border-color: var(--xp-border-accent);
176
+ }
177
+
178
+ .xp-slot-button-primary {
179
+ background: var(--xp-text-emphasis);
180
+ border-color: var(--xp-text-emphasis);
181
+ color: var(--xp-bg);
182
+ font-weight: 600;
183
+ }
184
+
185
+ .xp-slot-button-primary:hover:not(:disabled) {
186
+ background: var(--xp-text-strong);
187
+ border-color: var(--xp-text-strong);
188
+ }
189
+
190
+ .xp-slot-button:focus-visible,
191
+ .xp-slot-tab:focus-visible,
192
+ .xp-slot-input:focus-visible {
193
+ outline: 2px solid var(--xp-text);
194
+ outline-offset: 2px;
195
+ }
196
+
197
+ .xp-slot-button:disabled,
198
+ .xp-slot-tab:disabled,
199
+ .xp-slot-input:disabled {
200
+ background: var(--xp-surface);
201
+ border-color: var(--xp-border);
202
+ color: var(--xp-text-muted);
203
+ cursor: not-allowed;
204
+ opacity: 0.65;
205
+ }
206
+
207
+ .xp-slot-tabs {
208
+ display: flex;
209
+ flex-wrap: wrap;
210
+ gap: var(--xp-space-1);
211
+ padding: var(--xp-space-1);
212
+ border-radius: var(--xp-radius-large);
213
+ background: var(--xp-surface);
214
+ }
215
+
216
+ .xp-slot-tab {
217
+ background: transparent;
218
+ border-color: transparent;
219
+ }
220
+
221
+ .xp-slot-tab[aria-selected='true'] {
222
+ background: var(--xp-bg);
223
+ border-color: var(--xp-border-accent);
224
+ color: var(--xp-text-emphasis);
225
+ font-weight: 600;
226
+ box-shadow: 0 2px 0 var(--xp-text-emphasis);
227
+ }
228
+
229
+ .xp-slot-panel {
230
+ position: relative;
231
+ border: 0;
232
+ padding: 0;
233
+ }
234
+
235
+ .xp-slot-panel-empty {
236
+ margin-top: var(--xp-space-2);
237
+ border: 2px dashed var(--xp-border-accent);
238
+ border-radius: var(--xp-radius-large);
239
+ background: var(--xp-surface);
240
+ }
241
+
242
+ .xp-slot-drop-target {
243
+ border: 0;
244
+ padding: 0;
245
+ }
246
+
247
+ .xp-slot-panel-empty > .xp-slot-drop-target {
248
+ min-height: 160px;
249
+ }
250
+
251
+ .xp-slot-drop-label {
252
+ position: absolute;
253
+ inset: 0;
254
+ display: flex;
255
+ align-items: center;
256
+ justify-content: center;
257
+ padding: var(--xp-space-4);
258
+ color: var(--xp-text-muted);
259
+ font-family: var(--xp-font-body);
260
+ font-size: var(--xp-font-size-body);
261
+ line-height: 1.5;
262
+ text-align: center;
263
+ pointer-events: none;
264
+ }
265
+
266
+ .xp-slot-drawer {
267
+ display: grid;
268
+ gap: var(--xp-space-3);
269
+ margin-block: var(--xp-space-3);
270
+ padding: var(--xp-space-4);
271
+ border-color: var(--xp-border-accent);
272
+ background: var(--xp-surface);
273
+ }
274
+
275
+ .xp-slot-field {
276
+ display: grid;
277
+ gap: var(--xp-space-1);
278
+ }
279
+
280
+ .xp-slot-label {
281
+ font-weight: 600;
282
+ }
283
+
284
+ .xp-slot-input {
285
+ width: 100%;
286
+ min-width: 0;
287
+ background: var(--xp-input-bg);
288
+ cursor: text;
289
+ }
290
+
291
+ .xp-slot-error {
292
+ margin: 0;
293
+ color: var(--xp-error-text);
294
+ }
295
+
296
+ .xp-slot-confirm {
297
+ padding: var(--xp-space-2) var(--xp-space-3);
298
+ margin-block: var(--xp-space-3);
299
+ border: 1px solid var(--xp-warning);
300
+ border-radius: var(--xp-radius);
301
+ background: var(--xp-warning-surface);
302
+ }
@@ -1,9 +1,10 @@
1
1
  import type { DocId } from './contracts.js';
2
2
  import type { SerializedAttributeKeys, SerializedFlagPoint, SerializedMetricSource } from './admin.js';
3
- export type TestsRoute = 'home' | 'new' | 'test' | 'results';
3
+ export type TestsRoute = 'home' | 'new' | 'test' | 'results' | 'goals' | 'goalNew' | 'goal';
4
4
  export interface TestsSurfaceProps {
5
5
  route: TestsRoute;
6
6
  id?: string;
7
+ returnToTest?: string;
7
8
  adminRoute: string;
8
9
  apiRoute: string;
9
10
  isAdmin: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delmaredigital/payload-experiments",
3
- "version": "0.2.0-rc.2",
3
+ "version": "0.2.0-rc.4",
4
4
  "description": "No-code A/B testing for Payload CMS and Next.js: block, page and flag experiments with holdouts, layers, exposure logging and honest statistics inside the admin — no vendor",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -67,6 +67,7 @@
67
67
  "import": "./dist/exports/puckEditor.js",
68
68
  "default": "./dist/exports/puckEditor.js"
69
69
  },
70
+ "./puck/editor.css": "./dist/puck/editor/editor.css",
70
71
  "./package.json": "./package.json"
71
72
  },
72
73
  "files": [