@sanity/personalization-plugin 2.4.1 → 2.5.0-field-level-personalization.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 (35) hide show
  1. package/README.md +107 -5
  2. package/dist/_chunks-cjs/fieldExperiments.js +507 -0
  3. package/dist/_chunks-cjs/fieldExperiments.js.map +1 -0
  4. package/dist/_chunks-es/fieldExperiments.mjs +511 -0
  5. package/dist/_chunks-es/fieldExperiments.mjs.map +1 -0
  6. package/dist/growthbook/index.js +3 -3
  7. package/dist/growthbook/index.js.map +1 -1
  8. package/dist/growthbook/index.mjs +1 -1
  9. package/dist/index.d.mts +33 -12
  10. package/dist/index.d.ts +33 -12
  11. package/dist/index.js +158 -277
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +160 -277
  14. package/dist/index.mjs.map +1 -1
  15. package/package.json +20 -20
  16. package/src/components/ArrayItem.tsx +9 -0
  17. package/src/components/Select.tsx +1 -1
  18. package/src/components/{Array.tsx → experiment/Array.tsx} +2 -2
  19. package/src/components/{ExperimentContext.tsx → experiment/Context.tsx} +2 -2
  20. package/src/components/{ExperimentField.tsx → experiment/Field.tsx} +11 -8
  21. package/src/components/{ExperimentInput.tsx → experiment/Input.tsx} +4 -4
  22. package/src/components/{VariantInput.tsx → experiment/VariantInput.tsx} +2 -1
  23. package/src/components/{VariantPreview.tsx → experiment/VariantPreview.tsx} +2 -2
  24. package/src/components/experiment/index.ts +6 -0
  25. package/src/components/personalization/Array.tsx +59 -0
  26. package/src/components/personalization/Context.tsx +61 -0
  27. package/src/components/personalization/Field.tsx +134 -0
  28. package/src/components/personalization/SegmentInput.tsx +19 -0
  29. package/src/components/personalization/SegmentPreview.tsx +71 -0
  30. package/src/components/personalization/index.ts +5 -0
  31. package/src/fieldExperiments.tsx +44 -12
  32. package/src/fieldPersonalization.tsx +254 -0
  33. package/src/index.ts +1 -0
  34. package/src/types.ts +20 -2
  35. package/src/utils/clearChildGroups.ts +33 -0
package/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  ## Previously know as @sanity/personalisation-plugin
4
4
 
5
- > This is a **Sanity Studio v3** plugin.
6
-
7
5
  This plugin allows users to add a/b/n testing experiments to individual fields.
8
6
 
9
7
  ![image](./overview.gif)
