@meeovi/directus-client 1.0.0 → 1.0.2

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 (55) hide show
  1. package/README.md +238 -0
  2. package/dist/client/createClient.d.ts +14 -0
  3. package/dist/client/createClient.js +18 -0
  4. package/dist/generators/form-engine.d.ts +13 -0
  5. package/dist/generators/form-engine.js +22 -0
  6. package/dist/generators/table-engine.d.ts +6 -0
  7. package/dist/generators/table-engine.js +9 -0
  8. package/dist/generators/validation-engine.d.ts +2 -0
  9. package/dist/generators/validation-engine.js +25 -0
  10. package/dist/generators/widget-registry.d.ts +9 -0
  11. package/dist/generators/widget-registry.js +54 -0
  12. package/dist/index.d.ts +13 -0
  13. package/dist/index.js +17 -0
  14. package/dist/react/DirectusProvider.d.ts +7 -0
  15. package/dist/react/DirectusProvider.js +6 -0
  16. package/dist/react/useDirectus.d.ts +2 -0
  17. package/dist/react/useDirectus.js +9 -0
  18. package/dist/schema/introspect.d.ts +5 -0
  19. package/dist/schema/introspect.js +30 -0
  20. package/dist/schema/types.d.ts +47 -0
  21. package/dist/schema/types.js +1 -0
  22. package/dist/utils/collections.d.ts +5 -0
  23. package/dist/utils/collections.js +13 -0
  24. package/dist/utils/fields.d.ts +4 -0
  25. package/dist/utils/fields.js +40 -0
  26. package/dist/utils/livePreview.d.ts +11 -0
  27. package/dist/utils/livePreview.js +11 -0
  28. package/dist/utils/useDirectusField.d.ts +10 -0
  29. package/dist/utils/useDirectusField.js +119 -0
  30. package/dist/utils/useDirectusRequest.d.ts +7 -0
  31. package/dist/utils/useDirectusRequest.js +33 -0
  32. package/dist/utils/useLivePreview.d.ts +1 -0
  33. package/dist/utils/useLivePreview.js +16 -0
  34. package/dist/utils/useVisualEditing.d.ts +18 -0
  35. package/dist/utils/useVisualEditing.js +27 -0
  36. package/dist/utils/visualEditing.d.ts +21 -0
  37. package/dist/utils/visualEditing.js +25 -0
  38. package/dist/vue/DirectusProvider.d.ts +15 -0
  39. package/dist/vue/DirectusProvider.js +12 -0
  40. package/dist/vue/useDirectus.d.ts +2 -0
  41. package/dist/vue/useDirectus.js +9 -0
  42. package/package.json +16 -5
  43. package/src/client/createClient.ts +5 -4
  44. package/src/generators/form-engine.ts +31 -81
  45. package/src/generators/widget-registry.ts +71 -4
  46. package/src/react/useDirectus.ts +1 -1
  47. package/src/utils/livePreview.ts +24 -0
  48. package/src/utils/visualEditing.ts +46 -0
  49. package/src/vue/useDirectus.ts +1 -1
  50. package/tsconfig.json +15 -0
  51. package/src/utils/useDirectusField.ts +0 -144
  52. package/src/utils/useDirectusRequest.ts +0 -32
  53. package/src/utils/useDirectusSchema.js +0 -9
  54. package/src/utils/useLivePreview.ts +0 -17
  55. package/src/utils/useVisualEditing.ts +0 -38
