@sanity/personalization-plugin 2.5.0 → 3.0.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 (48) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +570 -144
  3. package/dist/growthbook/index.d.ts +12 -15
  4. package/dist/growthbook/index.d.ts.map +1 -0
  5. package/dist/growthbook/index.js +104 -68
  6. package/dist/growthbook/index.js.map +1 -1
  7. package/dist/index.d.ts +212 -252
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +398 -301
  10. package/dist/index.js.map +1 -1
  11. package/dist/launchDarkly/index.d.ts +9 -12
  12. package/dist/launchDarkly/index.d.ts.map +1 -0
  13. package/dist/launchDarkly/index.js +74 -46
  14. package/dist/launchDarkly/index.js.map +1 -1
  15. package/package.json +37 -79
  16. package/dist/growthbook/index.d.mts +0 -15
  17. package/dist/growthbook/index.mjs +0 -124
  18. package/dist/growthbook/index.mjs.map +0 -1
  19. package/dist/index.d.mts +0 -267
  20. package/dist/index.mjs +0 -472
  21. package/dist/index.mjs.map +0 -1
  22. package/dist/launchDarkly/index.d.mts +0 -12
  23. package/dist/launchDarkly/index.mjs +0 -107
  24. package/dist/launchDarkly/index.mjs.map +0 -1
  25. package/sanity.json +0 -8
  26. package/src/components/Array.tsx +0 -68
  27. package/src/components/ExperimentContext.tsx +0 -65
  28. package/src/components/ExperimentField.tsx +0 -138
  29. package/src/components/ExperimentInput.tsx +0 -75
  30. package/src/components/ExperimentItem.tsx +0 -10
  31. package/src/components/Select.tsx +0 -43
  32. package/src/components/VariantInput.tsx +0 -19
  33. package/src/components/VariantPreview.tsx +0 -75
  34. package/src/fieldExperiments.tsx +0 -266
  35. package/src/growthbook/Components/GrowthbookContext.tsx +0 -38
  36. package/src/growthbook/Components/Secrets.tsx +0 -47
  37. package/src/growthbook/index.ts +0 -54
  38. package/src/growthbook/types.ts +0 -15
  39. package/src/growthbook/utils.ts +0 -94
  40. package/src/index.ts +0 -3
  41. package/src/launchDarkly/components/LaunchDarklyContext.tsx +0 -36
  42. package/src/launchDarkly/components/Secrets.tsx +0 -46
  43. package/src/launchDarkly/index.ts +0 -52
  44. package/src/launchDarkly/types.ts +0 -193
  45. package/src/launchDarkly/utils.ts +0 -54
  46. package/src/types.ts +0 -245
  47. package/src/utils/flattenSchemaType.ts +0 -47
  48. package/v2-incompatible.js +0 -11
