@srothgan/sanity-plugin-autocomplete-input 3.0.2 → 3.1.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.
package/README.md CHANGED
@@ -39,45 +39,69 @@ yarn add @srothgan/sanity-plugin-autocomplete-input
39
39
  Add it as a plugin in sanity.config.ts (or .js):
40
40
 
41
41
  ```js
42
- import { autocompletInput } from "@srothgan/sanity-plugin-autocomplete-input";
42
+ import {autocompletInput} from '@srothgan/sanity-plugin-autocomplete-input'
43
43
 
44
44
  export default defineConfig({
45
45
  // ...
46
46
  plugins: [autocompletInput()],
47
- });
47
+ })
48
48
  ```
49
49
 
50
- You can just use it as a schema type. To customize the autocomplete list you have 3 options:
50
+ ### Configuration Options
51
+
52
+ You can configure the autocomplete behavior using one of three approaches:
53
+
54
+ #### 1. Auto-aggregate from Existing Documents
51
55
 
52
- 1. Specify the `autocompleteFieldPath` option, which the plugin will use to look for documents with the same field path to aggregate the option values.
53
- 2. Manually specify options in the schema option
54
- 3. Specify your own GROQ query returning a `[{ "value": "foobar" }]` format (you can use a `transform` function if this is not achievable using GROQ only)
56
+ The plugin automatically collects unique values from documents with the same field:
55
57
 
56
58
  ```javascript
57
- export default {
58
- fields: [
59
- {
60
- name: "autocomplete-input",
61
- type: "autocomplete",
62
- options: {
63
- // specify field path
64
- autocompleteFieldPath: "title",
65
- // this option can be used to disable using "new" values
66
- disableNew: false,
67
- // manually specify options
68
- options: [{ value: "Option 1" }, { value: "Option 2" }],
69
- // specify custom groq query
70
- groq: {
71
- query: '*[_type == $type] { "value": title }',
72
- params: {
73
- type: "page",
74
- },
75
- transform: (values) => values,
76
- },
59
+ {
60
+ name: "category",
61
+ type: "autocomplete",
62
+ options: {
63
+ autocompleteFieldPath: "category", // aggregates from all documents with this field
64
+ disableNew: false, // optional: prevent users from creating new values
65
+ },
66
+ }
67
+ ```
68
+
69
+ #### 2. Manual Options List
70
+
71
+ Provide a predefined list of options:
72
+
73
+ ```javascript
74
+ {
75
+ name: "status",
76
+ type: "autocomplete",
77
+ options: {
78
+ options: [
79
+ { value: "Draft" },
80
+ { value: "Published" },
81
+ { value: "Archived" }
82
+ ],
83
+ },
84
+ }
85
+ ```
86
+
87
+ #### 3. Custom GROQ Query
88
+
89
+ Define your own query for maximum flexibility:
90
+
91
+ ```javascript
92
+ {
93
+ name: "author",
94
+ type: "autocomplete",
95
+ options: {
96
+ groq: {
97
+ query: '*[_type == $type] { "value": title }',
98
+ params: {
99
+ type: "author",
77
100
  },
101
+ transform: (values) => values, // optional: transform results
78
102
  },
79
- ],
80
- };
103
+ },
104
+ }
81
105
  ```
82
106
 
83
107
  ### Advanced GROQ parameters
