@mapomodule/form 2.0.0-beta.1 → 2.0.0-beta.3

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 (144) hide show
  1. package/dist/__tests__/__mocks__/nuxt-imports.d.ts +4 -0
  2. package/dist/__tests__/__mocks__/nuxt-imports.d.ts.map +1 -0
  3. package/dist/__tests__/registry.test.d.ts +2 -0
  4. package/dist/__tests__/registry.test.d.ts.map +1 -0
  5. package/dist/__tests__/useFormFromSchema.test.d.ts +2 -0
  6. package/dist/__tests__/useFormFromSchema.test.d.ts.map +1 -0
  7. package/dist/__tests__/useMapoForm.test.d.ts +2 -0
  8. package/dist/__tests__/useMapoForm.test.d.ts.map +1 -0
  9. package/dist/index.d.mts +9 -1
  10. package/dist/index.d.ts +15 -1
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.mjs +7 -34
  13. package/dist/module.d.mts +20 -2
  14. package/dist/module.d.ts +12 -2
  15. package/dist/module.d.ts.map +1 -0
  16. package/dist/module.json +1 -1
  17. package/dist/module.mjs +167 -13
  18. package/dist/runtime/components/MapoFocusPortal.vue +113 -0
  19. package/dist/runtime/components/MapoFocusPortal.vue.d.ts +4 -0
  20. package/dist/runtime/components/MapoFocusPortal.vue.d.ts.map +1 -0
  21. package/dist/runtime/components/MapoForm.vue +168 -0
  22. package/dist/runtime/components/MapoForm.vue.d.ts +47 -0
  23. package/dist/runtime/components/MapoForm.vue.d.ts.map +1 -0
  24. package/dist/runtime/components/MapoFormField.vue +269 -0
  25. package/dist/runtime/components/MapoFormField.vue.d.ts +46 -0
  26. package/dist/runtime/components/MapoFormField.vue.d.ts.map +1 -0
  27. package/dist/runtime/components/MapoFormFlatSection.vue +149 -0
  28. package/dist/runtime/components/MapoFormFlatSection.vue.d.ts +15 -0
  29. package/dist/runtime/components/MapoFormFlatSection.vue.d.ts.map +1 -0
  30. package/dist/runtime/components/MapoFormGroup.vue +247 -0
  31. package/dist/runtime/components/MapoFormGroup.vue.d.ts +44 -0
  32. package/dist/runtime/components/MapoFormGroup.vue.d.ts.map +1 -0
  33. package/dist/runtime/components/MapoFormTabs.vue +108 -0
  34. package/dist/runtime/components/MapoFormTabs.vue.d.ts +15 -0
  35. package/dist/runtime/components/MapoFormTabs.vue.d.ts.map +1 -0
  36. package/dist/runtime/components/MapoUnknownField.vue +62 -0
  37. package/dist/runtime/components/MapoUnknownField.vue.d.ts +16 -0
  38. package/dist/runtime/components/MapoUnknownField.vue.d.ts.map +1 -0
  39. package/dist/runtime/components/fields/MapoDateField.vue +86 -0
  40. package/dist/runtime/components/fields/MapoDateField.vue.d.ts +17 -0
  41. package/dist/runtime/components/fields/MapoDateField.vue.d.ts.map +1 -0
  42. package/dist/runtime/components/fields/MapoDateTimeField.vue +157 -0
  43. package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts +17 -0
  44. package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts.map +1 -0
  45. package/dist/runtime/components/fields/MapoFileField.vue +97 -0
  46. package/dist/runtime/components/fields/MapoFileField.vue.d.ts +16 -0
  47. package/dist/runtime/components/fields/MapoFileField.vue.d.ts.map +1 -0
  48. package/dist/runtime/components/fields/MapoFksField.vue +118 -0
  49. package/dist/runtime/components/fields/MapoFksField.vue.d.ts +16 -0
  50. package/dist/runtime/components/fields/MapoFksField.vue.d.ts.map +1 -0
  51. package/dist/runtime/components/fields/MapoMapField.vue +97 -0
  52. package/dist/runtime/components/fields/MapoMapField.vue.d.ts +22 -0
  53. package/dist/runtime/components/fields/MapoMapField.vue.d.ts.map +1 -0
  54. package/dist/runtime/components/fields/MapoMapFieldClient.vue +67 -0
  55. package/dist/runtime/components/fields/MapoMapFieldClient.vue.d.ts +20 -0
  56. package/dist/runtime/components/fields/MapoMapFieldClient.vue.d.ts.map +1 -0
  57. package/dist/runtime/components/fields/MapoRepeater.vue +449 -0
  58. package/dist/runtime/components/fields/MapoRepeater.vue.d.ts +16 -0
  59. package/dist/runtime/components/fields/MapoRepeater.vue.d.ts.map +1 -0
  60. package/dist/runtime/components/fields/MapoRepeaterItem.vue +341 -0
  61. package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts +55 -0
  62. package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts.map +1 -0
  63. package/dist/runtime/components/fields/MapoSeoPreview.vue +118 -0
  64. package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts +22 -0
  65. package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts.map +1 -0
  66. package/dist/runtime/components/fields/MapoTimeField.vue +44 -0
  67. package/dist/runtime/components/fields/MapoTimeField.vue.d.ts +17 -0
  68. package/dist/runtime/components/fields/MapoTimeField.vue.d.ts.map +1 -0
  69. package/dist/runtime/components/fields/MapoWygEditor.vue +227 -0
  70. package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts +16 -0
  71. package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts.map +1 -0
  72. package/dist/runtime/components/fields/nui/NuiCheckbox.vue +9 -0
  73. package/dist/runtime/components/fields/nui/NuiCheckbox.vue.d.ts +4 -0
  74. package/dist/runtime/components/fields/nui/NuiCheckbox.vue.d.ts.map +1 -0
  75. package/dist/runtime/components/fields/nui/NuiInput.vue +9 -0
  76. package/dist/runtime/components/fields/nui/NuiInput.vue.d.ts +4 -0
  77. package/dist/runtime/components/fields/nui/NuiInput.vue.d.ts.map +1 -0
  78. package/dist/runtime/components/fields/nui/NuiSelectMenu.vue +18 -0
  79. package/dist/runtime/components/fields/nui/NuiSelectMenu.vue.d.ts +4 -0
  80. package/dist/runtime/components/fields/nui/NuiSelectMenu.vue.d.ts.map +1 -0
  81. package/dist/runtime/components/fields/nui/NuiSlider.vue +9 -0
  82. package/dist/runtime/components/fields/nui/NuiSlider.vue.d.ts +4 -0
  83. package/dist/runtime/components/fields/nui/NuiSlider.vue.d.ts.map +1 -0
  84. package/dist/runtime/components/fields/nui/NuiSwitch.vue +9 -0
  85. package/dist/runtime/components/fields/nui/NuiSwitch.vue.d.ts +4 -0
  86. package/dist/runtime/components/fields/nui/NuiSwitch.vue.d.ts.map +1 -0
  87. package/dist/runtime/components/fields/nui/NuiTextarea.vue +9 -0
  88. package/dist/runtime/components/fields/nui/NuiTextarea.vue.d.ts +4 -0
  89. package/dist/runtime/components/fields/nui/NuiTextarea.vue.d.ts.map +1 -0
  90. package/dist/runtime/components/fields/nui/useNuiFieldAttrs.d.ts +13 -0
  91. package/dist/runtime/components/fields/nui/useNuiFieldAttrs.d.ts.map +1 -0
  92. package/dist/runtime/components/fields/nui/useNuiFieldAttrs.js +19 -0
  93. package/dist/runtime/composables/defineFormField.d.ts +25 -0
  94. package/dist/runtime/composables/defineFormField.d.ts.map +1 -0
  95. package/dist/runtime/composables/defineFormField.js +27 -0
  96. package/dist/runtime/composables/useCurrentLang.d.ts +6 -0
  97. package/dist/runtime/composables/useCurrentLang.d.ts.map +1 -0
  98. package/dist/runtime/composables/useCurrentLang.js +9 -0
  99. package/dist/runtime/composables/useFocusMode.d.ts +40 -0
  100. package/dist/runtime/composables/useFocusMode.d.ts.map +1 -0
  101. package/dist/runtime/composables/useFocusMode.js +31 -0
  102. package/dist/runtime/composables/useFormDraft.d.ts +60 -0
  103. package/dist/runtime/composables/useFormDraft.d.ts.map +1 -0
  104. package/dist/runtime/composables/useFormDraft.js +52 -0
  105. package/dist/runtime/composables/useFormFromSchema.d.ts +21 -0
  106. package/dist/runtime/composables/useFormFromSchema.d.ts.map +1 -0
  107. package/dist/runtime/composables/useFormFromSchema.js +142 -0
  108. package/dist/runtime/composables/useMapoForm.d.ts +96 -0
  109. package/dist/runtime/composables/useMapoForm.d.ts.map +1 -0
  110. package/dist/runtime/composables/useMapoForm.js +323 -0
  111. package/dist/runtime/composables/useProgressiveDisclosure.d.ts +44 -0
  112. package/dist/runtime/composables/useProgressiveDisclosure.d.ts.map +1 -0
  113. package/dist/runtime/composables/useProgressiveDisclosure.js +23 -0
  114. package/dist/runtime/composables/useUnsavedChangesGuard.d.ts +24 -0
  115. package/dist/runtime/composables/useUnsavedChangesGuard.d.ts.map +1 -0
  116. package/dist/runtime/composables/useUnsavedChangesGuard.js +27 -0
  117. package/dist/runtime/devtools/DevtoolsFormsPage.vue +235 -0
  118. package/dist/runtime/devtools/DevtoolsFormsPage.vue.d.ts +4 -0
  119. package/dist/runtime/devtools/DevtoolsFormsPage.vue.d.ts.map +1 -0
  120. package/dist/runtime/plugins/form-registry.d.ts +18 -0
  121. package/dist/runtime/plugins/form-registry.d.ts.map +1 -0
  122. package/dist/runtime/plugins/form-registry.js +19 -0
  123. package/dist/runtime/registry/defaults.d.ts +3 -0
  124. package/dist/runtime/registry/defaults.d.ts.map +1 -0
  125. package/dist/runtime/registry/defaults.js +87 -0
  126. package/dist/runtime/types/descriptor.d.ts +359 -0
  127. package/dist/runtime/types/descriptor.d.ts.map +1 -0
  128. package/dist/runtime/types/descriptor.js +26 -0
  129. package/dist/runtime/types/index.d.ts +11 -0
  130. package/dist/runtime/types/index.d.ts.map +1 -0
  131. package/dist/runtime/types/index.js +6 -0
  132. package/dist/runtime/types/layout.d.ts +21 -0
  133. package/dist/runtime/types/layout.d.ts.map +1 -0
  134. package/dist/runtime/types/layout.js +0 -0
  135. package/dist/runtime/types/registry.d.ts +62 -0
  136. package/dist/runtime/types/registry.d.ts.map +1 -0
  137. package/dist/runtime/types/registry.js +19 -0
  138. package/dist/runtime/utils/flattenFieldGroups.d.ts +50 -0
  139. package/dist/runtime/utils/flattenFieldGroups.d.ts.map +1 -0
  140. package/dist/runtime/utils/flattenFieldGroups.js +24 -0
  141. package/dist/types.d.mts +6 -10
  142. package/package.json +3 -3
  143. package/dist/index.d.cts +0 -1
  144. package/dist/module.d.cts +0 -2
