@mapomodule/form 2.0.0-beta.0 → 2.0.0-beta.10
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/README.md +2 -1
- package/dist/__tests__/__mocks__/nuxt-imports.d.ts +4 -0
- package/dist/__tests__/__mocks__/nuxt-imports.d.ts.map +1 -0
- package/dist/__tests__/camomillaRoundTrip.test.d.ts +2 -0
- package/dist/__tests__/camomillaRoundTrip.test.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 +187 -13
- package/dist/runtime/components/MapoFocusPortal.vue +115 -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 +110 -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 +88 -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 +159 -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/MapoEnhancedMediaField.vue +130 -0
- package/dist/runtime/components/fields/MapoEnhancedMediaField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoEnhancedMediaField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoFileField.vue +99 -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 +122 -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 +99 -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/MapoMediaField.vue +91 -0
- package/dist/runtime/components/fields/MapoMediaField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoMediaField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMediaM2mField.vue +110 -0
- package/dist/runtime/components/fields/MapoMediaM2mField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoMediaM2mField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMediaUnavailable.vue +15 -0
- package/dist/runtime/components/fields/MapoMediaUnavailable.vue.d.ts +4 -0
- package/dist/runtime/components/fields/MapoMediaUnavailable.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoRepeater.vue +459 -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 +346 -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 +122 -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 +252 -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 +145 -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 +325 -0
- package/dist/runtime/composables/useMediaManager.d.ts +17 -0
- package/dist/runtime/composables/useMediaManager.d.ts.map +1 -0
- package/dist/runtime/composables/useMediaManager.js +6 -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 +28 -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 +93 -0
- package/dist/runtime/types/descriptor.d.ts +373 -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 +12 -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/media.d.ts +43 -0
- package/dist/runtime/types/media.d.ts.map +1 -0
- package/dist/runtime/types/media.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 +10 -8
- package/src/__tests__/article.schema.json +193 -0
- package/src/__tests__/camomillaRoundTrip.test.ts +73 -0
- package/src/__tests__/useFormFromSchema.test.ts +24 -0
- package/src/__tests__/useMapoForm.test.ts +5 -0
- package/src/index.ts +5 -0
- package/src/module.ts +34 -0
- package/src/runtime/components/MapoFocusPortal.vue +3 -1
- package/src/runtime/components/MapoFormTabs.vue +4 -1
- package/src/runtime/components/fields/MapoDateField.vue +3 -1
- package/src/runtime/components/fields/MapoDateTimeField.vue +3 -1
- package/src/runtime/components/fields/MapoEnhancedMediaField.vue +153 -0
- package/src/runtime/components/fields/MapoFileField.vue +3 -1
- package/src/runtime/components/fields/MapoFksField.vue +7 -2
- package/src/runtime/components/fields/MapoMapField.vue +5 -2
- package/src/runtime/components/fields/MapoMediaField.vue +104 -0
- package/src/runtime/components/fields/MapoMediaM2mField.vue +130 -0
- package/src/runtime/components/fields/MapoMediaUnavailable.vue +19 -0
- package/src/runtime/components/fields/MapoRepeater.vue +33 -26
- package/src/runtime/components/fields/MapoRepeaterItem.vue +14 -8
- package/src/runtime/components/fields/MapoSeoPreview.vue +17 -13
- package/src/runtime/components/fields/MapoWygEditor.vue +47 -5
- package/src/runtime/composables/useFormFromSchema.ts +4 -1
- package/src/runtime/composables/useMapoForm.ts +4 -2
- package/src/runtime/composables/useMediaManager.ts +21 -0
- package/src/runtime/composables/useUnsavedChangesGuard.ts +3 -3
- package/src/runtime/registry/defaults.ts +9 -1
- package/src/runtime/types/descriptor.ts +22 -7
- package/src/runtime/types/index.ts +4 -0
- package/src/runtime/types/media.ts +44 -0
- package/dist/index.d.cts +0 -1
- package/dist/module.d.cts +0 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flattenFieldGroups.d.ts","sourceRoot":"","sources":["../../../src/runtime/utils/flattenFieldGroups.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC/D,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,yFAAyF;IACzF,MAAM,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;CAChE;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,KAAK,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAC7D,eAAe,CAAC,EAAE,MAAM,EACxB,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAChC,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAsBzB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function isGroupDescriptor(item) {
|
|
2
|
+
return "fields" in item && Array.isArray(item.fields);
|
|
3
|
+
}
|
|
4
|
+
export function flattenFieldGroups(items, _inheritedGroup, _inheritedTab) {
|
|
5
|
+
const result = [];
|
|
6
|
+
for (const item of items) {
|
|
7
|
+
if (isGroupDescriptor(item)) {
|
|
8
|
+
const group = item.group ?? _inheritedGroup;
|
|
9
|
+
const tab = item.tab ?? _inheritedTab;
|
|
10
|
+
result.push(...flattenFieldGroups(item.fields, group, tab));
|
|
11
|
+
} else {
|
|
12
|
+
const field = item;
|
|
13
|
+
const merged = { ...field };
|
|
14
|
+
if (_inheritedGroup && !merged.group) {
|
|
15
|
+
Object.assign(merged, { group: _inheritedGroup });
|
|
16
|
+
}
|
|
17
|
+
if (_inheritedTab && !merged.tab) {
|
|
18
|
+
Object.assign(merged, { tab: _inheritedTab });
|
|
19
|
+
}
|
|
20
|
+
result.push(merged);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
package/dist/types.d.mts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NuxtModule } from '@nuxt/schema'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
interface RuntimeNuxtHooks extends ModuleRuntimeHooks {}
|
|
5
|
-
}
|
|
3
|
+
import type { default as Module } from './module.mjs'
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
interface NuxtHooks extends ModuleHooks {}
|
|
9
|
-
interface RuntimeConfig extends ModuleRuntimeConfig {}
|
|
10
|
-
interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {}
|
|
11
|
-
}
|
|
5
|
+
export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
|
|
12
6
|
|
|
13
|
-
export
|
|
7
|
+
export { default } from './module.mjs'
|
|
8
|
+
|
|
9
|
+
export { type MapoFormOptions } from './module.mjs'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mapomodule/form",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.10",
|
|
4
4
|
"description": "Mapo declarative typed form engine and field components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "nuxt-module-build build && vue-tsc -p tsconfig.build-types.json && node ../../../scripts/clean-empty-vue-dts.mjs dist",
|
|
42
42
|
"dev": "nuxt-module-build build --stub --watch",
|
|
43
|
-
"prepare": "nuxt-module-build build --stub",
|
|
44
43
|
"dev:prepare": "nuxt-module-build build --stub",
|
|
45
44
|
"typecheck": "vue-tsc --noEmit",
|
|
46
45
|
"lint": "eslint src",
|
|
@@ -48,14 +47,17 @@
|
|
|
48
47
|
},
|
|
49
48
|
"dependencies": {
|
|
50
49
|
"@internationalized/date": "^3.7.0",
|
|
51
|
-
"@mapomodule/
|
|
52
|
-
"@mapomodule/
|
|
50
|
+
"@mapomodule/i18n": "2.0.0-beta.2",
|
|
51
|
+
"@mapomodule/store": "2.0.0-beta.6",
|
|
52
|
+
"@mapomodule/utils": "2.0.0-beta.5",
|
|
53
|
+
"@tiptap/extension-image": "^2.11.5",
|
|
53
54
|
"@tiptap/extension-link": "^2.11.5",
|
|
54
55
|
"@tiptap/extension-underline": "^2.11.5",
|
|
55
56
|
"@tiptap/starter-kit": "^2.11.5",
|
|
56
57
|
"@tiptap/vue-3": "^2.11.5",
|
|
57
58
|
"leaflet": "^1.9.4",
|
|
58
|
-
"vue-draggable-plus": "^0.6.0"
|
|
59
|
+
"vue-draggable-plus": "^0.6.0",
|
|
60
|
+
"vue-i18n": "^11.4.0"
|
|
59
61
|
},
|
|
60
62
|
"optionalDependencies": {
|
|
61
63
|
"@types/leaflet": "^1.9.18"
|
|
@@ -68,10 +70,10 @@
|
|
|
68
70
|
"@nuxt/kit": "4.4.2",
|
|
69
71
|
"@nuxt/module-builder": "^1.0.2",
|
|
70
72
|
"@nuxt/schema": "4.4.2",
|
|
71
|
-
"nuxt": "^4.
|
|
73
|
+
"nuxt": "^4.5.1",
|
|
72
74
|
"vitest": "^3.2.0",
|
|
73
|
-
"vue": "^3.5.
|
|
74
|
-
"vue-router": "^5.0
|
|
75
|
+
"vue": "^3.5.40",
|
|
76
|
+
"vue-router": "^5.2.0",
|
|
75
77
|
"vue-tsc": "^3.3.4",
|
|
76
78
|
"zod": "^3.23.8"
|
|
77
79
|
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"properties": {
|
|
4
|
+
"indexable": {
|
|
5
|
+
"type": "boolean",
|
|
6
|
+
"translatable": true,
|
|
7
|
+
"title": "Indexable"
|
|
8
|
+
},
|
|
9
|
+
"ordering": {
|
|
10
|
+
"type": "integer",
|
|
11
|
+
"maximum": 9223372036854775807,
|
|
12
|
+
"format": "int64",
|
|
13
|
+
"minimum": 0,
|
|
14
|
+
"title": "Ordering"
|
|
15
|
+
},
|
|
16
|
+
"is_public": { "type": "boolean", "readOnly": true, "title": "Is public" },
|
|
17
|
+
"meta": { "type": "object", "title": "Meta" },
|
|
18
|
+
"routerlink": { "type": "string", "readOnly": true, "title": "Routerlink" },
|
|
19
|
+
"og_url": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"nullable": true,
|
|
22
|
+
"maxLength": 200,
|
|
23
|
+
"translatable": true,
|
|
24
|
+
"title": "Og url"
|
|
25
|
+
},
|
|
26
|
+
"tags": {
|
|
27
|
+
"type": "relation",
|
|
28
|
+
"model": "camomilla.Tag",
|
|
29
|
+
"multiple": true,
|
|
30
|
+
"endpoint": "/api/camomilla/tags/",
|
|
31
|
+
"title": "Tags"
|
|
32
|
+
},
|
|
33
|
+
"published_at": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"format": "date-time",
|
|
36
|
+
"nullable": true,
|
|
37
|
+
"translatable": true,
|
|
38
|
+
"title": "Published at"
|
|
39
|
+
},
|
|
40
|
+
"keywords": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"nullable": true,
|
|
43
|
+
"maxLength": 200,
|
|
44
|
+
"translatable": true,
|
|
45
|
+
"title": "Keywords"
|
|
46
|
+
},
|
|
47
|
+
"breadcrumbs_title": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"nullable": true,
|
|
50
|
+
"maxLength": 128,
|
|
51
|
+
"translatable": true,
|
|
52
|
+
"title": "Breadcrumbs title"
|
|
53
|
+
},
|
|
54
|
+
"content": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"translatable": true,
|
|
57
|
+
"title": "Content",
|
|
58
|
+
"format": "textarea"
|
|
59
|
+
},
|
|
60
|
+
"identifier": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"nullable": true,
|
|
63
|
+
"maxLength": 200,
|
|
64
|
+
"title": "Identifier"
|
|
65
|
+
},
|
|
66
|
+
"template": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"nullable": true,
|
|
69
|
+
"maxLength": 500,
|
|
70
|
+
"title": "Template"
|
|
71
|
+
},
|
|
72
|
+
"description": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"nullable": true,
|
|
75
|
+
"translatable": true,
|
|
76
|
+
"title": "Description",
|
|
77
|
+
"format": "textarea"
|
|
78
|
+
},
|
|
79
|
+
"id": { "type": "integer", "readOnly": true, "title": "ID" },
|
|
80
|
+
"og_title": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"nullable": true,
|
|
83
|
+
"maxLength": 200,
|
|
84
|
+
"translatable": true,
|
|
85
|
+
"title": "Og title"
|
|
86
|
+
},
|
|
87
|
+
"canonical": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"nullable": true,
|
|
90
|
+
"maxLength": 200,
|
|
91
|
+
"translatable": true,
|
|
92
|
+
"title": "Canonical"
|
|
93
|
+
},
|
|
94
|
+
"author": {
|
|
95
|
+
"type": "relation",
|
|
96
|
+
"model": "auth.User",
|
|
97
|
+
"multiple": false,
|
|
98
|
+
"endpoint": "/api/camomilla/users/",
|
|
99
|
+
"nullable": true,
|
|
100
|
+
"title": "Author"
|
|
101
|
+
},
|
|
102
|
+
"og_image": {
|
|
103
|
+
"type": "relation",
|
|
104
|
+
"model": "camomilla.Media",
|
|
105
|
+
"multiple": false,
|
|
106
|
+
"endpoint": "/api/camomilla/media/",
|
|
107
|
+
"nullable": true,
|
|
108
|
+
"title": "Og image"
|
|
109
|
+
},
|
|
110
|
+
"parent_page": {
|
|
111
|
+
"type": "relation",
|
|
112
|
+
"model": "camomilla.Article",
|
|
113
|
+
"multiple": false,
|
|
114
|
+
"endpoint": "/api/camomilla/articles/",
|
|
115
|
+
"nullable": true,
|
|
116
|
+
"title": "Parent page"
|
|
117
|
+
},
|
|
118
|
+
"highlight_image": {
|
|
119
|
+
"type": "relation",
|
|
120
|
+
"model": "camomilla.Media",
|
|
121
|
+
"multiple": false,
|
|
122
|
+
"endpoint": "/api/camomilla/media/",
|
|
123
|
+
"nullable": true,
|
|
124
|
+
"title": "Highlight image"
|
|
125
|
+
},
|
|
126
|
+
"template_file": {
|
|
127
|
+
"type": "string",
|
|
128
|
+
"readOnly": true,
|
|
129
|
+
"title": "Template file"
|
|
130
|
+
},
|
|
131
|
+
"date_updated_at": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"format": "date-time",
|
|
134
|
+
"readOnly": true,
|
|
135
|
+
"title": "Date updated at"
|
|
136
|
+
},
|
|
137
|
+
"breadcrumbs": {
|
|
138
|
+
"type": "string",
|
|
139
|
+
"readOnly": true,
|
|
140
|
+
"title": "Breadcrumbs"
|
|
141
|
+
},
|
|
142
|
+
"og_description": {
|
|
143
|
+
"type": "string",
|
|
144
|
+
"nullable": true,
|
|
145
|
+
"translatable": true,
|
|
146
|
+
"title": "Og description",
|
|
147
|
+
"format": "textarea"
|
|
148
|
+
},
|
|
149
|
+
"template_data": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"translatable": true,
|
|
152
|
+
"title": "Template data"
|
|
153
|
+
},
|
|
154
|
+
"date_created": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"format": "date-time",
|
|
157
|
+
"readOnly": true,
|
|
158
|
+
"title": "Date created"
|
|
159
|
+
},
|
|
160
|
+
"autopermalink": {
|
|
161
|
+
"type": "boolean",
|
|
162
|
+
"translatable": true,
|
|
163
|
+
"title": "Autopermalink"
|
|
164
|
+
},
|
|
165
|
+
"title": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"nullable": true,
|
|
168
|
+
"maxLength": 200,
|
|
169
|
+
"translatable": true,
|
|
170
|
+
"title": "Title"
|
|
171
|
+
},
|
|
172
|
+
"permalink": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"translatable": true,
|
|
175
|
+
"title": "Permalink"
|
|
176
|
+
},
|
|
177
|
+
"og_type": {
|
|
178
|
+
"type": "string",
|
|
179
|
+
"nullable": true,
|
|
180
|
+
"maxLength": 200,
|
|
181
|
+
"translatable": true,
|
|
182
|
+
"title": "Og type"
|
|
183
|
+
},
|
|
184
|
+
"status": { "type": "string", "readOnly": true, "title": "Status" },
|
|
185
|
+
"deleted_at": {
|
|
186
|
+
"type": "string",
|
|
187
|
+
"format": "date-time",
|
|
188
|
+
"readOnly": true,
|
|
189
|
+
"nullable": true,
|
|
190
|
+
"title": "Deleted at"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import type { AnyFieldDescriptor } from "../runtime/types/index.js";
|
|
4
|
+
import { useFormFromSchema } from "../runtime/composables/useFormFromSchema";
|
|
5
|
+
import articleSchema from "./article.schema.json";
|
|
6
|
+
|
|
7
|
+
// Captured from camomilla: FormAutoSchema().map_serializer(ArticleSerializer()).
|
|
8
|
+
// This is the contract between the two repos — if camomilla changes the shape,
|
|
9
|
+
// this fails here rather than as an empty form in someone's browser.
|
|
10
|
+
describe("camomilla OPTIONS schema -> mapo descriptors", () => {
|
|
11
|
+
const fields = useFormFromSchema(articleSchema as never);
|
|
12
|
+
const byKey = Object.fromEntries(fields.map((f) => [f.key, f]));
|
|
13
|
+
// `attrs` is not on every member of the AnyFieldDescriptor union.
|
|
14
|
+
const attrsOf = (f: AnyFieldDescriptor | undefined) =>
|
|
15
|
+
(f as { attrs?: Record<string, unknown> } | undefined)?.attrs;
|
|
16
|
+
|
|
17
|
+
it("produces a descriptor per field, not an empty array", () => {
|
|
18
|
+
expect(fields.length).toBeGreaterThan(20);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("maps m2m relations with a usable endpoint", () => {
|
|
22
|
+
expect(byKey.tags.type).toBe("m2m");
|
|
23
|
+
expect(attrsOf(byKey.tags)?.endpoint).toBe("/api/camomilla/tags/");
|
|
24
|
+
expect(attrsOf(byKey.tags)?.multiple).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("maps fk relations with a usable endpoint", () => {
|
|
28
|
+
expect(byKey.author.type).toBe("fks");
|
|
29
|
+
expect(attrsOf(byKey.author)?.endpoint).toBe("/api/camomilla/users/");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("does not leave any relation with an empty endpoint", () => {
|
|
33
|
+
// An empty endpoint is the silent failure: MapoFksField rewrites "" to "/",
|
|
34
|
+
// fetches the API root, and swallows the error as an empty picker.
|
|
35
|
+
const relations = fields.filter(
|
|
36
|
+
(f) => f.type === "fks" || f.type === "m2m",
|
|
37
|
+
);
|
|
38
|
+
expect(relations.length).toBeGreaterThan(0);
|
|
39
|
+
for (const rel of relations) expect(attrsOf(rel)?.endpoint).toBeTruthy();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("marks server-computed fields readonly rather than dropping them", () => {
|
|
43
|
+
// Without this they rendered as ordinary inputs and the backend discarded
|
|
44
|
+
// whatever was typed, with nothing to notice.
|
|
45
|
+
expect(byKey.id.readonly).toBe(true);
|
|
46
|
+
expect(byKey.status.readonly).toBe(true);
|
|
47
|
+
expect(byKey.date_created.readonly).toBe(true);
|
|
48
|
+
// An editable field must NOT be flagged.
|
|
49
|
+
expect(byKey.title.readonly).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("respects the multiline hint for unbounded text", () => {
|
|
53
|
+
// A Django TextField has no maxLength, so the >255 heuristic never fires.
|
|
54
|
+
expect(byKey.content.type).toBe("textarea");
|
|
55
|
+
expect(byKey.description.type).toBe("textarea");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("carries labels", () => {
|
|
59
|
+
expect(byKey.title.label).toBe("Title");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("types scalars correctly", () => {
|
|
63
|
+
expect(byKey.title.type).toBe("text");
|
|
64
|
+
expect(byKey.published_at.type).toBe("datetime");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("has no translations envelope and no localized twins", () => {
|
|
68
|
+
expect(byKey.translations).toBeUndefined();
|
|
69
|
+
expect(fields.filter((f) => /_(it|en)$/.test(String(f.key)))).toHaveLength(
|
|
70
|
+
0,
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -10,6 +10,8 @@ type DescriptorWithAttrs = {
|
|
|
10
10
|
};
|
|
11
11
|
fields?: unknown[];
|
|
12
12
|
visible?: (ctx: { model: Record<string, unknown> }) => boolean;
|
|
13
|
+
readonly?: boolean;
|
|
14
|
+
translatable?: boolean;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
// Pydantic v2 / DRF Spectacular style schema.
|
|
@@ -413,3 +415,25 @@ describe("useFormFromSchema — number attrs", () => {
|
|
|
413
415
|
expect(typed?.attrs?.max).toBe(5);
|
|
414
416
|
});
|
|
415
417
|
});
|
|
418
|
+
|
|
419
|
+
describe("useFormFromSchema — schema flags", () => {
|
|
420
|
+
// Camomilla's OPTIONS schema marks per-language properties. Dropping the flag
|
|
421
|
+
// renders them as plain fields under language tabs that no longer bind them.
|
|
422
|
+
const flagged: JSONSchema = {
|
|
423
|
+
type: "object",
|
|
424
|
+
properties: {
|
|
425
|
+
title: { type: "string", title: "Titolo", translatable: true },
|
|
426
|
+
identifier: { type: "string", title: "Identifier" },
|
|
427
|
+
created_at: { type: "string", format: "date-time", readOnly: true },
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
it("carries translatable and readOnly onto the descriptor", () => {
|
|
432
|
+
const fields = useFormFromSchema(flagged);
|
|
433
|
+
const by = (k: string) =>
|
|
434
|
+
fields.find((f) => f.key === k) as DescriptorWithAttrs | undefined;
|
|
435
|
+
expect(by("title")?.translatable).toBe(true);
|
|
436
|
+
expect(by("identifier")?.translatable).toBeUndefined();
|
|
437
|
+
expect(by("created_at")?.readonly).toBe(true);
|
|
438
|
+
});
|
|
439
|
+
});
|
|
@@ -10,6 +10,11 @@ vi.mock("@mapomodule/store/runtime/stores/auth", () => ({
|
|
|
10
10
|
vi.mock("@mapomodule/store/runtime/stores/snack", () => ({
|
|
11
11
|
useSnackStore: () => ({ show: vi.fn() }),
|
|
12
12
|
}));
|
|
13
|
+
// Pulls in `nuxt/app`, which cannot resolve outside a Nuxt build. Assertions
|
|
14
|
+
// below match on message keys, so echoing the key back is the useful stub.
|
|
15
|
+
vi.mock("@mapomodule/i18n/runtime/composables/useMapoT", () => ({
|
|
16
|
+
useMapoT: () => (key: string) => key,
|
|
17
|
+
}));
|
|
13
18
|
|
|
14
19
|
// provide/inject called outside setup() emits a warning; silence it in tests.
|
|
15
20
|
beforeEach(() => {
|
package/src/index.ts
CHANGED
|
@@ -42,7 +42,12 @@ export type {
|
|
|
42
42
|
MapDescriptor,
|
|
43
43
|
RepeaterDescriptor,
|
|
44
44
|
MediaDescriptor,
|
|
45
|
+
MediaM2mDescriptor,
|
|
46
|
+
EnhancedMediaDescriptor,
|
|
45
47
|
CustomDescriptor,
|
|
48
|
+
MediaLink,
|
|
49
|
+
MediaItem,
|
|
50
|
+
EnhancedMediaValue,
|
|
46
51
|
FieldComponentEntry,
|
|
47
52
|
FieldRegistry,
|
|
48
53
|
PartialFieldRegistry,
|
package/src/module.ts
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
createResolver,
|
|
7
7
|
addImports,
|
|
8
8
|
extendPages,
|
|
9
|
+
hasNuxtModule,
|
|
10
|
+
installModule,
|
|
9
11
|
} from "@nuxt/kit";
|
|
10
12
|
import type { NuxtModule } from "@nuxt/schema";
|
|
11
13
|
import type { PartialFieldRegistry } from "./runtime/types/index.js";
|
|
@@ -30,6 +32,23 @@ export default defineNuxtModule<MapoFormOptions>({
|
|
|
30
32
|
async setup(options, nuxt) {
|
|
31
33
|
const resolver = createResolver(import.meta.url);
|
|
32
34
|
|
|
35
|
+
// In consuming apps this runtime lives inside node_modules, where Nuxt
|
|
36
|
+
// skips auto-import injection by default (workspace apps are unaffected:
|
|
37
|
+
// pnpm symlinks resolve outside node_modules). Opt back in and transpile
|
|
38
|
+
// the runtime for the SSR build.
|
|
39
|
+
nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
|
|
40
|
+
nuxt.options.imports.transform ??= {};
|
|
41
|
+
(nuxt.options.imports.transform.include ??= []).push(
|
|
42
|
+
/@mapomodule[\\/]form[\\/]/,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Form components render user-facing strings through vue-i18n
|
|
46
|
+
// (`useI18n().t("mapo...")`). @mapomodule/i18n provides the catalogs and
|
|
47
|
+
// installs @nuxtjs/i18n unless the app manages it itself.
|
|
48
|
+
if (!hasNuxtModule("@mapomodule/i18n")) {
|
|
49
|
+
await installModule(await resolver.resolvePath("@mapomodule/i18n"));
|
|
50
|
+
}
|
|
51
|
+
|
|
33
52
|
// Only serializable values can go into runtimeConfig.public.
|
|
34
53
|
// `mapping` and `accessor` contain functions → not SSR-serializable.
|
|
35
54
|
// To extend mapping/accessor, use an app plugin that accesses $mapoFormRegistry:
|
|
@@ -111,6 +130,21 @@ export {};
|
|
|
111
130
|
});
|
|
112
131
|
}
|
|
113
132
|
|
|
133
|
+
// Media field components — registered so consumers can use them directly,
|
|
134
|
+
// and so MapoMediaUnavailable resolves inside them.
|
|
135
|
+
const mediaFieldComponents = [
|
|
136
|
+
"MapoMediaField",
|
|
137
|
+
"MapoMediaM2mField",
|
|
138
|
+
"MapoEnhancedMediaField",
|
|
139
|
+
"MapoMediaUnavailable",
|
|
140
|
+
];
|
|
141
|
+
for (const name of mediaFieldComponents) {
|
|
142
|
+
addComponent({
|
|
143
|
+
name,
|
|
144
|
+
filePath: resolver.resolve(`./runtime/components/fields/${name}.vue`),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
114
148
|
// Public auto-imported composables
|
|
115
149
|
addImports([
|
|
116
150
|
{
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { computed, ref, onMounted, onUnmounted } from "vue";
|
|
13
13
|
import { watch } from "vue";
|
|
14
|
+
import { useI18n } from "vue-i18n";
|
|
14
15
|
import { useFocusMode } from "../composables/useFocusMode.js";
|
|
15
16
|
import MapoForm from "./MapoForm.vue";
|
|
16
17
|
|
|
18
|
+
const { t } = useI18n();
|
|
17
19
|
const { focusTarget, exit } = useFocusMode();
|
|
18
20
|
|
|
19
21
|
const isOpen = computed(() => focusTarget.value !== null);
|
|
@@ -88,7 +90,7 @@ function onUpdate(val: Record<string, unknown>) {
|
|
|
88
90
|
variant="ghost"
|
|
89
91
|
color="neutral"
|
|
90
92
|
icon="i-lucide-x"
|
|
91
|
-
label="
|
|
93
|
+
:label="t('mapo.close')"
|
|
92
94
|
size="sm"
|
|
93
95
|
@click="exit"
|
|
94
96
|
/>
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, computed } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import type { TabEntry } from "../types/layout.js";
|
|
4
5
|
import { injectMapoForm } from "../composables/useMapoForm.js";
|
|
5
6
|
import MapoFormGroup from "./MapoFormGroup.vue";
|
|
6
7
|
import MapoFormFlatSection from "./MapoFormFlatSection.vue";
|
|
7
8
|
|
|
9
|
+
const { t } = useI18n();
|
|
10
|
+
|
|
8
11
|
const props = defineProps<{ tabs: TabEntry[] }>();
|
|
9
12
|
|
|
10
13
|
defineSlots<Record<string, unknown>>();
|
|
@@ -54,7 +57,7 @@ const tabsWithErrors = computed(() => {
|
|
|
54
57
|
<span
|
|
55
58
|
v-if="tabsWithErrors.has(tab.name)"
|
|
56
59
|
class="inline-block h-1.5 w-1.5 rounded-full bg-red-500"
|
|
57
|
-
aria-label="
|
|
60
|
+
:aria-label="t('mapo.form.tabErrors')"
|
|
58
61
|
/>
|
|
59
62
|
</button>
|
|
60
63
|
<!-- Extra content appended to the tab bar (actions, badges, …). -->
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, ref } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import { parseDate, CalendarDate } from "@internationalized/date";
|
|
4
5
|
import type { DateDescriptor } from "../../types/index.js";
|
|
5
6
|
|
|
@@ -14,6 +15,7 @@ const props = defineProps<{
|
|
|
14
15
|
|
|
15
16
|
const emit = defineEmits<{ "update:modelValue": [value: string | null] }>();
|
|
16
17
|
|
|
18
|
+
const { t } = useI18n();
|
|
17
19
|
const open = ref(false);
|
|
18
20
|
|
|
19
21
|
/** Parsed calendar value derived from the current ISO-like model string. */
|
|
@@ -30,7 +32,7 @@ const calendarValue = computed<CalendarDate | undefined>(() => {
|
|
|
30
32
|
|
|
31
33
|
const displayLabel = computed(() => {
|
|
32
34
|
const v = calendarValue.value;
|
|
33
|
-
if (!v) return "
|
|
35
|
+
if (!v) return t("mapo.dateField.selectDate");
|
|
34
36
|
return `${String(v.day).padStart(2, "0")}/${String(v.month).padStart(2, "0")}/${v.year}`;
|
|
35
37
|
});
|
|
36
38
|
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
today,
|
|
7
7
|
getLocalTimeZone,
|
|
8
8
|
} from "@internationalized/date";
|
|
9
|
+
import { useI18n } from "vue-i18n";
|
|
9
10
|
import type { DateDescriptor } from "../../types/index.js";
|
|
10
11
|
|
|
11
12
|
/** Datetime field with configurable naive/UTC serialisation semantics. */
|
|
@@ -19,6 +20,7 @@ const props = defineProps<{
|
|
|
19
20
|
|
|
20
21
|
const emit = defineEmits<{ "update:modelValue": [value: string | null] }>();
|
|
21
22
|
|
|
23
|
+
const { t } = useI18n();
|
|
22
24
|
const open = ref(false);
|
|
23
25
|
|
|
24
26
|
// Timezone strategy configurable through `descriptor.attrs.tz`:
|
|
@@ -72,7 +74,7 @@ function emitFromCalendar(v: CalendarDateTime) {
|
|
|
72
74
|
// Displayed label in the trigger button
|
|
73
75
|
const displayLabel = computed(() => {
|
|
74
76
|
const v = calendarValue.value;
|
|
75
|
-
if (!v) return "
|
|
77
|
+
if (!v) return t("mapo.dateField.selectDateTime");
|
|
76
78
|
const date = `${String(v.day).padStart(2, "0")}/${String(v.month).padStart(2, "0")}/${v.year}`;
|
|
77
79
|
const time = `${String(v.hour).padStart(2, "0")}:${String(v.minute).padStart(2, "0")}`;
|
|
78
80
|
return `${date} ${time}`;
|