@@ -88,11 +112,11 @@ It is also possible to refer to the current parent value (for a top-level field
88
112
  export default {
89
113
  fields: [
90
114
  {
91
- name: "autocomplete-input",
92
- type: "autocomplete",
115
+ name: 'autocomplete-input',
116
+ type: 'autocomplete',
93
117
  options: {
94
118
  groq: {
95
- query: "*[_id != $docId]",
119
+ query: '*[_id != $docId]',
96
120
  params: (parent) => ({
97
121
  docId: parent?._id,
98
122
  }),
@@ -100,34 +124,17 @@ export default {
100
124
  },
101
125
  },
102
126
  ],
103
- };
127
+ }
104
128
  ```
105
129
 
106
- ## Differences from Original
107
-
108
- This maintained fork includes the following enhancements:
109
-
110
- ### Updated Dependencies
111
- - **Sanity v4 & v5 Support**: Fully compatible with both Sanity Studio v4 and v5
112
- - **React 18 & 19 Support**: Works with React 18 (Sanity v4) and React 19 (Sanity v5)
113
- - **Modern Build Tooling**: Migrated from Babel to SWC for faster builds
114
- - **TypeScript 5.3**: Updated to latest TypeScript with improved type safety
115
-
116
- ### Maintained & Active
117
- - Regular dependency updates for security and compatibility
118
- - Active issue tracking and bug fixes
119
- - Community-driven improvements
120
-
121
- All original functionality and API remain unchanged for seamless migration.
122
-
123
- ## TypeScript Usage
130
+ ### TypeScript Usage
124
131
 
125
132
  The plugin is written in TypeScript and exports all necessary types:
126
133
 
127
134
  ```typescript
128
- import { defineConfig } from 'sanity'
129
- import { autocompletInput } from '@srothgan/sanity-plugin-autocomplete-input'
130
- import type { InputOptions } from '@srothgan/sanity-plugin-autocomplete-input'
135
+ import {defineConfig} from 'sanity'
136
+ import {autocompletInput} from '@srothgan/sanity-plugin-autocomplete-input'
137
+ import type {InputOptions} from '@srothgan/sanity-plugin-autocomplete-input'
131
138
 
132
139
  export default defineConfig({
133
140
  // ...
@@ -153,12 +160,33 @@ export default defineConfig({
153
160
  })
154
161
  ```
155
162
 
163
+ ## Differences from Original
164
+
165
+ This maintained fork includes the following enhancements:
166
+
167
+ ### Updated Dependencies
168
+
169
+ - **Sanity v4 & v5 Support**: Fully compatible with both Sanity Studio v4 and v5
170
+ - **React 18 & 19 Support**: Works with React 18 (Sanity v4) and React 19 (Sanity v5)
171
+ - **Modern Build Tooling**: Migrated from Babel to SWC for faster builds
172
+ - **TypeScript 5.3**: Updated to latest TypeScript with improved type safety
173
+
174
+ ### Maintained & Active
175
+
176
+ - Regular dependency updates for security and compatibility
177
+ - Active issue tracking and bug fixes
178
+ - Community-driven improvements
179
+
180
+ All original functionality and API remain unchanged for seamless migration.
181
+
156
182
  ## Troubleshooting
157
183
 
158
184
  ### Plugin not appearing in Studio
185
+
159
186
  Make sure you've added the plugin to your `sanity.config.ts`:
187
+
160
188
  ```typescript
161
- import { autocompletInput } from '@srothgan/sanity-plugin-autocomplete-input'
189
+ import {autocompletInput} from '@srothgan/sanity-plugin-autocomplete-input'
162
190
 
163
191
  export default defineConfig({
164
192
  plugins: [autocompletInput()],
@@ -166,12 +194,15 @@ export default defineConfig({
166
194
  ```
167
195
 
168
196
  ### Autocomplete options not loading
197
+
169
198
  - Verify your GROQ query returns data in the format `[{ "value": "..." }]`
170
199
  - Check the browser console for any query errors
171
200
  - Ensure the `autocompleteFieldPath` matches an existing field in your documents
172
201
 
173
202
  ### TypeScript errors
203
+
174
204
  Make sure your `tsconfig.json` includes:
205
+
175
206
  ```json
176
207
  {
177
208
  "compilerOptions": {
package/dist/index.cjs ADDED
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: !0 });
3
+ var sanity = require("sanity"), jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), compact = require("just-compact"), get = require("just-safe-get"), unique = require("just-unique"), React = require("react");
4
+ function _interopDefaultCompat(e) {
5
+ return e && typeof e == "object" && "default" in e ? e : { default: e };
6
+ }
7
+ var compact__default = /* @__PURE__ */ _interopDefaultCompat(compact), get__default = /* @__PURE__ */ _interopDefaultCompat(get), unique__default = /* @__PURE__ */ _interopDefaultCompat(unique), React__default = /* @__PURE__ */ _interopDefaultCompat(React);
8
+ const AutoCompleteInput = (props) => {
9
+ const { id, schemaType, value, validationError, readOnly, onChange } = props, sanityClient = sanity.useClient(), documentValue = sanity.useFormValue([]), [loading, setLoading] = React.useState(!1), [query, setQuery] = React__default.default.useState(""), [options, setOptions] = React__default.default.useState([]), canCreateNew = schemaType.options?.disableNew !== !0, optionsList = React.useMemo(() => {
10
+ const uniqueOptions = unique__default.default(
11
+ options.map(({ value: optionValue }) => optionValue),
12
+ !1,
13
+ !0
14
+ );
15
+ return !uniqueOptions.find((optionValue) => optionValue === query) && canCreateNew ? [
16
+ ...uniqueOptions.map((optionValue) => ({ value: optionValue })),
17
+ { value: query, isNew: !0 }
18
+ ] : uniqueOptions.map((optionValue) => ({ value: optionValue }));
19
+ }, [query, options, canCreateNew]), handleQueryChange = React.useCallback((queryValue) => {
20
+ setQuery(queryValue ?? "");
21
+ }, []), handleChange = React.useCallback(
22
+ (newValue) => {
23
+ onChange(sanity.PatchEvent.from(newValue ? sanity.set(newValue) : sanity.unset()));
24
+ },
25
+ [onChange]
26
+ ), renderOption = React.useCallback(
27
+ (option) => /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { as: "button", padding: 3, tone: option.isNew ? "primary" : "default", shadow: 1, children: option.isNew ? canCreateNew && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { children: [
28
+ 'Create new option "',
29
+ option.value,
30
+ '"'
31
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: option.value }) }),
32
+ [canCreateNew]
33
+ );
34
+ return React.useEffect(() => {
35
+ if (schemaType.options?.options) {
36
+ setOptions(schemaType.options.options), setLoading(!1);
37
+ return;
38
+ }
39
+ const path = schemaType.options?.autocompleteFieldPath ?? "title", {
40
+ query: groqQuery,
41
+ transform,
42
+ params = {}
43
+ } = schemaType.options?.groq || {
44
+ query: `*[defined(${path})] { "value": ${path} }`
45
+ }, resolvedParams = typeof params == "function" ? params(documentValue) : params;
46
+ setLoading(!0), sanityClient.fetch(groqQuery, resolvedParams).then((results) => {
47
+ if (Array.isArray(results)) {
48
+ const transformedResults = transform ? transform(results) : results, compactedValues = compact__default.default(transformedResults.map((doc) => get__default.default(doc, "value")));
49
+ setOptions(compactedValues.map((optionValue) => ({ value: String(optionValue) }))), setLoading(!1);
50
+ }
51
+ });
52
+ }, [schemaType.options, documentValue, sanityClient]), /* @__PURE__ */ jsxRuntime.jsx(
53
+ ui.Autocomplete,
54
+ {
55
+ id,
56
+ readOnly: readOnly ?? !1,
57
+ customValidity: validationError,
58
+ loading,
59
+ disabled: loading,
60
+ options: optionsList,
61
+ value: value ?? "",
62
+ onChange: handleChange,
63
+ onQueryChange: handleQueryChange,
64
+ renderOption
65
+ }
66
+ );
67
+ }, typeName = "autocomplete", autocompleteString = sanity.defineType({
68
+ name: typeName,
69
+ type: "string",
70
+ title: "Autocomplete",
71
+ components: { input: AutoCompleteInput }
72
+ }), autocompletInput = sanity.definePlugin({
73
+ name: "sanity-plugin-autocomplete-input",
74
+ schema: {
75
+ types: [autocompleteString]
76
+ }
77
+ });
78
+ exports.autocompletInput = autocompletInput;
79
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/AutoCompleteInput.tsx","../src/schemas/autocompleteString.ts","../src/index.ts"],"sourcesContent":["import {Autocomplete, Card, Text} from '@sanity/ui'\nimport compact from 'just-compact'\nimport get from 'just-safe-get'\nimport unique from 'just-unique'\nimport React, {useCallback, useEffect, useMemo, useState} from 'react'\nimport {\n PatchEvent,\n set,\n StringInputProps,\n StringSchemaType,\n unset,\n useClient,\n useFormValue,\n} from 'sanity'\n\nimport type {InputOptions, Option} from './types/index.js'\n\nexport type AutocompleteSchemaType = Omit<StringSchemaType, 'options'> & {\n options?: StringSchemaType['options'] & InputOptions\n}\nexport type InputProps = StringInputProps<AutocompleteSchemaType>\n\nexport const AutoCompleteInput = (props: InputProps): React.ReactElement => {\n const {id, schemaType, value, validationError, readOnly, onChange} = props\n\n const sanityClient = useClient()\n const documentValue = useFormValue([])\n const [loading, setLoading] = useState(false)\n const [query, setQuery] = React.useState('')\n const [options, setOptions] = React.useState<Option[]>([])\n const canCreateNew = schemaType.options?.disableNew !== true\n\n const optionsList = useMemo<(Option & {isNew?: boolean})[]>(() => {\n const uniqueOptions = unique(\n options.map(({value: optionValue}) => optionValue),\n false,\n true,\n )\n const queryInOptions = uniqueOptions.find((optionValue) => optionValue === query)\n if (!queryInOptions && canCreateNew) {\n return [\n ...uniqueOptions.map((optionValue) => ({value: optionValue})),\n {value: query, isNew: true},\n ]\n }\n\n return uniqueOptions.map((optionValue) => ({value: optionValue}))\n }, [query, options, canCreateNew])\n\n const handleQueryChange = useCallback((queryValue: string | null) => {\n setQuery(queryValue ?? '')\n }, [])\n\n const handleChange = useCallback(\n (newValue: string) => {\n onChange(PatchEvent.from(newValue ? set(newValue) : unset()))\n },\n [onChange],\n )\n\n const renderOption = useCallback(\n (option: Option & {isNew?: boolean}) => (\n <Card as=\"button\" padding={3} tone={option.isNew ? 'primary' : 'default'} shadow={1}>\n {option.isNew ? (\n canCreateNew && <Text>Create new option &quot;{option.value}&quot;</Text>\n ) : (\n <Text>{option.value}</Text>\n )}\n </Card>\n ),\n [canCreateNew],\n )\n\n useEffect(() => {\n if (schemaType.options?.options) {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setOptions(schemaType.options.options)\n setLoading(false)\n return\n }\n\n const path = schemaType.options?.autocompleteFieldPath ?? 'title'\n const {\n query: groqQuery,\n transform,\n params = {},\n } = schemaType.options?.groq || {\n query: `*[defined(${path})] { \"value\": ${path} }`,\n }\n\n const resolvedParams =\n typeof params === 'function'\n ? params(documentValue as Record<string, unknown> | undefined)\n : params\n\n setLoading(true)\n sanityClient.fetch(groqQuery, resolvedParams).then((results) => {\n if (Array.isArray(results)) {\n const transformedResults = transform ? transform(results) : results\n const compactedValues = compact(transformedResults.map((doc) => get(doc, 'value')))\n setOptions(compactedValues.map((optionValue) => ({value: String(optionValue)})))\n setLoading(false)\n }\n })\n }, [schemaType.options, documentValue, sanityClient])\n\n return (\n <Autocomplete\n id={id}\n readOnly={readOnly ?? false}\n customValidity={validationError}\n loading={loading}\n disabled={loading}\n options={optionsList}\n value={value ?? ''}\n onChange={handleChange}\n onQueryChange={handleQueryChange}\n renderOption={renderOption}\n />\n )\n}\n","import {defineType, StringDefinition} from 'sanity'\n\nimport {AutoCompleteInput} from '../AutoCompleteInput.js'\nimport type {InputOptions} from '../types/index.js'\n\nconst typeName = 'autocomplete' as const\n\n/**\n * @public\n */\nexport interface AutocompleteStringDefinition extends Omit<\n StringDefinition,\n 'type' | 'fields' | 'options'\n> {\n type: typeof typeName\n options?: InputOptions\n}\n\ndeclare module '@sanity/types' {\n // makes type: 'color' narrow correctly when using defineTyp/defineField/defineArrayMember\n export interface IntrinsicDefinitions {\n autocomplete: AutocompleteStringDefinition\n }\n}\n\nexport const autocompleteString = defineType({\n name: typeName,\n type: 'string',\n title: 'Autocomplete',\n components: {input: AutoCompleteInput},\n})\n","import {definePlugin} from 'sanity'\n\nimport {autocompleteString} from './schemas/autocompleteString.js'\n\nexport const autocompletInput = definePlugin({\n name: 'sanity-plugin-autocomplete-input',\n schema: {\n types: [autocompleteString],\n },\n})\n"],"names":["useClient","useFormValue","useState","React","useMemo","unique","useCallback","PatchEvent","set","unset","jsx","Card","Text","useEffect","compact","get","Autocomplete","defineType","definePlugin"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import {Plugin as Plugin_2} from 'sanity'
2
+
3
+ export declare const autocompletInput: Plugin_2<void>
4
+
5
+ export {}
@@ -0,0 +1,5 @@
1
+ import {Plugin as Plugin_2} from 'sanity'
2
+
3
+ export declare const autocompletInput: Plugin_2<void>
4
+
5
+ export {}
package/dist/index.js ADDED
@@ -0,0 +1,81 @@
1
+ import { useClient, useFormValue, PatchEvent, set, unset, defineType, definePlugin } from "sanity";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Card, Text, Autocomplete } from "@sanity/ui";
4
+ import compact from "just-compact";
5
+ import get from "just-safe-get";
6
+ import unique from "just-unique";
7
+ import React, { useState, useMemo, useCallback, useEffect } from "react";
8
+ const AutoCompleteInput = (props) => {
9
+ const { id, schemaType, value, validationError, readOnly, onChange } = props, sanityClient = useClient(), documentValue = useFormValue([]), [loading, setLoading] = useState(!1), [query, setQuery] = React.useState(""), [options, setOptions] = React.useState([]), canCreateNew = schemaType.options?.disableNew !== !0, optionsList = useMemo(() => {
10
+ const uniqueOptions = unique(
11
+ options.map(({ value: optionValue }) => optionValue),
12
+ !1,
13
+ !0
14
+ );
15
+ return !uniqueOptions.find((optionValue) => optionValue === query) && canCreateNew ? [
16
+ ...uniqueOptions.map((optionValue) => ({ value: optionValue })),
17
+ { value: query, isNew: !0 }
18
+ ] : uniqueOptions.map((optionValue) => ({ value: optionValue }));
19
+ }, [query, options, canCreateNew]), handleQueryChange = useCallback((queryValue) => {
20
+ setQuery(queryValue ?? "");
21
+ }, []), handleChange = useCallback(
22
+ (newValue) => {
23
+ onChange(PatchEvent.from(newValue ? set(newValue) : unset()));
24
+ },
25
+ [onChange]
26
+ ), renderOption = useCallback(
27
+ (option) => /* @__PURE__ */ jsx(Card, { as: "button", padding: 3, tone: option.isNew ? "primary" : "default", shadow: 1, children: option.isNew ? canCreateNew && /* @__PURE__ */ jsxs(Text, { children: [
28
+ 'Create new option "',
29
+ option.value,
30
+ '"'
31
+ ] }) : /* @__PURE__ */ jsx(Text, { children: option.value }) }),
32
+ [canCreateNew]
33
+ );
34
+ return useEffect(() => {
35
+ if (schemaType.options?.options) {
36
+ setOptions(schemaType.options.options), setLoading(!1);
37
+ return;
38
+ }
39
+ const path = schemaType.options?.autocompleteFieldPath ?? "title", {
40
+ query: groqQuery,
41
+ transform,
42
+ params = {}
43
+ } = schemaType.options?.groq || {
44
+ query: `*[defined(${path})] { "value": ${path} }`
45
+ }, resolvedParams = typeof params == "function" ? params(documentValue) : params;
46
+ setLoading(!0), sanityClient.fetch(groqQuery, resolvedParams).then((results) => {
47
+ if (Array.isArray(results)) {
48
+ const transformedResults = transform ? transform(results) : results, compactedValues = compact(transformedResults.map((doc) => get(doc, "value")));
49
+ setOptions(compactedValues.map((optionValue) => ({ value: String(optionValue) }))), setLoading(!1);
50
+ }
51
+ });
52
+ }, [schemaType.options, documentValue, sanityClient]), /* @__PURE__ */ jsx(
53
+ Autocomplete,
54
+ {
55
+ id,
56
+ readOnly: readOnly ?? !1,
57
+ customValidity: validationError,
58
+ loading,
59
+ disabled: loading,
60
+ options: optionsList,
61
+ value: value ?? "",
62
+ onChange: handleChange,
63
+ onQueryChange: handleQueryChange,
64
+ renderOption
65
+ }
66
+ );
67
+ }, typeName = "autocomplete", autocompleteString = defineType({
68
+ name: typeName,
69
+ type: "string",
70
+ title: "Autocomplete",
71
+ components: { input: AutoCompleteInput }
72
+ }), autocompletInput = definePlugin({
73
+ name: "sanity-plugin-autocomplete-input",
74
+ schema: {
75
+ types: [autocompleteString]
76
+ }
77
+ });
78
+ export {
79
+ autocompletInput
80
+ };
81
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/AutoCompleteInput.tsx","../src/schemas/autocompleteString.ts","../src/index.ts"],"sourcesContent":["import {Autocomplete, Card, Text} from '@sanity/ui'\nimport compact from 'just-compact'\nimport get from 'just-safe-get'\nimport unique from 'just-unique'\nimport React, {useCallback, useEffect, useMemo, useState} from 'react'\nimport {\n PatchEvent,\n set,\n StringInputProps,\n StringSchemaType,\n unset,\n useClient,\n useFormValue,\n} from 'sanity'\n\nimport type {InputOptions, Option} from './types/index.js'\n\nexport type AutocompleteSchemaType = Omit<StringSchemaType, 'options'> & {\n options?: StringSchemaType['options'] & InputOptions\n}\nexport type InputProps = StringInputProps<AutocompleteSchemaType>\n\nexport const AutoCompleteInput = (props: InputProps): React.ReactElement => {\n const {id, schemaType, value, validationError, readOnly, onChange} = props\n\n const sanityClient = useClient()\n const documentValue = useFormValue([])\n const [loading, setLoading] = useState(false)\n const [query, setQuery] = React.useState('')\n const [options, setOptions] = React.useState<Option[]>([])\n const canCreateNew = schemaType.options?.disableNew !== true\n\n const optionsList = useMemo<(Option & {isNew?: boolean})[]>(() => {\n const uniqueOptions = unique(\n options.map(({value: optionValue}) => optionValue),\n false,\n true,\n )\n const queryInOptions = uniqueOptions.find((optionValue) => optionValue === query)\n if (!queryInOptions && canCreateNew) {\n return [\n ...uniqueOptions.map((optionValue) => ({value: optionValue})),\n {value: query, isNew: true},\n ]\n }\n\n return uniqueOptions.map((optionValue) => ({value: optionValue}))\n }, [query, options, canCreateNew])\n\n const handleQueryChange = useCallback((queryValue: string | null) => {\n setQuery(queryValue ?? '')\n }, [])\n\n const handleChange = useCallback(\n (newValue: string) => {\n onChange(PatchEvent.from(newValue ? set(newValue) : unset()))\n },\n [onChange],\n )\n\n const renderOption = useCallback(\n (option: Option & {isNew?: boolean}) => (\n <Card as=\"button\" padding={3} tone={option.isNew ? 'primary' : 'default'} shadow={1}>\n {option.isNew ? (\n canCreateNew && <Text>Create new option &quot;{option.value}&quot;</Text>\n ) : (\n <Text>{option.value}</Text>\n )}\n </Card>\n ),\n [canCreateNew],\n )\n\n useEffect(() => {\n if (schemaType.options?.options) {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setOptions(schemaType.options.options)\n setLoading(false)\n return\n }\n\n const path = schemaType.options?.autocompleteFieldPath ?? 'title'\n const {\n query: groqQuery,\n transform,\n params = {},\n } = schemaType.options?.groq || {\n query: `*[defined(${path})] { \"value\": ${path} }`,\n }\n\n const resolvedParams =\n typeof params === 'function'\n ? params(documentValue as Record<string, unknown> | undefined)\n : params\n\n setLoading(true)\n sanityClient.fetch(groqQuery, resolvedParams).then((results) => {\n if (Array.isArray(results)) {\n const transformedResults = transform ? transform(results) : results\n const compactedValues = compact(transformedResults.map((doc) => get(doc, 'value')))\n setOptions(compactedValues.map((optionValue) => ({value: String(optionValue)})))\n setLoading(false)\n }\n })\n }, [schemaType.options, documentValue, sanityClient])\n\n return (\n <Autocomplete\n id={id}\n readOnly={readOnly ?? false}\n customValidity={validationError}\n loading={loading}\n disabled={loading}\n options={optionsList}\n value={value ?? ''}\n onChange={handleChange}\n onQueryChange={handleQueryChange}\n renderOption={renderOption}\n />\n )\n}\n","import {defineType, StringDefinition} from 'sanity'\n\nimport {AutoCompleteInput} from '../AutoCompleteInput.js'\nimport type {InputOptions} from '../types/index.js'\n\nconst typeName = 'autocomplete' as const\n\n/**\n * @public\n */\nexport interface AutocompleteStringDefinition extends Omit<\n StringDefinition,\n 'type' | 'fields' | 'options'\n> {\n type: typeof typeName\n options?: InputOptions\n}\n\ndeclare module '@sanity/types' {\n // makes type: 'color' narrow correctly when using defineTyp/defineField/defineArrayMember\n export interface IntrinsicDefinitions {\n autocomplete: AutocompleteStringDefinition\n }\n}\n\nexport const autocompleteString = defineType({\n name: typeName,\n type: 'string',\n title: 'Autocomplete',\n components: {input: AutoCompleteInput},\n})\n","import {definePlugin} from 'sanity'\n\nimport {autocompleteString} from './schemas/autocompleteString.js'\n\nexport const autocompletInput = definePlugin({\n name: 'sanity-plugin-autocomplete-input',\n schema: {\n types: [autocompleteString],\n },\n})\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,30 +1,65 @@
1
1
  {
2
2
  "name": "@srothgan/sanity-plugin-autocomplete-input",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
+ "description": "Autocomplete input plugin for Sanity",
5
+ "keywords": [
6
+ "sanity",
7
+ "sanity-plugin",
8
+ "autocomplete",
9
+ "input",
10
+ "v4",
11
+ "v5",
12
+ "maintained",
13
+ "sanity-v5"
14
+ ],
4
15
  "license": "MIT",
16
+ "author": "Simon Rothgang <simonrothgang@gmail.com>",
17
+ "contributors": [
18
+ {
19
+ "name": "Liam Martens",
20
+ "email": "liam@freighter.studio",
21
+ "url": "https://github.com/LiamMartens"
22
+ }
23
+ ],
24
+ "sideEffects": false,
5
25
  "type": "module",
6
- "scripts": {
7
- "build:js": "swc ./src -d ./lib --strip-leading-paths",
8
- "build:types": "tsc -p .",
9
- "build": "run-p build:*",
10
- "lint": "eslint src/**/*.{ts,tsx}",
11
- "lint:fix": "eslint src/**/*.{ts,tsx} --fix",
12
- "format": "prettier --write \"src/**/*.{ts,tsx,json}\"",
13
- "format:check": "prettier --check \"src/**/*.{ts,tsx,json}\""
14
- },
15
- "types": "./lib/index.d.ts",
16
- "module": "./lib/index.js",
17
26
  "exports": {
18
27
  ".": {
19
- "types": "./lib/index.d.ts",
20
- "import": "./lib/index.js",
21
- "default": "./lib/index.js"
22
- }
28
+ "source": "./src/index.ts",
29
+ "import": "./dist/index.js",
30
+ "require": "./dist/index.cjs",
31
+ "default": "./dist/index.js"
32
+ },
33
+ "./package.json": "./package.json"
23
34
  },
24
- "browserslist": "> 0.5%, last 2 versions, not dead",
35
+ "main": "./dist/index.cjs",
36
+ "module": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
25
38
  "files": [
26
- "lib"
39
+ "dist",
40
+ "sanity.json",
41
+ "src",
42
+ "v2-incompatible.js"
27
43
  ],
44
+ "publishConfig": {
45
+ "exports": {
46
+ ".": {
47
+ "import": "./dist/index.js",
48
+ "require": "./dist/index.cjs",
49
+ "default": "./dist/index.js"
50
+ },
51
+ "./package.json": "./package.json"
52
+ }
53
+ },
54
+ "scripts": {
55
+ "build": "plugin-kit verify-package --silent && pkg-utils build --strict --check --clean",
56
+ "format": "prettier --write --cache --ignore-unknown .",
57
+ "format:check": "prettier --check --cache --ignore-unknown .",
58
+ "link-watch": "plugin-kit link-watch",
59
+ "lint": "eslint .",
60
+ "prepublishOnly": "npm run build",
61
+ "watch": "pkg-utils watch --strict"
62
+ },
28
63
  "repository": {
29
64
  "type": "git",
30
65
  "url": "https://github.com/srothgan/srothgan-sanity-plugin-autocomplete-input"
@@ -32,51 +67,48 @@
32
67
  "bugs": {
33
68
  "url": "https://github.com/srothgan/srothgan-sanity-plugin-autocomplete-input/issues"
34
69
  },
35
- "author": {
36
- "name": "Simon Rothgang",
37
- "email": "simonrothgang@gmail.com"
70
+ "dependencies": {
71
+ "@sanity/incompatible-plugin": "^1.0.5",
72
+ "just-compact": "^3.2.0",
73
+ "just-pick": "^4.2.0",
74
+ "just-safe-get": "^4.2.0",
75
+ "just-unique": "^4.2.0"
38
76
  },
39
- "contributors": [
40
- {
41
- "name": "Liam Martens",
42
- "email": "liam@freighter.studio",
43
- "url": "https://github.com/LiamMartens"
44
- }
45
- ],
46
- "keywords": [
47
- "sanity-plugin",
48
- "autocomplete",
49
- "v4",
50
- "v5",
51
- "maintained",
52
- "input",
53
- "sanity-v5"
54
- ],
55
77
  "devDependencies": {
78
+ "@eslint/eslintrc": "^3.2.0",
56
79
  "@eslint/js": "^9.39.2",
80
+ "@sanity/pkg-utils": "^10.2.3",
81
+ "@sanity/plugin-kit": "^4.0.20",
57
82
  "@sanity/ui": "^3.1.11",
58
- "@swc/cli": "^0.7.9",
59
- "@swc/core": "^1.3.99",
60
- "@types/node": "^20.19.0",
61
- "@types/react": "^18.2.38",
62
- "@types/react-dom": "^18.2.17",
83
+ "@types/react": "^19.2.7",
63
84
  "@typescript-eslint/eslint-plugin": "^8.50.1",
64
85
  "@typescript-eslint/parser": "^8.50.1",
65
86
  "eslint": "^9.39.2",
66
87
  "eslint-config-prettier": "^10.1.8",
88
+ "eslint-config-sanity": "^7.1.4",
89
+ "eslint-plugin-prettier": "^5.5.4",
67
90
  "eslint-plugin-react": "^7.37.5",
68
91
  "eslint-plugin-react-hooks": "^7.0.1",
69
- "npm-run-all": "^4.1.5",
92
+ "prettier": "^3.7.4",
93
+ "prettier-plugin-packagejson": "^2.5.20",
70
94
  "react": "^19.2.3",
71
95
  "react-dom": "^19.2.3",
72
96
  "sanity": "^5.1.0",
73
97
  "styled-components": "^6.1.19",
74
- "typescript": "~5.3.2"
98
+ "typescript": "^5.9.3"
75
99
  },
76
- "dependencies": {
77
- "just-compact": "^3.2.0",
78
- "just-pick": "^4.2.0",
79
- "just-safe-get": "^4.2.0",
80
- "just-unique": "^4.2.0"
100
+ "peerDependencies": {
101
+ "@sanity/ui": "^2 || ^3",
102
+ "react": "^18 || ^19",
103
+ "sanity": "^4 || ^5"
104
+ },
105
+ "engines": {
106
+ "node": ">=18"
107
+ },
108
+ "browserslist": "extends @sanity/browserslist-config",
109
+ "sanityPlugin": {
110
+ "verifyPackage": {
111
+ "eslintImports": false
112
+ }
81
113
  }
82
114
  }
package/sanity.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "parts": [
3
+ {
4
+ "implements": "part:@sanity/base/sanity-root",
5
+ "path": "./version-incompatible.js"
6
+ }
7
+ ]
8
+ }
@@ -0,0 +1,121 @@
1
+ import {Autocomplete, Card, Text} from '@sanity/ui'
2
+ import compact from 'just-compact'
3
+ import get from 'just-safe-get'
4
+ import unique from 'just-unique'
5
+ import React, {useCallback, useEffect, useMemo, useState} from 'react'
6
+ import {
7
+ PatchEvent,
8
+ set,
9
+ StringInputProps,
10
+ StringSchemaType,
11
+ unset,
12
+ useClient,
13
+ useFormValue,
14
+ } from 'sanity'
15
+
16
+ import type {InputOptions, Option} from './types/index.js'
17
+
18
+ export type AutocompleteSchemaType = Omit<StringSchemaType, 'options'> & {
19
+ options?: StringSchemaType['options'] & InputOptions
20
+ }
21
+ export type InputProps = StringInputProps<AutocompleteSchemaType>
22
+
23
+ export const AutoCompleteInput = (props: InputProps): React.ReactElement => {
24
+ const {id, schemaType, value, validationError, readOnly, onChange} = props
25
+
26
+ const sanityClient = useClient()
27
+ const documentValue = useFormValue([])
28
+ const [loading, setLoading] = useState(false)
29
+ const [query, setQuery] = React.useState('')
30
+ const [options, setOptions] = React.useState<Option[]>([])
31
+ const canCreateNew = schemaType.options?.disableNew !== true
32
+
33
+ const optionsList = useMemo<(Option & {isNew?: boolean})[]>(() => {
34
+ const uniqueOptions = unique(
35
+ options.map(({value: optionValue}) => optionValue),
36
+ false,
37
+ true,
38
+ )
39
+ const queryInOptions = uniqueOptions.find((optionValue) => optionValue === query)
40
+ if (!queryInOptions && canCreateNew) {
41
+ return [
42
+ ...uniqueOptions.map((optionValue) => ({value: optionValue})),
43
+ {value: query, isNew: true},
44
+ ]
45
+ }
46
+
47
+ return uniqueOptions.map((optionValue) => ({value: optionValue}))
48
+ }, [query, options, canCreateNew])
49
+
50
+ const handleQueryChange = useCallback((queryValue: string | null) => {
51
+ setQuery(queryValue ?? '')
52
+ }, [])
53
+
54
+ const handleChange = useCallback(
55
+ (newValue: string) => {
56
+ onChange(PatchEvent.from(newValue ? set(newValue) : unset()))
57
+ },
58
+ [onChange],
59
+ )
60
+
61
+ const renderOption = useCallback(
62
+ (option: Option & {isNew?: boolean}) => (
63
+ <Card as="button" padding={3} tone={option.isNew ? 'primary' : 'default'} shadow={1}>
64
+ {option.isNew ? (
65
+ canCreateNew && <Text>Create new option &quot;{option.value}&quot;</Text>
66
+ ) : (
67
+ <Text>{option.value}</Text>
68
+ )}
69
+ </Card>
70
+ ),
71
+ [canCreateNew],
72
+ )
73
+
74
+ useEffect(() => {
75
+ if (schemaType.options?.options) {
76
+ // eslint-disable-next-line react-hooks/set-state-in-effect
77
+ setOptions(schemaType.options.options)
78
+ setLoading(false)
79
+ return
80
+ }
81
+
82
+ const path = schemaType.options?.autocompleteFieldPath ?? 'title'
83
+ const {
84
+ query: groqQuery,
85
+ transform,
86
+ params = {},
87
+ } = schemaType.options?.groq || {
88
+ query: `*[defined(${path})] { "value": ${path} }`,
89
+ }
90
+
91
+ const resolvedParams =
92
+ typeof params === 'function'
93
+ ? params(documentValue as Record<string, unknown> | undefined)
94
+ : params
95
+
96
+ setLoading(true)
97
+ sanityClient.fetch(groqQuery, resolvedParams).then((results) => {
98
+ if (Array.isArray(results)) {
99
+ const transformedResults = transform ? transform(results) : results
100
+ const compactedValues = compact(transformedResults.map((doc) => get(doc, 'value')))
101
+ setOptions(compactedValues.map((optionValue) => ({value: String(optionValue)})))
102
+ setLoading(false)
103
+ }
104
+ })
105
+ }, [schemaType.options, documentValue, sanityClient])
106
+
107
+ return (
108
+ <Autocomplete
109
+ id={id}
110
+ readOnly={readOnly ?? false}
111
+ customValidity={validationError}
112
+ loading={loading}
113
+ disabled={loading}
114
+ options={optionsList}
115
+ value={value ?? ''}
116
+ onChange={handleChange}
117
+ onQueryChange={handleQueryChange}
118
+ renderOption={renderOption}
119
+ />
120
+ )
121
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ import {definePlugin} from 'sanity'
2
+
3
+ import {autocompleteString} from './schemas/autocompleteString.js'
4
+
5
+ export const autocompletInput = definePlugin({
6
+ name: 'sanity-plugin-autocomplete-input',
7
+ schema: {
8
+ types: [autocompleteString],
9
+ },
10
+ })
@@ -0,0 +1,31 @@
1
+ import {defineType, StringDefinition} from 'sanity'
2
+
3
+ import {AutoCompleteInput} from '../AutoCompleteInput.js'
4
+ import type {InputOptions} from '../types/index.js'
5
+
6
+ const typeName = 'autocomplete' as const
7
+
8
+ /**
9
+ * @public
10
+ */
11
+ export interface AutocompleteStringDefinition extends Omit<
12
+ StringDefinition,
13
+ 'type' | 'fields' | 'options'
14
+ > {
15
+ type: typeof typeName
16
+ options?: InputOptions
17
+ }
18
+
19
+ declare module '@sanity/types' {
20
+ // makes type: 'color' narrow correctly when using defineTyp/defineField/defineArrayMember
21
+ export interface IntrinsicDefinitions {
22
+ autocomplete: AutocompleteStringDefinition
23
+ }
24
+ }
25
+
26
+ export const autocompleteString = defineType({
27
+ name: typeName,
28
+ type: 'string',
29
+ title: 'Autocomplete',
30
+ components: {input: AutoCompleteInput},
31
+ })
@@ -0,0 +1,12 @@
1
+ import type {Option} from './Option.js'
2
+
3
+ export type InputOptions<Parent = Record<string, unknown>, Params = Record<string, unknown>> = {
4
+ autocompleteFieldPath?: string
5
+ disableNew?: boolean
6
+ options?: Option[]
7
+ groq?: {
8
+ query: string
9
+ params?: Params | ((parent?: Parent) => Params)
10
+ transform?: (result: unknown) => Option[]
11
+ }
12
+ }
@@ -1,3 +1,3 @@
1
1
  export type Option = {
2
- value: string;
3
- };
2
+ value: string
3
+ }
@@ -0,0 +1,2 @@
1
+ export type {InputOptions} from './InputOptions.js'
2
+ export type {Option} from './Option.js'
@@ -1,8 +0,0 @@
1
- import React from "react";
2
- import { StringInputProps, StringSchemaType } from "sanity";
3
- import type { InputOptions } from "./types";
4
- export type AutocompleteSchemaType = Omit<StringSchemaType, "options"> & {
5
- options?: StringSchemaType["options"] & InputOptions;
6
- };
7
- export type InputProps = StringInputProps<AutocompleteSchemaType>;
8
- export declare const AutoCompleteInput: (props: InputProps) => React.JSX.Element;
@@ -1,90 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from "react";
2
- import get from "just-safe-get";
3
- import compact from "just-compact";
4
- import unique from "just-unique";
5
- import { Autocomplete, Text, Card } from "@sanity/ui";
6
- import { PatchEvent, set, unset, useClient, useFormValue } from "sanity";
7
- export const AutoCompleteInput = (props)=>{
8
- const { id, schemaType, value, validationError, readOnly, onChange } = props;
9
- const sanityClient = useClient();
10
- const documentValue = useFormValue([]);
11
- const [loading, setLoading] = useState(false);
12
- const [query, setQuery] = React.useState("");
13
- const [options, setOptions] = React.useState([]);
14
- const canCreateNew = schemaType.options?.disableNew !== true;
15
- const optionsList = useMemo(()=>{
16
- const uniqueOptions = unique(options.map(({ value })=>value), false, true);
17
- const queryInOptions = uniqueOptions.find((value)=>value === query);
18
- if (!queryInOptions && canCreateNew) {
19
- return [
20
- ...uniqueOptions.map((value)=>({
21
- value
22
- })),
23
- {
24
- value: query,
25
- isNew: true
26
- }
27
- ];
28
- }
29
- return uniqueOptions.map((value)=>({
30
- value
31
- }));
32
- }, [
33
- query,
34
- options,
35
- canCreateNew
36
- ]);
37
- const handleQueryChange = useCallback((query)=>{
38
- setQuery(query ?? "");
39
- }, []);
40
- const handleChange = useCallback((value)=>{
41
- onChange(PatchEvent.from(value ? set(value) : unset()));
42
- }, [
43
- onChange
44
- ]);
45
- useEffect(()=>{
46
- if (schemaType.options?.options) {
47
- // eslint-disable-next-line react-hooks/set-state-in-effect
48
- setOptions(schemaType.options.options);
49
- setLoading(false);
50
- return;
51
- }
52
- const path = schemaType.options?.autocompleteFieldPath ?? "title";
53
- const { query, transform, params = {} } = schemaType.options?.groq || {
54
- query: `*[defined(${path})] { "value": ${path} }`
55
- };
56
- const resolvedParams = typeof params === "function" ? params(documentValue) : params;
57
- setLoading(true);
58
- sanityClient.fetch(query, resolvedParams).then((results)=>{
59
- if (Array.isArray(results)) {
60
- const transformedResults = transform ? transform(results) : results;
61
- const compactedValues = compact(transformedResults.map((doc)=>get(doc, "value")));
62
- setOptions(compactedValues.map((value)=>({
63
- value: String(value)
64
- })));
65
- setLoading(false);
66
- }
67
- });
68
- }, [
69
- schemaType.options,
70
- documentValue,
71
- sanityClient
72
- ]);
73
- return /*#__PURE__*/ React.createElement(Autocomplete, {
74
- id: id,
75
- readOnly: readOnly ?? false,
76
- customValidity: validationError,
77
- loading: loading,
78
- disabled: loading,
79
- options: optionsList,
80
- value: value ?? "",
81
- onChange: handleChange,
82
- onQueryChange: handleQueryChange,
83
- renderOption: (option)=>/*#__PURE__*/ React.createElement(Card, {
84
- as: "button",
85
- padding: 3,
86
- tone: option.isNew ? "primary" : "default",
87
- shadow: 1
88
- }, option.isNew ? canCreateNew && /*#__PURE__*/ React.createElement(Text, null, 'Create new option "', option.value, '"') : /*#__PURE__*/ React.createElement(Text, null, option.value))
89
- });
90
- };
package/lib/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare const autocompletInput: import("sanity").Plugin<void>;
package/lib/index.js DELETED
@@ -1,10 +0,0 @@
1
- import { definePlugin } from "sanity";
2
- import { autocompleteString } from "./schemas/autocompleteString";
3
- export const autocompletInput = definePlugin({
4
- name: "sanity-plugin-autocomplete-input",
5
- schema: {
6
- types: [
7
- autocompleteString
8
- ]
9
- }
10
- });
@@ -1,20 +0,0 @@
1
- import { StringDefinition } from "sanity";
2
- import type { InputOptions } from "../types";
3
- declare const typeName: "autocomplete";
4
- /**
5
- * @public
6
- */
7
- export interface AutocompleteStringDefinition extends Omit<StringDefinition, "type" | "fields" | "options"> {
8
- type: typeof typeName;
9
- options?: InputOptions;
10
- }
11
- declare module "@sanity/types" {
12
- interface IntrinsicDefinitions {
13
- autocomplete: AutocompleteStringDefinition;
14
- }
15
- }
16
- export declare const autocompleteString: {
17
- type: "string";
18
- name: "autocomplete";
19
- } & Omit<StringDefinition, "preview">;
20
- export {};
@@ -1,11 +0,0 @@
1
- import { defineType } from "sanity";
2
- import { AutoCompleteInput } from "../AutoCompleteInput";
3
- const typeName = "autocomplete";
4
- export const autocompleteString = defineType({
5
- name: typeName,
6
- type: "string",
7
- title: "Autocomplete",
8
- components: {
9
- input: AutoCompleteInput
10
- }
11
- });
@@ -1,11 +0,0 @@
1
- import type { Option } from "./Option";
2
- export type InputOptions<Parent = Record<string, unknown>, Params = Record<string, unknown>> = {
3
- autocompleteFieldPath?: string;
4
- disableNew?: boolean;
5
- options?: Option[];
6
- groq?: {
7
- query: string;
8
- params?: Params | ((parent?: Parent) => Params);
9
- transform?: (result: unknown) => Option[];
10
- };
11
- };
@@ -1 +0,0 @@
1
- export { };
@@ -1 +0,0 @@
1
- export { };
@@ -1,2 +0,0 @@
1
- export type { InputOptions } from "./InputOptions";
2
- export type { Option } from "./Option";
@@ -1 +0,0 @@
1
- export { };