@@ -0,0 +1,23 @@
1
+ import { getNestedValue } from "@mapomodule/utils";
2
+ export function when(...conditions) {
3
+ return (ctx) => conditions.every((c) => c(ctx));
4
+ }
5
+ export function whenAny(...conditions) {
6
+ return (ctx) => conditions.some((c) => c(ctx));
7
+ }
8
+ export function whenNot(condition) {
9
+ return (ctx) => !condition(ctx);
10
+ }
11
+ export function matchesField(key, matcher) {
12
+ return (ctx) => {
13
+ const val = getNestedValue(ctx.model, key);
14
+ return typeof matcher === "function" ? matcher(val) : val === matcher;
15
+ };
16
+ }
17
+ export const isOneOf = (options) => (val) => options.includes(val);
18
+ export const isNoneOf = (options) => (val) => !options.includes(val);
19
+ export const isNotEmpty = () => (val) => val != null && val !== "" && val !== false && (!Array.isArray(val) || val.length > 0);
20
+ export const isEmpty = () => (val) => !isNotEmpty()(val);
21
+ export const greaterThan = (n) => (val) => Number(val) > n;
22
+ export const lessThan = (n) => (val) => Number(val) < n;
23
+ export const matches = (pattern) => (val) => pattern.test(String(val ?? ""));
@@ -0,0 +1,24 @@
1
+ import { type Ref } from "vue";
2
+ /** Optional configuration for the unsaved-changes navigation guard. */
3
+ export interface UnsavedChangesGuardOptions {
4
+ message?: string;
5
+ enabled?: Ref<boolean> | (() => boolean);
6
+ }
7
+ /**
8
+ * Registers two guards that warn the user before abandoning a dirty form:
9
+ * - A Vue Router `onBeforeRouteLeave` guard (in-app navigation).
10
+ * - A native `beforeunload` event listener (tab close / hard refresh).
11
+ *
12
+ * Both guards are no-ops when `isDirty` is `false` or when `options.enabled`
13
+ * returns `false`. The browser guard is skipped in SSR environments.
14
+ *
15
+ * @param isDirty - Reactive flag indicating whether the form has unsaved changes.
16
+ * @param options - Optional message override and an `enabled` predicate.
17
+ *
18
+ * @example
19
+ * useUnsavedChangesGuard(form.isDirty, {
20
+ * message: 'Leave without saving your article?',
21
+ * })
22
+ */
23
+ export declare function useUnsavedChangesGuard(isDirty: Ref<boolean>, options?: UnsavedChangesGuardOptions): void;
24
+ //# sourceMappingURL=useUnsavedChangesGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useUnsavedChangesGuard.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useUnsavedChangesGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAGhD,uEAAuE;AACvE,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,EACrB,OAAO,GAAE,0BAA+B,QA8BzC"}
@@ -0,0 +1,27 @@
1
+ import { onBeforeUnmount } from "vue";
2
+ import { onBeforeRouteLeave } from "vue-router";
3
+ export function useUnsavedChangesGuard(isDirty, options = {}) {
4
+ const message = options.message ?? "You have unsaved changes. Do you really want to leave?";
5
+ const canUseWindow = typeof window !== "undefined";
6
+ const isEnabled = () => {
7
+ if (typeof options.enabled === "function") return options.enabled();
8
+ if (options.enabled) return options.enabled.value;
9
+ return true;
10
+ };
11
+ onBeforeRouteLeave(() => {
12
+ if (!isEnabled() || !isDirty.value) return true;
13
+ if (!canUseWindow) return true;
14
+ return window.confirm(message);
15
+ });
16
+ const beforeUnload = (e) => {
17
+ if (!isEnabled() || !isDirty.value) return;
18
+ e.preventDefault();
19
+ e.returnValue = "";
20
+ };
21
+ if (canUseWindow) {
22
+ window.addEventListener("beforeunload", beforeUnload);
23
+ onBeforeUnmount(
24
+ () => window.removeEventListener("beforeunload", beforeUnload)
25
+ );
26
+ }
27
+ }
@@ -0,0 +1,235 @@
1
+ <script setup>
2
+ import { definePageMeta, useNuxtApp, useRuntimeConfig } from "#imports";
3
+ import { computed } from "vue";
4
+ definePageMeta({ layout: false });
5
+ const { $mapoFormRegistry } = useNuxtApp();
6
+ const config = useRuntimeConfig().public.mapoForm;
7
+ const registeredTypes = computed(() => {
8
+ const mapping = $mapoFormRegistry?.mapping || {};
9
+ return Object.keys(mapping).sort();
10
+ });
11
+ const builtInTypes = /* @__PURE__ */ new Set([
12
+ "text",
13
+ "textarea",
14
+ "number",
15
+ "boolean",
16
+ "switch",
17
+ "color",
18
+ "file",
19
+ "slider",
20
+ "select",
21
+ "date",
22
+ "time",
23
+ "datetime",
24
+ "fks",
25
+ "m2m",
26
+ "seo",
27
+ "editor",
28
+ "map",
29
+ "repeater"
30
+ ]);
31
+ const userMappings = computed(
32
+ () => registeredTypes.value.filter((t) => !builtInTypes.has(t))
33
+ );
34
+ const globalDebounce = computed(() => config?.debounce || 300);
35
+ const globalExpanded = computed(() => config?.groups?.expanded !== false);
36
+ function componentLabel(type) {
37
+ const v = $mapoFormRegistry?.mapping?.[type];
38
+ if (!v) return "\u2014";
39
+ if (typeof v === "string") return v;
40
+ if (typeof v === "function") return "(lazy)";
41
+ if (typeof v === "object") {
42
+ const name = v.__name;
43
+ if (name) return name;
44
+ }
45
+ return "(component)";
46
+ }
47
+ function attrsKeys(type) {
48
+ const a = $mapoFormRegistry?.attrs?.[type];
49
+ return a ? Object.keys(a).join(", ") : "";
50
+ }
51
+ function accessorKeys(type) {
52
+ const ac = $mapoFormRegistry?.accessor?.[type];
53
+ return ac ? Object.keys(ac).join(" / ") : "";
54
+ }
55
+ function isUserType(type) {
56
+ return userMappings.value.includes(type);
57
+ }
58
+ </script>
59
+
60
+ <template>
61
+ <div class="min-h-screen bg-gray-950 text-gray-100 font-mono text-sm p-6">
62
+ <!-- Header -->
63
+ <div class="flex items-center gap-3 mb-8 border-b border-gray-800 pb-4">
64
+ <span class="text-2xl">🗒️</span>
65
+ <div>
66
+ <h1 class="text-lg font-semibold text-white">Mapo Forms</h1>
67
+ <p class="text-gray-400 text-xs">Devtools panel — development only</p>
68
+ </div>
69
+ </div>
70
+
71
+ <div class="grid grid-cols-3 gap-6">
72
+ <!-- Column 1: Global configuration -->
73
+ <div class="col-span-1">
74
+ <h2
75
+ class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3"
76
+ >
77
+ Global config
78
+ </h2>
79
+ <div
80
+ class="bg-gray-900 rounded-lg border border-gray-800 p-4 space-y-3"
81
+ >
82
+ <div>
83
+ <span class="text-gray-500">debounce</span>
84
+ <span class="ml-2 text-amber-400">{{ globalDebounce }}ms</span>
85
+ </div>
86
+ <div>
87
+ <span class="text-gray-500">groups.expanded</span>
88
+ <span
89
+ class="ml-2"
90
+ :class="globalExpanded ? 'text-green-400' : 'text-red-400'"
91
+ >
92
+ {{ globalExpanded }}
93
+ </span>
94
+ </div>
95
+ <div>
96
+ <span class="text-gray-500">custom types</span>
97
+ <span class="ml-2 text-blue-400">{{ userMappings.length }}</span>
98
+ </div>
99
+ </div>
100
+
101
+ <!-- User-defined custom types -->
102
+ <h2
103
+ class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3 mt-6"
104
+ >
105
+ Registered custom fields
106
+ </h2>
107
+ <div class="bg-gray-900 rounded-lg border border-gray-800 p-4">
108
+ <div
109
+ v-if="userMappings.length === 0"
110
+ class="text-gray-500 text-xs italic"
111
+ >
112
+ No custom types (nuxt.config → mapoForm.fields.mapping)
113
+ </div>
114
+ <div
115
+ v-for="type in userMappings"
116
+ :key="type"
117
+ class="flex items-center gap-2 py-1"
118
+ >
119
+ <span class="w-2 h-2 rounded-full bg-blue-500 shrink-0" />
120
+ <span class="text-blue-300">{{ type }}</span>
121
+ </div>
122
+ </div>
123
+ </div>
124
+
125
+ <!-- Columns 2-3: Full registry -->
126
+ <div class="col-span-2">
127
+ <h2
128
+ class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3"
129
+ >
130
+ Field registry
131
+ <span class="ml-2 text-gray-600 normal-case font-normal"
132
+ >({{ registeredTypes.length }} types)</span
133
+ >
134
+ </h2>
135
+ <div
136
+ class="bg-gray-900 rounded-lg border border-gray-800 overflow-hidden"
137
+ >
138
+ <table class="w-full text-xs">
139
+ <thead>
140
+ <tr class="border-b border-gray-800 bg-gray-900/80">
141
+ <th class="text-left px-4 py-2 text-gray-500 font-medium w-8">
142
+ #
143
+ </th>
144
+ <th class="text-left px-4 py-2 text-gray-500 font-medium">
145
+ type
146
+ </th>
147
+ <th class="text-left px-4 py-2 text-gray-500 font-medium">
148
+ component
149
+ </th>
150
+ <th class="text-left px-4 py-2 text-gray-500 font-medium">
151
+ default attrs
152
+ </th>
153
+ <th class="text-left px-4 py-2 text-gray-500 font-medium">
154
+ accessor
155
+ </th>
156
+ </tr>
157
+ </thead>
158
+ <tbody>
159
+ <tr
160
+ v-for="(type, i) in registeredTypes"
161
+ :key="type"
162
+ class="border-b border-gray-800/50 hover:bg-gray-800/40 transition-colors"
163
+ >
164
+ <td class="px-4 py-2 text-gray-600">
165
+ {{ i + 1 }}
166
+ </td>
167
+ <td class="px-4 py-2">
168
+ <span
169
+ class="px-1.5 py-0.5 rounded text-xs"
170
+ :class="
171
+ isUserType(type) ? 'bg-blue-900/50 text-blue-300' : 'bg-gray-800 text-gray-200'
172
+ "
173
+ >{{ type }}</span
174
+ >
175
+ </td>
176
+ <td class="px-4 py-2 text-green-300">
177
+ {{ componentLabel(type) }}
178
+ </td>
179
+ <td class="px-4 py-2 text-gray-400">
180
+ <span v-if="attrsKeys(type)" class="text-amber-300/80">{{
181
+ attrsKeys(type)
182
+ }}</span>
183
+ <span v-else class="text-gray-700">—</span>
184
+ </td>
185
+ <td class="px-4 py-2">
186
+ <span v-if="accessorKeys(type)" class="text-purple-400">{{
187
+ accessorKeys(type)
188
+ }}</span>
189
+ <span v-else class="text-gray-700">—</span>
190
+ </td>
191
+ </tr>
192
+ </tbody>
193
+ </table>
194
+ </div>
195
+
196
+ <!-- Quick reference -->
197
+ <h2
198
+ class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3 mt-6"
199
+ >
200
+ Quick reference — add a custom type
201
+ </h2>
202
+ <div class="bg-gray-900 rounded-lg border border-gray-800 p-4">
203
+ <pre
204
+ class="text-xs text-gray-300 overflow-x-auto leading-relaxed"
205
+ ><span class="text-gray-500">// nuxt.config.ts</span>
206
+ <span class="text-blue-300">mapoForm</span>: {
207
+ fields: {
208
+ mapping: {
209
+ <span class="text-amber-300">'my-type'</span>: () => import(<span class="text-green-300">'~/components/MyField.vue'</span>),
210
+ },
211
+ attrs: {
212
+ <span class="text-amber-300">'my-type'</span>: { <span class="text-purple-300">someProp</span>: <span class="text-green-300">true</span> },
213
+ },
214
+ },
215
+ }
216
+
217
+ <span class="text-gray-500">// descriptor</span>
218
+ { key: <span class="text-green-300">'fieldKey'</span>, type: <span class="text-amber-300">'my-type'</span> }</pre>
219
+ </div>
220
+
221
+ <!-- If MapoUnknownField is rendered -->
222
+ <div
223
+ class="mt-4 p-3 bg-amber-900/20 border border-amber-700/40 rounded-lg text-xs text-amber-300"
224
+ >
225
+ <strong>⚠ MapoUnknownField</strong> — if a yellow placeholder appears
226
+ in a form, the type is missing from the registry. Check
227
+ <code class="bg-amber-900/30 px-1 rounded">descriptor.type</code> and
228
+ add the mapping shown above. In development, a
229
+ <code class="bg-amber-900/30 px-1 rounded">console.warn</code> is
230
+ emitted with the full path.
231
+ </div>
232
+ </div>
233
+ </div>
234
+ </div>
235
+ </template>
@@ -0,0 +1,4 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
4
+ //# sourceMappingURL=DevtoolsFormsPage.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevtoolsFormsPage.vue.d.ts","sourceRoot":"","sources":["../../../src/runtime/devtools/DevtoolsFormsPage.vue"],"names":[],"mappings":"AAmwBA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -0,0 +1,18 @@
1
+ import type { FieldRegistry } from "../types/index.js";
2
+ declare module "#app" {
3
+ interface NuxtApp {
4
+ $mapoFormRegistry: FieldRegistry;
5
+ }
6
+ }
7
+ declare module "vue" {
8
+ interface ComponentCustomProperties {
9
+ $mapoFormRegistry: FieldRegistry;
10
+ }
11
+ }
12
+ declare const _default: import("#app").Plugin<{
13
+ mapoFormRegistry: FieldRegistry;
14
+ }> & import("#app").ObjectPlugin<{
15
+ mapoFormRegistry: FieldRegistry;
16
+ }>;
17
+ export default _default;
18
+ //# sourceMappingURL=form-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-registry.d.ts","sourceRoot":"","sources":["../../../src/runtime/plugins/form-registry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAyB,MAAM,mBAAmB,CAAC;AAE9E,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAU,OAAO;QACf,iBAAiB,EAAE,aAAa,CAAC;KAClC;CACF;AAED,OAAO,QAAQ,KAAK,CAAC;IACnB,UAAU,yBAAyB;QACjC,iBAAiB,EAAE,aAAa,CAAC;KAClC;CACF;;;;;;AAED,wBA0BG"}
@@ -0,0 +1,19 @@
1
+ import { defineNuxtPlugin, useRuntimeConfig } from "#app";
2
+ import { defaultRegistry } from "../registry/defaults.js";
3
+ export default defineNuxtPlugin(() => {
4
+ const config = useRuntimeConfig();
5
+ const mapoForm = config.public.mapoForm;
6
+ const userAttrs = mapoForm?.fields?.attrs ?? {};
7
+ const mergedAttrs = { ...defaultRegistry.attrs };
8
+ for (const [type, overrides] of Object.entries(userAttrs)) {
9
+ mergedAttrs[type] = { ...mergedAttrs[type] ?? {}, ...overrides };
10
+ }
11
+ const registry = {
12
+ mapping: { ...defaultRegistry.mapping },
13
+ attrs: mergedAttrs,
14
+ accessor: { ...defaultRegistry.accessor }
15
+ };
16
+ return {
17
+ provide: { mapoFormRegistry: registry }
18
+ };
19
+ });
@@ -0,0 +1,3 @@
1
+ import type { FieldRegistry } from "../types/index.js";
2
+ export declare const defaultRegistry: FieldRegistry;
3
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../../src/runtime/registry/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAMvD,eAAO,MAAM,eAAe,EAAE,aA0F7B,CAAC"}
@@ -0,0 +1,87 @@
1
+ export const defaultRegistry = {
2
+ mapping: {
3
+ // ─── Nuxt UI — thin wrappers that use <UInput> etc. in the template ──────
4
+ text: () => import("../components/fields/nui/NuiInput.vue"),
5
+ textarea: () => import("../components/fields/nui/NuiTextarea.vue"),
6
+ email: () => import("../components/fields/nui/NuiInput.vue"),
7
+ url: () => import("../components/fields/nui/NuiInput.vue"),
8
+ number: () => import("../components/fields/nui/NuiInput.vue"),
9
+ boolean: () => import("../components/fields/nui/NuiCheckbox.vue"),
10
+ switch: () => import("../components/fields/nui/NuiSwitch.vue"),
11
+ slider: () => import("../components/fields/nui/NuiSlider.vue"),
12
+ color: () => import("../components/fields/nui/NuiInput.vue"),
13
+ file: () => import("../components/fields/MapoFileField.vue"),
14
+ // ─── Nuxt UI with specific config ────────────────────────────────────────
15
+ select: () => import("../components/fields/nui/NuiSelectMenu.vue"),
16
+ // ─── Custom Mapo components (non-trivial logic) ───────────────────────────
17
+ date: () => import("../components/fields/MapoDateField.vue"),
18
+ time: () => import("../components/fields/MapoTimeField.vue"),
19
+ datetime: () => import("../components/fields/MapoDateTimeField.vue"),
20
+ fks: () => import("../components/fields/MapoFksField.vue"),
21
+ m2m: () => import("../components/fields/MapoFksField.vue"),
22
+ seo: () => import("../components/fields/MapoSeoPreview.vue"),
23
+ editor: () => import("../components/fields/MapoWygEditor.vue"),
24
+ map: () => import("../components/fields/MapoMapField.vue"),
25
+ repeater: () => import("../components/fields/MapoRepeater.vue")
26
+ // media, media-m2m, enhanced-media: added in Phase 6
27
+ },
28
+ attrs: {
29
+ // Applied to all fields
30
+ All: {
31
+ class: "w-full"
32
+ },
33
+ number: {
34
+ type: "number"
35
+ },
36
+ email: {
37
+ type: "email"
38
+ },
39
+ url: {
40
+ type: "url"
41
+ },
42
+ color: {
43
+ type: "color"
44
+ },
45
+ select: {
46
+ // NUI v3 uses `labelKey`/`valueKey`; descriptor uses `{ text, value }` convention.
47
+ labelKey: "text",
48
+ valueKey: "value"
49
+ },
50
+ fks: {
51
+ itemValue: "id",
52
+ returnObject: true
53
+ },
54
+ m2m: {
55
+ itemValue: "id",
56
+ returnObject: true,
57
+ multiple: true
58
+ }
59
+ },
60
+ accessor: {
61
+ // date/time/datetime: conversion handled internally by the Mapo*Field components
62
+ // (ISO string ↔ @internationalized/date CalendarDate/CalendarDateTime).
63
+ seo: {
64
+ // v1 used `permalink` key — accept both `permalink` and `url` aliases, normalise to `permalink`
65
+ get: ({ val }) => {
66
+ const v = val ?? {};
67
+ const { url, ...rest } = v;
68
+ return {
69
+ title: "",
70
+ description: "",
71
+ ...rest,
72
+ permalink: rest.permalink ?? url ?? ""
73
+ };
74
+ },
75
+ set: ({ val }) => {
76
+ const v = val ?? {};
77
+ const { url, ...rest } = v;
78
+ return {
79
+ title: "",
80
+ description: "",
81
+ ...rest,
82
+ permalink: rest.permalink ?? url ?? ""
83
+ };
84
+ }
85
+ }
86
+ }
87
+ };