@@ -0,0 +1,10 @@
1
+ export declare function useDirectusField(field: any, modelValue: any, emit: any, formContext?: any): {
2
+ internalValue: import("vue").WritableComputedRef<any, any>;
3
+ internalAuto: import("vue").ComputedRef<boolean>;
4
+ label: import("vue").ComputedRef<string>;
5
+ fieldLookup: import("vue").ComputedRef<any>;
6
+ required: import("vue").ComputedRef<boolean>;
7
+ safeOptions: import("vue").ComputedRef<any>;
8
+ defaultValue: import("vue").ComputedRef<any>;
9
+ isMultiple: import("vue").ComputedRef<boolean>;
10
+ };
@@ -0,0 +1,119 @@
1
+ import { ref, computed, watch } from "vue";
2
+ import Input from "~/components/ui/forms/TextInput.vue";
3
+ import Select from "~/components/ui/forms/SelectInput.vue";
4
+ import DateTime from "~/components/ui/forms/DateTime.vue";
5
+ import FileInput from "~/components/ui/forms/FileInput.vue";
6
+ import BooleanInput from "~/components/ui/forms/BooleanInput.vue";
7
+ import RelationSelect from "~/components/ui/forms/RelationSelect.vue";
8
+ import TiptapEditor from "~/components/ui/forms/TiptapEditor.vue";
9
+ export function useDirectusField(field, modelValue, emit, formContext) {
10
+ const internalValue = computed({
11
+ get: () => modelValue,
12
+ set: (v) => emit("update:modelValue", v),
13
+ });
14
+ const previousAuto = ref(null);
15
+ const internalAuto = computed(() => {
16
+ return previousAuto.value != null && internalValue.value === previousAuto.value;
17
+ });
18
+ const slugify = (val) => String(val || "")
19
+ .toLowerCase()
20
+ .normalize("NFKD")
21
+ .replace(/\s+/g, "-")
22
+ .replace(/[^a-z0-9\-]/g, "")
23
+ .replace(/-+/g, "-")
24
+ .replace(/^-|-$/g, "");
25
+ const isAliasField = computed(() => {
26
+ const meta = field?.meta ?? {};
27
+ return (meta.interface === "alias" ||
28
+ (meta.options && (meta.options.source || meta.options.from)));
29
+ });
30
+ if (isAliasField.value && formContext && formContext.form) {
31
+ const meta = field?.meta ?? {};
32
+ let sourceField = meta?.options?.source ?? meta?.options?.from ?? null;
33
+ if (!sourceField && formContext.fields) {
34
+ const candidates = ["name", "title"];
35
+ sourceField =
36
+ candidates.find((c) => formContext.fields.value.some((f) => f.field === c)) ?? null;
37
+ }
38
+ if (sourceField) {
39
+ watch(() => formContext.form.value?.[sourceField], (newVal) => {
40
+ const target = field.field;
41
+ const current = formContext.form.value?.[target] ?? "";
42
+ const generated = slugify(String(newVal ?? ""));
43
+ if (!generated)
44
+ return;
45
+ // Case 1: Empty or previously auto‑generated → regenerate
46
+ if (!current || current === previousAuto.value) {
47
+ formContext.form.value[target] = generated;
48
+ internalValue.value = generated;
49
+ previousAuto.value = generated;
50
+ }
51
+ }, { immediate: true });
52
+ // Watch the target itself: if cleared, regenerate
53
+ watch(() => formContext.form.value?.[field.field], (newVal) => {
54
+ if (!newVal) {
55
+ const sourceVal = formContext.form.value?.[sourceField] ?? "";
56
+ const regenerated = slugify(String(sourceVal));
57
+ if (regenerated) {
58
+ formContext.form.value[field.field] = regenerated;
59
+ internalValue.value = regenerated;
60
+ previousAuto.value = regenerated;
61
+ }
62
+ }
63
+ });
64
+ }
65
+ }
66
+ // --- Label prettifier ---
67
+ const prettify = (s) => s.replace(/_/g, " ")
68
+ .split(" ")
69
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
70
+ .join(" ");
71
+ const label = computed(() => {
72
+ const raw = field?.meta?.field ?? field?.field ?? "";
73
+ return prettify(raw);
74
+ });
75
+ // --- Field lookup ---
76
+ const fieldLookup = computed(() => {
77
+ const iface = field?.meta?.interface;
78
+ const name = String(iface ?? "").toLowerCase();
79
+ if (["input", "string"].includes(name))
80
+ return Input;
81
+ if (["textarea", "wysiwyg", "rich-text", "input-multiline", "input-rich-text", "input-rich-text-md"].includes(name))
82
+ return TiptapEditor;
83
+ if (["select", "select-dropdown", "select-multiple"].includes(name))
84
+ return Select;
85
+ if (name === "datetime")
86
+ return DateTime;
87
+ if (["file", "files", "file-image"].includes(name))
88
+ return FileInput;
89
+ if (name === "boolean")
90
+ return BooleanInput;
91
+ if (name.includes("many") || name.includes("one") || name.includes("to"))
92
+ return RelationSelect;
93
+ return Input;
94
+ });
95
+ const required = computed(() => {
96
+ const meta = field?.meta ?? {};
97
+ return Boolean(meta?.validation?.required ?? meta?.required ?? false);
98
+ });
99
+ const safeOptions = computed(() => field?.meta?.options ?? null);
100
+ const defaultValue = computed(() => field?.schema?.default_value ?? null);
101
+ const isMultiple = computed(() => {
102
+ const name = String(field?.meta?.interface ?? "").toLowerCase();
103
+ if (name === "select-multiple")
104
+ return true;
105
+ if (name.includes("many"))
106
+ return true;
107
+ return Boolean(field?.meta?.options?.multiple || false);
108
+ });
109
+ return {
110
+ internalValue,
111
+ internalAuto,
112
+ label,
113
+ fieldLookup,
114
+ required,
115
+ safeOptions,
116
+ defaultValue,
117
+ isMultiple,
118
+ };
119
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Centralized safe wrapper around `$directus.request`.
3
+ * Shows a toast error when the Directus client or `.request` is not available.
4
+ */
5
+ export default function useDirectusRequest(): {
6
+ request: (config: any) => Promise<any>;
7
+ };
@@ -0,0 +1,33 @@
1
+ import { useNuxtApp } from '#imports';
2
+ /**
3
+ * Centralized safe wrapper around `$directus.request`.
4
+ * Shows a toast error when the Directus client or `.request` is not available.
5
+ */
6
+ export default function useDirectusRequest() {
7
+ const nuxt = useNuxtApp();
8
+ async function request(config) {
9
+ try {
10
+ const client = nuxt?.$directus;
11
+ if (!client || typeof client.request !== 'function') {
12
+ try {
13
+ const toast = nuxt?.$toast;
14
+ if (toast && typeof toast.error === 'function')
15
+ toast.error('Directus client unavailable');
16
+ }
17
+ catch (_) { }
18
+ throw new Error('Directus client.request is not available');
19
+ }
20
+ return await client.request(config);
21
+ }
22
+ catch (e) {
23
+ try {
24
+ const toast = nuxt?.$toast;
25
+ if (toast && typeof toast.error === 'function')
26
+ toast.error('Request failed');
27
+ }
28
+ catch (_) { }
29
+ throw e;
30
+ }
31
+ }
32
+ return { request };
33
+ }
@@ -0,0 +1 @@
1
+ export declare function useLivePreview(): any;
@@ -0,0 +1,16 @@
1
+ export function useLivePreview() {
2
+ return usePreviewMode({
3
+ // Enable preview mode when both preview and token params exist in URL
4
+ shouldEnable: () => {
5
+ const route = useRoute();
6
+ return !!route.query.preview && !!route.query.token;
7
+ },
8
+ // Store the token from the URL for use in API calls
9
+ getState: (currentState) => {
10
+ const route = useRoute();
11
+ return {
12
+ token: route.query.token || currentState.token,
13
+ };
14
+ },
15
+ });
16
+ }
@@ -0,0 +1,18 @@
1
+ import { setAttr } from '@directus/visual-editing';
2
+ import type { PrimaryKey } from '@directus/types';
3
+ interface ApplyOptions {
4
+ directusUrl: string;
5
+ elements?: HTMLElement[] | HTMLElement;
6
+ onSaved?: (data: {
7
+ collection?: string;
8
+ item?: PrimaryKey | null;
9
+ payload?: Record<string, any>;
10
+ }) => void;
11
+ customClass?: string;
12
+ }
13
+ export default function useVisualEditing(): {
14
+ isVisualEditingEnabled: any;
15
+ apply: (options: Pick<ApplyOptions, "elements" | "onSaved" | "customClass">) => void;
16
+ setAttr: typeof setAttr;
17
+ };
18
+ export {};
@@ -0,0 +1,27 @@
1
+ import { apply as applyVisualEditing, setAttr } from '@directus/visual-editing';
2
+ export default function useVisualEditing() {
3
+ // Use useState for state that persists across navigation
4
+ const isVisualEditingEnabled = useState('visual-editing-enabled', () => false);
5
+ const route = useRoute();
6
+ const { public: { enableVisualEditing, directusUrl }, } = useRuntimeConfig();
7
+ // Check query param on composable initialization.
8
+ if (route.query['visual-editing'] === 'true' && enableVisualEditing) {
9
+ isVisualEditingEnabled.value = true;
10
+ }
11
+ else if (route.query['visual-editing'] === 'false') {
12
+ isVisualEditingEnabled.value = false;
13
+ }
14
+ const apply = (options) => {
15
+ if (!isVisualEditingEnabled.value)
16
+ return;
17
+ applyVisualEditing({
18
+ ...options,
19
+ directusUrl,
20
+ });
21
+ };
22
+ return {
23
+ isVisualEditingEnabled,
24
+ apply,
25
+ setAttr,
26
+ };
27
+ }
@@ -0,0 +1,21 @@
1
+ import { setAttr } from '@directus/visual-editing';
2
+ import type { PrimaryKey } from '@directus/types';
3
+ export interface VisualEditingOptions {
4
+ enableVisualEditing?: boolean;
5
+ directusUrl: string;
6
+ query?: Record<string, string | undefined>;
7
+ }
8
+ export interface ApplyOptions {
9
+ elements?: HTMLElement[] | HTMLElement;
10
+ onSaved?: (data: {
11
+ collection?: string;
12
+ item?: PrimaryKey | null;
13
+ payload?: Record<string, any>;
14
+ }) => void;
15
+ customClass?: string;
16
+ }
17
+ export declare function createVisualEditing(options: VisualEditingOptions): {
18
+ isEnabled: boolean;
19
+ apply: (opts: ApplyOptions) => void;
20
+ setAttr: typeof setAttr;
21
+ };
@@ -0,0 +1,25 @@
1
+ import { apply as applyVisualEditing, setAttr } from '@directus/visual-editing';
2
+ export function createVisualEditing(options) {
3
+ const { enableVisualEditing = false, directusUrl, query = {} } = options;
4
+ let isEnabled = false;
5
+ // Determine if visual editing should be active
6
+ if (query['visual-editing'] === 'true' && enableVisualEditing) {
7
+ isEnabled = true;
8
+ }
9
+ else if (query['visual-editing'] === 'false') {
10
+ isEnabled = false;
11
+ }
12
+ const apply = (opts) => {
13
+ if (!isEnabled)
14
+ return;
15
+ applyVisualEditing({
16
+ ...opts,
17
+ directusUrl,
18
+ });
19
+ };
20
+ return {
21
+ isEnabled,
22
+ apply,
23
+ setAttr,
24
+ };
25
+ }
@@ -0,0 +1,15 @@
1
+ export declare const DirectusKey: unique symbol;
2
+ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
3
+ client: {
4
+ type: ObjectConstructor;
5
+ required: true;
6
+ };
7
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
8
+ [key: string]: any;
9
+ }>[] | undefined, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
10
+ client: {
11
+ type: ObjectConstructor;
12
+ required: true;
13
+ };
14
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
15
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import { defineComponent, provide } from 'vue';
2
+ export const DirectusKey = Symbol('DirectusClient');
3
+ export default defineComponent({
4
+ name: 'DirectusProvider',
5
+ props: {
6
+ client: { type: Object, required: true }
7
+ },
8
+ setup(props, { slots }) {
9
+ provide(DirectusKey, props.client);
10
+ return () => slots.default?.();
11
+ }
12
+ });
@@ -0,0 +1,2 @@
1
+ import type { MeeoviDirectusClient } from '../client/createClient';
2
+ export declare function useVueDirectus<Schema>(): MeeoviDirectusClient<Schema>;
@@ -0,0 +1,9 @@
1
+ import { inject } from 'vue';
2
+ import { DirectusKey } from './DirectusProvider';
3
+ export function useVueDirectus() {
4
+ const client = inject(DirectusKey);
5
+ if (!client) {
6
+ throw new Error('Directus client not provided. Wrap your app in <DirectusProvider>.');
7
+ }
8
+ return client;
9
+ }
package/package.json CHANGED
@@ -1,20 +1,31 @@
1
1
  {
2
2
  "name": "@meeovi/directus-client",
3
- "version": "1.0.0",
4
- "description": "",
5
- "main": "index.js",
3
+ "version": "1.0.2",
4
+ "description": "Directus Client Library with auto generating forms and tables, Directus SDK and Types, and vue/react components for Directus Visual Editing.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
6
14
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
15
+ "test": "echo \"Error: no test specified\" && exit 1",
16
+ "build": "tsc -p tsconfig.json"
8
17
  },
