@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,46 @@
1
+ import { apply as applyVisualEditing, setAttr } from '@directus/visual-editing';
2
+ import type { PrimaryKey } from '@directus/types';
3
+
4
+ export interface VisualEditingOptions {
5
+ enableVisualEditing?: boolean;
6
+ directusUrl: string;
7
+ query?: Record<string, string | undefined>;
8
+ }
9
+
10
+ export interface ApplyOptions {
11
+ elements?: HTMLElement[] | HTMLElement;
12
+ onSaved?: (data: {
13
+ collection?: string;
14
+ item?: PrimaryKey | null;
15
+ payload?: Record<string, any>;
16
+ }) => void;
17
+ customClass?: string;
18
+ }
19
+
20
+ export function createVisualEditing(options: VisualEditingOptions) {
21
+ const { enableVisualEditing = false, directusUrl, query = {} } = options;
22
+
23
+ let isEnabled = false;
24
+
25
+ // Determine if visual editing should be active
26
+ if (query['visual-editing'] === 'true' && enableVisualEditing) {
27
+ isEnabled = true;
28
+ } else if (query['visual-editing'] === 'false') {
29
+ isEnabled = false;
30
+ }
31
+
32
+ const apply = (opts: ApplyOptions) => {
33
+ if (!isEnabled) return;
34
+
35
+ applyVisualEditing({
36
+ ...opts,
37
+ directusUrl,
38
+ });
39
+ };
40
+
41
+ return {
42
+ isEnabled,
43
+ apply,
44
+ setAttr,
45
+ };
46
+ }
@@ -2,7 +2,7 @@ import { inject } from 'vue';
2
2
  import { DirectusKey } from './DirectusProvider';
3
3
  import type { MeeoviDirectusClient } from '../client/createClient';
4
4
 
