@srothgan/sanity-plugin-autocomplete-input 3.0.3 → 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 +17 -10
- package/dist/index.cjs +79 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/package.json +81 -49
- package/sanity.json +8 -0
- package/src/AutoCompleteInput.tsx +121 -0
- package/src/index.ts +10 -0
- package/src/schemas/autocompleteString.ts +31 -0
- package/src/types/InputOptions.ts +12 -0
- package/{lib/types/Option.d.ts → src/types/Option.ts} +2 -2
- package/src/types/index.ts +2 -0
- package/lib/AutoCompleteInput.d.ts +0 -8
- package/lib/AutoCompleteInput.js +0 -90
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -10
- package/lib/schemas/autocompleteString.d.ts +0 -20
- package/lib/schemas/autocompleteString.js +0 -11
- package/lib/types/InputOptions.d.ts +0 -11
- package/lib/types/InputOptions.js +0 -1
- package/lib/types/Option.js +0 -1
- package/lib/types/index.d.ts +0 -2
- package/lib/types/index.js +0 -1
package/README.md
CHANGED
|
@@ -39,12 +39,12 @@ 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 {
|
|
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
50
|
### Configuration Options
|
|
@@ -112,11 +112,11 @@ It is also possible to refer to the current parent value (for a top-level field
|
|
|
112
112
|
export default {
|
|
113
113
|
fields: [
|
|
114
114
|
{
|
|
115
|
-
name:
|
|
116
|
-
type:
|
|
115
|
+
name: 'autocomplete-input',
|
|
116
|
+
type: 'autocomplete',
|
|
117
117
|
options: {
|
|
118
118
|
groq: {
|
|
119
|
-
query:
|
|
119
|
+
query: '*[_id != $docId]',
|
|
120
120
|
params: (parent) => ({
|
|
121
121
|
docId: parent?._id,
|
|
122
122
|
}),
|
|
@@ -124,7 +124,7 @@ export default {
|
|
|
124
124
|
},
|
|
125
125
|
},
|
|
126
126
|
],
|
|
127
|
-
}
|
|
127
|
+
}
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
### TypeScript Usage
|
|
@@ -132,9 +132,9 @@ export default {
|
|
|
132
132
|
The plugin is written in TypeScript and exports all necessary types:
|
|
133
133
|
|
|
134
134
|
```typescript
|
|
135
|
-
import {
|
|
136
|
-
import {
|
|
137
|
-
import type {
|
|
135
|
+
import {defineConfig} from 'sanity'
|
|
136
|
+
import {autocompletInput} from '@srothgan/sanity-plugin-autocomplete-input'
|
|
137
|
+
import type {InputOptions} from '@srothgan/sanity-plugin-autocomplete-input'
|
|
138
138
|
|
|
139
139
|
export default defineConfig({
|
|
140
140
|
// ...
|
|
@@ -165,12 +165,14 @@ export default defineConfig({
|
|
|
165
165
|
This maintained fork includes the following enhancements:
|
|
166
166
|
|
|
167
167
|
### Updated Dependencies
|
|
168
|
+
|
|
168
169
|
- **Sanity v4 & v5 Support**: Fully compatible with both Sanity Studio v4 and v5
|
|
169
170
|
- **React 18 & 19 Support**: Works with React 18 (Sanity v4) and React 19 (Sanity v5)
|
|
170
171
|
- **Modern Build Tooling**: Migrated from Babel to SWC for faster builds
|
|
171
172
|
- **TypeScript 5.3**: Updated to latest TypeScript with improved type safety
|
|
172
173
|
|
|
173
174
|
### Maintained & Active
|
|
175
|
+
|
|
174
176
|
- Regular dependency updates for security and compatibility
|
|
175
177
|
- Active issue tracking and bug fixes
|
|
176
178
|
- Community-driven improvements
|
|
@@ -180,9 +182,11 @@ All original functionality and API remain unchanged for seamless migration.
|
|
|
180
182
|
## Troubleshooting
|
|
181
183
|
|
|
182
184
|
### Plugin not appearing in Studio
|
|
185
|
+
|
|
183
186
|
Make sure you've added the plugin to your `sanity.config.ts`:
|
|
187
|
+
|
|
184
188
|
```typescript
|
|
185
|
-
import {
|
|
189
|
+
import {autocompletInput} from '@srothgan/sanity-plugin-autocomplete-input'
|
|
186
190
|
|
|
187
191
|
export default defineConfig({
|
|
188
192
|
plugins: [autocompletInput()],
|
|
@@ -190,12 +194,15 @@ export default defineConfig({
|
|
|
190
194
|
```
|
|
191
195
|
|
|
192
196
|
### Autocomplete options not loading
|
|
197
|
+
|
|
193
198
|
- Verify your GROQ query returns data in the format `[{ "value": "..." }]`
|
|
194
199
|
- Check the browser console for any query errors
|
|
195
200
|
- Ensure the `autocompleteFieldPath` matches an existing field in your documents
|
|
196
201
|
|
|
197
202
|
### TypeScript errors
|
|
203
|
+
|
|
198
204
|
Make sure your `tsconfig.json` includes:
|
|
205
|
+
|
|
199
206
|
```json
|
|
200
207
|
{
|
|
201
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 "{option.value}"</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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"}
|
package/dist/index.d.cts
ADDED
package/dist/index.d.ts
ADDED
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 "{option.value}"</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
|
|
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
|
-
"
|
|
20
|
-
"import": "./
|
|
21
|
-
"
|
|
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
|
-
"
|
|
35
|
+
"main": "./dist/index.cjs",
|
|
36
|
+
"module": "./dist/index.js",
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
25
38
|
"files": [
|
|
26
|
-
"
|
|
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
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
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
|
-
"@
|
|
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
|
-
"
|
|
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": "
|
|
98
|
+
"typescript": "^5.9.3"
|
|
75
99
|
},
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
|
|
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,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 "{option.value}"</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,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
|
-
|
|
3
|
-
}
|
|
2
|
+
value: string
|
|
3
|
+
}
|
|
@@ -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;
|
package/lib/AutoCompleteInput.js
DELETED
|
@@ -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,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 { };
|
package/lib/types/Option.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|
package/lib/types/index.d.ts
DELETED
package/lib/types/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|