@10x-media/folder-picker 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/LICENSE +21 -0
  3. package/README.md +54 -0
  4. package/dist/exports/client.d.ts +2 -0
  5. package/dist/exports/client.js +3 -0
  6. package/dist/exports/i18n.d.ts +3 -0
  7. package/dist/exports/i18n.js +3 -0
  8. package/dist/exports/types.d.ts +2 -0
  9. package/dist/exports/types.js +1 -0
  10. package/dist/folder/BulkUploadButton.js +84 -0
  11. package/dist/folder/BulkUploadButton.js.map +1 -0
  12. package/dist/folder/FolderActions.js +133 -0
  13. package/dist/folder/FolderActions.js.map +1 -0
  14. package/dist/folder/FolderBrowser.js +417 -0
  15. package/dist/folder/FolderBrowser.js.map +1 -0
  16. package/dist/folder/chosenUploadIds.js +63 -0
  17. package/dist/folder/chosenUploadIds.js.map +1 -0
  18. package/dist/folder/folder.css +74 -0
  19. package/dist/folder/index.d.ts +13 -0
  20. package/dist/folder/index.js +65 -0
  21. package/dist/folder/index.js.map +1 -0
  22. package/dist/folder/isMacPlatform.js +21 -0
  23. package/dist/folder/isMacPlatform.js.map +1 -0
  24. package/dist/folder/native.js +391 -0
  25. package/dist/folder/native.js.map +1 -0
  26. package/dist/folder/useChosenUploads.js +27 -0
  27. package/dist/folder/useChosenUploads.js.map +1 -0
  28. package/dist/folder/useFolderTargets.js +126 -0
  29. package/dist/folder/useFolderTargets.js.map +1 -0
  30. package/dist/index.d.ts +33 -0
  31. package/dist/index.js +24 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/plugin/registerFolderListView.js +36 -0
  34. package/dist/plugin/registerFolderListView.js.map +1 -0
  35. package/dist/plugin/registerTranslations.js +19 -0
  36. package/dist/plugin/registerTranslations.js.map +1 -0
  37. package/dist/translations/de.js +15 -0
  38. package/dist/translations/de.js.map +1 -0
  39. package/dist/translations/en.js +20 -0
  40. package/dist/translations/en.js.map +1 -0
  41. package/dist/translations/index.d.ts +16 -0
  42. package/dist/translations/index.js +35 -0
  43. package/dist/translations/index.js.map +1 -0
  44. package/dist/translations/keys.d.ts +19 -0
  45. package/dist/translations/keys.js +19 -0
  46. package/dist/translations/keys.js.map +1 -0
  47. package/dist/translations/uk.js +15 -0
  48. package/dist/translations/uk.js.map +1 -0
  49. package/dist/translations/useTranslation.js +12 -0
  50. package/dist/translations/useTranslation.js.map +1 -0
  51. package/package.json +110 -0