9
18
  "keywords": [],
10
19
  "author": "",
11
20
  "license": "ISC",
12
- "type": "commonjs",
21
+ "type": "module",
13
22
  "dependencies": {
14
23
  "@directus/sdk": "^18.0.3",
15
24
  "@directus/types": "^14.0.0",
16
25
  "@directus/visual-editing": "^1.1.0",
17
26
  "react": "^19.2.3",
27
+ "tsc": "^2.0.4",
28
+ "typescript": "^5.9.3",
18
29
  "vue": "^3.5.24"
19
30
  },
20
31
  "devDependencies": {
@@ -1,5 +1,3 @@
1
- // src/client/createClient.ts
2
-
3
1
  import {
4
2
  createDirectus,
5
3
  rest,
@@ -11,11 +9,13 @@ import {
11
9
  deleteItem,
12
10
  uploadFiles,
13
11
  readSingleton,
14
- readFieldsByCollection
12
+ readFieldsByCollection,
13
+ type DirectusClient
15
14
  } from '@directus/sdk';
16
15
 
17
16
  export interface MeeoviDirectusClient<Schema> {
18
- client: ReturnType<typeof createDirectus<Schema>>;
17
+ client: DirectusClient<Schema>;
18
+ request: any;
19
19
  readItem: typeof readItem;
20
20
  readItems: typeof readItems;
21
21
  createItem: typeof createItem;
@@ -33,6 +33,7 @@ export function createMeeoviDirectusClient<Schema>(url: string): MeeoviDirectusC
33
33
 
34
34
  return {
35
35
  client,
36
+ request: client.request,
36
37
  readItem,
37
38
  readItems,
38
39
  createItem,
@@ -1,89 +1,39 @@
1
1
  import type { DirectusField } from '../schema/types';
2
-
3
- export interface FormEngineOptions {
4
- clearOnSuccess?: boolean;
5
- onSuccess?: () => void;
6
- onError?: (msg: string) => void;
2
+ import { widgetRegistry } from './widget-registry';
3
+
4
+ export interface GeneratedFormField {
5
+ key: string;
6
+ widget: string;
7
+ type: string;
8
+ options?: Record<string, any>;
9
+ fields?: GeneratedFormField[];
10
+ isRepeatable?: boolean;
11
+ isFile?: boolean;
12
+ isRelational?: boolean;
7
13
  }
8
14
 
9
- export function createFormEngine(
10
- collectionName: string,
11
- fields: DirectusField[],
12
- directusClient: any,
13
- opts?: FormEngineOptions
14
- ) {
15
- const form: Record<string, any> = {};
16
- let error: string | null = null;
17
- let success: string | null = null;
18
-
19
- const validate = () => {
20
- error = null;
21
-
22
- for (const field of fields) {
23
- const meta = field;
24
- if (!meta?.validation) continue;
25
-
26
- try {
27
- const validation = meta.validation;
28
-
29
- if (validation._and) {
30
- for (const rule of validation._and) {
31
- const fieldName = Object.keys(rule)[0];
32
- if (!fieldName) continue;
33
-
34
- const ruleDef = (rule as any)[fieldName];
35
-
36
- if (ruleDef?._regex) {
37
- const regex = new RegExp(ruleDef._regex);
38
- const value = String(form[field.field] ?? '');
39
-
40
- if (!regex.test(value)) {
41
- error = meta.validation_message || `${field.field} failed validation`;
42
- return false;
43
- }
44
- }
45
- }
46
- }
47
- } catch {
48
- error = `Validation error for ${field.field}`;
49
- return false;
50
- }
51
- }
52
-
53
- return true;
15
+ export function generateFormField(field: DirectusField): GeneratedFormField {
16
+ const widget = widgetRegistry[field.interface || 'input'];
17
+
18
+ const base: GeneratedFormField = {
19
+ key: field.field,
20
+ widget: widget.component,
21
+ type: field.type,
22
+ options: field.options || {},
23
+ isRepeatable: widget.isRepeatable,
24
+ isFile: widget.isFile,
25
+ isRelational: widget.isRelational
54
26
  };
55
27
 
56
- const submit = async () => {
57
- if (!validate()) return { error, success };
28
+ if ((field.interface === 'repeater' || field.interface === 'group') && field.options?.fields) {
29
+ base.fields = field.options.fields.map((sub: any) => generateFormField(sub));
30
+ }
58
31
 
59
- const result = await directusClient.request(
60
- directusClient.createItem(collectionName, form)
61
- );
62
-
63
- if (result?.error) {
64
- error = result.error.message;
65
- return { error, success };
66
- }
67
-
68
- success = `${collectionName} created successfully`;
69
-
70
- if (opts?.clearOnSuccess) {
71
- for (const key of Object.keys(form)) delete form[key];
72
- }
73
-
74
- opts?.onSuccess?.();
75
-
76
- return { error, success };
77
- };
32
+ return base;
33
+ }
78
34
 
79
- return {
80
- form,
81
- submit,
82
- get error() {
83
- return error;
84
- },
85
- get success() {
86
- return success;
87
- }
88
- };
35
+ export function generateFormSchema(fields: DirectusField[]): GeneratedFormField[] {
36
+ return fields
37
+ .filter(f => f.interface !== 'presentation' && f.interface !== 'divider')
38
+ .map(generateFormField);
89
39
  }
@@ -1,7 +1,74 @@
1
- export const widgetRegistry = {
2
- text: { component: 'TextInput' },
3
- number: { component: 'NumberInput' },
1
+ import { readItems } from "@directus/sdk";
2
+
3
+ export interface WidgetDefinition {
4
+ component: string;
5
+ props?: Record<string, any>;
6
+ isRepeatable?: boolean;
7
+ isRelational?: boolean;
8
+ isFile?: boolean;
9
+ }
10
+
11
+ export const widgetRegistry: Record<string, WidgetDefinition> = {
12
+ // Basic inputs
13
+ input: { component: 'TextInput' },
14
+ textarea: { component: 'TextareaInput' },
15
+ boolean: { component: 'ToggleInput' },
16
+ slider: { component: 'SliderInput' },
17
+ color: { component: 'ColorPicker' },
18
+ rating: { component: 'RatingInput' },
19
+
20
+ // Selects
21
+ 'select-dropdown': { component: 'SelectInput' },
22
+ 'select-multiple-dropdown': { component: 'MultiSelectInput' },
23
+ tags: { component: 'TagInput' },
4
24
  checkbox: { component: 'CheckboxInput' },
25
+ radio: { component: 'RadioInput' },
26
+
27
+ // Date/time
28
+ datetime: { component: 'DateTimeInput' },
5
29
  date: { component: 'DateInput' },
6
- json: { component: 'JsonEditor' }
30
+ time: { component: 'TimeInput' },
31
+
32
+ // Files
33
+ file: { component: 'FileInput', isFile: true },
34
+ files: { component: 'FilesInput', isFile: true },
35
+ image: { component: 'ImageInput', isFile: true },
36
+ images: { component: 'ImagesInput', isFile: true },
37
+
38
+ // Complex
39
+ repeater: { component: 'RepeaterInput', isRepeatable: true },
40
+ group: { component: 'GroupInput' },
41
+ json: { component: 'JsonEditor' },
42
+ code: { component: 'CodeEditor' },
43
+ wysiwyg: { component: 'WysiwygEditor' },
44
+ markdown: { component: 'MarkdownEditor' },
45
+
46
+ // Directus-specific
47
+ icon: { component: 'IconPicker' },
48
+ user: { component: 'UserSelect' },
49
+ role: { component: 'RoleSelect' },
50
+ translation: { component: 'TranslationInput' },
51
+
52
+ // Presentation (ignored in forms)
53
+ presentation: { component: 'PresentationBlock' },
54
+ divider: { component: 'DividerBlock' }
7
55
  };
56
+
57
+ export async function extendWidgetRegistryFromDirectus(client: any) {
58
+ const extensions = await client.request(
59
+ readItems<any, any, any>('directus_extensions')
60
+ );
61
+
62
+ for (const ext of extensions) {
63
+ if (ext.type !== 'interface') continue;
64
+
65
+ const name = ext.name;
66
+
67
+ if (!widgetRegistry[name]) {
68
+ widgetRegistry[name] = {
69
+ component: 'CustomInterfaceRenderer',
70
+ props: { interfaceName: name }
71
+ };
72
+ }
73
+ }
74
+ }
@@ -2,7 +2,7 @@ import { useContext } from 'react';
2
2
  import { DirectusContext } from './DirectusProvider';
3
3
  import type { MeeoviDirectusClient } from '../client/createClient';
4
4
 
5
- export function useRDirectus<Schema>() {
5
+ export function useReactDirectus<Schema>() {
6
6
  const client = useContext(DirectusContext) as MeeoviDirectusClient<Schema> | null;
7
7
 
8
8
  if (!client) {
@@ -0,0 +1,24 @@
1
+ export interface LivePreviewState {
2
+ token?: string;
3
+ }
4
+
5
+ export interface LivePreviewOptions {
6
+ query?: Record<string, string | undefined>;
7
+ initialState?: LivePreviewState;
8
+ }
9
+
10
+ export function createLivePreview(options: LivePreviewOptions) {
11
+ const { query = {}, initialState = {} } = options;
12
+
13
+ const shouldEnable =
14
+ Boolean(query.preview) && Boolean(query.token);
15
+
16
+ const state: LivePreviewState = {
17
+ token: query.token || initialState.token,
18
+ };
19
+
20
+ return {
21
+ enabled: shouldEnable,
22
+ state,
23
+ };
24
+ }