package/dist/index.mjs DELETED
@@ -1,472 +0,0 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useClient, useWorkspace, useFormValue, defineDocumentFieldAction, set, unset, useDocumentOperation, getPublishedId, isReference, isImage, isDocumentSchemaType, definePlugin, isObjectInputProps, defineType, defineField } from "sanity";
3
- import { Stack, Inline, Button, Select as Select$1, Card, Text } from "@sanity/ui";
4
- import { uuid } from "@sanity/uuid";
5
- import { createContext, useMemo, useContext, useCallback, forwardRef, useState, useEffect } from "react";
6
- import equal from "fast-deep-equal";
7
- import { suspend } from "suspend-react";
8
- import { GiSoapExperiment } from "react-icons/gi";
9
- const CONFIG_DEFAULT = {
10
- fields: [],
11
- apiVersion: "2024-11-07",
12
- experimentNameOverride: "experiment",
13
- variantNameOverride: "variant",
14
- variantId: "variantId",
15
- variantArrayName: "variants",
16
- experimentId: "experimentId"
17
- }, ExperimentContext = createContext({
18
- ...CONFIG_DEFAULT,
19
- experiments: []
20
- });
21
- function useExperimentContext() {
22
- return useContext(ExperimentContext);
23
- }
24
- function ExperimentProvider(props) {
25
- const { experimentFieldPluginConfig } = props, client = useClient({ apiVersion: experimentFieldPluginConfig.apiVersion }), workspace = useWorkspace(), experiments = Array.isArray(experimentFieldPluginConfig.experiments) ? experimentFieldPluginConfig.experiments : suspend(
26
- // eslint-disable-next-line require-await
27
- async () => typeof experimentFieldPluginConfig.experiments == "function" ? experimentFieldPluginConfig.experiments(client) : experimentFieldPluginConfig.experiments,
28
- [workspace],
29
- { equal }
30
- ), context = useMemo(
31
- () => ({ ...experimentFieldPluginConfig, experiments }),
32
- [experimentFieldPluginConfig, experiments]
33
- );
34
- return /* @__PURE__ */ jsx(ExperimentContext.Provider, { value: context, children: props.renderDefault(props) });
35
- }
36
- const ArrayInput = (props) => {
37
- const fieldPath = props.path.slice(0, -1), { onItemAppend, variantName, variantId, experimentId } = props, experimentValue = useFormValue([...fieldPath, experimentId]), { experiments } = useExperimentContext(), handleClick = useCallback(
38
- async (variant) => {
39
- const item = {
40
- _key: uuid(),
41
- [variantId]: variant.id,
42
- [experimentId]: experimentValue,
43
- _type: variantName
44
- };
45
- onItemAppend(item);
46
- },
47
- [variantId, experimentId, experimentValue, variantName, onItemAppend]
48
- ), filteredVariants = experiments.find((option) => option.id === experimentValue)?.variants || [], usedVariants = (props.value || [])?.map((variant) => variant[variantId]);
49
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
50
- props.renderDefault({ ...props, arrayFunctions: () => null }),
51
- /* @__PURE__ */ jsx(Inline, { space: 1, children: filteredVariants.map((variant) => /* @__PURE__ */ jsx(
52
- Button,
53
- {
54
- text: `Add ${variant.label}`,
55
- mode: "ghost",
56
- disabled: usedVariants?.includes(variant.id),
57
- onClick: () => handleClick(variant)
58
- },
59
- `${experimentValue}-${variant.id}`
60
- )) })
61
- ] });
62
- }, CloseIcon = /* @__PURE__ */ forwardRef(function(props, ref) {
63
- return /* @__PURE__ */ jsx(
64
- "svg",
65
- {
66
- "data-sanity-icon": "close",
67
- width: "1em",
68
- height: "1em",
69
- viewBox: "0 0 25 25",
70
- fill: "none",
71
- xmlns: "http://www.w3.org/2000/svg",
72
- ...props,
73
- ref,
74
- children: /* @__PURE__ */ jsx(
75
- "path",
76
- {
77
- d: "M18 7L7 18M7 7L18 18",
78
- stroke: "currentColor",
79
- strokeWidth: 1.2,
80
- strokeLinejoin: "round"
81
- }
82
- )
83
- }
84
- );
85
- }), useAddExperimentAction = (props) => {
86
- const { onChange, active, experimentNameOverride } = props, handleAddAction = useCallback(() => {
87
- onChange([set(!active, ["active"])]);
88
- }, [onChange, active]);
89
- return {
90
- title: `Add ${experimentNameOverride}`,
91
- type: "action",
92
- icon: GiSoapExperiment,
93
- onAction: handleAddAction,
94
- renderAsButton: !0
95
- };
96
- }, useRemoveExperimentAction = (props) => {
97
- const { onChange, active, experimentId, experimentNameOverride, variantNameOverride } = props, handleClearAction = useCallback(() => {
98
- const activeId = ["active"], experiment = [experimentId], variants = [`${variantNameOverride}s`];
99
- onChange([set(!active, activeId), unset(experiment), unset(variants)]);
100
- }, [onChange, active, experimentId, variantNameOverride]);
101
- return {
102
- title: `Remove ${experimentNameOverride}`,
103
- type: "action",
104
- icon: CloseIcon,
105
- onAction: handleClearAction,
106
- renderAsButton: !0
107
- };
108
- }, createActions = ({
109
- onChange,
110
- inputId,
111
- active,
112
- experimentNameOverride,
113
- experimentId,
114
- variantNameOverride
115
- }) => {
116
- const removeAction = defineDocumentFieldAction({
117
- name: `Remove ${experimentNameOverride}`,
118
- useAction: (props) => useRemoveExperimentAction({
119
- active: !0,
120
- onChange,
121
- experimentNameOverride,
122
- experimentId,
123
- variantNameOverride
124
- })
125
- }), addAction = defineDocumentFieldAction({
126
- name: `Add ${experimentNameOverride}`,
127
- useAction: (props) => useAddExperimentAction({
128
- active: !1,
129
- onChange,
130
- experimentNameOverride
131
- })
132
- });
133
- return active ? removeAction : addAction;
134
- }, ExperimentField = (props) => {
135
- const { onChange } = props.inputProps, { inputId, experimentNameOverride, experimentId, variantNameOverride } = props, active = props.value?.active, actionProps = useMemo(
136
- () => ({
137
- onChange,
138
- inputId,
139
- active,
140
- experimentNameOverride,
141
- experimentId,
142
- variantNameOverride
143
- }),
144
- [onChange, inputId, active, experimentNameOverride, experimentId, variantNameOverride]
145
- ), memoizedActions = useMemo(() => {
146
- const oldActions = props.actions || [];
147
- return [createActions(actionProps), ...oldActions];
148
- }, [actionProps, props.actions]), withActionProps = useMemo(
149
- () => ({
150
- ...props,
151
- actions: memoizedActions
152
- }),
153
- [props, memoizedActions]
154
- );
155
- return props.renderDefault(withActionProps);
156
- }, Select = (props) => {
157
- const {
158
- value,
159
- // Current field value
160
- onChange,
161
- // Method to handle patch events,
162
- elementProps,
163
- listOptions,
164
- handleChange
165
- } = props;
166
- return /* @__PURE__ */ jsxs(
167
- Select$1,
168
- {
169
- ...elementProps,
170
- fontSize: 2,
171
- padding: 3,
172
- space: [3, 3, 4],
173
- value: value || "",
174
- onChange: (event) => handleChange(event, onChange),
175
- children: [
176
- /* @__PURE__ */ jsx("option", { value: "", children: "Select an option..." }),
177
- listOptions.map(({ value: optionValue, title }) => /* @__PURE__ */ jsx("option", { value: optionValue, children: title }, optionValue))
178
- ]
179
- }
180
- );
181
- }, formatlistOptions = (experiments) => experiments.map((experiment) => ({
182
- title: experiment.label,
183
- value: experiment.id
184
- })), ExperimentInput = (props) => {
185
- const { experiments } = useExperimentContext(), id = useFormValue(["_id"]), additionalChangePath = useMemo(
186
- () => [...props.path.slice(0, -1), `${props.variantNameOverride}s`],
187
- [props.variantNameOverride, props.path]
188
- ), subValues = useFormValue(additionalChangePath), { patch } = useDocumentOperation(getPublishedId(id), props.schemaType.name), handleChange = useCallback(
189
- (event, onChange) => {
190
- const inputValue = event.currentTarget.value;
191
- if (onChange(inputValue ? set(inputValue) : unset()), subValues) {
192
- const patchEvent = {
193
- unset: [additionalChangePath.join(".")]
194
- };
195
- patch.execute([patchEvent]);
196
- }
197
- },
198
- [patch, subValues, additionalChangePath]
199
- );
200
- return experiments.length ? /* @__PURE__ */ jsx(Select, { ...props, listOptions: formatlistOptions(experiments), handleChange }) : /* @__PURE__ */ jsx(Card, { padding: [3, 3, 4], radius: 2, shadow: 1, tone: "caution", children: /* @__PURE__ */ jsxs(Text, { align: "center", size: [2, 2, 3], children: [
201
- "There are no defined ",
202
- props.experimentNameOverride,
203
- "s"
204
- ] }) });
205
- }, ExperimentItem = (props) => {
206
- const { active } = props.value;
207
- return active || props.inputProps.onChange(set(!0, ["active"])), props.renderDefault(props);
208
- }, VariantInput = (props) => {
209
- const experimentPath = props.path.slice(0, -2), defaultValue = useFormValue([...experimentPath, "default"]), handleClick = () => {
210
- props.onChange(set(defaultValue, ["value"]));
211
- };
212
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
213
- props.renderDefault(props),
214
- /* @__PURE__ */ jsx(Inline, { space: 1, children: /* @__PURE__ */ jsx(Button, { text: "Copy default", mode: "ghost", onClick: () => handleClick() }) })
215
- ] });
216
- }, VariantPreview = (props) => {
217
- const [subtitle, setSubtitle] = useState(void 0), [title, setTitle] = useState(void 0), [media, setMedia] = useState(void 0), client = 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);
218
- useEffect(() => {
219
- (async () => {
220
- if (setTitle(`${selectedExperiment?.label} - ${selectedVariant?.label}`), typeof value == "string")
221
- return setSubtitle(value);
222
- if (isReference(value)) {
223
- 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 || {};
224
- Object.keys(previewFields).forEach((key) => {
225
- const valueKey = referenceType?.preview?.select?.[key];
226
- selectFields[key] = valueKey && doc ? valueKey?.split(".").reduce((acc, index) => acc[index], doc) : void 0;
227
- });
228
- const previewContent = referenceType?.preview?.prepare?.(selectFields);
229
- return setMedia(previewContent?.media || selectFields.media), setSubtitle(previewContent?.title || selectFields?.title);
230
- }
231
- return isImage(value) && setMedia(value), "";
232
- })();
233
- }, [value, client, selectedExperiment?.label, selectedVariant?.label, props.schemaType]);
234
- const previewProps = {
235
- ...props,
236
- title,
237
- subtitle,
238
- media
239
- };
240
- return props.renderDefault(previewProps);
241
- };
242
- function flattenSchemaType(schemaType) {
243
- return isDocumentSchemaType(schemaType) ? extractInnerFields(schemaType.fields, [], 5) : (console.error("Schema type is not a document"), []);
244
- }
245
- function extractInnerFields(fields, path, maxDepth) {
246
- return path.length >= maxDepth ? [] : fields.reduce((acc, field) => {
247
- const thisFieldWithPath = { path: [...path, field.name], ...field };
248
- if (field.type.jsonType === "object") {
249
- const innerFields = extractInnerFields(field.type.fields, [...path, field.name], maxDepth);
250
- return [...acc, thisFieldWithPath, ...innerFields];
251
- } else if (field.type.jsonType === "array") {
252
- const innerFields = (field.type.of || []).reduce((arrayAcc, arrayType) => {
253
- if ("fields" in arrayType) {
254
- const typeFields = extractInnerFields(arrayType.fields, [...path, field.name], maxDepth);
255
- return [...arrayAcc, ...typeFields];
256
- }
257
- return arrayAcc;
258
- }, []);
259
- return [...acc, thisFieldWithPath, ...innerFields];
260
- }
261
- return [...acc, thisFieldWithPath];
262
- }, []);
263
- }
264
- const createExperimentType = ({
265
- field,
266
- experimentNameOverride,
267
- variantNameOverride,
268
- variantId,
269
- variantArrayName,
270
- experimentId
271
- }) => {
272
- const typeName = typeof field == "string" ? field : field.name, usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1), variantName = `${variantNameOverride}${usedName}`;
273
- return defineType({
274
- name: `${experimentNameOverride}${usedName}`,
275
- type: "object",
276
- components: {
277
- field: (props) => /* @__PURE__ */ jsx(
278
- ExperimentField,
279
- {
280
- ...props,
281
- experimentId,
282
- experimentNameOverride,
283
- variantNameOverride
284
- }
285
- ),
286
- item: ExperimentItem
287
- },
288
- fields: [
289
- typeof field == "string" ? (
290
- // Define a simple field if all we have is the name as a string
291
- defineField({
292
- name: "default",
293
- type: field
294
- })
295
- ) : (
296
- // Pass in the configured options, but overwrite the name
297
- {
298
- ...field,
299
- name: "default"
300
- }
301
- ),
302
- defineField({
303
- name: "active",
304
- type: "boolean",
305
- hidden: !0,
306
- initialValue: !1
307
- }),
308
- defineField({
309
- name: experimentId,
310
- type: "string",
311
- components: {
312
- input: (props) => /* @__PURE__ */ jsx(
313
- ExperimentInput,
314
- {
315
- ...props,
316
- experimentNameOverride,
317
- variantNameOverride
318
- }
319
- )
320
- },
321
- hidden: ({ parent }) => !parent?.active
322
- }),
323
- defineField({
324
- name: variantArrayName,
325
- type: "array",
326
- hidden: ({ parent }) => !parent?.[experimentId],
327
- components: {
328
- input: (props) => /* @__PURE__ */ jsx(
329
- ArrayInput,
330
- {
331
- ...props,
332
- variantName,
333
- variantId,
334
- experimentId
335
- }
336
- )
337
- },
338
- of: [
339
- defineField({
340
- name: variantName,
341
- type: variantName
342
- })
343
- ]
344
- })
345
- ],
346
- preview: {
347
- select: {
348
- base: "default",
349
- experiment: experimentId
350
- },
351
- prepare: ({ base, experiment }) => {
352
- const title = base?.title || base?.name || typeof base == "string" ? base : "", experimentTitle = experiment ? `Experiment: ${experiment}` : "", media = base?.image || base?.photo || base?.media || "";
353
- return {
354
- title: title || experimentTitle,
355
- subtitle: title ? experimentTitle : "",
356
- media
357
- };
358
- }
359
- }
360
- });
361
- }, createVariantType = ({
362
- field,
363
- variantNameOverride,
364
- variantId,
365
- experimentId
366
- }) => {
367
- const typeName = typeof field == "string" ? field : field.name, usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1);
368
- return defineType({
369
- name: `${variantNameOverride}${usedName}`,
370
- title: `${variantNameOverride} array ${usedName}`,
371
- type: "object",
372
- components: {
373
- preview: VariantPreview,
374
- input: VariantInput
375
- },
376
- fields: [
377
- {
378
- type: "string",
379
- name: variantId,
380
- readOnly: !0
381
- },
382
- {
383
- type: "string",
384
- name: experimentId,
385
- hidden: !0
386
- },
387
- typeof field == "string" ? (
388
- // Define a simple field if all we have is the name as a string
389
- defineField({
390
- name: "value",
391
- type: field
392
- // hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
393
- })
394
- ) : (
395
- // Pass in the configured options, but overwrite the name
396
- {
397
- ...field,
398
- name: "value"
399
- // hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
400
- }
401
- )
402
- ],
403
- preview: {
404
- select: {
405
- variant: variantId,
406
- experiment: experimentId,
407
- value: "value"
408
- }
409
- }
410
- });
411
- }, fieldSchema = ({
412
- fields,
413
- experimentNameOverride,
414
- variantNameOverride,
415
- variantId,
416
- variantArrayName,
417
- experimentId
418
- }) => [
419
- ...fields.map(
420
- (field) => createVariantType({ field, variantNameOverride, variantId, experimentId })
421
- ),
422
- ...fields.map(
423
- (field) => createExperimentType({
424
- field,
425
- experimentNameOverride,
426
- variantNameOverride,
427
- variantId,
428
- variantArrayName,
429
- experimentId
430
- })
431
- )
432
- ], fieldLevelExperiments = definePlugin((config) => {
433
- const pluginConfig = { ...CONFIG_DEFAULT, ...config }, { fields, experimentNameOverride, variantNameOverride } = pluginConfig, experimentId = `${experimentNameOverride}Id`, variantArrayName = `${variantNameOverride}s`, variantId = `${variantNameOverride}Id`;
434
- return {
435
- name: "sanity-personalistaion-plugin-field-level-experiments",
436
- schema: {
437
- types: fieldSchema({
438
- fields,
439
- experimentNameOverride,
440
- variantNameOverride,
441
- variantId,
442
- variantArrayName,
443
- experimentId
444
- })
445
- },
446
- form: {
447
- components: {
448
- input: (props) => {
449
- if (!(props.id === "root" && isObjectInputProps(props)) || !flattenSchemaType(props.schemaType).some(
450
- (field) => field.type.name.startsWith(experimentNameOverride) || field.name.startsWith(experimentNameOverride)
451
- ))
452
- return props.renderDefault(props);
453
- const providerProps = {
454
- ...props,
455
- experimentFieldPluginConfig: {
456
- ...pluginConfig,
457
- variantId,
458
- variantArrayName,
459
- experimentId
460
- }
461
- };
462
- return ExperimentProvider(providerProps);
463
- }
464
- }
465
- }
466
- };
467
- });
468
- export {
469
- fieldLevelExperiments,
470
- flattenSchemaType
471
- };
472
- //# sourceMappingURL=index.mjs.map