@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.
- package/dist/__tests__/__mocks__/nuxt-imports.d.ts +4 -0
- package/dist/__tests__/__mocks__/nuxt-imports.d.ts.map +1 -0
- package/dist/__tests__/registry.test.d.ts +2 -0
- package/dist/__tests__/registry.test.d.ts.map +1 -0
- package/dist/__tests__/useFormFromSchema.test.d.ts +2 -0
- package/dist/__tests__/useFormFromSchema.test.d.ts.map +1 -0
- package/dist/__tests__/useMapoForm.test.d.ts +2 -0
- package/dist/__tests__/useMapoForm.test.d.ts.map +1 -0
- package/dist/index.d.mts +9 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +7 -34
- package/dist/module.d.mts +20 -2
- package/dist/module.d.ts +12 -2
- package/dist/module.d.ts.map +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +167 -13
- package/dist/runtime/components/MapoFocusPortal.vue +113 -0
- package/dist/runtime/components/MapoFocusPortal.vue.d.ts +4 -0
- package/dist/runtime/components/MapoFocusPortal.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoForm.vue +168 -0
- package/dist/runtime/components/MapoForm.vue.d.ts +47 -0
- package/dist/runtime/components/MapoForm.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormField.vue +269 -0
- package/dist/runtime/components/MapoFormField.vue.d.ts +46 -0
- package/dist/runtime/components/MapoFormField.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormFlatSection.vue +149 -0
- package/dist/runtime/components/MapoFormFlatSection.vue.d.ts +15 -0
- package/dist/runtime/components/MapoFormFlatSection.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormGroup.vue +247 -0
- package/dist/runtime/components/MapoFormGroup.vue.d.ts +44 -0
- package/dist/runtime/components/MapoFormGroup.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormTabs.vue +108 -0
- package/dist/runtime/components/MapoFormTabs.vue.d.ts +15 -0
- package/dist/runtime/components/MapoFormTabs.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoUnknownField.vue +62 -0
- package/dist/runtime/components/MapoUnknownField.vue.d.ts +16 -0
- package/dist/runtime/components/MapoUnknownField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoDateField.vue +86 -0
- package/dist/runtime/components/fields/MapoDateField.vue.d.ts +17 -0
- package/dist/runtime/components/fields/MapoDateField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoDateTimeField.vue +157 -0
- package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts +17 -0
- package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoFileField.vue +97 -0
- package/dist/runtime/components/fields/MapoFileField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoFileField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoFksField.vue +118 -0
- package/dist/runtime/components/fields/MapoFksField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoFksField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMapField.vue +97 -0
- package/dist/runtime/components/fields/MapoMapField.vue.d.ts +22 -0
- package/dist/runtime/components/fields/MapoMapField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMapFieldClient.vue +67 -0
- package/dist/runtime/components/fields/MapoMapFieldClient.vue.d.ts +20 -0
- package/dist/runtime/components/fields/MapoMapFieldClient.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoRepeater.vue +449 -0
- package/dist/runtime/components/fields/MapoRepeater.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoRepeater.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoRepeaterItem.vue +341 -0
- package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts +55 -0
- package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoSeoPreview.vue +118 -0
- package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts +22 -0
- package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoTimeField.vue +44 -0
- package/dist/runtime/components/fields/MapoTimeField.vue.d.ts +17 -0
- package/dist/runtime/components/fields/MapoTimeField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoWygEditor.vue +227 -0
- package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiCheckbox.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiCheckbox.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiCheckbox.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiInput.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiInput.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiInput.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiSelectMenu.vue +18 -0
- package/dist/runtime/components/fields/nui/NuiSelectMenu.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiSelectMenu.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiSlider.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiSlider.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiSlider.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiSwitch.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiSwitch.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiSwitch.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiTextarea.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiTextarea.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiTextarea.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/useNuiFieldAttrs.d.ts +13 -0
- package/dist/runtime/components/fields/nui/useNuiFieldAttrs.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/useNuiFieldAttrs.js +19 -0
- package/dist/runtime/composables/defineFormField.d.ts +25 -0
- package/dist/runtime/composables/defineFormField.d.ts.map +1 -0
- package/dist/runtime/composables/defineFormField.js +27 -0
- package/dist/runtime/composables/useCurrentLang.d.ts +6 -0
- package/dist/runtime/composables/useCurrentLang.d.ts.map +1 -0
- package/dist/runtime/composables/useCurrentLang.js +9 -0
- package/dist/runtime/composables/useFocusMode.d.ts +40 -0
- package/dist/runtime/composables/useFocusMode.d.ts.map +1 -0
- package/dist/runtime/composables/useFocusMode.js +31 -0
- package/dist/runtime/composables/useFormDraft.d.ts +60 -0
- package/dist/runtime/composables/useFormDraft.d.ts.map +1 -0
- package/dist/runtime/composables/useFormDraft.js +52 -0
- package/dist/runtime/composables/useFormFromSchema.d.ts +21 -0
- package/dist/runtime/composables/useFormFromSchema.d.ts.map +1 -0
- package/dist/runtime/composables/useFormFromSchema.js +142 -0
- package/dist/runtime/composables/useMapoForm.d.ts +96 -0
- package/dist/runtime/composables/useMapoForm.d.ts.map +1 -0
- package/dist/runtime/composables/useMapoForm.js +323 -0
- package/dist/runtime/composables/useProgressiveDisclosure.d.ts +44 -0
- package/dist/runtime/composables/useProgressiveDisclosure.d.ts.map +1 -0
- package/dist/runtime/composables/useProgressiveDisclosure.js +23 -0
- package/dist/runtime/composables/useUnsavedChangesGuard.d.ts +24 -0
- package/dist/runtime/composables/useUnsavedChangesGuard.d.ts.map +1 -0
- package/dist/runtime/composables/useUnsavedChangesGuard.js +27 -0
- package/dist/runtime/devtools/DevtoolsFormsPage.vue +235 -0
- package/dist/runtime/devtools/DevtoolsFormsPage.vue.d.ts +4 -0
- package/dist/runtime/devtools/DevtoolsFormsPage.vue.d.ts.map +1 -0
- package/dist/runtime/plugins/form-registry.d.ts +18 -0
- package/dist/runtime/plugins/form-registry.d.ts.map +1 -0
- package/dist/runtime/plugins/form-registry.js +19 -0
- package/dist/runtime/registry/defaults.d.ts +3 -0
- package/dist/runtime/registry/defaults.d.ts.map +1 -0
- package/dist/runtime/registry/defaults.js +87 -0
- package/dist/runtime/types/descriptor.d.ts +359 -0
- package/dist/runtime/types/descriptor.d.ts.map +1 -0
- package/dist/runtime/types/descriptor.js +26 -0
- package/dist/runtime/types/index.d.ts +11 -0
- package/dist/runtime/types/index.d.ts.map +1 -0
- package/dist/runtime/types/index.js +6 -0
- package/dist/runtime/types/layout.d.ts +21 -0
- package/dist/runtime/types/layout.d.ts.map +1 -0
- package/dist/runtime/types/layout.js +0 -0
- package/dist/runtime/types/registry.d.ts +62 -0
- package/dist/runtime/types/registry.d.ts.map +1 -0
- package/dist/runtime/types/registry.js +19 -0
- package/dist/runtime/utils/flattenFieldGroups.d.ts +50 -0
- package/dist/runtime/utils/flattenFieldGroups.d.ts.map +1 -0
- package/dist/runtime/utils/flattenFieldGroups.js +24 -0
- package/dist/types.d.mts +6 -10
- package/package.json +3 -3
- package/dist/index.d.cts +0 -1
- 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 @@
|
|
|
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
|
+
};
|