@sanity/assist 1.2.16 → 2.0.0

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 (50) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +551 -30
  3. package/dist/index.cjs.mjs +1 -0
  4. package/dist/index.d.ts +253 -11
  5. package/dist/index.esm.js +2385 -373
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +2379 -366
  8. package/dist/index.js.map +1 -1
  9. package/package.json +10 -9
  10. package/src/_lib/form/DocumentForm.tsx +2 -1
  11. package/src/_lib/form/constants.ts +1 -0
  12. package/src/assistDocument/AssistDocumentInput.tsx +24 -4
  13. package/src/assistDocument/RequestRunInstructionProvider.tsx +37 -21
  14. package/src/assistDocument/components/instruction/InstructionInput.tsx +5 -4
  15. package/src/assistDocument/components/instruction/InstructionOutputField.tsx +45 -0
  16. package/src/assistDocument/components/instruction/InstructionOutputInput.tsx +205 -0
  17. package/src/assistDocument/hooks/useStudioAssistDocument.ts +5 -32
  18. package/src/assistFormComponents/AssistField.tsx +11 -5
  19. package/src/assistFormComponents/AssistFormBlock.tsx +2 -3
  20. package/src/assistFormComponents/validation/listItem.tsx +2 -2
  21. package/src/assistInspector/AssistInspector.tsx +6 -0
  22. package/src/assistInspector/FieldAutocomplete.tsx +1 -0
  23. package/src/assistInspector/helpers.ts +9 -11
  24. package/src/assistLayout/AssistLayout.tsx +9 -9
  25. package/src/components/ImageContext.tsx +19 -9
  26. package/src/components/SafeValueInput.tsx +4 -1
  27. package/src/fieldActions/assistFieldActions.tsx +42 -13
  28. package/src/fieldActions/generateCaptionActions.tsx +2 -2
  29. package/src/fieldActions/generateImageActions.tsx +57 -0
  30. package/src/helpers/assistSupported.ts +10 -16
  31. package/src/helpers/conditionalMembers.test.ts +200 -0
  32. package/src/helpers/conditionalMembers.ts +127 -0
  33. package/src/helpers/typeUtils.ts +19 -5
  34. package/src/index.ts +3 -0
  35. package/src/plugin.tsx +14 -5
  36. package/src/schemas/assistDocumentSchema.tsx +40 -1
  37. package/src/schemas/serialize/serializeSchema.test.ts +239 -8
  38. package/src/schemas/serialize/serializeSchema.ts +77 -10
  39. package/src/schemas/typeDefExtensions.ts +89 -5
  40. package/src/translate/FieldTranslationProvider.tsx +360 -0
  41. package/src/translate/getLanguageParams.ts +26 -0
  42. package/src/translate/languageStore.ts +18 -0
  43. package/src/translate/paths.test.ts +133 -0
  44. package/src/translate/paths.ts +175 -0
  45. package/src/translate/translateActions.tsx +188 -0
  46. package/src/translate/types.ts +160 -0
  47. package/src/types.ts +33 -12
  48. package/src/useApiClient.ts +130 -2
  49. package/src/assistLayout/AlphaMigration.tsx +0 -310
  50. package/src/legacy-types.ts +0 -72
@@ -20,7 +20,7 @@ import {
20
20
  } from 'sanity'
21
21
  import {ComponentType, useContext, useMemo} from 'react'
22
22
  import {AssistInspectorRouteParams, documentRootKey, fieldPathParam} from '../types'
23
- import {usePaneRouter} from 'sanity/desk'
23
+ import {type PaneRouterContextValue, usePaneRouter} from 'sanity/desk'
24
24
  import {isAssistSupported} from '../helpers/assistSupported'
25
25
  import {isPortableTextArray, isType} from '../helpers/typeUtils'
26
26
  import {SelectedFieldContext} from '../assistDocument/components/SelectedFieldContext'
