@whatworks/payload-select-search-field 1.0.2 → 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.
- package/README.md +21 -10
- package/dist/endpoint.js +19 -9
- package/dist/endpoint.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/selectSearchField.d.ts +30 -2
- package/dist/selectSearchField.js +42 -7
- package/dist/selectSearchField.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui/SelectSearchField.js +95 -16
- package/dist/ui/SelectSearchField.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,20 +28,31 @@ import { selectSearch } from '@whatworks/payload-select-search-field'
|
|
|
28
28
|
selectSearch({
|
|
29
29
|
name: 'stripeCustomer',
|
|
30
30
|
hasMany: true,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
search: {
|
|
32
|
+
debounce: {
|
|
33
|
+
query: 250,
|
|
34
|
+
watchedFields: 600,
|
|
35
|
+
},
|
|
36
|
+
passDataToSearchFunction: true,
|
|
37
|
+
passSiblingDataToSearchFunction: true,
|
|
38
|
+
watchFieldPaths: ['customerType', 'region'],
|
|
39
|
+
searchFunction: async ({ query, selectedValues }) => {
|
|
40
|
+
return [
|
|
41
|
+
{ value: 'cus_123', label: `Result for ${query}` },
|
|
42
|
+
...selectedValues.map((value) => ({
|
|
43
|
+
value,
|
|
44
|
+
label: `Selected: ${value}`,
|
|
45
|
+
})),
|
|
46
|
+
]
|
|
47
|
+
},
|
|
41
48
|
},
|
|
42
49
|
})
|
|
43
50
|
```
|
|
44
51
|
|
|
52
|
+
`debounce` controls request timing:
|
|
53
|
+
- `query`: debounce in ms for search input typing.
|
|
54
|
+
- `watchedFields`: debounce in ms for `watchFieldPaths`-driven refetches.
|
|
55
|
+
|
|
45
56
|
`searchFunction` receives:
|
|
46
57
|
- `query`: the current input text.
|
|
47
58
|
- `selectedValues`: an array of currently selected values (empty array when nothing is selected).
|
package/dist/endpoint.js
CHANGED
|
@@ -10,9 +10,13 @@ const parseBody = async (req)=>{
|
|
|
10
10
|
}
|
|
11
11
|
return {};
|
|
12
12
|
};
|
|
13
|
+
const parseData = (value)=>{
|
|
14
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
};
|
|
13
19
|
export const selectSearchEndpointHandler = ()=>({
|
|
14
|
-
method: 'post',
|
|
15
|
-
path: selectSearchEndpoint,
|
|
16
20
|
handler: async (req)=>{
|
|
17
21
|
if (!req.user) {
|
|
18
22
|
return Response.json({
|
|
@@ -24,14 +28,14 @@ export const selectSearchEndpointHandler = ()=>({
|
|
|
24
28
|
let body;
|
|
25
29
|
try {
|
|
26
30
|
body = await parseBody(req);
|
|
27
|
-
} catch
|
|
31
|
+
} catch {
|
|
28
32
|
return Response.json({
|
|
29
33
|
error: 'Invalid JSON body'
|
|
30
34
|
}, {
|
|
31
35
|
status: 400
|
|
32
36
|
});
|
|
33
37
|
}
|
|
34
|
-
const {
|
|
38
|
+
const { slug, entityType, schemaPath } = body;
|
|
35
39
|
if (entityType !== 'collection' && entityType !== 'global') {
|
|
36
40
|
return Response.json({
|
|
37
41
|
error: 'Invalid entityType'
|
|
@@ -48,6 +52,8 @@ export const selectSearchEndpointHandler = ()=>({
|
|
|
48
52
|
}
|
|
49
53
|
const safeQuery = String(body.query || '').slice(0, maxQueryLength);
|
|
50
54
|
const selectedValues = Array.isArray(body.selectedValues) ? body.selectedValues.map((value)=>String(value)) : [];
|
|
55
|
+
const data = parseData(body.data);
|
|
56
|
+
const siblingData = parseData(body.siblingData);
|
|
51
57
|
const config = req.payload.config;
|
|
52
58
|
const entityConfig = entityType === 'collection' ? config.collections?.find((collection)=>collection.slug === slug) : config.globals?.find((global)=>global.slug === slug);
|
|
53
59
|
if (!entityConfig) {
|
|
@@ -87,12 +93,14 @@ export const selectSearchEndpointHandler = ()=>({
|
|
|
87
93
|
const collectionConfig = entityType === 'collection' ? entityConfig : undefined;
|
|
88
94
|
const globalConfig = entityType === 'global' ? entityConfig : undefined;
|
|
89
95
|
const options = await searchFunction({
|
|
90
|
-
|
|
96
|
+
collection: collectionConfig,
|
|
97
|
+
data,
|
|
98
|
+
field: fieldResult.field,
|
|
99
|
+
global: globalConfig,
|
|
91
100
|
query: safeQuery,
|
|
101
|
+
req,
|
|
92
102
|
selectedValues,
|
|
93
|
-
|
|
94
|
-
collection: collectionConfig,
|
|
95
|
-
global: globalConfig
|
|
103
|
+
siblingData
|
|
96
104
|
});
|
|
97
105
|
if (!Array.isArray(options)) {
|
|
98
106
|
return Response.json({
|
|
@@ -105,7 +113,9 @@ export const selectSearchEndpointHandler = ()=>({
|
|
|
105
113
|
options
|
|
106
114
|
};
|
|
107
115
|
return Response.json(res);
|
|
108
|
-
}
|
|
116
|
+
},
|
|
117
|
+
method: 'post',
|
|
118
|
+
path: selectSearchEndpoint
|
|
109
119
|
});
|
|
110
120
|
|
|
111
121
|
//# sourceMappingURL=endpoint.js.map
|
package/dist/endpoint.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/endpoint.ts"],"sourcesContent":["import type {\n Endpoint,\n PayloadRequest,\n SanitizedCollectionConfig,\n SanitizedGlobalConfig,\n} from 'payload'\nimport { getFieldByPath } from 'payload'\nimport type { SelectSearchRequest, SelectSearchResponse
|
|
1
|
+
{"version":3,"sources":["../src/endpoint.ts"],"sourcesContent":["import type {\n Data,\n Endpoint,\n PayloadRequest,\n SanitizedCollectionConfig,\n SanitizedGlobalConfig,\n} from 'payload'\n\nimport { getFieldByPath } from 'payload'\n\nimport type { SelectSearchFunction, SelectSearchRequest, SelectSearchResponse } from './types.js'\n\nimport { selectSearchEndpoint } from './endpointName.js'\n\nconst maxQueryLength = 200\n\nconst parseBody = async (req: PayloadRequest): Promise<Partial<SelectSearchRequest>> => {\n if (typeof req.json === 'function') {\n return (await req.json()) as Partial<SelectSearchRequest>\n }\n\n if (req.body && typeof req.body === 'object') {\n return req.body as Partial<SelectSearchRequest>\n }\n\n return {}\n}\n\nconst parseData = (value: unknown): Data | undefined => {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n return value as Data\n }\n\n return undefined\n}\n\nexport const selectSearchEndpointHandler = (): Endpoint => ({\n handler: async (req: PayloadRequest) => {\n if (!req.user) {\n return Response.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n let body: Partial<SelectSearchRequest>\n try {\n body = await parseBody(req)\n } catch {\n return Response.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n\n const { slug, entityType, schemaPath } = body\n if (entityType !== 'collection' && entityType !== 'global') {\n return Response.json({ error: 'Invalid entityType' }, { status: 400 })\n }\n\n if (!slug || !schemaPath) {\n return Response.json({ error: 'Missing slug or schemaPath' }, { status: 400 })\n }\n\n const safeQuery = String(body.query || '').slice(0, maxQueryLength)\n const selectedValues = Array.isArray(body.selectedValues)\n ? body.selectedValues.map((value) => String(value))\n : []\n const data = parseData(body.data)\n const siblingData = parseData(body.siblingData)\n\n const config = req.payload.config\n const entityConfig =\n entityType === 'collection'\n ? config.collections?.find((collection) => collection.slug === slug)\n : config.globals?.find((global) => global.slug === slug)\n\n if (!entityConfig) {\n return Response.json({ error: 'Unknown entity' }, { status: 404 })\n }\n\n const fields = entityConfig.flattenedFields\n if (!Array.isArray(fields)) {\n return Response.json({ error: 'Fields not searchable' }, { status: 400 })\n }\n\n const fieldResult = getFieldByPath({\n fields,\n path: schemaPath.split('.').slice(1).join('.'),\n })\n\n if (!fieldResult) {\n return Response.json({ error: 'Field not found' }, { status: 400 })\n }\n\n const searchFunction = fieldResult?.field?.custom?.searchFunction as\n | SelectSearchFunction\n | undefined\n\n if (typeof searchFunction !== 'function') {\n return Response.json({ error: 'Field not searchable' }, { status: 400 })\n }\n\n const collectionConfig =\n entityType === 'collection' ? (entityConfig as SanitizedCollectionConfig) : undefined\n const globalConfig =\n entityType === 'global' ? (entityConfig as SanitizedGlobalConfig) : undefined\n\n const options = await searchFunction({\n collection: collectionConfig,\n data,\n field: fieldResult.field,\n global: globalConfig,\n query: safeQuery,\n req,\n selectedValues,\n siblingData,\n })\n\n if (!Array.isArray(options)) {\n return Response.json({ error: 'Invalid searchFunction response' }, { status: 500 })\n }\n\n const res: SelectSearchResponse = {\n options,\n }\n\n return Response.json(res)\n },\n method: 'post',\n path: selectSearchEndpoint,\n})\n"],"names":["getFieldByPath","selectSearchEndpoint","maxQueryLength","parseBody","req","json","body","parseData","value","Array","isArray","undefined","selectSearchEndpointHandler","handler","user","Response","error","status","slug","entityType","schemaPath","safeQuery","String","query","slice","selectedValues","map","data","siblingData","config","payload","entityConfig","collections","find","collection","globals","global","fields","flattenedFields","fieldResult","path","split","join","searchFunction","field","custom","collectionConfig","globalConfig","options","res","method"],"mappings":"AAQA,SAASA,cAAc,QAAQ,UAAS;AAIxC,SAASC,oBAAoB,QAAQ,oBAAmB;AAExD,MAAMC,iBAAiB;AAEvB,MAAMC,YAAY,OAAOC;IACvB,IAAI,OAAOA,IAAIC,IAAI,KAAK,YAAY;QAClC,OAAQ,MAAMD,IAAIC,IAAI;IACxB;IAEA,IAAID,IAAIE,IAAI,IAAI,OAAOF,IAAIE,IAAI,KAAK,UAAU;QAC5C,OAAOF,IAAIE,IAAI;IACjB;IAEA,OAAO,CAAC;AACV;AAEA,MAAMC,YAAY,CAACC;IACjB,IAAIA,SAAS,OAAOA,UAAU,YAAY,CAACC,MAAMC,OAAO,CAACF,QAAQ;QAC/D,OAAOA;IACT;IAEA,OAAOG;AACT;AAEA,OAAO,MAAMC,8BAA8B,IAAiB,CAAA;QAC1DC,SAAS,OAAOT;YACd,IAAI,CAACA,IAAIU,IAAI,EAAE;gBACb,OAAOC,SAASV,IAAI,CAAC;oBAAEW,OAAO;gBAAe,GAAG;oBAAEC,QAAQ;gBAAI;YAChE;YAEA,IAAIX;YACJ,IAAI;gBACFA,OAAO,MAAMH,UAAUC;YACzB,EAAE,OAAM;gBACN,OAAOW,SAASV,IAAI,CAAC;oBAAEW,OAAO;gBAAoB,GAAG;oBAAEC,QAAQ;gBAAI;YACrE;YAEA,MAAM,EAAEC,IAAI,EAAEC,UAAU,EAAEC,UAAU,EAAE,GAAGd;YACzC,IAAIa,eAAe,gBAAgBA,eAAe,UAAU;gBAC1D,OAAOJ,SAASV,IAAI,CAAC;oBAAEW,OAAO;gBAAqB,GAAG;oBAAEC,QAAQ;gBAAI;YACtE;YAEA,IAAI,CAACC,QAAQ,CAACE,YAAY;gBACxB,OAAOL,SAASV,IAAI,CAAC;oBAAEW,OAAO;gBAA6B,GAAG;oBAAEC,QAAQ;gBAAI;YAC9E;YAEA,MAAMI,YAAYC,OAAOhB,KAAKiB,KAAK,IAAI,IAAIC,KAAK,CAAC,GAAGtB;YACpD,MAAMuB,iBAAiBhB,MAAMC,OAAO,CAACJ,KAAKmB,cAAc,IACpDnB,KAAKmB,cAAc,CAACC,GAAG,CAAC,CAAClB,QAAUc,OAAOd,UAC1C,EAAE;YACN,MAAMmB,OAAOpB,UAAUD,KAAKqB,IAAI;YAChC,MAAMC,cAAcrB,UAAUD,KAAKsB,WAAW;YAE9C,MAAMC,SAASzB,IAAI0B,OAAO,CAACD,MAAM;YACjC,MAAME,eACJZ,eAAe,eACXU,OAAOG,WAAW,EAAEC,KAAK,CAACC,aAAeA,WAAWhB,IAAI,KAAKA,QAC7DW,OAAOM,OAAO,EAAEF,KAAK,CAACG,SAAWA,OAAOlB,IAAI,KAAKA;YAEvD,IAAI,CAACa,cAAc;gBACjB,OAAOhB,SAASV,IAAI,CAAC;oBAAEW,OAAO;gBAAiB,GAAG;oBAAEC,QAAQ;gBAAI;YAClE;YAEA,MAAMoB,SAASN,aAAaO,eAAe;YAC3C,IAAI,CAAC7B,MAAMC,OAAO,CAAC2B,SAAS;gBAC1B,OAAOtB,SAASV,IAAI,CAAC;oBAAEW,OAAO;gBAAwB,GAAG;oBAAEC,QAAQ;gBAAI;YACzE;YAEA,MAAMsB,cAAcvC,eAAe;gBACjCqC;gBACAG,MAAMpB,WAAWqB,KAAK,CAAC,KAAKjB,KAAK,CAAC,GAAGkB,IAAI,CAAC;YAC5C;YAEA,IAAI,CAACH,aAAa;gBAChB,OAAOxB,SAASV,IAAI,CAAC;oBAAEW,OAAO;gBAAkB,GAAG;oBAAEC,QAAQ;gBAAI;YACnE;YAEA,MAAM0B,iBAAiBJ,aAAaK,OAAOC,QAAQF;YAInD,IAAI,OAAOA,mBAAmB,YAAY;gBACxC,OAAO5B,SAASV,IAAI,CAAC;oBAAEW,OAAO;gBAAuB,GAAG;oBAAEC,QAAQ;gBAAI;YACxE;YAEA,MAAM6B,mBACJ3B,eAAe,eAAgBY,eAA6CpB;YAC9E,MAAMoC,eACJ5B,eAAe,WAAYY,eAAyCpB;YAEtE,MAAMqC,UAAU,MAAML,eAAe;gBACnCT,YAAYY;gBACZnB;gBACAiB,OAAOL,YAAYK,KAAK;gBACxBR,QAAQW;gBACRxB,OAAOF;gBACPjB;gBACAqB;gBACAG;YACF;YAEA,IAAI,CAACnB,MAAMC,OAAO,CAACsC,UAAU;gBAC3B,OAAOjC,SAASV,IAAI,CAAC;oBAAEW,OAAO;gBAAkC,GAAG;oBAAEC,QAAQ;gBAAI;YACnF;YAEA,MAAMgC,MAA4B;gBAChCD;YACF;YAEA,OAAOjC,SAASV,IAAI,CAAC4C;QACvB;QACAC,QAAQ;QACRV,MAAMvC;IACR,CAAA,EAAE"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { selectSearchEndpointHandler } from './endpoint.js';
|
|
2
2
|
export { selectSearchEndpoint } from './endpointName.js';
|
|
3
3
|
export { selectSearchPlugin as selectSearchPlugin } from './plugin.js';
|
|
4
|
+
export { selectSearch } from './selectSearchField.js';
|
|
4
5
|
export { selectSearchField } from './selectSearchField.js';
|
|
5
6
|
export type { SelectSearchFunction, SelectSearchFunctionArgs, SelectSearchOption, SelectSearchRequest, SelectSearchResponse, } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { selectSearchPlugin as selectSearchPlugin } from './plugin.js';
|
|
2
|
-
export { selectSearchEndpoint } from './endpointName.js';
|
|
3
1
|
export { selectSearchEndpointHandler } from './endpoint.js';
|
|
2
|
+
export { selectSearchEndpoint } from './endpointName.js';
|
|
3
|
+
export { selectSearchPlugin as selectSearchPlugin } from './plugin.js';
|
|
4
|
+
export { selectSearch } from './selectSearchField.js';
|
|
4
5
|
export { selectSearchField } from './selectSearchField.js';
|
|
5
6
|
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { selectSearchEndpointHandler } from './endpoint.js'\nexport { selectSearchEndpoint } from './endpointName.js'\nexport { selectSearchPlugin as selectSearchPlugin } from './plugin.js'\nexport { selectSearch } from './selectSearchField.js'\nexport { selectSearchField } from './selectSearchField.js'\nexport type {\n SelectSearchFunction,\n SelectSearchFunctionArgs,\n SelectSearchOption,\n SelectSearchRequest,\n SelectSearchResponse,\n} from './types.js'\n"],"names":["selectSearchEndpointHandler","selectSearchEndpoint","selectSearchPlugin","selectSearch","selectSearchField"],"mappings":"AAAA,SAASA,2BAA2B,QAAQ,gBAAe;AAC3D,SAASC,oBAAoB,QAAQ,oBAAmB;AACxD,SAASC,sBAAsBA,kBAAkB,QAAQ,cAAa;AACtE,SAASC,YAAY,QAAQ,yBAAwB;AACrD,SAASC,iBAAiB,QAAQ,yBAAwB"}
|
package/dist/plugin.js
CHANGED
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Plugin } from 'payload'\nimport { selectSearchEndpointHandler } from './endpoint.js'\n\nexport const selectSearchPlugin = (): Plugin => {\n return
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Plugin } from 'payload'\n\nimport { selectSearchEndpointHandler } from './endpoint.js'\n\nexport const selectSearchPlugin = (): Plugin => {\n return (config) => {\n config.endpoints = [...(config.endpoints || []), selectSearchEndpointHandler()]\n return config\n }\n}\n"],"names":["selectSearchEndpointHandler","selectSearchPlugin","config","endpoints"],"mappings":"AAEA,SAASA,2BAA2B,QAAQ,gBAAe;AAE3D,OAAO,MAAMC,qBAAqB;IAChC,OAAO,CAACC;QACNA,OAAOC,SAAS,GAAG;eAAKD,OAAOC,SAAS,IAAI,EAAE;YAAGH;SAA8B;QAC/E,OAAOE;IACT;AACF,EAAC"}
|
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
import type { Field, TextField } from 'payload';
|
|
2
2
|
import type { SelectSearchFunction } from './types.js';
|
|
3
|
+
export type SelectSearchConfig = {
|
|
4
|
+
/** Debounce timings for client-side option refetching.
|
|
5
|
+
*/
|
|
6
|
+
debounce?: SelectSearchDebounceConfig;
|
|
7
|
+
/** Send full form data to `searchFunction` as `data` on each request.
|
|
8
|
+
* @default false
|
|
9
|
+
*/
|
|
10
|
+
passDataToSearchFunction?: boolean;
|
|
11
|
+
/** Send sibling field data to `searchFunction` as `siblingData` on each request.
|
|
12
|
+
* @default false
|
|
13
|
+
*/
|
|
14
|
+
passSiblingDataToSearchFunction?: boolean;
|
|
15
|
+
searchFunction: SelectSearchFunction;
|
|
16
|
+
/** Re-fetch options when any of these field values change
|
|
17
|
+
* @default []
|
|
18
|
+
*/
|
|
19
|
+
watchFieldPaths?: string[];
|
|
20
|
+
};
|
|
21
|
+
export type SelectSearchDebounceConfig = {
|
|
22
|
+
/** Debounce delay (ms) for query typing changes.
|
|
23
|
+
* @default 300
|
|
24
|
+
*/
|
|
25
|
+
query?: number;
|
|
26
|
+
/** Debounce delay (ms) for watched field value changes.
|
|
27
|
+
* @default 700
|
|
28
|
+
*/
|
|
29
|
+
watchedFields?: number;
|
|
30
|
+
};
|
|
3
31
|
export type SelectSearchFieldArgs = {
|
|
4
32
|
admin?: TextField['admin'];
|
|
5
33
|
custom?: Record<string, unknown>;
|
|
6
34
|
hasMany?: boolean;
|
|
7
|
-
|
|
8
|
-
searchFunction: SelectSearchFunction;
|
|
35
|
+
search: SelectSearchConfig;
|
|
9
36
|
type?: 'text';
|
|
10
37
|
} & Omit<TextField, 'admin' | 'custom' | 'hasMany' | 'type'>;
|
|
38
|
+
export declare const selectSearch: (args: SelectSearchFieldArgs) => Field;
|
|
11
39
|
export declare const selectSearchField: (args: SelectSearchFieldArgs) => Field;
|
|
@@ -1,20 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const normalizeWatchFieldPaths = (value)=>{
|
|
2
|
+
if (!Array.isArray(value)) {
|
|
3
|
+
return [];
|
|
4
|
+
}
|
|
5
|
+
const paths = value.map((entry)=>typeof entry === 'string' ? entry.trim() : '').filter(Boolean);
|
|
6
|
+
return Array.from(new Set(paths)).sort();
|
|
7
|
+
};
|
|
8
|
+
const normalizeDebounceMs = (value, fallback, key)=>{
|
|
9
|
+
if (value === undefined) {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
13
|
+
throw new Error(`Invalid search.debounce.${key}: expected a finite number >= 0`);
|
|
14
|
+
}
|
|
15
|
+
return Math.floor(value);
|
|
16
|
+
};
|
|
17
|
+
export const selectSearch = (args)=>{
|
|
18
|
+
const { search, ...rest } = args;
|
|
19
|
+
const resolvedPassDataToSearchFunction = search.passDataToSearchFunction === true;
|
|
20
|
+
const resolvedPassSiblingDataToSearchFunction = search.passSiblingDataToSearchFunction === true;
|
|
21
|
+
const resolvedWatchFieldPaths = normalizeWatchFieldPaths(search.watchFieldPaths);
|
|
22
|
+
const resolvedQueryDebounceMs = normalizeDebounceMs(search.debounce?.query, 300, 'query');
|
|
23
|
+
const resolvedWatchedFieldsDebounceMs = normalizeDebounceMs(search.debounce?.watchedFields, 700, 'watchedFields');
|
|
3
24
|
return {
|
|
4
25
|
...rest,
|
|
5
26
|
type: 'text',
|
|
6
|
-
custom: {
|
|
7
|
-
...args.custom,
|
|
8
|
-
searchFunction: args.searchFunction
|
|
9
|
-
},
|
|
10
27
|
admin: {
|
|
11
28
|
...args.admin,
|
|
12
29
|
components: {
|
|
13
30
|
...args.admin?.components,
|
|
14
|
-
Field:
|
|
31
|
+
Field: {
|
|
32
|
+
clientProps: {
|
|
33
|
+
debounce: {
|
|
34
|
+
query: resolvedQueryDebounceMs,
|
|
35
|
+
watchedFields: resolvedWatchedFieldsDebounceMs
|
|
36
|
+
},
|
|
37
|
+
passDataToSearchFunction: resolvedPassDataToSearchFunction,
|
|
38
|
+
passSiblingDataToSearchFunction: resolvedPassSiblingDataToSearchFunction,
|
|
39
|
+
watchFieldPaths: resolvedWatchFieldPaths
|
|
40
|
+
},
|
|
41
|
+
path: '@whatworks/payload-select-search-field/client#SelectSearchField'
|
|
42
|
+
}
|
|
15
43
|
}
|
|
44
|
+
},
|
|
45
|
+
custom: {
|
|
46
|
+
...args.custom,
|
|
47
|
+
searchFunction: search.searchFunction
|
|
16
48
|
}
|
|
17
49
|
};
|
|
18
50
|
};
|
|
51
|
+
export const selectSearchField = (args)=>{
|
|
52
|
+
return selectSearch(args);
|
|
53
|
+
};
|
|
19
54
|
|
|
20
55
|
//# sourceMappingURL=selectSearchField.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/selectSearchField.ts"],"sourcesContent":["import type { Field, TextField } from 'payload'\nimport type { SelectSearchFunction } from './types.js'\n\nexport type SelectSearchFieldArgs = Omit<TextField, 'admin' | 'custom' | '
|
|
1
|
+
{"version":3,"sources":["../src/selectSearchField.ts"],"sourcesContent":["import type { Field, TextField } from 'payload'\n\nimport type { SelectSearchFunction } from './types.js'\n\nexport type SelectSearchConfig = {\n /** Debounce timings for client-side option refetching.\n */\n debounce?: SelectSearchDebounceConfig\n /** Send full form data to `searchFunction` as `data` on each request.\n * @default false\n */\n passDataToSearchFunction?: boolean\n /** Send sibling field data to `searchFunction` as `siblingData` on each request.\n * @default false\n */\n passSiblingDataToSearchFunction?: boolean\n searchFunction: SelectSearchFunction\n /** Re-fetch options when any of these field values change\n * @default []\n */\n watchFieldPaths?: string[]\n}\n\nexport type SelectSearchDebounceConfig = {\n /** Debounce delay (ms) for query typing changes.\n * @default 300\n */\n query?: number\n /** Debounce delay (ms) for watched field value changes.\n * @default 700\n */\n watchedFields?: number\n}\n\nexport type SelectSearchFieldArgs = {\n admin?: TextField['admin']\n custom?: Record<string, unknown>\n hasMany?: boolean\n search: SelectSearchConfig\n type?: 'text'\n} & Omit<TextField, 'admin' | 'custom' | 'hasMany' | 'type'>\n\nconst normalizeWatchFieldPaths = (value: unknown): string[] => {\n if (!Array.isArray(value)) {\n return []\n }\n\n const paths = value\n .map((entry) => (typeof entry === 'string' ? entry.trim() : ''))\n .filter(Boolean)\n\n return Array.from(new Set(paths)).sort()\n}\n\nconst normalizeDebounceMs = (\n value: unknown,\n fallback: number,\n key: 'query' | 'watchedFields',\n): number => {\n if (value === undefined) {\n return fallback\n }\n\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {\n throw new Error(`Invalid search.debounce.${key}: expected a finite number >= 0`)\n }\n\n return Math.floor(value)\n}\n\nexport const selectSearch = (args: SelectSearchFieldArgs): Field => {\n const { search, ...rest } = args\n\n const resolvedPassDataToSearchFunction = search.passDataToSearchFunction === true\n const resolvedPassSiblingDataToSearchFunction = search.passSiblingDataToSearchFunction === true\n const resolvedWatchFieldPaths = normalizeWatchFieldPaths(search.watchFieldPaths)\n const resolvedQueryDebounceMs = normalizeDebounceMs(search.debounce?.query, 300, 'query')\n const resolvedWatchedFieldsDebounceMs = normalizeDebounceMs(\n search.debounce?.watchedFields,\n 700,\n 'watchedFields',\n )\n\n return {\n ...rest,\n type: 'text',\n admin: {\n ...args.admin,\n components: {\n ...args.admin?.components,\n Field: {\n clientProps: {\n debounce: {\n query: resolvedQueryDebounceMs,\n watchedFields: resolvedWatchedFieldsDebounceMs,\n },\n passDataToSearchFunction: resolvedPassDataToSearchFunction,\n passSiblingDataToSearchFunction: resolvedPassSiblingDataToSearchFunction,\n watchFieldPaths: resolvedWatchFieldPaths,\n },\n path: '@whatworks/payload-select-search-field/client#SelectSearchField',\n },\n },\n },\n custom: {\n ...args.custom,\n searchFunction: search.searchFunction,\n },\n } as Field\n}\n\nexport const selectSearchField = (args: SelectSearchFieldArgs): Field => {\n return selectSearch(args)\n}\n"],"names":["normalizeWatchFieldPaths","value","Array","isArray","paths","map","entry","trim","filter","Boolean","from","Set","sort","normalizeDebounceMs","fallback","key","undefined","Number","isFinite","Error","Math","floor","selectSearch","args","search","rest","resolvedPassDataToSearchFunction","passDataToSearchFunction","resolvedPassSiblingDataToSearchFunction","passSiblingDataToSearchFunction","resolvedWatchFieldPaths","watchFieldPaths","resolvedQueryDebounceMs","debounce","query","resolvedWatchedFieldsDebounceMs","watchedFields","type","admin","components","Field","clientProps","path","custom","searchFunction","selectSearchField"],"mappings":"AA0CA,MAAMA,2BAA2B,CAACC;IAChC,IAAI,CAACC,MAAMC,OAAO,CAACF,QAAQ;QACzB,OAAO,EAAE;IACX;IAEA,MAAMG,QAAQH,MACXI,GAAG,CAAC,CAACC,QAAW,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK,IAC3DC,MAAM,CAACC;IAEV,OAAOP,MAAMQ,IAAI,CAAC,IAAIC,IAAIP,QAAQQ,IAAI;AACxC;AAEA,MAAMC,sBAAsB,CAC1BZ,OACAa,UACAC;IAEA,IAAId,UAAUe,WAAW;QACvB,OAAOF;IACT;IAEA,IAAI,OAAOb,UAAU,YAAY,CAACgB,OAAOC,QAAQ,CAACjB,UAAUA,QAAQ,GAAG;QACrE,MAAM,IAAIkB,MAAM,CAAC,wBAAwB,EAAEJ,IAAI,+BAA+B,CAAC;IACjF;IAEA,OAAOK,KAAKC,KAAK,CAACpB;AACpB;AAEA,OAAO,MAAMqB,eAAe,CAACC;IAC3B,MAAM,EAAEC,MAAM,EAAE,GAAGC,MAAM,GAAGF;IAE5B,MAAMG,mCAAmCF,OAAOG,wBAAwB,KAAK;IAC7E,MAAMC,0CAA0CJ,OAAOK,+BAA+B,KAAK;IAC3F,MAAMC,0BAA0B9B,yBAAyBwB,OAAOO,eAAe;IAC/E,MAAMC,0BAA0BnB,oBAAoBW,OAAOS,QAAQ,EAAEC,OAAO,KAAK;IACjF,MAAMC,kCAAkCtB,oBACtCW,OAAOS,QAAQ,EAAEG,eACjB,KACA;IAGF,OAAO;QACL,GAAGX,IAAI;QACPY,MAAM;QACNC,OAAO;YACL,GAAGf,KAAKe,KAAK;YACbC,YAAY;gBACV,GAAGhB,KAAKe,KAAK,EAAEC,UAAU;gBACzBC,OAAO;oBACLC,aAAa;wBACXR,UAAU;4BACRC,OAAOF;4BACPI,eAAeD;wBACjB;wBACAR,0BAA0BD;wBAC1BG,iCAAiCD;wBACjCG,iBAAiBD;oBACnB;oBACAY,MAAM;gBACR;YACF;QACF;QACAC,QAAQ;YACN,GAAGpB,KAAKoB,MAAM;YACdC,gBAAgBpB,OAAOoB,cAAc;QACvC;IACF;AACF,EAAC;AAED,OAAO,MAAMC,oBAAoB,CAACtB;IAChC,OAAOD,aAAaC;AACtB,EAAC"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type {\n FlattenedField,\n PayloadRequest,\n SanitizedCollectionConfig,\n SanitizedGlobalConfig,\n} from 'payload'\n\nexport type SelectSearchOption = {\n
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type {\n Data,\n FlattenedField,\n PayloadRequest,\n SanitizedCollectionConfig,\n SanitizedGlobalConfig,\n} from 'payload'\n\nexport type SelectSearchOption = {\n [key: string]: unknown\n label: string\n value: string\n}\n\nexport type SelectSearchFunctionArgs = {\n collection?: SanitizedCollectionConfig\n data?: Data\n field: FlattenedField\n global?: SanitizedGlobalConfig\n query: string\n req: PayloadRequest\n selectedValues: string[]\n siblingData?: Data\n}\n\nexport type SelectSearchFunction = (\n args: SelectSearchFunctionArgs,\n) => Promise<SelectSearchOption[]> | SelectSearchOption[]\n\nexport type SelectSearchRequest = {\n data?: Data\n entityType: 'collection' | 'global'\n query?: string\n schemaPath: string\n selectedValues?: string[]\n siblingData?: Data\n slug: string\n}\n\nexport type SelectSearchResponse = {\n options: SelectSearchOption[]\n}\n"],"names":[],"mappings":"AAuCA,WAEC"}
|
|
@@ -1,24 +1,42 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { SelectInput, useConfig, useDocumentInfo, useField } from '@payloadcms/ui';
|
|
3
|
+
import { SelectInput, useConfig, useDocumentInfo, useField, useForm, useFormFields } from '@payloadcms/ui';
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { selectSearchEndpoint } from '../endpointName.js';
|
|
6
|
-
const
|
|
6
|
+
const defaultQueryDebounceMs = 300;
|
|
7
|
+
const defaultWatchedFieldsDebounceMs = 700;
|
|
8
|
+
const serializeRefetchValue = (value)=>{
|
|
9
|
+
try {
|
|
10
|
+
return JSON.stringify(value) ?? '';
|
|
11
|
+
} catch {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
};
|
|
7
15
|
export const SelectSearchField = (props)=>{
|
|
8
16
|
const { field, path, schemaPath: schemaPathProp } = props;
|
|
9
|
-
const {
|
|
17
|
+
const { setValue, showError, value } = useField({
|
|
10
18
|
path
|
|
11
19
|
});
|
|
12
20
|
const { collectionSlug, globalSlug } = useDocumentInfo();
|
|
13
21
|
const { config } = useConfig();
|
|
22
|
+
const { getData, getSiblingData } = useForm();
|
|
14
23
|
const [options, setOptions] = useState([]);
|
|
15
24
|
const [inputValue, setInputValue] = useState('');
|
|
16
25
|
const [remoteError, setRemoteError] = useState(null);
|
|
17
26
|
const abortRef = useRef(null);
|
|
27
|
+
const fetchOptionsRef = useRef(null);
|
|
28
|
+
const latestInputValueRef = useRef('');
|
|
29
|
+
const hasInitializedWatchedFieldsEffectRef = useRef(false);
|
|
18
30
|
const entityType = globalSlug ? 'global' : 'collection';
|
|
19
31
|
const slug = globalSlug || collectionSlug;
|
|
20
32
|
const schemaPath = schemaPathProp ?? field.name;
|
|
21
33
|
const hasMany = field.hasMany ?? false;
|
|
34
|
+
const passDataToSearchFunction = props.passDataToSearchFunction === true;
|
|
35
|
+
const passSiblingDataToSearchFunction = props.passSiblingDataToSearchFunction === true;
|
|
36
|
+
// `selectSearch` normalizes these values before passing to `clientProps`.
|
|
37
|
+
const queryDebounceMs = props.debounce?.query ?? defaultQueryDebounceMs;
|
|
38
|
+
const watchedFieldsDebounceMs = props.debounce?.watchedFields ?? defaultWatchedFieldsDebounceMs;
|
|
39
|
+
const watchFieldPaths = props.watchFieldPaths ?? [];
|
|
22
40
|
const apiPath = config.routes?.api || '/api';
|
|
23
41
|
const apiRoute = apiPath.startsWith('/') ? apiPath : `/${apiPath}`;
|
|
24
42
|
const baseURL = config.serverURL || '';
|
|
@@ -37,6 +55,22 @@ export const SelectSearchField = (props)=>{
|
|
|
37
55
|
hasMany,
|
|
38
56
|
value
|
|
39
57
|
]);
|
|
58
|
+
const watchedFieldPathsRefetchToken = useFormFields(([fields])=>{
|
|
59
|
+
if (watchFieldPaths.length === 0) {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
const watchedPathValues = [];
|
|
63
|
+
for (const watchPath of watchFieldPaths){
|
|
64
|
+
if (!Object.prototype.hasOwnProperty.call(fields, watchPath)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
watchedPathValues.push([
|
|
68
|
+
watchPath,
|
|
69
|
+
fields[watchPath]?.value
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
return serializeRefetchValue(watchedPathValues);
|
|
73
|
+
});
|
|
40
74
|
const fetchOptions = useCallback(async (query)=>{
|
|
41
75
|
if (!slug || !schemaPath) {
|
|
42
76
|
setOptions([]);
|
|
@@ -48,20 +82,27 @@ export const SelectSearchField = (props)=>{
|
|
|
48
82
|
const controller = new AbortController();
|
|
49
83
|
abortRef.current = controller;
|
|
50
84
|
setRemoteError(null);
|
|
85
|
+
const payload = {
|
|
86
|
+
slug,
|
|
87
|
+
entityType,
|
|
88
|
+
query,
|
|
89
|
+
schemaPath,
|
|
90
|
+
selectedValues
|
|
91
|
+
};
|
|
92
|
+
if (passDataToSearchFunction) {
|
|
93
|
+
payload.data = getData();
|
|
94
|
+
}
|
|
95
|
+
if (passSiblingDataToSearchFunction) {
|
|
96
|
+
payload.siblingData = getSiblingData(path);
|
|
97
|
+
}
|
|
51
98
|
const res = await fetch(endpointURL, {
|
|
52
|
-
|
|
99
|
+
body: JSON.stringify(payload),
|
|
100
|
+
credentials: 'include',
|
|
53
101
|
headers: {
|
|
54
102
|
'Content-Type': 'application/json'
|
|
55
103
|
},
|
|
56
|
-
|
|
57
|
-
signal: controller.signal
|
|
58
|
-
body: JSON.stringify({
|
|
59
|
-
entityType,
|
|
60
|
-
slug,
|
|
61
|
-
schemaPath,
|
|
62
|
-
query,
|
|
63
|
-
selectedValues
|
|
64
|
-
})
|
|
104
|
+
method: 'POST',
|
|
105
|
+
signal: controller.signal
|
|
65
106
|
});
|
|
66
107
|
if (!res.ok) {
|
|
67
108
|
const errorBody = await res.json().catch(()=>null);
|
|
@@ -75,26 +116,64 @@ export const SelectSearchField = (props)=>{
|
|
|
75
116
|
}, [
|
|
76
117
|
endpointURL,
|
|
77
118
|
entityType,
|
|
119
|
+
getData,
|
|
120
|
+
getSiblingData,
|
|
121
|
+
passDataToSearchFunction,
|
|
122
|
+
passSiblingDataToSearchFunction,
|
|
123
|
+
path,
|
|
78
124
|
schemaPath,
|
|
79
125
|
selectedValues,
|
|
80
126
|
slug
|
|
81
127
|
]);
|
|
128
|
+
useEffect(()=>{
|
|
129
|
+
fetchOptionsRef.current = fetchOptions;
|
|
130
|
+
}, [
|
|
131
|
+
fetchOptions
|
|
132
|
+
]);
|
|
133
|
+
useEffect(()=>{
|
|
134
|
+
latestInputValueRef.current = inputValue;
|
|
135
|
+
}, [
|
|
136
|
+
inputValue
|
|
137
|
+
]);
|
|
82
138
|
useEffect(()=>{
|
|
83
139
|
if (!slug || !schemaPath) {
|
|
84
140
|
return;
|
|
85
141
|
}
|
|
142
|
+
// Query typing should feel responsive, so use a shorter debounce.
|
|
86
143
|
const timeout = setTimeout(()=>{
|
|
87
|
-
void
|
|
88
|
-
},
|
|
144
|
+
void fetchOptionsRef.current?.(inputValue);
|
|
145
|
+
}, queryDebounceMs);
|
|
89
146
|
return ()=>{
|
|
90
147
|
clearTimeout(timeout);
|
|
91
148
|
};
|
|
92
149
|
}, [
|
|
93
|
-
fetchOptions,
|
|
94
150
|
inputValue,
|
|
151
|
+
queryDebounceMs,
|
|
95
152
|
schemaPath,
|
|
96
153
|
slug
|
|
97
154
|
]);
|
|
155
|
+
useEffect(()=>{
|
|
156
|
+
if (!slug || !schemaPath || watchFieldPaths.length === 0) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (!hasInitializedWatchedFieldsEffectRef.current) {
|
|
160
|
+
hasInitializedWatchedFieldsEffectRef.current = true;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Watched field changes can happen in bursts, so use a longer debounce.
|
|
164
|
+
const timeout = setTimeout(()=>{
|
|
165
|
+
void fetchOptionsRef.current?.(latestInputValueRef.current);
|
|
166
|
+
}, watchedFieldsDebounceMs);
|
|
167
|
+
return ()=>{
|
|
168
|
+
clearTimeout(timeout);
|
|
169
|
+
};
|
|
170
|
+
}, [
|
|
171
|
+
schemaPath,
|
|
172
|
+
slug,
|
|
173
|
+
watchFieldPaths.length,
|
|
174
|
+
watchedFieldsDebounceMs,
|
|
175
|
+
watchedFieldPathsRefetchToken
|
|
176
|
+
]);
|
|
98
177
|
useEffect(()=>{
|
|
99
178
|
return ()=>{
|
|
100
179
|
if (abortRef.current) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ui/SelectSearchField.tsx"],"sourcesContent":["'use client'\n\nimport type { OptionObject, TextFieldClientComponent } from 'payload'\nimport type { ReactSelectOption } from '@payloadcms/ui'\nimport { SelectInput, useConfig, useDocumentInfo, useField } from '@payloadcms/ui'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { selectSearchEndpoint } from '../endpointName.js'\n\nconst debounceMs = 300\n\nexport const SelectSearchField: TextFieldClientComponent = (props) => {\n const { field, path, schemaPath: schemaPathProp } = props\n\n const { value, setValue, showError } = useField<string | string[]>({\n path,\n })\n\n const { collectionSlug, globalSlug } = useDocumentInfo()\n const { config } = useConfig()\n\n const [options, setOptions] = useState<OptionObject[]>([])\n\n const [inputValue, setInputValue] = useState('')\n const [remoteError, setRemoteError] = useState<string | null>(null)\n\n const abortRef = useRef<AbortController | null>(null)\n\n const entityType = globalSlug ? 'global' : 'collection'\n const slug = globalSlug || collectionSlug\n\n const schemaPath = schemaPathProp ?? field.name\n const hasMany = field.hasMany ?? false\n\n const apiPath = config.routes?.api || '/api'\n const apiRoute = apiPath.startsWith('/') ? apiPath : `/${apiPath}`\n const baseURL = config.serverURL || ''\n const endpointURL = `${baseURL}${apiRoute}${selectSearchEndpoint}`\n\n const selectedValues = useMemo(() => {\n if (hasMany) {\n return Array.isArray(value) ? value.map((entry) => String(entry)) : []\n }\n\n if (Array.isArray(value) || value === null || value === undefined) {\n return []\n }\n\n return [String(value)]\n }, [hasMany, value])\n\n const fetchOptions = useCallback(\n async (query: string) => {\n if (!slug || !schemaPath) {\n setOptions([])\n return\n }\n\n if (abortRef.current) {\n abortRef.current.abort()\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n\n setRemoteError(null)\n\n const res = await fetch(endpointURL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n credentials: 'include',\n signal: controller.signal,\n body: JSON.stringify({\n entityType,\n slug,\n schemaPath,\n query,\n selectedValues,\n }),\n })\n\n if (!res.ok) {\n const errorBody = await res.json().catch(() => null)\n const message = errorBody?.error || 'Failed to fetch options'\n setRemoteError(message)\n setOptions([])\n return\n }\n\n const data = (await res.json()) as { options?: OptionObject[] }\n setOptions(Array.isArray(data.options) ? data.options : [])\n },\n [endpointURL, entityType, schemaPath, selectedValues, slug],\n )\n\n useEffect(() => {\n if (!slug || !schemaPath) {\n return\n }\n\n const timeout = setTimeout(() => {\n void fetchOptions(inputValue)\n }, debounceMs)\n\n return () => {\n clearTimeout(timeout)\n }\n }, [fetchOptions, inputValue, schemaPath, slug])\n\n useEffect(() => {\n return () => {\n if (abortRef.current) {\n abortRef.current.abort()\n }\n }\n }, [])\n\n const handleChange = useCallback(\n (option: ReactSelectOption | ReactSelectOption[] | null) => {\n if (Array.isArray(option)) {\n const values = option.map((entry) => String(entry.value))\n setValue(values)\n return\n }\n\n if (!option) {\n setValue(hasMany ? [] : null)\n return\n }\n\n setValue(String(option.value))\n },\n [hasMany, setValue],\n )\n\n const description = useMemo(() => {\n if (remoteError) {\n return remoteError\n }\n\n return field.admin?.description\n }, [field.admin?.description, remoteError])\n\n const selectValue = useMemo(() => {\n if (hasMany) {\n return Array.isArray(value) ? value : []\n }\n\n return Array.isArray(value) ? '' : (value ?? '')\n }, [hasMany, value])\n\n return (\n <SelectInput\n description={description}\n hasMany={hasMany}\n label={field.label as string}\n localized={field.localized}\n name={field.name}\n onChange={handleChange as (value: ReactSelectOption | ReactSelectOption[]) => void}\n onInputChange={setInputValue}\n options={options}\n path={path}\n required={field.required}\n showError={showError}\n value={selectValue}\n />\n )\n}\n"],"names":["SelectInput","useConfig","useDocumentInfo","useField","useCallback","useEffect","useMemo","useRef","useState","selectSearchEndpoint","debounceMs","SelectSearchField","props","field","path","schemaPath","schemaPathProp","value","setValue","showError","collectionSlug","globalSlug","config","options","setOptions","inputValue","setInputValue","remoteError","setRemoteError","abortRef","entityType","slug","name","hasMany","apiPath","routes","api","apiRoute","startsWith","baseURL","serverURL","endpointURL","selectedValues","Array","isArray","map","entry","String","undefined","fetchOptions","query","current","abort","controller","AbortController","res","fetch","method","headers","credentials","signal","body","JSON","stringify","ok","errorBody","json","catch","message","error","data","timeout","setTimeout","clearTimeout","handleChange","option","values","description","admin","selectValue","label","localized","onChange","onInputChange","required"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/ui/SelectSearchField.tsx"],"sourcesContent":["'use client'\n\nimport type { ReactSelectOption } from '@payloadcms/ui'\nimport type { OptionObject, TextFieldClientComponent, TextFieldClientProps } from 'payload'\n\nimport {\n SelectInput,\n useConfig,\n useDocumentInfo,\n useField,\n useForm,\n useFormFields,\n} from '@payloadcms/ui'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport type { SelectSearchRequest } from '../types.js'\n\nimport { selectSearchEndpoint } from '../endpointName.js'\n\nconst defaultQueryDebounceMs = 300\nconst defaultWatchedFieldsDebounceMs = 700\n\nconst serializeRefetchValue = (value: unknown): string => {\n try {\n return JSON.stringify(value) ?? ''\n } catch {\n return ''\n }\n}\n\ntype SelectSearchFieldClientProps = {\n debounce?: {\n query?: number\n watchedFields?: number\n }\n passDataToSearchFunction?: boolean\n passSiblingDataToSearchFunction?: boolean\n watchFieldPaths?: string[]\n} & TextFieldClientProps\n\nexport const SelectSearchField: TextFieldClientComponent = (\n props: SelectSearchFieldClientProps,\n) => {\n const { field, path, schemaPath: schemaPathProp } = props\n\n const { setValue, showError, value } = useField<string | string[]>({\n path,\n })\n\n const { collectionSlug, globalSlug } = useDocumentInfo()\n const { config } = useConfig()\n const { getData, getSiblingData } = useForm()\n\n const [options, setOptions] = useState<OptionObject[]>([])\n\n const [inputValue, setInputValue] = useState('')\n const [remoteError, setRemoteError] = useState<null | string>(null)\n\n const abortRef = useRef<AbortController | null>(null)\n const fetchOptionsRef = useRef<((query: string) => Promise<void>) | null>(null)\n const latestInputValueRef = useRef('')\n const hasInitializedWatchedFieldsEffectRef = useRef(false)\n\n const entityType = globalSlug ? 'global' : 'collection'\n const slug = globalSlug || collectionSlug\n\n const schemaPath = schemaPathProp ?? field.name\n const hasMany = field.hasMany ?? false\n const passDataToSearchFunction = props.passDataToSearchFunction === true\n const passSiblingDataToSearchFunction = props.passSiblingDataToSearchFunction === true\n // `selectSearch` normalizes these values before passing to `clientProps`.\n const queryDebounceMs = props.debounce?.query ?? defaultQueryDebounceMs\n const watchedFieldsDebounceMs = props.debounce?.watchedFields ?? defaultWatchedFieldsDebounceMs\n const watchFieldPaths = props.watchFieldPaths ?? []\n\n const apiPath = config.routes?.api || '/api'\n const apiRoute = apiPath.startsWith('/') ? apiPath : `/${apiPath}`\n const baseURL = config.serverURL || ''\n const endpointURL = `${baseURL}${apiRoute}${selectSearchEndpoint}`\n\n const selectedValues = useMemo(() => {\n if (hasMany) {\n return Array.isArray(value) ? value.map((entry) => String(entry)) : []\n }\n\n if (Array.isArray(value) || value === null || value === undefined) {\n return []\n }\n\n return [String(value)]\n }, [hasMany, value])\n\n const watchedFieldPathsRefetchToken = useFormFields(([fields]) => {\n if (watchFieldPaths.length === 0) {\n return ''\n }\n\n const watchedPathValues: Array<[string, unknown]> = []\n\n for (const watchPath of watchFieldPaths) {\n if (!Object.prototype.hasOwnProperty.call(fields, watchPath)) {\n continue\n }\n\n watchedPathValues.push([watchPath, fields[watchPath]?.value])\n }\n\n return serializeRefetchValue(watchedPathValues)\n })\n\n const fetchOptions = useCallback(\n async (query: string) => {\n if (!slug || !schemaPath) {\n setOptions([])\n return\n }\n\n if (abortRef.current) {\n abortRef.current.abort()\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n\n setRemoteError(null)\n\n const payload: SelectSearchRequest = {\n slug,\n entityType,\n query,\n schemaPath,\n selectedValues,\n }\n\n if (passDataToSearchFunction) {\n payload.data = getData()\n }\n\n if (passSiblingDataToSearchFunction) {\n payload.siblingData = getSiblingData(path)\n }\n\n const res = await fetch(endpointURL, {\n body: JSON.stringify(payload),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n signal: controller.signal,\n })\n\n if (!res.ok) {\n const errorBody = await res.json().catch(() => null)\n const message = errorBody?.error || 'Failed to fetch options'\n setRemoteError(message)\n setOptions([])\n return\n }\n\n const data = (await res.json()) as { options?: OptionObject[] }\n setOptions(Array.isArray(data.options) ? data.options : [])\n },\n [\n endpointURL,\n entityType,\n getData,\n getSiblingData,\n passDataToSearchFunction,\n passSiblingDataToSearchFunction,\n path,\n schemaPath,\n selectedValues,\n slug,\n ],\n )\n\n useEffect(() => {\n fetchOptionsRef.current = fetchOptions\n }, [fetchOptions])\n\n useEffect(() => {\n latestInputValueRef.current = inputValue\n }, [inputValue])\n\n useEffect(() => {\n if (!slug || !schemaPath) {\n return\n }\n\n // Query typing should feel responsive, so use a shorter debounce.\n const timeout = setTimeout(() => {\n void fetchOptionsRef.current?.(inputValue)\n }, queryDebounceMs)\n\n return () => {\n clearTimeout(timeout)\n }\n }, [inputValue, queryDebounceMs, schemaPath, slug])\n\n useEffect(() => {\n if (!slug || !schemaPath || watchFieldPaths.length === 0) {\n return\n }\n\n if (!hasInitializedWatchedFieldsEffectRef.current) {\n hasInitializedWatchedFieldsEffectRef.current = true\n return\n }\n\n // Watched field changes can happen in bursts, so use a longer debounce.\n const timeout = setTimeout(() => {\n void fetchOptionsRef.current?.(latestInputValueRef.current)\n }, watchedFieldsDebounceMs)\n\n return () => {\n clearTimeout(timeout)\n }\n }, [schemaPath, slug, watchFieldPaths.length, watchedFieldsDebounceMs, watchedFieldPathsRefetchToken])\n\n useEffect(() => {\n return () => {\n if (abortRef.current) {\n abortRef.current.abort()\n }\n }\n }, [])\n\n const handleChange = useCallback(\n (option: null | ReactSelectOption | ReactSelectOption[]) => {\n if (Array.isArray(option)) {\n const values = option.map((entry) => String(entry.value))\n setValue(values)\n return\n }\n\n if (!option) {\n setValue(hasMany ? [] : null)\n return\n }\n\n setValue(String(option.value))\n },\n [hasMany, setValue],\n )\n\n const description = useMemo(() => {\n if (remoteError) {\n return remoteError\n }\n\n return field.admin?.description\n }, [field.admin?.description, remoteError])\n\n const selectValue = useMemo(() => {\n if (hasMany) {\n return Array.isArray(value) ? value : []\n }\n\n return Array.isArray(value) ? '' : (value ?? '')\n }, [hasMany, value])\n\n return (\n <SelectInput\n description={description}\n hasMany={hasMany}\n label={field.label as string}\n localized={field.localized}\n name={field.name}\n onChange={handleChange as (value: ReactSelectOption | ReactSelectOption[]) => void}\n onInputChange={setInputValue}\n options={options}\n path={path}\n required={field.required}\n showError={showError}\n value={selectValue}\n />\n )\n}\n"],"names":["SelectInput","useConfig","useDocumentInfo","useField","useForm","useFormFields","useCallback","useEffect","useMemo","useRef","useState","selectSearchEndpoint","defaultQueryDebounceMs","defaultWatchedFieldsDebounceMs","serializeRefetchValue","value","JSON","stringify","SelectSearchField","props","field","path","schemaPath","schemaPathProp","setValue","showError","collectionSlug","globalSlug","config","getData","getSiblingData","options","setOptions","inputValue","setInputValue","remoteError","setRemoteError","abortRef","fetchOptionsRef","latestInputValueRef","hasInitializedWatchedFieldsEffectRef","entityType","slug","name","hasMany","passDataToSearchFunction","passSiblingDataToSearchFunction","queryDebounceMs","debounce","query","watchedFieldsDebounceMs","watchedFields","watchFieldPaths","apiPath","routes","api","apiRoute","startsWith","baseURL","serverURL","endpointURL","selectedValues","Array","isArray","map","entry","String","undefined","watchedFieldPathsRefetchToken","fields","length","watchedPathValues","watchPath","Object","prototype","hasOwnProperty","call","push","fetchOptions","current","abort","controller","AbortController","payload","data","siblingData","res","fetch","body","credentials","headers","method","signal","ok","errorBody","json","catch","message","error","timeout","setTimeout","clearTimeout","handleChange","option","values","description","admin","selectValue","label","localized","onChange","onInputChange","required"],"mappings":"AAAA;;AAKA,SACEA,WAAW,EACXC,SAAS,EACTC,eAAe,EACfC,QAAQ,EACRC,OAAO,EACPC,aAAa,QACR,iBAAgB;AACvB,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAIzE,SAASC,oBAAoB,QAAQ,qBAAoB;AAEzD,MAAMC,yBAAyB;AAC/B,MAAMC,iCAAiC;AAEvC,MAAMC,wBAAwB,CAACC;IAC7B,IAAI;QACF,OAAOC,KAAKC,SAAS,CAACF,UAAU;IAClC,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAYA,OAAO,MAAMG,oBAA8C,CACzDC;IAEA,MAAM,EAAEC,KAAK,EAAEC,IAAI,EAAEC,YAAYC,cAAc,EAAE,GAAGJ;IAEpD,MAAM,EAAEK,QAAQ,EAAEC,SAAS,EAAEV,KAAK,EAAE,GAAGZ,SAA4B;QACjEkB;IACF;IAEA,MAAM,EAAEK,cAAc,EAAEC,UAAU,EAAE,GAAGzB;IACvC,MAAM,EAAE0B,MAAM,EAAE,GAAG3B;IACnB,MAAM,EAAE4B,OAAO,EAAEC,cAAc,EAAE,GAAG1B;IAEpC,MAAM,CAAC2B,SAASC,WAAW,GAAGtB,SAAyB,EAAE;IAEzD,MAAM,CAACuB,YAAYC,cAAc,GAAGxB,SAAS;IAC7C,MAAM,CAACyB,aAAaC,eAAe,GAAG1B,SAAwB;IAE9D,MAAM2B,WAAW5B,OAA+B;IAChD,MAAM6B,kBAAkB7B,OAAkD;IAC1E,MAAM8B,sBAAsB9B,OAAO;IACnC,MAAM+B,uCAAuC/B,OAAO;IAEpD,MAAMgC,aAAad,aAAa,WAAW;IAC3C,MAAMe,OAAOf,cAAcD;IAE3B,MAAMJ,aAAaC,kBAAkBH,MAAMuB,IAAI;IAC/C,MAAMC,UAAUxB,MAAMwB,OAAO,IAAI;IACjC,MAAMC,2BAA2B1B,MAAM0B,wBAAwB,KAAK;IACpE,MAAMC,kCAAkC3B,MAAM2B,+BAA+B,KAAK;IAClF,0EAA0E;IAC1E,MAAMC,kBAAkB5B,MAAM6B,QAAQ,EAAEC,SAASrC;IACjD,MAAMsC,0BAA0B/B,MAAM6B,QAAQ,EAAEG,iBAAiBtC;IACjE,MAAMuC,kBAAkBjC,MAAMiC,eAAe,IAAI,EAAE;IAEnD,MAAMC,UAAUzB,OAAO0B,MAAM,EAAEC,OAAO;IACtC,MAAMC,WAAWH,QAAQI,UAAU,CAAC,OAAOJ,UAAU,CAAC,CAAC,EAAEA,SAAS;IAClE,MAAMK,UAAU9B,OAAO+B,SAAS,IAAI;IACpC,MAAMC,cAAc,GAAGF,UAAUF,WAAW7C,sBAAsB;IAElE,MAAMkD,iBAAiBrD,QAAQ;QAC7B,IAAIoC,SAAS;YACX,OAAOkB,MAAMC,OAAO,CAAChD,SAASA,MAAMiD,GAAG,CAAC,CAACC,QAAUC,OAAOD,UAAU,EAAE;QACxE;QAEA,IAAIH,MAAMC,OAAO,CAAChD,UAAUA,UAAU,QAAQA,UAAUoD,WAAW;YACjE,OAAO,EAAE;QACX;QAEA,OAAO;YAACD,OAAOnD;SAAO;IACxB,GAAG;QAAC6B;QAAS7B;KAAM;IAEnB,MAAMqD,gCAAgC/D,cAAc,CAAC,CAACgE,OAAO;QAC3D,IAAIjB,gBAAgBkB,MAAM,KAAK,GAAG;YAChC,OAAO;QACT;QAEA,MAAMC,oBAA8C,EAAE;QAEtD,KAAK,MAAMC,aAAapB,gBAAiB;YACvC,IAAI,CAACqB,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACP,QAAQG,YAAY;gBAC5D;YACF;YAEAD,kBAAkBM,IAAI,CAAC;gBAACL;gBAAWH,MAAM,CAACG,UAAU,EAAEzD;aAAM;QAC9D;QAEA,OAAOD,sBAAsByD;IAC/B;IAEA,MAAMO,eAAexE,YACnB,OAAO2C;QACL,IAAI,CAACP,QAAQ,CAACpB,YAAY;YACxBU,WAAW,EAAE;YACb;QACF;QAEA,IAAIK,SAAS0C,OAAO,EAAE;YACpB1C,SAAS0C,OAAO,CAACC,KAAK;QACxB;QAEA,MAAMC,aAAa,IAAIC;QACvB7C,SAAS0C,OAAO,GAAGE;QAEnB7C,eAAe;QAEf,MAAM+C,UAA+B;YACnCzC;YACAD;YACAQ;YACA3B;YACAuC;QACF;QAEA,IAAIhB,0BAA0B;YAC5BsC,QAAQC,IAAI,GAAGvD;QACjB;QAEA,IAAIiB,iCAAiC;YACnCqC,QAAQE,WAAW,GAAGvD,eAAeT;QACvC;QAEA,MAAMiE,MAAM,MAAMC,MAAM3B,aAAa;YACnC4B,MAAMxE,KAAKC,SAAS,CAACkE;YACrBM,aAAa;YACbC,SAAS;gBACP,gBAAgB;YAClB;YACAC,QAAQ;YACRC,QAAQX,WAAWW,MAAM;QAC3B;QAEA,IAAI,CAACN,IAAIO,EAAE,EAAE;YACX,MAAMC,YAAY,MAAMR,IAAIS,IAAI,GAAGC,KAAK,CAAC,IAAM;YAC/C,MAAMC,UAAUH,WAAWI,SAAS;YACpC9D,eAAe6D;YACfjE,WAAW,EAAE;YACb;QACF;QAEA,MAAMoD,OAAQ,MAAME,IAAIS,IAAI;QAC5B/D,WAAW8B,MAAMC,OAAO,CAACqB,KAAKrD,OAAO,IAAIqD,KAAKrD,OAAO,GAAG,EAAE;IAC5D,GACA;QACE6B;QACAnB;QACAZ;QACAC;QACAe;QACAC;QACAzB;QACAC;QACAuC;QACAnB;KACD;IAGHnC,UAAU;QACR+B,gBAAgByC,OAAO,GAAGD;IAC5B,GAAG;QAACA;KAAa;IAEjBvE,UAAU;QACRgC,oBAAoBwC,OAAO,GAAG9C;IAChC,GAAG;QAACA;KAAW;IAEf1B,UAAU;QACR,IAAI,CAACmC,QAAQ,CAACpB,YAAY;YACxB;QACF;QAEA,kEAAkE;QAClE,MAAM6E,UAAUC,WAAW;YACzB,KAAK9D,gBAAgByC,OAAO,GAAG9C;QACjC,GAAGc;QAEH,OAAO;YACLsD,aAAaF;QACf;IACF,GAAG;QAAClE;QAAYc;QAAiBzB;QAAYoB;KAAK;IAElDnC,UAAU;QACR,IAAI,CAACmC,QAAQ,CAACpB,cAAc8B,gBAAgBkB,MAAM,KAAK,GAAG;YACxD;QACF;QAEA,IAAI,CAAC9B,qCAAqCuC,OAAO,EAAE;YACjDvC,qCAAqCuC,OAAO,GAAG;YAC/C;QACF;QAEA,wEAAwE;QACxE,MAAMoB,UAAUC,WAAW;YACzB,KAAK9D,gBAAgByC,OAAO,GAAGxC,oBAAoBwC,OAAO;QAC5D,GAAG7B;QAEH,OAAO;YACLmD,aAAaF;QACf;IACF,GAAG;QAAC7E;QAAYoB;QAAMU,gBAAgBkB,MAAM;QAAEpB;QAAyBkB;KAA8B;IAErG7D,UAAU;QACR,OAAO;YACL,IAAI8B,SAAS0C,OAAO,EAAE;gBACpB1C,SAAS0C,OAAO,CAACC,KAAK;YACxB;QACF;IACF,GAAG,EAAE;IAEL,MAAMsB,eAAehG,YACnB,CAACiG;QACC,IAAIzC,MAAMC,OAAO,CAACwC,SAAS;YACzB,MAAMC,SAASD,OAAOvC,GAAG,CAAC,CAACC,QAAUC,OAAOD,MAAMlD,KAAK;YACvDS,SAASgF;YACT;QACF;QAEA,IAAI,CAACD,QAAQ;YACX/E,SAASoB,UAAU,EAAE,GAAG;YACxB;QACF;QAEApB,SAAS0C,OAAOqC,OAAOxF,KAAK;IAC9B,GACA;QAAC6B;QAASpB;KAAS;IAGrB,MAAMiF,cAAcjG,QAAQ;QAC1B,IAAI2B,aAAa;YACf,OAAOA;QACT;QAEA,OAAOf,MAAMsF,KAAK,EAAED;IACtB,GAAG;QAACrF,MAAMsF,KAAK,EAAED;QAAatE;KAAY;IAE1C,MAAMwE,cAAcnG,QAAQ;QAC1B,IAAIoC,SAAS;YACX,OAAOkB,MAAMC,OAAO,CAAChD,SAASA,QAAQ,EAAE;QAC1C;QAEA,OAAO+C,MAAMC,OAAO,CAAChD,SAAS,KAAMA,SAAS;IAC/C,GAAG;QAAC6B;QAAS7B;KAAM;IAEnB,qBACE,KAACf;QACCyG,aAAaA;QACb7D,SAASA;QACTgE,OAAOxF,MAAMwF,KAAK;QAClBC,WAAWzF,MAAMyF,SAAS;QAC1BlE,MAAMvB,MAAMuB,IAAI;QAChBmE,UAAUR;QACVS,eAAe7E;QACfH,SAASA;QACTV,MAAMA;QACN2F,UAAU5F,MAAM4F,QAAQ;QACxBvF,WAAWA;QACXV,OAAO4F;;AAGb,EAAC"}
|