@@ -0,0 +1,126 @@
1
+ "use client";
2
+ import { getTranslation } from "@payloadcms/translations";
3
+ import { toast, useConfig, useFolder, useTranslation } from "@payloadcms/ui";
4
+ import React from "react";
5
+ //#region src/folder/useFolderTargets.ts
6
+ /**
7
+ * `fetch` rejects only on a network failure, so a 401, 403 or 500 arrives as an ordinary response.
8
+ * Left unchecked the caller would clear the selection and reload, presenting a refusal as a
9
+ * success: the same rows, no message, and the user believing the move or the delete happened.
10
+ */
11
+ const reportFailure = async (response) => {
12
+ const body = await response.json().catch(() => null);
13
+ toast.error(body?.errors?.[0]?.message ?? `${response.status} ${response.statusText}`);
14
+ };
15
+ /**
16
+ * What the folder actions operate on, shared by the selection bar and the menu so the two can never
17
+ * disagree.
18
+ *
19
+ * A single click selects a card and a double click opens it, so a selection is the target when
20
+ * there is one and the folder in view is the target otherwise. Editing needs exactly one target,
21
+ * which is why it disappears once several are selected.
22
+ */
23
+ const useFolderTargets = ({ collectionSlug, currentFolderName, folderCollectionSlug, folderFieldName, onChanged, parentFolderID }) => {
24
+ const { config, getEntityConfig } = useConfig();
25
+ const { clearSelections, folderID, getSelectedItems, moveToFolder } = useFolder();
26
+ const { i18n, t } = useTranslation();
27
+ const selected = getSelectedItems?.() ?? [];
28
+ const count = selected.length;
29
+ const folderLabel = getTranslation(getEntityConfig({ collectionSlug: folderCollectionSlug })?.labels?.singular ?? "", i18n);
30
+ /**
31
+ * Editing needs one folder; with none selected that is the folder in view. Read out rather than
32
+ * indexed twice, so noUncheckedIndexedAccess narrows once for the branch.
33
+ *
34
+ * A selected document is not a target. The drawer this feeds is keyed to the folder collection,
35
+ * so handing it a document's id sends the admin to `?notFound=<id>`, and Payload's own selection
36
+ * bar hides the action in exactly this case rather than editing the document.
37
+ */
38
+ const [firstSelected] = selected;
39
+ const editTarget = count === 1 && firstSelected && firstSelected.relationTo === folderCollectionSlug ? {
40
+ id: firstSelected.value.id,
41
+ name: String(firstSelected.value._folderOrDocumentTitle ?? "")
42
+ } : count === 0 && folderID ? {
43
+ id: folderID,
44
+ name: currentFolderName
45
+ } : null;
46
+ const move = React.useCallback(async (destination) => {
47
+ const itemsToMove = count > 0 ? selected : [];
48
+ if (itemsToMove.length > 0) {
49
+ await moveToFolder({
50
+ itemsToMove,
51
+ toFolderID: destination.id ?? void 0
52
+ });
53
+ clearSelections();
54
+ await onChanged(folderID ?? null);
55
+ return;
56
+ }
57
+ if (!folderID) return;
58
+ const response = await fetch(`${config.routes.api}/${folderCollectionSlug}/${folderID}`, {
59
+ body: JSON.stringify({ [folderFieldName]: destination.id ?? null }),
60
+ credentials: "include",
61
+ headers: { "Content-Type": "application/json" },
62
+ method: "PATCH"
63
+ });
64
+ if (!response.ok) {
65
+ await reportFailure(response);
66
+ return;
67
+ }
68
+ await onChanged(folderID);
69
+ }, [
70
+ clearSelections,
71
+ config.routes.api,
72
+ count,
73
+ folderCollectionSlug,
74
+ folderFieldName,
75
+ folderID,
76
+ moveToFolder,
77
+ onChanged,
78
+ selected
79
+ ]);
80
+ const remove = React.useCallback(async () => {
81
+ const targets = count > 0 ? selected : folderID ? [{
82
+ relationTo: folderCollectionSlug,
83
+ value: { id: folderID }
84
+ }] : [];
85
+ if (targets.length === 0) return;
86
+ for (const item of targets) {
87
+ const response = await fetch(`${config.routes.api}/${item.relationTo}/${item.value.id}`, {
88
+ credentials: "include",
89
+ method: "DELETE"
90
+ });
91
+ if (!response.ok) {
92
+ await reportFailure(response);
93
+ break;
94
+ }
95
+ }
96
+ clearSelections();
97
+ await onChanged(count > 0 ? folderID ?? null : parentFolderID ?? null);
98
+ }, [
99
+ clearSelections,
100
+ config.routes.api,
101
+ count,
102
+ folderCollectionSlug,
103
+ folderID,
104
+ onChanged,
105
+ parentFolderID,
106
+ selected
107
+ ]);
108
+ return {
109
+ clearSelections,
110
+ collectionSlug,
111
+ count,
112
+ editTarget,
113
+ folderLabel,
114
+ hasTarget: count > 0 || Boolean(folderID),
115
+ move,
116
+ remove,
117
+ selected,
118
+ t,
119
+ viewFolderID: folderID ?? void 0,
120
+ viewFolderName: currentFolderName
121
+ };
122
+ };
123
+ //#endregion
124
+ export { useFolderTargets };
125
+
126
+ //# sourceMappingURL=useFolderTargets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFolderTargets.js","names":[],"sources":["../../src/folder/useFolderTargets.ts"],"sourcesContent":["'use client'\n\nimport { getTranslation } from '@payloadcms/translations'\nimport { toast, useConfig, useFolder, useTranslation } from '@payloadcms/ui'\nimport type { CollectionSlug } from 'payload'\nimport type { FolderOrDocument } from 'payload/shared'\nimport React from 'react'\n\n/**\n * `fetch` rejects only on a network failure, so a 401, 403 or 500 arrives as an ordinary response.\n * Left unchecked the caller would clear the selection and reload, presenting a refusal as a\n * success: the same rows, no message, and the user believing the move or the delete happened.\n */\nconst reportFailure = async (response: Response): Promise<void> => {\n\tconst body = (await response.json().catch(() => null)) as null | {\n\t\terrors?: { message?: string }[]\n\t}\n\n\ttoast.error(body?.errors?.[0]?.message ?? `${response.status} ${response.statusText}`)\n}\n\ntype Args = {\n\tcollectionSlug: CollectionSlug\n\tcurrentFolderName: string\n\tfolderCollectionSlug: CollectionSlug\n\t/** `config.folders.fieldName`, which a host is free to rename away from the default `folder`. */\n\tfolderFieldName: string\n\tonChanged: (nextFolderID: null | number | string) => Promise<void> | void\n\tparentFolderID?: number | string\n}\n\n/**\n * What the folder actions operate on, shared by the selection bar and the menu so the two can never\n * disagree.\n *\n * A single click selects a card and a double click opens it, so a selection is the target when\n * there is one and the folder in view is the target otherwise. Editing needs exactly one target,\n * which is why it disappears once several are selected.\n */\nexport const useFolderTargets = ({\n\tcollectionSlug,\n\tcurrentFolderName,\n\tfolderCollectionSlug,\n\tfolderFieldName,\n\tonChanged,\n\tparentFolderID,\n}: Args) => {\n\tconst { config, getEntityConfig } = useConfig()\n\tconst { clearSelections, folderID, getSelectedItems, moveToFolder } = useFolder()\n\tconst { i18n, t } = useTranslation()\n\n\tconst selected: FolderOrDocument[] = getSelectedItems?.() ?? []\n\tconst count = selected.length\n\tconst folderLabel = getTranslation(\n\t\tgetEntityConfig({ collectionSlug: folderCollectionSlug })?.labels?.singular ?? '',\n\t\ti18n\n\t)\n\n\t/**\n\t * Editing needs one folder; with none selected that is the folder in view. Read out rather than\n\t * indexed twice, so noUncheckedIndexedAccess narrows once for the branch.\n\t *\n\t * A selected document is not a target. The drawer this feeds is keyed to the folder collection,\n\t * so handing it a document's id sends the admin to `?notFound=<id>`, and Payload's own selection\n\t * bar hides the action in exactly this case rather than editing the document.\n\t */\n\tconst [firstSelected] = selected\n\tconst editTarget =\n\t\tcount === 1 && firstSelected && firstSelected.relationTo === folderCollectionSlug\n\t\t\t? {\n\t\t\t\t\tid: firstSelected.value.id,\n\t\t\t\t\tname: String(firstSelected.value._folderOrDocumentTitle ?? ''),\n\t\t\t\t}\n\t\t\t: count === 0 && folderID\n\t\t\t\t? { id: folderID, name: currentFolderName }\n\t\t\t\t: null\n\n\tconst move = React.useCallback(\n\t\tasync (destination: { id: null | number | string }) => {\n\t\t\tconst itemsToMove = count > 0 ? selected : []\n\t\t\tif (itemsToMove.length > 0) {\n\t\t\t\tawait moveToFolder({ itemsToMove, toFolderID: destination.id ?? undefined })\n\t\t\t\tclearSelections()\n\t\t\t\tawait onChanged(folderID ?? null)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Nothing selected: the folder in view is being moved, so follow it. A folder's parent is\n\t\t\t// held in the same configured field its documents use, which the host may have renamed.\n\t\t\tif (!folderID) return\n\t\t\tconst response = await fetch(`${config.routes.api}/${folderCollectionSlug}/${folderID}`, {\n\t\t\t\tbody: JSON.stringify({ [folderFieldName]: destination.id ?? null }),\n\t\t\t\tcredentials: 'include',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tmethod: 'PATCH',\n\t\t\t})\n\n\t\t\tif (!response.ok) {\n\t\t\t\tawait reportFailure(response)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tawait onChanged(folderID)\n\t\t},\n\t\t[\n\t\t\tclearSelections,\n\t\t\tconfig.routes.api,\n\t\t\tcount,\n\t\t\tfolderCollectionSlug,\n\t\t\tfolderFieldName,\n\t\t\tfolderID,\n\t\t\tmoveToFolder,\n\t\t\tonChanged,\n\t\t\tselected,\n\t\t]\n\t)\n\n\tconst remove = React.useCallback(async () => {\n\t\tconst targets =\n\t\t\tcount > 0\n\t\t\t\t? selected\n\t\t\t\t: folderID\n\t\t\t\t\t? [{ relationTo: folderCollectionSlug, value: { id: folderID } }]\n\t\t\t\t\t: []\n\t\tif (targets.length === 0) return\n\n\t\tfor (const item of targets) {\n\t\t\tconst response = await fetch(`${config.routes.api}/${item.relationTo}/${item.value.id}`, {\n\t\t\t\tcredentials: 'include',\n\t\t\t\tmethod: 'DELETE',\n\t\t\t})\n\n\t\t\t// Stopping on the first refusal rather than carrying on: whatever denied one delete, a\n\t\t\t// permission or a relationship, will deny the rest, and the ones already gone still need\n\t\t\t// the view refreshed below.\n\t\t\tif (!response.ok) {\n\t\t\t\tawait reportFailure(response)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tclearSelections()\n\t\t// Deleting the folder in view leaves nowhere to stand, so fall back to its parent.\n\t\tawait onChanged(count > 0 ? (folderID ?? null) : (parentFolderID ?? null))\n\t}, [\n\t\tclearSelections,\n\t\tconfig.routes.api,\n\t\tcount,\n\t\tfolderCollectionSlug,\n\t\tfolderID,\n\t\tonChanged,\n\t\tparentFolderID,\n\t\tselected,\n\t])\n\n\treturn {\n\t\tclearSelections,\n\t\tcollectionSlug,\n\t\tcount,\n\t\teditTarget,\n\t\tfolderLabel,\n\t\thasTarget: count > 0 || Boolean(folderID),\n\t\tmove,\n\t\tremove,\n\t\tselected,\n\t\tt,\n\t\tviewFolderID: folderID ?? undefined,\n\t\tviewFolderName: currentFolderName,\n\t}\n}\n"],"mappings":";;;;;;;;;;AAaA,MAAM,gBAAgB,OAAO,aAAsC;CAClE,MAAM,OAAQ,MAAM,SAAS,KAAK,EAAE,YAAY,IAAI;CAIpD,MAAM,MAAM,MAAM,SAAS,IAAI,WAAW,GAAG,SAAS,OAAO,GAAG,SAAS,YAAY;AACtF;;;;;;;;;AAoBA,MAAa,oBAAoB,EAChC,gBACA,mBACA,sBACA,iBACA,WACA,qBACW;CACX,MAAM,EAAE,QAAQ,oBAAoB,UAAU;CAC9C,MAAM,EAAE,iBAAiB,UAAU,kBAAkB,iBAAiB,UAAU;CAChF,MAAM,EAAE,MAAM,MAAM,eAAe;CAEnC,MAAM,WAA+B,mBAAmB,KAAK,CAAC;CAC9D,MAAM,QAAQ,SAAS;CACvB,MAAM,cAAc,eACnB,gBAAgB,EAAE,gBAAgB,qBAAqB,CAAC,GAAG,QAAQ,YAAY,IAC/E,IACD;;;;;;;;;CAUA,MAAM,CAAC,iBAAiB;CACxB,MAAM,aACL,UAAU,KAAK,iBAAiB,cAAc,eAAe,uBAC1D;EACA,IAAI,cAAc,MAAM;EACxB,MAAM,OAAO,cAAc,MAAM,0BAA0B,EAAE;CAC9D,IACC,UAAU,KAAK,WACd;EAAE,IAAI;EAAU,MAAM;CAAkB,IACxC;CAEL,MAAM,OAAO,MAAM,YAClB,OAAO,gBAAgD;EACtD,MAAM,cAAc,QAAQ,IAAI,WAAW,CAAC;EAC5C,IAAI,YAAY,SAAS,GAAG;GAC3B,MAAM,aAAa;IAAE;IAAa,YAAY,YAAY,MAAM,KAAA;GAAU,CAAC;GAC3E,gBAAgB;GAChB,MAAM,UAAU,YAAY,IAAI;GAChC;EACD;EAIA,IAAI,CAAC,UAAU;EACf,MAAM,WAAW,MAAM,MAAM,GAAG,OAAO,OAAO,IAAI,GAAG,qBAAqB,GAAG,YAAY;GACxF,MAAM,KAAK,UAAU,GAAG,kBAAkB,YAAY,MAAM,KAAK,CAAC;GAClE,aAAa;GACb,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,QAAQ;EACT,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GACjB,MAAM,cAAc,QAAQ;GAC5B;EACD;EAEA,MAAM,UAAU,QAAQ;CACzB,GACA;EACC;EACA,OAAO,OAAO;EACd;EACA;EACA;EACA;EACA;EACA;EACA;CACD,CACD;CAEA,MAAM,SAAS,MAAM,YAAY,YAAY;EAC5C,MAAM,UACL,QAAQ,IACL,WACA,WACC,CAAC;GAAE,YAAY;GAAsB,OAAO,EAAE,IAAI,SAAS;EAAE,CAAC,IAC9D,CAAC;EACN,IAAI,QAAQ,WAAW,GAAG;EAE1B,KAAK,MAAM,QAAQ,SAAS;GAC3B,MAAM,WAAW,MAAM,MAAM,GAAG,OAAO,OAAO,IAAI,GAAG,KAAK,WAAW,GAAG,KAAK,MAAM,MAAM;IACxF,aAAa;IACb,QAAQ;GACT,CAAC;GAKD,IAAI,CAAC,SAAS,IAAI;IACjB,MAAM,cAAc,QAAQ;IAC5B;GACD;EACD;EAEA,gBAAgB;EAEhB,MAAM,UAAU,QAAQ,IAAK,YAAY,OAAS,kBAAkB,IAAK;CAC1E,GAAG;EACF;EACA,OAAO,OAAO;EACd;EACA;EACA;EACA;EACA;EACA;CACD,CAAC;CAED,OAAO;EACN;EACA;EACA;EACA;EACA;EACA,WAAW,QAAQ,KAAK,QAAQ,QAAQ;EACxC;EACA;EACA;EACA;EACA,cAAc,YAAY,KAAA;EAC1B,gBAAgB;CACjB;AACD"}
@@ -0,0 +1,33 @@
1
+ import { TranslationsOption } from "./translations/index.js";
2
+
3
+ //#region src/index.d.ts
4
+ type FolderPickerPluginOptions = {
5
+ /**
6
+ * Disable the plugin entirely (incoming config returned untouched).
7
+ * Useful for opting out per environment without removing the plugin call.
8
+ */
9
+ disabled?: boolean;
10
+ /**
11
+ * Per-locale overrides for this plugin's UI strings, keyed by the typed
12
+ * translation keys exported from `@10x-media/folder-picker/i18n`. Values win
13
+ * over the built-in locales key-by-key; locales the plugin does not ship are
14
+ * added whole. App-level `i18n.translations` still wins over both.
15
+ */
16
+ translations?: TranslationsOption;
17
+ };
18
+ declare module 'payload' {
19
+ interface RegisteredPlugins {
20
+ '@10x-media/folder-picker': FolderPickerPluginOptions;
21
+ }
22
+ }
23
+ /**
24
+ * Folder Picker plugin for Payload v3. Swaps the list view of every folder-enabled
25
+ * collection so the list drawer browses folders instead of a flat list. Payload
26
+ * resolves that one component for both the collection route and the drawer, so upload
27
+ * fields, relationship fields and the lexical upload node are covered without patching
28
+ * a single field. Authored with `definePlugin` so sibling plugins can detect it by slug.
29
+ */
30
+ declare const folderPicker: (options: FolderPickerPluginOptions) => import("payload").Plugin;
31
+ //#endregion
32
+ export { FolderPickerPluginOptions, type FolderPickerPluginOptions as PluginOptions, folderPicker };
33
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ import { registerFolderListView } from "./plugin/registerFolderListView.js";
2
+ import { registerTranslations } from "./plugin/registerTranslations.js";
3
+ import { definePlugin } from "payload";
4
+ //#region src/index.ts
5
+ /**
6
+ * Folder Picker plugin for Payload v3. Swaps the list view of every folder-enabled
7
+ * collection so the list drawer browses folders instead of a flat list. Payload
8
+ * resolves that one component for both the collection route and the drawer, so upload
9
+ * fields, relationship fields and the lexical upload node are covered without patching
10
+ * a single field. Authored with `definePlugin` so sibling plugins can detect it by slug.
11
+ */
12
+ const folderPicker = definePlugin({
13
+ slug: "@10x-media/folder-picker",
14
+ plugin: ({ config, plugins: _plugins, ...options }) => {
15
+ if (options.disabled === true) return config;
16
+ registerTranslations(config, options.translations);
17
+ registerFolderListView(config);
18
+ return config;
19
+ }
20
+ });
21
+ //#endregion
22
+ export { folderPicker };
23
+
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Config, definePlugin } from 'payload'\nimport { registerFolderListView } from './plugin/registerFolderListView'\nimport { registerTranslations } from './plugin/registerTranslations'\nimport type { TranslationsOption } from './translations'\nexport type FolderPickerPluginOptions = {\n\t/**\n\t * Disable the plugin entirely (incoming config returned untouched).\n\t * Useful for opting out per environment without removing the plugin call.\n\t */\n\tdisabled?: boolean\n\t/**\n\t * Per-locale overrides for this plugin's UI strings, keyed by the typed\n\t * translation keys exported from `@10x-media/folder-picker/i18n`. Values win\n\t * over the built-in locales key-by-key; locales the plugin does not ship are\n\t * added whole. App-level `i18n.translations` still wins over both.\n\t */\n\ttranslations?: TranslationsOption\n}\n\ndeclare module 'payload' {\n\tinterface RegisteredPlugins {\n\t\t'@10x-media/folder-picker': FolderPickerPluginOptions\n\t}\n}\n\n/**\n * Folder Picker plugin for Payload v3. Swaps the list view of every folder-enabled\n * collection so the list drawer browses folders instead of a flat list. Payload\n * resolves that one component for both the collection route and the drawer, so upload\n * fields, relationship fields and the lexical upload node are covered without patching\n * a single field. Authored with `definePlugin` so sibling plugins can detect it by slug.\n */\nexport const folderPicker = definePlugin<FolderPickerPluginOptions>({\n\tslug: '@10x-media/folder-picker',\n\tplugin: ({ config, plugins: _plugins, ...options }): Config => {\n\t\tif (options.disabled === true) {\n\t\t\treturn config\n\t\t}\n\t\tregisterTranslations(config, options.translations)\n\n\t\tregisterFolderListView(config)\n\t\treturn config\n\t},\n})\n\nexport type { FolderPickerPluginOptions as PluginOptions }\n"],"mappings":";;;;;;;;;;;AAgCA,MAAa,eAAe,aAAwC;CACnE,MAAM;CACN,SAAS,EAAE,QAAQ,SAAS,UAAU,GAAG,cAAsB;EAC9D,IAAI,QAAQ,aAAa,MACxB,OAAO;EAER,qBAAqB,QAAQ,QAAQ,YAAY;EAEjD,uBAAuB,MAAM;EAC7B,OAAO;CACR;AACD,CAAC"}
@@ -0,0 +1,36 @@
1
+ //#region src/plugin/registerFolderListView.ts
2
+ const LIST_VIEW = "@10x-media/folder-picker/client#FolderListView";
3
+ /**
4
+ * Gives every folder-enabled collection a list view that offers folder browsing inside a list
5
+ * drawer. Payload renders `admin.components.views.list.Component` for both the list route and the
6
+ * drawer, so one component covers the upload field's "choose from existing" without touching the
7
+ * upload field itself. Collections that already declare a custom list view are left alone.
8
+ */
9
+ const registerFolderListView = (config) => {
10
+ if (!config.folders) return;
11
+ config.collections = config.collections?.map((collection) => {
12
+ if (!collection.folders) return collection;
13
+ const views = collection.admin?.components?.views;
14
+ if (views?.list?.Component) return collection;
15
+ return {
16
+ ...collection,
17
+ admin: {
18
+ ...collection.admin,
19
+ components: {
20
+ ...collection.admin?.components,
21
+ views: {
22
+ ...views,
23
+ list: {
24
+ ...views?.list,
25
+ Component: LIST_VIEW
26
+ }
27
+ }
28
+ }
29
+ }
30
+ };
31
+ });
32
+ };
33
+ //#endregion
34
+ export { registerFolderListView };
35
+
36
+ //# sourceMappingURL=registerFolderListView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registerFolderListView.js","names":[],"sources":["../../src/plugin/registerFolderListView.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nconst LIST_VIEW = '@10x-media/folder-picker/client#FolderListView'\n\n/**\n * Gives every folder-enabled collection a list view that offers folder browsing inside a list\n * drawer. Payload renders `admin.components.views.list.Component` for both the list route and the\n * drawer, so one component covers the upload field's \"choose from existing\" without touching the\n * upload field itself. Collections that already declare a custom list view are left alone.\n */\nexport const registerFolderListView = (config: Config): void => {\n\tif (!config.folders) {\n\t\treturn\n\t}\n\n\tconfig.collections = config.collections?.map((collection) => {\n\t\tif (!collection.folders) {\n\t\t\treturn collection\n\t\t}\n\n\t\tconst views = collection.admin?.components?.views\n\t\tif (views?.list?.Component) {\n\t\t\treturn collection\n\t\t}\n\n\t\treturn {\n\t\t\t...collection,\n\t\t\tadmin: {\n\t\t\t\t...collection.admin,\n\t\t\t\tcomponents: {\n\t\t\t\t\t...collection.admin?.components,\n\t\t\t\t\tviews: {\n\t\t\t\t\t\t...views,\n\t\t\t\t\t\tlist: { ...views?.list, Component: LIST_VIEW },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t})\n}\n"],"mappings":";AAEA,MAAM,YAAY;;;;;;;AAQlB,MAAa,0BAA0B,WAAyB;CAC/D,IAAI,CAAC,OAAO,SACX;CAGD,OAAO,cAAc,OAAO,aAAa,KAAK,eAAe;EAC5D,IAAI,CAAC,WAAW,SACf,OAAO;EAGR,MAAM,QAAQ,WAAW,OAAO,YAAY;EAC5C,IAAI,OAAO,MAAM,WAChB,OAAO;EAGR,OAAO;GACN,GAAG;GACH,OAAO;IACN,GAAG,WAAW;IACd,YAAY;KACX,GAAG,WAAW,OAAO;KACrB,OAAO;MACN,GAAG;MACH,MAAM;OAAE,GAAG,OAAO;OAAM,WAAW;MAAU;KAC9C;IACD;GACD;EACD;CACD,CAAC;AACF"}
@@ -0,0 +1,19 @@
1
+ import { toNested, translations } from "../translations/index.js";
2
+ import { deepMergeSimple } from "payload/shared";
3
+ //#region src/plugin/registerTranslations.ts
4
+ /**
5
+ * Merge this plugin's translations into the host config. Plugin-level
6
+ * `overrides` win over the built-ins key-by-key and may add locales the plugin
7
+ * does not ship. A host value wins over both (`deepMergeSimple` lets the second
8
+ * argument override), so projects can override any string.
9
+ */
10
+ const registerTranslations = (config, overrides) => {
11
+ const nested = {};
12
+ for (const [locale, flat] of Object.entries(overrides ?? {})) nested[locale] = toNested(flat);
13
+ config.i18n ??= {};
14
+ config.i18n.translations = deepMergeSimple(deepMergeSimple(translations, nested), config.i18n.translations ?? {});
15
+ };
16
+ //#endregion
17
+ export { registerTranslations };
18
+
19
+ //# sourceMappingURL=registerTranslations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registerTranslations.js","names":[],"sources":["../../src/plugin/registerTranslations.ts"],"sourcesContent":["import type { Config } from 'payload'\nimport { deepMergeSimple } from 'payload/shared'\n\nimport { type TranslationsOption, toNested, translations } from '../translations'\n\ntype Translations = NonNullable<NonNullable<Config['i18n']>['translations']>\n\n/**\n * Merge this plugin's translations into the host config. Plugin-level\n * `overrides` win over the built-ins key-by-key and may add locales the plugin\n * does not ship. A host value wins over both (`deepMergeSimple` lets the second\n * argument override), so projects can override any string.\n */\nexport const registerTranslations = (config: Config, overrides?: TranslationsOption): void => {\n\tconst nested: Record<string, Record<string, Record<string, string>>> = {}\n\tfor (const [locale, flat] of Object.entries(overrides ?? {})) {\n\t\tnested[locale] = toNested(flat)\n\t}\n\tconfig.i18n ??= {}\n\tconfig.i18n.translations = deepMergeSimple<Translations>(\n\t\tdeepMergeSimple(translations, nested),\n\t\tconfig.i18n.translations ?? {}\n\t)\n}\n"],"mappings":";;;;;;;;;AAaA,MAAa,wBAAwB,QAAgB,cAAyC;CAC7F,MAAM,SAAiE,CAAC;CACxE,KAAK,MAAM,CAAC,QAAQ,SAAS,OAAO,QAAQ,aAAa,CAAC,CAAC,GAC1D,OAAO,UAAU,SAAS,IAAI;CAE/B,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,eAAe,gBAC1B,gBAAgB,cAAc,MAAM,GACpC,OAAO,KAAK,gBAAgB,CAAC,CAC9B;AACD"}
@@ -0,0 +1,15 @@
1
+ import { keys } from "./keys.js";
2
+ //#region src/translations/de.ts
3
+ const de = {
4
+ [keys.gridView]: "Als Raster anzeigen",
5
+ [keys.listView]: "Als Liste anzeigen",
6
+ [keys.orderLabel]: "Reihenfolge",
7
+ [keys.pickManyHint]: "{{modifier}} halten, um mehrere auszuwählen, {{range}} für einen Bereich.",
8
+ [keys.pluginName]: "Ordnerauswahl",
9
+ [keys.retry]: "Erneut versuchen",
10
+ [keys.sortByLabel]: "Sortieren nach"
11
+ };
12
+ //#endregion
13
+ export { de };
14
+
15
+ //# sourceMappingURL=de.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"de.js","names":[],"sources":["../../src/translations/de.ts"],"sourcesContent":["import { keys, type TranslationKey } from './keys'\n\nexport const de: Record<TranslationKey, string> = {\n\t[keys.gridView]: 'Als Raster anzeigen',\n\t[keys.listView]: 'Als Liste anzeigen',\n\t[keys.orderLabel]: 'Reihenfolge',\n\t[keys.pickManyHint]: '{{modifier}} halten, um mehrere auszuwählen, {{range}} für einen Bereich.',\n\t[keys.pluginName]: 'Ordnerauswahl',\n\t[keys.retry]: 'Erneut versuchen',\n\t[keys.sortByLabel]: 'Sortieren nach',\n}\n"],"mappings":";;AAEA,MAAa,KAAqC;EAChD,KAAK,WAAW;EAChB,KAAK,WAAW;EAChB,KAAK,aAAa;EAClB,KAAK,eAAe;EACpB,KAAK,aAAa;EAClB,KAAK,QAAQ;EACb,KAAK,cAAc;AACrB"}
@@ -0,0 +1,20 @@
1
+ import { keys } from "./keys.js";
2
+ //#region src/translations/en.ts
3
+ /**
4
+ * English values, keyed by the typed constants in `keys.ts` so the two stay in
5
+ * lockstep. The `Record<TranslationKey, string>` annotation makes a missing or
6
+ * unknown key a type error. `translations/index.ts` nests these for Payload.
7
+ */
8
+ const en = {
9
+ [keys.gridView]: "Show as grid",
10
+ [keys.listView]: "Show as list",
11
+ [keys.orderLabel]: "Order",
12
+ [keys.pickManyHint]: "Hold {{modifier}} to pick more than one, {{range}} to pick a range.",
13
+ [keys.pluginName]: "Folder Picker",
14
+ [keys.retry]: "Try again",
15
+ [keys.sortByLabel]: "Sort by"
16
+ };
17
+ //#endregion
18
+ export { en };
19
+
20
+ //# sourceMappingURL=en.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"en.js","names":[],"sources":["../../src/translations/en.ts"],"sourcesContent":["import { keys, type TranslationKey } from './keys'\n\n/**\n * English values, keyed by the typed constants in `keys.ts` so the two stay in\n * lockstep. The `Record<TranslationKey, string>` annotation makes a missing or\n * unknown key a type error. `translations/index.ts` nests these for Payload.\n */\nexport const en: Record<TranslationKey, string> = {\n\t[keys.gridView]: 'Show as grid',\n\t[keys.listView]: 'Show as list',\n\t[keys.orderLabel]: 'Order',\n\t[keys.pickManyHint]: 'Hold {{modifier}} to pick more than one, {{range}} to pick a range.',\n\t[keys.pluginName]: 'Folder Picker',\n\t[keys.retry]: 'Try again',\n\t[keys.sortByLabel]: 'Sort by',\n}\n"],"mappings":";;;;;;;AAOA,MAAa,KAAqC;EAChD,KAAK,WAAW;EAChB,KAAK,WAAW;EAChB,KAAK,aAAa;EAClB,KAAK,eAAe;EACpB,KAAK,aAAa;EAClB,KAAK,QAAQ;EACb,KAAK,cAAc;AACrB"}
@@ -0,0 +1,16 @@
1
+ import { TranslationKey, keys } from "./keys.js";
2
+
3
+ //#region src/translations/index.d.ts
4
+ /** Per-locale string overrides keyed by this plugin's typed translation keys. */
5
+ type TranslationsOption = {
6
+ [locale: string]: Partial<Record<TranslationKey, string>>;
7
+ };
8
+ /** Per-locale messages merged into `config.i18n.translations`. */
9
+ declare const translations: {
10
+ de: Record<string, Record<string, string>>;
11
+ en: Record<string, Record<string, string>>;
12
+ uk: Record<string, Record<string, string>>;
13
+ };
14
+ //#endregion
15
+ export { TranslationsOption, translations };
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,35 @@
1
+ import "./keys.js";
2
+ import { de } from "./de.js";
3
+ import { en } from "./en.js";
4
+ import { uk } from "./uk.js";
5
+ //#region src/translations/index.ts
6
+ /**
7
+ * Flat `folderPicker:foo` entries to the nested `{ folderPicker: { foo } }`
8
+ * shape Payload resolves `t('folderPicker:foo')` against (it splits on `:`).
9
+ * Undefined values are skipped so `Partial` override maps pass through.
10
+ * A key with no `:` has no namespace to nest under, so it is dropped rather than
11
+ * split at -1, which would file it under the key with its last character shaved off.
12
+ */
13
+ const toNested = (flat) => {
14
+ const out = {};
15
+ for (const [fullKey, value] of Object.entries(flat)) {
16
+ if (typeof value !== "string") continue;
17
+ const separator = fullKey.indexOf(":");
18
+ if (separator < 1) continue;
19
+ const namespace = fullKey.slice(0, separator);
20
+ const bucket = out[namespace] ?? {};
21
+ bucket[fullKey.slice(separator + 1)] = value;
22
+ out[namespace] = bucket;
23
+ }
24
+ return out;
25
+ };
26
+ /** Per-locale messages merged into `config.i18n.translations`. */
27
+ const translations = {
28
+ de: toNested(de),
29
+ en: toNested(en),
30
+ uk: toNested(uk)
31
+ };
32
+ //#endregion
33
+ export { toNested, translations };
34
+
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/translations/index.ts"],"sourcesContent":["import { de } from './de'\nimport { en } from './en'\nimport type { TranslationKey } from './keys'\nimport { uk } from './uk'\n\nexport type { TranslationKey } from './keys'\nexport { keys } from './keys'\n\n/** Per-locale string overrides keyed by this plugin's typed translation keys. */\nexport type TranslationsOption = {\n\t[locale: string]: Partial<Record<TranslationKey, string>>\n}\n\n/**\n * Flat `folderPicker:foo` entries to the nested `{ folderPicker: { foo } }`\n * shape Payload resolves `t('folderPicker:foo')` against (it splits on `:`).\n * Undefined values are skipped so `Partial` override maps pass through.\n * A key with no `:` has no namespace to nest under, so it is dropped rather than\n * split at -1, which would file it under the key with its last character shaved off.\n */\nexport const toNested = (flat: {\n\t[key: string]: string | undefined\n}): Record<string, Record<string, string>> => {\n\tconst out: Record<string, Record<string, string>> = {}\n\tfor (const [fullKey, value] of Object.entries(flat)) {\n\t\tif (typeof value !== 'string') {\n\t\t\tcontinue\n\t\t}\n\t\tconst separator = fullKey.indexOf(':')\n\t\tif (separator < 1) {\n\t\t\tcontinue\n\t\t}\n\t\tconst namespace = fullKey.slice(0, separator)\n\t\tconst bucket = out[namespace] ?? {}\n\t\tbucket[fullKey.slice(separator + 1)] = value\n\t\tout[namespace] = bucket\n\t}\n\treturn out\n}\n\n/** Per-locale messages merged into `config.i18n.translations`. */\nexport const translations = {\n\tde: toNested(de),\n\ten: toNested(en),\n\tuk: toNested(uk),\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,MAAa,YAAY,SAEqB;CAC7C,MAAM,MAA8C,CAAC;CACrD,KAAK,MAAM,CAAC,SAAS,UAAU,OAAO,QAAQ,IAAI,GAAG;EACpD,IAAI,OAAO,UAAU,UACpB;EAED,MAAM,YAAY,QAAQ,QAAQ,GAAG;EACrC,IAAI,YAAY,GACf;EAED,MAAM,YAAY,QAAQ,MAAM,GAAG,SAAS;EAC5C,MAAM,SAAS,IAAI,cAAc,CAAC;EAClC,OAAO,QAAQ,MAAM,YAAY,CAAC,KAAK;EACvC,IAAI,aAAa;CAClB;CACA,OAAO;AACR;;AAGA,MAAa,eAAe;CAC3B,IAAI,SAAS,EAAE;CACf,IAAI,SAAS,EAAE;CACf,IAAI,SAAS,EAAE;AAChB"}
@@ -0,0 +1,19 @@
1
+ //#region src/translations/keys.d.ts
2
+ /**
3
+ * Typed translation keys. Lookups must go through these constants, not string
4
+ * literals (enforced by requireI18nKeysTyped.grit). Every key here must have a
5
+ * value in every locale (`en.ts`), or it is a type error.
6
+ */
7
+ declare const keys: {
8
+ readonly gridView: "folderPicker:gridView";
9
+ readonly listView: "folderPicker:listView";
10
+ readonly orderLabel: "folderPicker:orderLabel";
11
+ readonly pickManyHint: "folderPicker:pickManyHint";
12
+ readonly pluginName: "folderPicker:pluginName";
13
+ readonly retry: "folderPicker:retry";
14
+ readonly sortByLabel: "folderPicker:sortByLabel";
15
+ };
16
+ type TranslationKey = (typeof keys)[keyof typeof keys];
17
+ //#endregion
18
+ export { TranslationKey, keys };
19
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1,19 @@
1
+ //#region src/translations/keys.ts
2
+ /**
3
+ * Typed translation keys. Lookups must go through these constants, not string
4
+ * literals (enforced by requireI18nKeysTyped.grit). Every key here must have a
5
+ * value in every locale (`en.ts`), or it is a type error.
6
+ */
7
+ const keys = {
8
+ gridView: "folderPicker:gridView",
9
+ listView: "folderPicker:listView",
10
+ orderLabel: "folderPicker:orderLabel",
11
+ pickManyHint: "folderPicker:pickManyHint",
12
+ pluginName: "folderPicker:pluginName",
13
+ retry: "folderPicker:retry",
14
+ sortByLabel: "folderPicker:sortByLabel"
15
+ };
16
+ //#endregion
17
+ export { keys };
18
+
19
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","names":[],"sources":["../../src/translations/keys.ts"],"sourcesContent":["/**\n * Typed translation keys. Lookups must go through these constants, not string\n * literals (enforced by requireI18nKeysTyped.grit). Every key here must have a\n * value in every locale (`en.ts`), or it is a type error.\n */\nexport const keys = {\n\tgridView: 'folderPicker:gridView',\n\tlistView: 'folderPicker:listView',\n\torderLabel: 'folderPicker:orderLabel',\n\tpickManyHint: 'folderPicker:pickManyHint',\n\tpluginName: 'folderPicker:pluginName',\n\tretry: 'folderPicker:retry',\n\tsortByLabel: 'folderPicker:sortByLabel',\n} as const\n\nexport type TranslationKey = (typeof keys)[keyof typeof keys]\n"],"mappings":";;;;;;AAKA,MAAa,OAAO;CACnB,UAAU;CACV,UAAU;CACV,YAAY;CACZ,cAAc;CACd,YAAY;CACZ,OAAO;CACP,aAAa;AACd"}
@@ -0,0 +1,15 @@
1
+ import { keys } from "./keys.js";
2
+ //#region src/translations/uk.ts
3
+ const uk = {
4
+ [keys.gridView]: "Показати сіткою",
5
+ [keys.listView]: "Показати списком",
6
+ [keys.orderLabel]: "Порядок",
7
+ [keys.pickManyHint]: "Утримуйте {{modifier}}, щоб вибрати кілька, {{range}} для діапазону.",
8
+ [keys.pluginName]: "Вибір теки",
9
+ [keys.retry]: "Спробувати ще раз",
10
+ [keys.sortByLabel]: "Сортувати за"
11
+ };
12
+ //#endregion
13
+ export { uk };
14
+
15
+ //# sourceMappingURL=uk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uk.js","names":[],"sources":["../../src/translations/uk.ts"],"sourcesContent":["import { keys, type TranslationKey } from './keys'\n\nexport const uk: Record<TranslationKey, string> = {\n\t[keys.gridView]: 'Показати сіткою',\n\t[keys.listView]: 'Показати списком',\n\t[keys.orderLabel]: 'Порядок',\n\t[keys.pickManyHint]: 'Утримуйте {{modifier}}, щоб вибрати кілька, {{range}} для діапазону.',\n\t[keys.pluginName]: 'Вибір теки',\n\t[keys.retry]: 'Спробувати ще раз',\n\t[keys.sortByLabel]: 'Сортувати за',\n}\n"],"mappings":";;AAEA,MAAa,KAAqC;EAChD,KAAK,WAAW;EAChB,KAAK,WAAW;EAChB,KAAK,aAAa;EAClB,KAAK,eAAe;EACpB,KAAK,aAAa;EAClB,KAAK,QAAQ;EACb,KAAK,cAAc;AACrB"}
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { useTranslation } from "@payloadcms/ui";
3
+ //#region src/translations/useTranslation.ts
4
+ /**
5
+ * `useTranslation` bound to this plugin's keys, so `t(keys.X)` typechecks without
6
+ * a per-call `@ts-expect-error`. Returns Payload's `{ t, i18n }` unchanged.
7
+ */
8
+ const useTranslation$1 = () => useTranslation();
9
+ //#endregion
10
+ export { useTranslation$1 as useTranslation };
11
+
12
+ //# sourceMappingURL=useTranslation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTranslation.js","names":["useTranslation","usePayloadTranslation"],"sources":["../../src/translations/useTranslation.ts"],"sourcesContent":["'use client'\n\nimport { useTranslation as usePayloadTranslation } from '@payloadcms/ui'\n\nimport type { TranslationKey } from './keys'\n\n/**\n * `useTranslation` bound to this plugin's keys, so `t(keys.X)` typechecks without\n * a per-call `@ts-expect-error`. Returns Payload's `{ t, i18n }` unchanged.\n */\nexport const useTranslation = () => usePayloadTranslation<Record<string, never>, TranslationKey>()\n"],"mappings":";;;;;;;AAUA,MAAaA,yBAAuBC,eAA6D"}
package/package.json ADDED
@@ -0,0 +1,110 @@
1
+ {
2
+ "name": "@10x-media/folder-picker",
3
+ "version": "0.1.0-beta.0",
4
+ "description": "Browse and select files through your folder hierarchy directly inside Payload CMS upload fields",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/10x-media/payload-plugins.git",
9
+ "directory": "packages/folder-picker"
10
+ },
11
+ "homepage": "https://github.com/10x-media/payload-plugins/tree/main/packages/folder-picker",
12
+ "bugs": "https://github.com/10x-media/payload-plugins/issues",
13
+ "keywords": [
14
+ "payload",
15
+ "payloadcms",
16
+ "plugin"
17
+ ],
18
+ "author": "10x-media",
19
+ "engines": {
20
+ "node": ">=22.18.0"
21
+ },
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "./types": {
31
+ "types": "./dist/exports/types.d.ts",
32
+ "import": "./dist/exports/types.js",
33
+ "default": "./dist/exports/types.js"
34
+ },
35
+ "./client": {
36
+ "types": "./dist/exports/client.d.ts",
37
+ "import": "./dist/exports/client.js",
38
+ "default": "./dist/exports/client.js"
39
+ },
40
+ "./i18n": {
41
+ "types": "./dist/exports/i18n.d.ts",
42
+ "import": "./dist/exports/i18n.js",
43
+ "default": "./dist/exports/i18n.js"
44
+ }
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "README.md",
49
+ "CHANGELOG.md",
50
+ "LICENSE"
51
+ ],
52
+ "peerDependencies": {
53
+ "@dnd-kit/core": "6.3.1",
54
+ "@payloadcms/translations": "^3.83.0",
55
+ "@payloadcms/ui": "^3.83.0",
56
+ "payload": "^3.83.0",
57
+ "react": "^19.0.0",
58
+ "react-dom": "^19.0.0"
59
+ },
60
+ "devDependencies": {
61
+ "@dnd-kit/core": "6.3.1",
62
+ "@payloadcms/db-mongodb": "3.85.0",
63
+ "@payloadcms/db-postgres": "3.85.0",
64
+ "@payloadcms/ui": "3.85.0",
65
+ "@playwright/test": "1.60.0",
66
+ "@payloadcms/translations": "3.85.0",
67
+ "@types/node": "22.19.19",
68
+ "@types/react": "19.2.15",
69
+ "@types/react-dom": "19.2.3",
70
+ "payload": "3.85.0",
71
+ "playwright": "1.60.0",
72
+ "react": "19.2.6",
73
+ "react-dom": "19.2.6",
74
+ "tsdown": "0.22.1",
75
+ "typescript": "5.9.3",
76
+ "vitest": "4.1.7",
77
+ "@10x-media/tsdown-config": "0.0.0",
78
+ "@10x-media/payload-test-harness": "0.0.0",
79
+ "@10x-media/tsconfig": "0.0.0",
80
+ "@10x-media/vitest-config": "0.0.0"
81
+ },
82
+ "publishConfig": {
83
+ "access": "public"
84
+ },
85
+ "scripts": {
86
+ "build": "tsdown",
87
+ "lint": "biome check src tests dev",
88
+ "lint:fix": "biome check --write src tests dev",
89
+ "typecheck": "tsc -p tsconfig.json --noEmit",
90
+ "test": "vitest run",
91
+ "test:unit": "vitest run src",
92
+ "test:int": "vitest run tests/int",
93
+ "test:matrix": "DB_MATRIX=mongo vitest run tests/int/matrix.int.spec.ts && DB_MATRIX=postgres vitest run tests/int/matrix.int.spec.ts",
94
+ "test:container": "TEST_DB=container DB_MATRIX=mongo vitest run tests/int/matrix.int.spec.ts && TEST_DB=container DB_MATRIX=postgres vitest run tests/int/matrix.int.spec.ts",
95
+ "test:e2e": "bash scripts/e2e.sh",
96
+ "dev": "pnpm --filter @10x-media/folder-picker-dev dev",
97
+ "start": "pnpm --filter @10x-media/folder-picker-dev start",
98
+ "generate": "pnpm --filter @10x-media/folder-picker-dev generate",
99
+ "generate:types": "pnpm --filter @10x-media/folder-picker-dev generate:types",
100
+ "generate:importmap": "pnpm --filter @10x-media/folder-picker-dev generate:importmap",
101
+ "migrate": "pnpm --filter @10x-media/folder-picker-dev migrate",
102
+ "migrate:create": "pnpm --filter @10x-media/folder-picker-dev migrate:create",
103
+ "migrate:down": "pnpm --filter @10x-media/folder-picker-dev migrate:down",
104
+ "migrate:refresh": "pnpm --filter @10x-media/folder-picker-dev migrate:refresh",
105
+ "migrate:reset": "pnpm --filter @10x-media/folder-picker-dev migrate:reset",
106
+ "migrate:status": "pnpm --filter @10x-media/folder-picker-dev migrate:status",
107
+ "migrate:fresh": "pnpm --filter @10x-media/folder-picker-dev migrate:fresh",
108
+ "clean": "rm -rf dist *.tsbuildinfo dev/.next"
109
+ }
110
+ }