@@ -96,7 +96,7 @@ export function getFieldRefs(
96
96
 
97
97
  const syntheticFields =
98
98
  field.type.jsonType === 'array' ? getSyntheticFields(field.type, fieldRef, depth + 1) : []
99
- if (!isAssistSupported(field.type, true)) {
99
+ if (!isAssistSupported(field.type)) {
100
100
  return [...fields, ...syntheticFields]
101
101
  }
102
102
  return [fieldRef, ...fields, ...syntheticFields]
@@ -125,7 +125,7 @@ function getSyntheticFields(schemaType: ArraySchemaType, parent?: FieldRef, dept
125
125
  const fields =
126
126
  itemType.jsonType === 'object' ? getFieldRefs(itemType, fieldRef, depth + 1) : []
127
127
 
128
- if (!isAssistSupported(itemType, true)) {
128
+ if (!isAssistSupported(itemType)) {
129
129
  return fields
130
130
  }
131
131
  return [fieldRef, ...fields]
@@ -198,18 +198,16 @@ export function getFieldTitle(field?: FieldRef) {
198
198
  return field?.title ?? schemaType?.title ?? schemaType?.name ?? 'Untitled'
199
199
  }
200
200
 
201
+ export type AiPaneRouter = Omit<PaneRouterContextValue, 'setParams' | 'params'> & {
202
+ params: AssistInspectorRouteParams
203
+ setParams: (p: Record<keyof AssistInspectorRouteParams, string | undefined>) => void
204
+ }
205
+
201
206
  export function useAiPaneRouter() {
202
207
  const paneRouter = usePaneRouter()
203
208
 
204
209
  return useMemo(
205
- () =>
206
- ({...paneRouter, params: paneRouter.params ?? {}} as Omit<
207
- typeof paneRouter,
208
- 'setParams' | 'params'
209
- > & {
210
- params: AssistInspectorRouteParams
211
- setParams: (p: Record<keyof AssistInspectorRouteParams, string | undefined>) => void
212
- }),
210
+ () => ({...paneRouter, params: paneRouter.params ?? {}} as AiPaneRouter),
213
211
  [paneRouter]
214
212
  )
215
213
  }
@@ -8,7 +8,7 @@ import {RunInstructionRequest} from '../useApiClient'
8
8
  import {StudioInstruction} from '../types'
9
9
  import {RunInstructionProvider} from './RunInstructionProvider'
10
10
  import {ThemeProvider} from '@sanity/ui'
11
- import {AlphaMigration} from './AlphaMigration'
11
+ import {FieldTranslationProvider} from '../translate/FieldTranslationProvider'
12
12
 
13
13
  export interface AIStudioLayoutProps extends LayoutProps {
14
14
  config: AssistPluginConfig
@@ -20,18 +20,18 @@ export type RunInstructionArgs = Omit<RunInstructionRequest, 'instructionKey' |
20
20
 
21
21
  export function AssistLayout(props: AIStudioLayoutProps) {
22
22
  const [connectors, setConnectors] = useState<Connector[]>([])
23
- const migrate = props.config.alphaMigration ?? true
24
23
 
25
24
  return (
26
25
  <AiAssistanceConfigProvider config={props.config}>
27
- {migrate ? <AlphaMigration /> : null}
28
26
  <RunInstructionProvider>
29
- <ConnectorsProvider onConnectorsChange={setConnectors}>
30
- {props.renderDefault(props)}
31
- <ThemeProvider tone="default">
32
- <AssistConnectorsOverlay connectors={connectors} />
33
- </ThemeProvider>
34
- </ConnectorsProvider>
27
+ <FieldTranslationProvider>
28
+ <ConnectorsProvider onConnectorsChange={setConnectors}>
29
+ {props.renderDefault(props)}
30
+ <ThemeProvider tone="default">
31
+ <AssistConnectorsOverlay connectors={connectors} />
32
+ </ThemeProvider>
33
+ </ConnectorsProvider>
34
+ </FieldTranslationProvider>
35
35
  </RunInstructionProvider>
36
36
  </AiAssistanceConfigProvider>
37
37
  )
@@ -1,17 +1,18 @@
1
1
  import {createContext, useEffect, useMemo, useState} from 'react'
2
2
  import {InputProps, pathToString, useSyncState} from 'sanity'
3
- import {getCaptionFieldOption} from '../helpers/typeUtils'
3
+ import {getDescriptionFieldOption, getImageInstructionFieldOption} from '../helpers/typeUtils'
4
4
  import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
5
5
  import {useApiClient, useGenerateCaption} from '../useApiClient'
6
6
  import {useAiAssistanceConfig} from '../assistLayout/AiAssistanceConfigContext'
7
7
  import {publicId} from '../helpers/ids'
8
8
 
9
9
  export interface ImageContextValue {
10
- captionPath: string
10
+ imageDescriptionPath?: string
11
+ imageInstructionPath?: string
11
12
  assetRef?: string
12
13
  }
13
14
 
14
- export const ImageContext = createContext<ImageContextValue | undefined>(undefined)
15
+ export const ImageContext = createContext<ImageContextValue>({})
15
16
 
16
17
  export function ImageContextProvider(props: InputProps) {
17
18
  const {schemaType, path, value} = props
@@ -26,16 +27,25 @@ export function ImageContextProvider(props: InputProps) {
26
27
  const {isSyncing} = useSyncState(publicId(documentId), documentSchemaType.name)
27
28
 
28
29
  useEffect(() => {
29
- const captionField = getCaptionFieldOption(schemaType)
30
- if (assetRef && documentId && captionField && assetRef !== assetRefState && !isSyncing) {
30
+ const descriptionField = getDescriptionFieldOption(schemaType)
31
+ if (assetRef && documentId && descriptionField && assetRef !== assetRefState && !isSyncing) {
31
32
  setAssetRefState(assetRef)
32
- generateCaption({path: pathToString([...path, captionField]), documentId: documentId})
33
+ generateCaption({path: pathToString([...path, descriptionField]), documentId: documentId})
33
34
  }
34
35
  }, [schemaType, path, assetRef, assetRefState, documentId, generateCaption, isSyncing])
35
36
 
36
- const context: ImageContextValue | undefined = useMemo(() => {
37
- const captionField = getCaptionFieldOption(schemaType)
38
- return captionField ? {captionPath: pathToString([...path, captionField]), assetRef} : undefined
37
+ const context: ImageContextValue = useMemo(() => {
38
+ const descriptionField = getDescriptionFieldOption(schemaType)
39
+ const imageInstructionField = getImageInstructionFieldOption(schemaType)
40
+ return {
41
+ imageDescriptionPath: descriptionField
42
+ ? pathToString([...path, descriptionField])
43
+ : undefined,
44
+ imageInstructionPath: imageInstructionField
45
+ ? pathToString([...path, imageInstructionField])
46
+ : undefined,
47
+ assetRef,
48
+ }
39
49
  }, [schemaType, path, assetRef])
40
50
 
41
51
  return <ImageContext.Provider value={context}>{props.renderDefault(props)}</ImageContext.Provider>
@@ -31,7 +31,10 @@ export function ErrorWrapper(
31
31
  [setError]
32
32
  )
33
33
 
34
- const unsetValue = useCallback(() => onChange(PatchEvent.from(unset())), [onChange])
34
+ const unsetValue = useCallback(() => {
35
+ onChange(PatchEvent.from(unset()))
36
+ setError(undefined)
37
+ }, [onChange])
35
38
  const dismiss = useCallback(() => setError(undefined), [])
36
39
  const catcher = <ErrorBoundary onCatch={catchError}>{props.children}</ErrorBoundary>
37
40
 
@@ -3,11 +3,12 @@ import {
3
3
  DocumentFieldActionGroup,
4
4
  DocumentFieldActionItem,
5
5
  ObjectSchemaType,
6
+ typed,
6
7
  useCurrentUser,
7
8
  } from 'sanity'
8
9
  import {ControlsIcon, SparklesIcon} from '@sanity/icons'
9
- import {useCallback, useMemo} from 'react'
10
- import {pluginTitle, pluginTitleShort} from '../constants'
10
+ import {useCallback, useMemo, useRef} from 'react'
11
+ import {pluginTitleShort} from '../constants'
11
12
  import {useAssistSupported} from '../helpers/useAssistSupported'
12
13
  import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
13
14
  import {getInstructionTitle, usePathKey} from '../helpers/misc'
@@ -24,6 +25,9 @@ import {generateCaptionsActions} from './generateCaptionActions'
24
25
  import {useDocumentPane} from 'sanity/desk'
25
26
  import {useSelectedField, useTypePath} from '../assistInspector/helpers'
26
27
  import {isSchemaAssistEnabled} from '../helpers/assistSupported'
28
+ import {translateActions, TranslateProps} from '../translate/translateActions'
29
+ import {generateImagActions} from './generateImageActions'
30
+ import {getConditionalMembers} from '../helpers/conditionalMembers'
27
31
 
28
32
  function node(node: DocumentFieldActionItem | DocumentFieldActionGroup) {
29
33
  return node
@@ -47,6 +51,7 @@ export const assistFieldActions: DocumentFieldAction = {
47
51
  documentSchemaType,
48
52
  documentId,
49
53
  selectedPath,
54
+ assistableDocumentId,
50
55
  } =
51
56
  // document field actions do not have access to the document context
52
57
  // conditional hook _should_ be safe here since the logical path will be stable
@@ -56,7 +61,10 @@ export const assistFieldActions: DocumentFieldAction = {
56
61
  : // eslint-disable-next-line react-hooks/rules-of-hooks
57
62
  useAssistDocumentContext()
58
63
 
59
- const {value: docValue} = useDocumentPane()
64
+ const {value: docValue, formState} = useDocumentPane()
65
+ const formStateRef = useRef(formState)
66
+ formStateRef.current = formState
67
+
60
68
  const currentUser = useCurrentUser()
61
69
  const isHidden = !assistDocument
62
70
  const pathKey = usePathKey(props.path)
@@ -73,7 +81,8 @@ export const assistFieldActions: DocumentFieldAction = {
73
81
  const assistSupported =
74
82
  useAssistSupported(props.path, schemaType) &&
75
83
  isSelectable &&
76
- isSchemaAssistEnabled(documentSchemaType)
84
+ isSchemaAssistEnabled(documentSchemaType) &&
85
+ schemaType.readOnly !== true
77
86
 
78
87
  const fieldAssist = useMemo(
79
88
  () =>
@@ -89,7 +98,15 @@ export const assistFieldActions: DocumentFieldAction = {
89
98
  const isSelected = isInspectorOpen && isPathSelected
90
99
 
91
100
  const imageCaptionAction = generateCaptionsActions.useAction(props)
92
-
101
+ const imageGenAction = generateImagActions.useAction(props)
102
+ const translateAction = translateActions.useAction(
103
+ typed<TranslateProps>({
104
+ ...props,
105
+ documentId: assistableDocumentId,
106
+ documentIsAssistable,
107
+ documentSchemaType,
108
+ })
109
+ )
93
110
  const manageInstructions = useCallback(
94
111
  () =>
95
112
  isSelected
@@ -112,6 +129,9 @@ export const assistFieldActions: DocumentFieldAction = {
112
129
  path: pathKey,
113
130
  typePath,
114
131
  instruction,
132
+ conditionalMembers: formStateRef.current
133
+ ? getConditionalMembers(formStateRef.current)
134
+ : [],
115
135
  })
116
136
  },
117
137
  [requestRunInstruction, assistableDocId, pathKey, typePath, assistDocumentId, fieldAssistKey]
@@ -134,7 +154,7 @@ export const assistFieldActions: DocumentFieldAction = {
134
154
  )
135
155
 
136
156
  const runInstructionsGroup = useMemo(() => {
137
- return instructions?.length || imageCaptionAction
157
+ return instructions?.length || imageCaptionAction || translateAction || imageGenAction
138
158
  ? node({
139
159
  type: 'group',
140
160
  icon: () => null,
@@ -151,7 +171,8 @@ export const assistFieldActions: DocumentFieldAction = {
151
171
  })
152
172
  ),
153
173
  imageCaptionAction,
154
- ].filter(Boolean),
174
+ imageGenAction,
175
+ ].filter((a): a is DocumentFieldActionItem => !!a),
155
176
  expanded: true,
156
177
  })
157
178
  : undefined
@@ -163,6 +184,8 @@ export const assistFieldActions: DocumentFieldAction = {
163
184
  documentIsNew,
164
185
  assistSupported,
165
186
  imageCaptionAction,
187
+ translateAction,
188
+ imageGenAction,
166
189
  ])
167
190
 
168
191
  const instructionsLength = instructions?.length || 0
@@ -185,12 +208,16 @@ export const assistFieldActions: DocumentFieldAction = {
185
208
  type: 'group',
186
209
  icon: SparklesIcon,
187
210
  title: pluginTitleShort,
188
- children: [runInstructionsGroup, assistSupported && manageInstructionsItem].filter(
189
- (c): c is DocumentFieldActionItem | DocumentFieldActionGroup => !!c
190
- ),
211
+ children: [
212
+ runInstructionsGroup,
213
+ translateAction,
214
+ assistSupported && manageInstructionsItem,
215
+ ]
216
+ .filter((c): c is DocumentFieldActionItem | DocumentFieldActionGroup => !!c)
217
+ .filter((c) => (c.type === 'group' ? c.children.length : true)),
191
218
  expanded: false,
192
219
  renderAsButton: true,
193
- hidden: !assistSupported && !imageCaptionAction,
220
+ hidden: !assistSupported && !imageCaptionAction && !translateAction && !imageGenAction,
194
221
  }),
195
222
  [
196
223
  //documentIsNew,
@@ -198,6 +225,8 @@ export const assistFieldActions: DocumentFieldAction = {
198
225
  manageInstructionsItem,
199
226
  assistSupported,
200
227
  imageCaptionAction,
228
+ translateAction,
229
+ imageGenAction,
201
230
  ]
202
231
  )
203
232
 
@@ -216,7 +245,7 @@ export const assistFieldActions: DocumentFieldAction = {
216
245
  )
217
246
 
218
247
  // If there are no instructions, we don't want to render the group
219
- if (instructionsLength === 0 && !imageCaptionAction) {
248
+ if (instructionsLength === 0 && !imageCaptionAction && !translateAction && !imageGenAction) {
220
249
  return emptyAction
221
250
  }
222
251
 
@@ -239,7 +268,7 @@ function instructionItem(props: {
239
268
  iconRight: isPrivate ? PrivateIcon : undefined,
240
269
  title: getInstructionTitle(instruction),
241
270
  onAction: () => onInstructionAction(instruction),
242
- disabled: assistSupported ? false : {reason: `${pluginTitle} is not supported for this field`},
271
+ disabled: !assistSupported,
243
272
  hidden,
244
273
  })
245
274
  }
@@ -23,7 +23,7 @@ export const generateCaptionsActions: DocumentFieldAction = {
23
23
 
24
24
  const imageContext = useContext(ImageContext)
25
25
 
26
- if (imageContext && pathKey === imageContext?.captionPath) {
26
+ if (imageContext && pathKey === imageContext?.imageDescriptionPath) {
27
27
  //if this is true, it is stable, and not breaking rules of hooks
28
28
  // eslint-disable-next-line react-hooks/rules-of-hooks
29
29
  const {documentId} = useAssistDocumentContext()
@@ -38,7 +38,7 @@ export const generateCaptionsActions: DocumentFieldAction = {
38
38
  </Box>
39
39
  )
40
40
  : ImageIcon,
41
- title: 'Generate caption',
41
+ title: 'Generate image description',
42
42
  onAction: () => {
43
43
  if (loading) {
44
44
  return
@@ -0,0 +1,57 @@
1
+ import {DocumentFieldAction, DocumentFieldActionGroup, DocumentFieldActionItem} from 'sanity'
2
+ import {ImageIcon} from '@sanity/icons'
3
+ import {useContext, useMemo} from 'react'
4
+ import {usePathKey} from '../helpers/misc'
5
+ import {useApiClient, useGenerateImage} from '../useApiClient'
6
+ import {useAiAssistanceConfig} from '../assistLayout/AiAssistanceConfigContext'
7
+ import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
8
+ import {ImageContext} from '../components/ImageContext'
9
+ import {Box, Spinner} from '@sanity/ui'
10
+
11
+ function node(node: DocumentFieldActionItem | DocumentFieldActionGroup) {
12
+ return node
13
+ }
14
+
15
+ export const generateImagActions: DocumentFieldAction = {
16
+ name: 'sanity-assist-generate-image',
17
+ useAction(props) {
18
+ const pathKey = usePathKey(props.path)
19
+
20
+ const {config} = useAiAssistanceConfig()
21
+ const apiClient = useApiClient(config?.__customApiClient)
22
+ const {generateImage, loading} = useGenerateImage(apiClient)
23
+
24
+ const imageContext = useContext(ImageContext)
25
+
26
+ if (imageContext && pathKey === imageContext?.imageInstructionPath) {
27
+ //if this is true, it is stable, and not breaking rules of hooks
28
+ // eslint-disable-next-line react-hooks/rules-of-hooks
29
+ const {documentId} = useAssistDocumentContext()
30
+ // eslint-disable-next-line react-hooks/rules-of-hooks
31
+ return useMemo(() => {
32
+ return node({
33
+ type: 'action',
34
+ icon: loading
35
+ ? () => (
36
+ <Box style={{height: 17}}>
37
+ <Spinner style={{transform: 'translateY(6px)'}} />
38
+ </Box>
39
+ )
40
+ : ImageIcon,
41
+ title: 'Generate image from prompt',
42
+ onAction: () => {
43
+ if (loading) {
44
+ return
45
+ }
46
+ generateImage({path: pathKey, documentId: documentId ?? ''})
47
+ },
48
+ renderAsButton: true,
49
+ disabled: loading,
50
+ })
51
+ }, [generateImage, pathKey, documentId, loading])
52
+ }
53
+
54
+ // works but not supported by types
55
+ return undefined as unknown as DocumentFieldActionItem
56
+ },
57
+ }
@@ -1,41 +1,34 @@
1
- import {SchemaType} from 'sanity'
1
+ import {ReferenceOptions, SchemaType} from 'sanity'
2
2
  import {AssistOptions} from '../schemas/typeDefExtensions'
3
3
  import {isType} from './typeUtils'
4
4
 
5
5
  export function isSchemaAssistEnabled(type: SchemaType) {
6
- return !(type.options as AssistOptions | undefined)?.aiWritingAssistance?.exclude
6
+ return !(type.options as AssistOptions | undefined)?.aiAssist?.exclude
7
7
  }
8
8
 
9
- export function isAssistSupported(type: SchemaType, allowReadonlyHidden = false) {
9
+ export function isAssistSupported(type: SchemaType) {
10
10
  if (!isSchemaAssistEnabled(type)) {
11
11
  return false
12
12
  }
13
13
 
14
- if (isDisabled(type, allowReadonlyHidden)) {
14
+ if (isDisabled(type)) {
15
15
  return false
16
16
  }
17
17
 
18
18
  if (type.jsonType === 'array') {
19
- const unsupportedArray = type.of.every((t) => isDisabled(t, allowReadonlyHidden))
19
+ const unsupportedArray = type.of.every((t) => isDisabled(t))
20
20
  return !unsupportedArray
21
21
  }
22
22
 
23
23
  if (type.jsonType === 'object') {
24
- const unsupportedObject = type.fields.every((field) =>
25
- isDisabled(field.type, allowReadonlyHidden)
26
- )
24
+ const unsupportedObject = type.fields.every((field) => isDisabled(field.type))
27
25
  return !unsupportedObject
28
26
  }
29
27
  return true
30
28
  }
31
29
 
32
- function isDisabled(type: SchemaType, allowReadonlyHidden: boolean) {
33
- const readonlyHidden = !!type.readOnly || !!type.hidden
34
- return (
35
- !isSchemaAssistEnabled(type) ||
36
- isUnsupportedType(type) ||
37
- (!allowReadonlyHidden && readonlyHidden)
38
- )
30
+ function isDisabled(type: SchemaType) {
31
+ return !isSchemaAssistEnabled(type) || isUnsupportedType(type)
39
32
  }
40
33
 
41
34
  function isUnsupportedType(type: SchemaType) {
@@ -43,7 +36,8 @@ function isUnsupportedType(type: SchemaType) {
43
36
  type.jsonType === 'number' ||
44
37
  type.name === 'sanity.imageCrop' ||
45
38
  type.name === 'sanity.imageHotspot' ||
46
- isType(type, 'reference') ||
39
+ (isType(type, 'reference') &&
40
+ !(type?.options as ReferenceOptions)?.aiAssist?.embeddingsIndex) ||
47
41
  isType(type, 'crossDatasetReference') ||
48
42
  isType(type, 'slug') ||
49
43
  isType(type, 'url') ||
@@ -0,0 +1,200 @@
1
+ import {describe, expect, test} from 'vitest'
2
+ import {Schema} from '@sanity/schema'
3
+ import {ArraySchemaType, defineField, defineType, ObjectSchemaType} from 'sanity'
4
+ import {getConditionalMembers} from './conditionalMembers'
5
+
6
+ describe('conditionalMembers', () => {
7
+ test('should not include paths without conditional hidden/readonly', () => {
8
+ const docSchema: ObjectSchemaType = Schema.compile({
9
+ name: 'test',
10
+ types: [
11
+ defineType({
12
+ type: 'document',
13
+ name: 'article',
14
+ fields: [{type: 'string', name: 'title'}],
15
+ }),
16
+ ],
17
+ }).get('article')
18
+
19
+ const docState = {
20
+ path: [],
21
+ schemaType: docSchema,
22
+ members: [
23
+ {
24
+ kind: 'field',
25
+ field: {path: [docSchema.fields[0].name], schemaType: docSchema.fields[0].type},
26
+ },
27
+ ],
28
+ } as any
29
+ const conditionalMembers = getConditionalMembers(docState)
30
+
31
+ expect(conditionalMembers).toEqual([])
32
+ })
33
+
34
+ test('should include path with conditional readonly', () => {
35
+ const docSchema: ObjectSchemaType = Schema.compile({
36
+ name: 'test',
37
+ types: [
38
+ defineType({
39
+ type: 'document',
40
+ name: 'article',
41
+ fields: [{type: 'string', name: 'title', readOnly: () => false}],
42
+ }),
43
+ ],
44
+ }).get('article')
45
+
46
+ const docState = {
47
+ path: [],
48
+ schemaType: docSchema,
49
+ members: [
50
+ {
51
+ kind: 'field',
52
+ field: {path: [docSchema.fields[0].name], schemaType: docSchema.fields[0].type},
53
+ },
54
+ ],
55
+ } as any
56
+ const conditionalMembers = getConditionalMembers(docState)
57
+
58
+ expect(conditionalMembers).toEqual([{path: 'title', hidden: false, readOnly: false}])
59
+ })
60
+
61
+ test('should include array item path with conditional readonly', () => {
62
+ const docSchema: ObjectSchemaType = Schema.compile({
63
+ name: 'test',
64
+ types: [
65
+ defineType({
66
+ type: 'document',
67
+ name: 'article',
68
+ fields: [{type: 'array', name: 'array', of: [{type: 'string', readOnly: () => true}]}],
69
+ }),
70
+ ],
71
+ }).get('article')
72
+
73
+ const docState = {
74
+ path: [],
75
+ schemaType: docSchema,
76
+ members: [
77
+ {
78
+ kind: 'field',
79
+ field: {
80
+ path: [docSchema.fields[0].name],
81
+ schemaType: docSchema.fields[0].type,
82
+ members: [
83
+ {
84
+ kind: 'item',
85
+ item: {
86
+ path: [docSchema.fields[0].name, 0],
87
+ schemaType: (docSchema.fields[0].type as ArraySchemaType).of[0],
88
+ readOnly: true,
89
+ },
90
+ },
91
+ ],
92
+ },
93
+ },
94
+ ],
95
+ } as any
96
+ const conditionalMembers = getConditionalMembers(docState)
97
+
98
+ expect(conditionalMembers).toEqual([
99
+ {
100
+ path: 'array[0]',
101
+ hidden: false,
102
+ readOnly: true,
103
+ },
104
+ ])
105
+ })
106
+
107
+ test('should include object path with conditional hidden', () => {
108
+ const docSchema: ObjectSchemaType = Schema.compile({
109
+ name: 'test',
110
+ types: [
111
+ defineType({
112
+ type: 'document',
113
+ name: 'article',
114
+ fields: [
115
+ defineField({
116
+ type: 'object',
117
+ name: 'object',
118
+ fields: [{type: 'string', name: 'title', hidden: () => false}],
119
+ }),
120
+ ],
121
+ }),
122
+ ],
123
+ }).get('article')
124
+
125
+ const docState = {
126
+ path: [],
127
+ schemaType: docSchema,
128
+ members: [
129
+ {
130
+ kind: 'field',
131
+ field: {
132
+ path: [docSchema.fields[0].name],
133
+ schemaType: docSchema.fields[0].type,
134
+ members: [
135
+ {
136
+ kind: 'field',
137
+ field: {
138
+ path: [docSchema.fields[0].name, 'title'],
139
+ schemaType: (docSchema.fields[0].type as ObjectSchemaType).fields[0].type,
140
+ },
141
+ },
142
+ ],
143
+ },
144
+ },
145
+ ],
146
+ } as any
147
+ const conditionalMembers = getConditionalMembers(docState)
148
+
149
+ expect(conditionalMembers).toEqual([
150
+ {
151
+ path: 'object.title',
152
+ hidden: false,
153
+ readOnly: false,
154
+ },
155
+ ])
156
+ })
157
+
158
+ test('should include path with fieldset with conditional state', () => {
159
+ const docSchema: ObjectSchemaType = Schema.compile({
160
+ name: 'test',
161
+ types: [
162
+ defineType({
163
+ type: 'document',
164
+ name: 'article',
165
+ fieldsets: [{name: 'set', hidden: () => false}],
166
+ fields: [{type: 'string', fieldset: 'set', name: 'title'}],
167
+ }),
168
+ ],
169
+ }).get('article')
170
+
171
+ const docState = {
172
+ path: [],
173
+ schemaType: docSchema,
174
+ members: [
175
+ {
176
+ kind: 'fieldSet',
177
+ fieldSet: {
178
+ name: 'set',
179
+ path: ['set'],
180
+ members: [
181
+ {
182
+ kind: 'field',
183
+ field: {path: [docSchema.fields[0].name], schemaType: docSchema.fields[0].type},
184
+ },
185
+ ],
186
+ },
187
+ },
188
+ ],
189
+ } as any
190
+ const conditionalMembers = getConditionalMembers(docState)
191
+
192
+ expect(conditionalMembers).toEqual([
193
+ {
194
+ path: 'title',
195
+ hidden: false,
196
+ readOnly: false,
197
+ },
198
+ ])
199
+ })
200
+ })