@@ -20,6 +18,7 @@ Once configured you can query the values using the ids of the experiment and var
20
18
  - [Validation of individual array items](#validation-of-individual-array-items)
21
19
  - [Shape of stored data](#shape-of-stored-data)
22
20
  - [Querying data](#querying-data)
21
+ - [Split testing](#split-testing)
23
22
  - [Using experiment fields in an array](#using-experiment-fields-in-an-array)
24
23
  - [License](#license)
25
24
  - [Develop \& test](#develop--test)
@@ -208,7 +207,6 @@ This would also create two new fields in your schema.
208
207
 
209
208
  Note that the name key in the field gets rewritten to value and is instead used to name the object field.
210
209
 
211
-
212
210
  ## Validation of individual array items
213
211
 
214
212
  You may wish to validate individual fields for various reasons. From the variant array field, add a validation rule that can look through all the array items, and return item-specific validation messages at the path of that array item.
@@ -260,7 +258,8 @@ The custom input contains buttons which will add new array items with the experi
260
258
  }
261
259
  ```
262
260
 
263
- Querying data
261
+ ## Querying data
262
+
264
263
  Using GROQ filters you can query for a specific experiment, with a fallback to default value like so:
265
264
 
266
265
  ```ts
@@ -269,9 +268,112 @@ Using GROQ filters you can query for a specific experiment, with a fallback to d
269
268
  }
270
269
  ```
271
270
 
271
+ ## Split testing
272
+
273
+ Split testing involves splitting traffic for one url over 2+ pages, this is used when you want to test more than just a single field in an experiment.
274
+
275
+ ### Studio Setup
276
+
277
+ To do split testing using this plugin define a type that can store a url path
278
+
279
+ ```ts
280
+ export const path = defineType({
281
+ name: 'path',
282
+ type: 'string',
283
+ validation: (Rule) =>
284
+ Rule.required().custom(async (value: string | undefined, context) => {
285
+ if (!value) return true
286
+ if (!value.startsWith('/')) return 'Must start with "/"'
287
+ return true
288
+ }),
289
+ })
290
+ ```
291
+
292
+ add the type to the studio `schema.types` and the plugin config fields:
293
+
294
+ ```ts
295
+ fieldLevelExperiments({
296
+ fields: ['path', ...otherFields],
297
+ experiments: getExperiments,
298
+ }),
299
+ ```
300
+
301
+ and then create a document type that stores the routing information:
302
+
303
+ ```ts
304
+ export const routing = defineType({
305
+ name: 'routing',
306
+ type: 'document',
307
+ title: 'Routing Experiments',
308
+ fields: [
309
+ {
310
+ name: 'pathExperiment',
311
+ type: 'experimentPath',
312
+ initialValue: {active: true},
313
+ },
314
+ ],
315
+ preview: {
316
+ select: {
317
+ path: 'pathExperiment.default',
318
+ experiment: 'pathExperiment.experimentId',
319
+ },
320
+ prepare({path, experiment}) {
321
+ return {
322
+ title: `${path} - ${experiment}`,
323
+ }
324
+ },
325
+ },
326
+ })
327
+ ```
328
+
329
+ ### Frontend usage
330
+
331
+ In your frontend you will need some middleware that can intercept the request for the page and check if the route is included in your split page testing and if so decide which page the user should see.
332
+
333
+ In Next.js Middleware it could be something like
334
+
335
+ ```ts
336
+ const ROUTING_QUERY = defineQuery(`*[
337
+ _type == "routing" &&
338
+ pathExperiment.default == $path
339
+ ][0]{
340
+ "route": coalesce(pathExperiment.variants[experimentId == $experimentId && variantId == $variantId][0].value, pathExperiment.default)
341
+ }`)
342
+
343
+ export async function middleware(request: NextRequest) {
344
+ let response = NextResponse.next()
345
+ const path = request.nextUrl.pathname
346
+ // getExperimentValue is a function that will determine a variant based on some user properties
347
+ const {variant} = await getExperimentValue(path)
348
+
349
+ const queryParams = {
350
+ path,
351
+ experimentId: 'homepage',
352
+ variantId: variant?.id || '',
353
+ }
354
+
355
+ // Instead of doing a query for every page this could be queried at build time and stored in a file.
356
+ const data = await client.fetch(ROUTING_QUERY, queryParams)
357
+ if (data?.route) {
358
+ const url = request.nextUrl.clone()
359
+ url.pathname = data.route
360
+ const rewrite = NextResponse.rewrite(url)
361
+ return rewrite
362
+ }
363
+
364
+ return response
365
+ }
366
+
367
+ export const config = {
368
+ //only run the middleware on pages
369
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)'],
370
+ }
371
+ ```
372
+
272
373
  ## Using experiment fields in an array
273
374
 
274
375
  You may want to add experiment fields as a type with in an array in oder to do this you would need to set an initial value for the experiment to active the schema would be something like:
376
+
275
377
  ```ts
276
378
  defineField({
277
379
  name: 'components',
@@ -286,7 +388,7 @@ defineField({
286
388
  }),
287
389
  ```
288
390
 
289
- You can then use a groq filter to return the base version of you array member so you don't have to create an experiment specific version
391
+ You can then use a groq filter to return the base version of you array member so you don't have to create an experiment specific version
290
392
 
291
393
  ```ts
292
394
  *[
@@ -0,0 +1,507 @@
1
+ "use strict";
2
+ var jsxRuntime = require("react/jsx-runtime"), sanity = require("sanity"), ui = require("@sanity/ui"), uuid = require("@sanity/uuid"), react = require("react"), equal = require("fast-deep-equal"), suspendReact = require("suspend-react"), gi = require("react-icons/gi");
3
+ function _interopDefaultCompat(e) {
4
+ return e && typeof e == "object" && "default" in e ? e : { default: e };
5
+ }
6
+ var equal__default = /* @__PURE__ */ _interopDefaultCompat(equal);
7
+ const ArrayItem = (props) => {
8
+ const { active } = props.value;
9
+ return active || props.inputProps.onChange(sanity.set(!0, ["active"])), props.renderDefault(props);
10
+ }, CONFIG_DEFAULT = {
11
+ fields: [],
12
+ apiVersion: "2024-11-07",
13
+ experimentNameOverride: "experiment",
14
+ variantNameOverride: "variant",
15
+ variantId: "variantId",
16
+ variantArrayName: "variants",
17
+ experimentId: "experimentId"
18
+ }, ExperimentContext = react.createContext({
19
+ ...CONFIG_DEFAULT,
20
+ experiments: []
21
+ });
22
+ function useExperimentContext() {
23
+ return react.useContext(ExperimentContext);
24
+ }
25
+ function ExperimentProvider(props) {
26
+ const { experimentFieldPluginConfig } = props, client = sanity.useClient({ apiVersion: experimentFieldPluginConfig.apiVersion }), workspace = sanity.useWorkspace(), experiments = Array.isArray(experimentFieldPluginConfig.experiments) ? experimentFieldPluginConfig.experiments : suspendReact.suspend(
27
+ // eslint-disable-next-line require-await
28
+ async () => typeof experimentFieldPluginConfig.experiments == "function" ? experimentFieldPluginConfig.experiments(client) : experimentFieldPluginConfig.experiments,
29
+ [workspace],
30
+ { equal: equal__default.default }
31
+ ), context = react.useMemo(
32
+ () => ({ ...experimentFieldPluginConfig, experiments }),
33
+ [experimentFieldPluginConfig, experiments]
34
+ );
35
+ return /* @__PURE__ */ jsxRuntime.jsx(ExperimentContext.Provider, { value: context, children: props.renderDefault(props) });
36
+ }
37
+ const ArrayInput = (props) => {
38
+ const fieldPath = props.path.slice(0, -1), { onItemAppend, variantName, variantId, experimentId } = props, experimentValue = sanity.useFormValue([...fieldPath, experimentId]), { experiments } = useExperimentContext(), handleClick = react.useCallback(
39
+ async (variant) => {
40
+ const item = {
41
+ _key: uuid.uuid(),
42
+ [variantId]: variant.id,
43
+ [experimentId]: experimentValue,
44
+ _type: variantName
45
+ };
46
+ onItemAppend(item);
47
+ },
48
+ [variantId, experimentId, experimentValue, variantName, onItemAppend]
49
+ ), filteredVariants = experiments.find((option) => option.id === experimentValue)?.variants || [], usedVariants = (props.value || [])?.map((variant) => variant[variantId]);
50
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
51
+ props.renderDefault({ ...props, arrayFunctions: () => null }),
52
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Inline, { space: 1, children: filteredVariants.map((variant) => /* @__PURE__ */ jsxRuntime.jsx(
53
+ ui.Button,
54
+ {
55
+ text: `Add ${variant.label}`,
56
+ mode: "ghost",
57
+ disabled: usedVariants?.includes(variant.id),
58
+ onClick: () => handleClick(variant)
59
+ },
60
+ `${experimentValue}-${variant.id}`
61
+ )) })
62
+ ] });
63
+ }, CloseIcon = /* @__PURE__ */ react.forwardRef(function(props, ref) {
64
+ return /* @__PURE__ */ jsxRuntime.jsx(
65
+ "svg",
66
+ {
67
+ "data-sanity-icon": "close",
68
+ width: "1em",
69
+ height: "1em",
70
+ viewBox: "0 0 25 25",
71
+ fill: "none",
72
+ xmlns: "http://www.w3.org/2000/svg",
73
+ ...props,
74
+ ref,
75
+ children: /* @__PURE__ */ jsxRuntime.jsx(
76
+ "path",
77
+ {
78
+ d: "M18 7L7 18M7 7L18 18",
79
+ stroke: "currentColor",
80
+ strokeWidth: 1.2,
81
+ strokeLinejoin: "round"
82
+ }
83
+ )
84
+ }
85
+ );
86
+ }), clearChildrenGroups = (props) => {
87
+ const children = props.children;
88
+ return !children || typeof children != "object" || !children.props ? props : {
89
+ ...props,
90
+ children: {
91
+ ...children,
92
+ props: {
93
+ ...children.props,
94
+ children: {
95
+ ...children.props.children,
96
+ props: {
97
+ ...children.props.children?.props,
98
+ groups: []
99
+ }
100
+ }
101
+ }
102
+ }
103
+ };
104
+ }, useAddExperimentAction = (props) => {
105
+ const { onChange, active, experimentNameOverride } = props, handleAddAction = react.useCallback(() => {
106
+ onChange([sanity.set(!active, ["active"])]);
107
+ }, [onChange, active]);
108
+ return {
109
+ title: `Add ${experimentNameOverride}`,
110
+ type: "action",
111
+ icon: gi.GiSoapExperiment,
112
+ onAction: handleAddAction,
113
+ renderAsButton: !0
114
+ };
115
+ }, useRemoveExperimentAction = (props) => {
116
+ const { onChange, active, experimentId, experimentNameOverride, variantNameOverride } = props, handleClearAction = react.useCallback(() => {
117
+ const activeId = ["active"], experiment = [experimentId], variants = [`${variantNameOverride}s`];
118
+ onChange([sanity.set(!active, activeId), sanity.unset(experiment), sanity.unset(variants)]);
119
+ }, [onChange, active, experimentId, variantNameOverride]);
120
+ return {
121
+ title: `Remove ${experimentNameOverride}`,
122
+ type: "action",
123
+ icon: CloseIcon,
124
+ onAction: handleClearAction,
125
+ renderAsButton: !0
126
+ };
127
+ }, createActions = ({
128
+ onChange,
129
+ inputId,
130
+ active,
131
+ experimentNameOverride,
132
+ experimentId,
133
+ variantNameOverride
134
+ }) => {
135
+ const removeAction = sanity.defineDocumentFieldAction({
136
+ name: `Remove ${experimentNameOverride}`,
137
+ useAction: (props) => useRemoveExperimentAction({
138
+ active: !0,
139
+ onChange,
140
+ experimentNameOverride,
141
+ experimentId,
142
+ variantNameOverride
143
+ })
144
+ }), addAction = sanity.defineDocumentFieldAction({
145
+ name: `Add ${experimentNameOverride}`,
146
+ useAction: (props) => useAddExperimentAction({
147
+ active: !1,
148
+ onChange,
149
+ experimentNameOverride
150
+ })
151
+ });
152
+ return active ? removeAction : addAction;
153
+ }, Field = (props) => {
154
+ const { onChange } = props.inputProps, { inputId, experimentNameOverride, experimentId, variantNameOverride } = props, active = props.value?.active, actionProps = react.useMemo(
155
+ () => ({
156
+ onChange,
157
+ inputId,
158
+ active,
159
+ experimentNameOverride,
160
+ experimentId,
161
+ variantNameOverride
162
+ }),
163
+ [onChange, inputId, active, experimentNameOverride, experimentId, variantNameOverride]
164
+ ), memoizedActions = react.useMemo(() => {
165
+ const oldActions = props.actions || [];
166
+ return [createActions(actionProps), ...oldActions];
167
+ }, [actionProps, props.actions]), enhancedProps = react.useMemo(() => ({
168
+ ...clearChildrenGroups(props),
169
+ actions: memoizedActions
170
+ }), [props, memoizedActions]);
171
+ return props.renderDefault(enhancedProps);
172
+ }, Select = (props) => {
173
+ const {
174
+ value,
175
+ // Current field value
176
+ onChange,
177
+ // Method to handle patch events,
178
+ elementProps,
179
+ listOptions,
180
+ handleChange
181
+ } = props;
182
+ return /* @__PURE__ */ jsxRuntime.jsxs(
183
+ ui.Select,
184
+ {
185
+ ...elementProps,
186
+ fontSize: 2,
187
+ padding: 3,
188
+ space: [3, 3, 4],
189
+ value: value || "",
190
+ onChange: (event) => handleChange(event, onChange),
191
+ children: [
192
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select an option..." }),
193
+ listOptions.map(({ value: optionValue, title }) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: optionValue, children: title }, optionValue))
194
+ ]
195
+ }
196
+ );
197
+ }, formatlistOptions = (experiments) => experiments.map((experiment) => ({
198
+ title: experiment.label,
199
+ value: experiment.id
200
+ })), Input = (props) => {
201
+ const { experiments } = useExperimentContext(), id = sanity.useFormValue(["_id"]), additionalChangePath = react.useMemo(
202
+ () => [...props.path.slice(0, -1), `${props.variantNameOverride}s`],
203
+ [props.variantNameOverride, props.path]
204
+ ), subValues = sanity.useFormValue(additionalChangePath), { patch } = sanity.useDocumentOperation(sanity.getPublishedId(id), props.schemaType.name), handleChange = react.useCallback(
205
+ (event, onChange) => {
206
+ const inputValue = event.currentTarget.value;
207
+ if (onChange(inputValue ? sanity.set(inputValue) : sanity.unset()), subValues) {
208
+ const patchEvent = {
209
+ unset: [additionalChangePath.join(".")]
210
+ };
211
+ patch.execute([patchEvent]);
212
+ }
213
+ },
214
+ [patch, subValues, additionalChangePath]
215
+ );
216
+ return experiments.length ? /* @__PURE__ */ jsxRuntime.jsx(Select, { ...props, listOptions: formatlistOptions(experiments), handleChange }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: [3, 3, 4], radius: 2, shadow: 1, tone: "caution", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { align: "center", size: [2, 2, 3], children: [
217
+ "There are no defined ",
218
+ props.experimentNameOverride,
219
+ "s"
220
+ ] }) });
221
+ }, VariantInput = (props) => {
222
+ const experimentPath = props.path.slice(0, -2), defaultValue = sanity.useFormValue([...experimentPath, "default"]), handleClick = () => {
223
+ props.onChange(sanity.set(defaultValue, ["value"]));
224
+ };
225
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
226
+ props.renderDefault(props),
227
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Inline, { space: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { text: "Copy default", mode: "ghost", onClick: () => handleClick() }) })
228
+ ] });
229
+ }, VariantPreview = (props) => {
230
+ const [subtitle, setSubtitle] = react.useState(void 0), [title, setTitle] = react.useState(void 0), [media, setMedia] = react.useState(void 0), client = sanity.useClient({ apiVersion: "2025-01-01" }), { experiments } = useExperimentContext(), { experiment, variant, value } = props, selectedExperiment = experiments.find((experimentItem) => experimentItem.id === experiment), selectedVariant = selectedExperiment?.variants.find((variantItem) => variantItem.id === variant);
231
+ react.useEffect(() => {
232
+ (async () => {
233
+ if (setTitle(`${selectedExperiment?.label} - ${selectedVariant?.label}`), typeof value == "string")
234
+ return setSubtitle(value);
235
+ if (sanity.isReference(value)) {
236
+ const doc = await client.getDocument(value._ref), referenceType = (props.schemaType.fields.find((field) => field.name === "value")?.type).to.find((field) => field.type?.name === doc?._type), selectFields = {}, previewFields = referenceType?.preview?.select || {};
237
+ Object.keys(previewFields).forEach((key) => {
238
+ const valueKey = referenceType?.preview?.select?.[key];
239
+ selectFields[key] = valueKey && doc ? valueKey?.split(".").reduce((acc, index) => acc[index], doc) : void 0;
240
+ });
241
+ const previewContent = referenceType?.preview?.prepare?.(selectFields);
242
+ return setMedia(previewContent?.media || selectFields.media), setSubtitle(previewContent?.title || selectFields?.title);
243
+ }
244
+ return sanity.isImage(value) && setMedia(value), "";
245
+ })();
246
+ }, [value, client, selectedExperiment?.label, selectedVariant?.label, props.schemaType]);
247
+ const previewProps = {
248
+ ...props,
249
+ title,
250
+ subtitle,
251
+ media
252
+ };
253
+ return props.renderDefault(previewProps);
254
+ };
255
+ function flattenSchemaType(schemaType) {
256
+ return sanity.isDocumentSchemaType(schemaType) ? extractInnerFields(schemaType.fields, [], 5) : (console.error("Schema type is not a document"), []);
257
+ }
258
+ function extractInnerFields(fields, path, maxDepth) {
259
+ return path.length >= maxDepth ? [] : fields.reduce((acc, field) => {
260
+ const thisFieldWithPath = { path: [...path, field.name], ...field };
261
+ if (field.type.jsonType === "object") {
262
+ const innerFields = extractInnerFields(field.type.fields, [...path, field.name], maxDepth);
263
+ return [...acc, thisFieldWithPath, ...innerFields];
264
+ } else if (field.type.jsonType === "array") {
265
+ const innerFields = (field.type.of || []).reduce((arrayAcc, arrayType) => {
266
+ if ("fields" in arrayType) {
267
+ const typeFields = extractInnerFields(arrayType.fields, [...path, field.name], maxDepth);
268
+ return [...arrayAcc, ...typeFields];
269
+ }
270
+ return arrayAcc;
271
+ }, []);
272
+ return [...acc, thisFieldWithPath, ...innerFields];
273
+ }
274
+ return [...acc, thisFieldWithPath];
275
+ }, []);
276
+ }
277
+ const createExperimentType = ({
278
+ field,
279
+ experimentNameOverride,
280
+ variantNameOverride,
281
+ variantId,
282
+ variantArrayName,
283
+ experimentId
284
+ }) => {
285
+ const typeName = typeof field == "string" ? field : field.name, usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1), variantName = `${variantNameOverride}${usedName}`;
286
+ return sanity.defineType({
287
+ name: `${experimentNameOverride}${usedName}`,
288
+ type: "object",
289
+ groups: [
290
+ {
291
+ name: "default",
292
+ title: "Default",
293
+ hidden: ({ parent }) => !Array.isArray(parent)
294
+ },
295
+ {
296
+ name: "experiments",
297
+ title: "Experiments",
298
+ hidden: ({ parent }) => !Array.isArray(parent)
299
+ },
300
+ {
301
+ name: "all-fields",
302
+ title: "All fields",
303
+ hidden: ({ parent }) => Array.isArray(parent)
304
+ }
305
+ ],
306
+ components: {
307
+ field: (props) => /* @__PURE__ */ jsxRuntime.jsx(
308
+ Field,
309
+ {
310
+ ...props,
311
+ experimentId,
312
+ experimentNameOverride,
313
+ variantNameOverride
314
+ }
315
+ ),
316
+ item: ArrayItem
317
+ },
318
+ fields: [
319
+ typeof field == "string" ? (
320
+ // Define a simple field if all we have is the name as a string
321
+ sanity.defineField({
322
+ name: "default",
323
+ type: field,
324
+ group: "default"
325
+ })
326
+ ) : (
327
+ // Pass in the configured options, but overwrite the name
328
+ {
329
+ ...field,
330
+ name: "default",
331
+ group: "default"
332
+ }
333
+ ),
334
+ sanity.defineField({
335
+ name: "active",
336
+ type: "boolean",
337
+ hidden: !0,
338
+ initialValue: !1
339
+ }),
340
+ sanity.defineField({
341
+ name: experimentId,
342
+ type: "string",
343
+ group: "experiments",
344
+ components: {
345
+ input: (props) => /* @__PURE__ */ jsxRuntime.jsx(
346
+ Input,
347
+ {
348
+ ...props,
349
+ experimentNameOverride,
350
+ variantNameOverride
351
+ }
352
+ )
353
+ },
354
+ hidden: ({ parent }) => !parent?.active
355
+ }),
356
+ sanity.defineField({
357
+ name: variantArrayName,
358
+ type: "array",
359
+ group: "experiments",
360
+ hidden: ({ parent }) => !parent?.[experimentId],
361
+ components: {
362
+ input: (props) => /* @__PURE__ */ jsxRuntime.jsx(
363
+ ArrayInput,
364
+ {
365
+ ...props,
366
+ variantName,
367
+ variantId,
368
+ experimentId
369
+ }
370
+ )
371
+ },
372
+ of: [
373
+ sanity.defineField({
374
+ name: variantName,
375
+ type: variantName
376
+ })
377
+ ]
378
+ })
379
+ ],
380
+ preview: {
381
+ select: {
382
+ base: "default",
383
+ experiment: experimentId
384
+ },
385
+ prepare: ({ base, experiment }) => {
386
+ const title = base?.title || base?.name || typeof base == "string" ? base : "", experimentTitle = experiment ? `Experiment: ${experiment}` : "", media = base?.image || base?.photo || base?.media || "";
387
+ return {
388
+ title: title || experimentTitle,
389
+ subtitle: title ? experimentTitle : "",
390
+ media
391
+ };
392
+ }
393
+ }
394
+ });
395
+ }, createVariantType = ({
396
+ field,
397
+ variantNameOverride,
398
+ variantId,
399
+ experimentId
400
+ }) => {
401
+ const typeName = typeof field == "string" ? field : field.name, usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1);
402
+ return sanity.defineType({
403
+ name: `${variantNameOverride}${usedName}`,
404
+ title: `${variantNameOverride} array ${usedName}`,
405
+ type: "object",
406
+ components: {
407
+ preview: VariantPreview,
408
+ input: VariantInput
409
+ },
410
+ fields: [
411
+ {
412
+ type: "string",
413
+ name: variantId,
414
+ readOnly: !0
415
+ },
416
+ {
417
+ type: "string",
418
+ name: experimentId,
419
+ hidden: !0
420
+ },
421
+ typeof field == "string" ? (
422
+ // Define a simple field if all we have is the name as a string
423
+ sanity.defineField({
424
+ name: "value",
425
+ type: field
426
+ // hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
427
+ })
428
+ ) : (
429
+ // Pass in the configured options, but overwrite the name
430
+ {
431
+ ...field,
432
+ name: "value"
433
+ // hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
434
+ }
435
+ )
436
+ ],
437
+ preview: {
438
+ select: {
439
+ variant: variantId,
440
+ experiment: experimentId,
441
+ value: "value"
442
+ }
443
+ }
444
+ });
445
+ }, fieldSchema = ({
446
+ fields,
447
+ experimentNameOverride,
448
+ variantNameOverride,
449
+ variantId,
450
+ variantArrayName,
451
+ experimentId
452
+ }) => [
453
+ ...fields.map(
454
+ (field) => createVariantType({ field, variantNameOverride, variantId, experimentId })
455
+ ),
456
+ ...fields.map(
457
+ (field) => createExperimentType({
458
+ field,
459
+ experimentNameOverride,
460
+ variantNameOverride,
461
+ variantId,
462
+ variantArrayName,
463
+ experimentId
464
+ })
465
+ )
466
+ ], fieldLevelExperiments = sanity.definePlugin((config) => {
467
+ const pluginConfig = { ...CONFIG_DEFAULT, ...config }, { fields, experimentNameOverride, variantNameOverride } = pluginConfig, experimentId = `${experimentNameOverride}Id`, variantArrayName = `${variantNameOverride}s`, variantId = `${variantNameOverride}Id`;
468
+ return {
469
+ name: "sanity-personalistaion-plugin-field-level-experiments",
470
+ schema: {
471
+ types: fieldSchema({
472
+ fields,
473
+ experimentNameOverride,
474
+ variantNameOverride,
475
+ variantId,
476
+ variantArrayName,
477
+ experimentId
478
+ })
479
+ },
480
+ form: {
481
+ components: {
482
+ input: (props) => {
483
+ if (!(props.id === "root" && sanity.isObjectInputProps(props)) || !flattenSchemaType(props.schemaType).some(
484
+ (field) => field.type.name.startsWith(experimentNameOverride) || field.name.startsWith(experimentNameOverride)
485
+ ))
486
+ return props.renderDefault(props);
487
+ const providerProps = {
488
+ ...props,
489
+ experimentFieldPluginConfig: {
490
+ ...pluginConfig,
491
+ variantId,
492
+ variantArrayName,
493
+ experimentId
494
+ }
495
+ };
496
+ return ExperimentProvider(providerProps);
497
+ }
498
+ }
499
+ }
500
+ };
501
+ });
502
+ exports.ArrayItem = ArrayItem;
503
+ exports.CloseIcon = CloseIcon;
504
+ exports.clearChildrenGroups = clearChildrenGroups;
505
+ exports.fieldLevelExperiments = fieldLevelExperiments;
506
+ exports.flattenSchemaType = flattenSchemaType;
507
+ //# sourceMappingURL=fieldExperiments.js.map