5
- export function useVDirectus<Schema>() {
5
+ export function useVueDirectus<Schema>() {
6
6
  const client = inject<MeeoviDirectusClient<Schema>>(DirectusKey);
7
7
 
8
8
  if (!client) {
package/tsconfig.json CHANGED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "declaration": true,
4
+ "emitDeclarationOnly": false,
5
+ "outDir": "dist",
6
+ "moduleResolution": "bundler",
7
+ "module": "ESNext",
8
+ "target": "ESNext",
9
+ "strict": true,
10
+ "jsx": "react-jsx",
11
+ "skipLibCheck": true,
12
+ "noEmitOnError": false
13
+ },
14
+ "include": ["src"]
15
+ }
@@ -1,144 +0,0 @@
1
- import { ref, computed, watch } from "vue"
2
- import Input from "~/components/ui/forms/TextInput.vue"
3
- import TextArea from "~/components/ui/forms/TextArea.vue"
4
- import Select from "~/components/ui/forms/SelectInput.vue"
5
- import DateTime from "~/components/ui/forms/DateTime.vue"
6
- import FileInput from "~/components/ui/forms/FileInput.vue"
7
- import BooleanInput from "~/components/ui/forms/BooleanInput.vue"
8
- import RelationSelect from "~/components/ui/forms/RelationSelect.vue"
9
- import RepeaterInput from "~/components/ui/forms/RepeaterInput.vue"
10
- import TiptapEditor from "~/components/ui/forms/TiptapEditor.vue"
11
-
12
- export function useDirectusField(field: any, modelValue: any, emit: any, formContext?: any) {
13
- const internalValue = computed({
14
- get: () => modelValue,
15
- set: (v) => emit("update:modelValue", v),
16
- })
17
-
18
- const previousAuto = ref<string | null>(null)
19
- const internalAuto = computed(() => {
20
- return previousAuto.value != null && internalValue.value === previousAuto.value
21
- })
22
-
23
- const slugify = (val: string) =>
24
- String(val || "")
25
- .toLowerCase()
26
- .normalize("NFKD")
27
- .replace(/\s+/g, "-")
28
- .replace(/[^a-z0-9\-]/g, "")
29
- .replace(/-+/g, "-")
30
- .replace(/^-|-$/g, "")
31
-
32
- const isAliasField = computed(() => {
33
- const meta = field?.meta ?? {}
34
- return (
35
- meta.interface === "alias" ||
36
- (meta.options && (meta.options.source || meta.options.from))
37
- )
38
- })
39
-
40
- if (isAliasField.value && formContext && formContext.form) {
41
- const meta = field?.meta ?? {}
42
- let sourceField: string | null = meta?.options?.source ?? meta?.options?.from ?? null
43
-
44
- if (!sourceField && formContext.fields) {
45
- const candidates = ["name", "title"]
46
- sourceField =
47
- candidates.find((c: string) =>
48
- formContext.fields.value.some((f: any) => f.field === c)
49
- ) ?? null
50
- }
51
-
52
- if (sourceField) {
53
- watch(
54
- () => formContext.form.value?.[sourceField as string],
55
- (newVal) => {
56
- const target = field.field
57
- const current = formContext.form.value?.[target] ?? ""
58
- const generated = slugify(String(newVal ?? ""))
59
-
60
- if (!generated) return
61
-
62
- // Case 1: Empty or previously auto‑generated → regenerate
63
- if (!current || current === previousAuto.value) {
64
- formContext.form.value[target] = generated
65
- internalValue.value = generated
66
- previousAuto.value = generated
67
- }
68
- },
69
- { immediate: true }
70
- )
71
-
72
- // Watch the target itself: if cleared, regenerate
73
- watch(
74
- () => formContext.form.value?.[field.field],
75
- (newVal) => {
76
- if (!newVal) {
77
- const sourceVal = formContext.form.value?.[sourceField as string] ?? ""
78
- const regenerated = slugify(String(sourceVal))
79
- if (regenerated) {
80
- formContext.form.value[field.field] = regenerated
81
- internalValue.value = regenerated
82
- previousAuto.value = regenerated
83
- }
84
- }
85
- }
86
- )
87
- }
88
- }
89
-
90
- // --- Label prettifier ---
91
- const prettify = (s: string) =>
92
- s.replace(/_/g, " ")
93
- .split(" ")
94
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
95
- .join(" ")
96
-
97
- const label = computed(() => {
98
- const raw = field?.meta?.field ?? field?.field ?? ""
99
- return prettify(raw)
100
- })
101
-
102
- // --- Field lookup ---
103
- const fieldLookup = computed(() => {
104
- const iface = field?.meta?.interface
105
- const name = String(iface ?? "").toLowerCase()
106
-
107
- if (["input", "string"].includes(name)) return Input
108
- if (
109
- ["textarea", "wysiwyg", "rich-text", "input-multiline", "input-rich-text", "input-rich-text-md"].includes(name)
110
- )
111
- return TiptapEditor
112
- if (["select", "select-dropdown", "select-multiple"].includes(name)) return Select
113
- if (name === "datetime") return DateTime
114
- if (["file", "files", "file-image"].includes(name)) return FileInput
115
- if (name === "boolean") return BooleanInput
116
- if (name.includes("many") || name.includes("one") || name.includes("to")) return RelationSelect
117
- return Input
118
- })
119
-
120
- const required = computed(() => {
121
- const meta = field?.meta ?? {}
122
- return Boolean(meta?.validation?.required ?? meta?.required ?? false)
123
- })
124
-
125
- const safeOptions = computed(() => field?.meta?.options ?? null)
126
- const defaultValue = computed(() => field?.schema?.default_value ?? null)
127
- const isMultiple = computed(() => {
128
- const name = String(field?.meta?.interface ?? "").toLowerCase()
129
- if (name === "select-multiple") return true
130
- if (name.includes("many")) return true
131
- return Boolean(field?.meta?.options?.multiple || false)
132
- })
133
-
134
- return {
135
- internalValue,
136
- internalAuto,
137
- label,
138
- fieldLookup,
139
- required,
140
- safeOptions,
141
- defaultValue,
142
- isMultiple,
143
- }
144
- }
@@ -1,32 +0,0 @@
1
- import { useNuxtApp } from '#imports'
2
-
3
- /**
4
- * Centralized safe wrapper around `$directus.request`.
5
- * Shows a toast error when the Directus client or `.request` is not available.
6
- */
7
- export default function useDirectusRequest() {
8
- const nuxt = useNuxtApp() as any
9
-
10
- async function request(config: any) {
11
- try {
12
- const client = nuxt?.$directus
13
- if (!client || typeof client.request !== 'function') {
14
- try {
15
- const toast = nuxt?.$toast
16
- if (toast && typeof toast.error === 'function') toast.error('Directus client unavailable')
17
- } catch (_) {}
18
- throw new Error('Directus client.request is not available')
19
- }
20
-
21
- return await client.request(config)
22
- } catch (e) {
23
- try {
24
- const toast = nuxt?.$toast
25
- if (toast && typeof toast.error === 'function') toast.error('Request failed')
26
- } catch (_) {}
27
- throw e
28
- }
29
- }
30
-
31
- return { request }
32
- }
@@ -1,9 +0,0 @@
1
- export async function useDirectusSchema(collection) {
2
- const config = useRuntimeConfig()
3
-
4
- return await $fetch(`${config.public.directus.url}/fields/${collection}`, {
5
- headers: {
6
- Authorization: `Bearer ${config.public.directus.auth.token}`
7
- }
8
- })
9
- }
@@ -1,17 +0,0 @@
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
-
9
- // Store the token from the URL for use in API calls
10
- getState: (currentState) => {
11
- const route = useRoute();
12
- return {
13
- token: route.query.token || currentState.token,
14
- };
15
- },
16
- });
17
- }
@@ -1,38 +0,0 @@
1
- import { apply as applyVisualEditing, setAttr } from '@directus/visual-editing';
2
- import type { PrimaryKey } from '@directus/types';
3
-
4
- interface ApplyOptions {
5
- directusUrl: string;
6
- elements?: HTMLElement[] | HTMLElement;
7
- onSaved?: (data: { collection?: string; item?: PrimaryKey | null; payload?: Record<string, any> }) => void;
8
- customClass?: string;
9
- }
10
- export default function useVisualEditing() {
11
- // Use useState for state that persists across navigation
12
- const isVisualEditingEnabled = useState('visual-editing-enabled', () => false);
13
- const route = useRoute();
14
- const {
15
- public: { enableVisualEditing, directusUrl },
16
- } = useRuntimeConfig();
17
-
18
- // Check query param on composable initialization.
19
- if (route.query['visual-editing'] === 'true' && enableVisualEditing) {
20
- isVisualEditingEnabled.value = true;
21
- } else if (route.query['visual-editing'] === 'false') {
22
- isVisualEditingEnabled.value = false;
23
- }
24
-
25
- const apply = (options: Pick<ApplyOptions, 'elements' | 'onSaved' | 'customClass'>) => {
26
- if (!isVisualEditingEnabled.value) return;
27
- applyVisualEditing({
28
- ...options,
29
- directusUrl,
30
- });
31
- };
32
-
33
- return {
34
- isVisualEditingEnabled,
35
- apply,
36
- setAttr,
37
- };
38
- }