@mapomodule/form 2.0.0-beta.4 → 2.0.0-beta.6
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/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/module.d.ts.map +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +16 -1
- package/dist/runtime/components/MapoFocusPortal.vue +3 -1
- package/dist/runtime/components/MapoFocusPortal.vue.d.ts.map +1 -1
- package/dist/runtime/components/MapoFormTabs.vue +4 -2
- package/dist/runtime/components/MapoFormTabs.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoDateField.vue +3 -1
- package/dist/runtime/components/fields/MapoDateField.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoDateTimeField.vue +5 -3
- package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts.map +1 -1
- 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 +3 -1
- package/dist/runtime/components/fields/MapoFileField.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoFksField.vue +6 -2
- package/dist/runtime/components/fields/MapoFksField.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoMapField.vue +4 -2
- package/dist/runtime/components/fields/MapoMapField.vue.d.ts.map +1 -1
- 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 +32 -22
- package/dist/runtime/components/fields/MapoRepeater.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoRepeaterItem.vue +13 -8
- package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoSeoPreview.vue +16 -12
- package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoWygEditor.vue +30 -5
- package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts.map +1 -1
- package/dist/runtime/composables/useMapoForm.d.ts.map +1 -1
- package/dist/runtime/composables/useMapoForm.js +4 -2
- 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/useUnsavedChangesGuard.d.ts.map +1 -1
- package/dist/runtime/composables/useUnsavedChangesGuard.js +3 -2
- package/dist/runtime/registry/defaults.d.ts.map +1 -1
- package/dist/runtime/registry/defaults.js +8 -2
- package/dist/runtime/types/descriptor.d.ts +18 -4
- package/dist/runtime/types/descriptor.d.ts.map +1 -1
- package/dist/runtime/types/index.d.ts +2 -1
- package/dist/runtime/types/index.d.ts.map +1 -1
- 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/package.json +9 -6
- package/src/__tests__/useMapoForm.test.ts +5 -0
- package/src/index.ts +5 -0
- package/src/module.ts +24 -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/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/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Form engine for Mapo v2 — descriptor-driven, typesafe, headless-ready admin fo
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Descriptor-driven**: define fields as `FieldDescriptor[]` — layout, validation, i18n in one object
|
|
8
|
-
- **All field types built-in**: text, select, date/time, FK/M2M autocomplete, WYSIWYG editor, SEO preview, map, repeater
|
|
8
|
+
- **All field types built-in**: text, select, date/time, FK/M2M autocomplete, WYSIWYG editor, SEO preview, map, repeater, media pickers
|
|
9
9
|
- **Headless composable**: `useMapoForm()` for fully custom layouts; `<MapoForm>` for the default shell
|
|
10
10
|
- **Registry auto-injection**: `<MapoForm>` and `<MapoDetail>` resolve `$mapoFormRegistry` automatically — no boilerplate
|
|
11
11
|
- **Typed extension API**: `defineFormField(type, entry, opts)` to register custom fields with collision detection
|
|
@@ -16,6 +16,7 @@ Form engine for Mapo v2 — descriptor-driven, typesafe, headless-ready admin fo
|
|
|
16
16
|
- **Consistent repeater items**: `MapoRepeater` drives each item through `useMapoForm`, so nested-path keys, `translatable`, accessors, `descriptor.is` overrides and **client validation** behave identically inside a repeater
|
|
17
17
|
- **Draft persistence**: `draftKey` option on `useMapoForm()` auto-saves to localStorage every 2 s; `checkDraft()` / `draftBanner` offer restore/discard; clears on successful `submit()`
|
|
18
18
|
- **Fail-soft**: unknown field types render a non-destructive placeholder, never crash the form
|
|
19
|
+
- **Media pickers**: `media`, `media-m2m` and `enhanced-media` field types are built in; they drive the Media Manager from [`@mapomodule/uikit`](../uikit/README.md), resolved at runtime — install it to enable them
|
|
19
20
|
|
|
20
21
|
## Install
|
|
21
22
|
|
package/dist/index.d.mts
CHANGED
|
@@ -5,5 +5,5 @@ export { FocusTarget, useFocusMode } from '../dist/runtime/composables/useFocusM
|
|
|
5
5
|
export { UnsavedChangesGuardOptions, useUnsavedChangesGuard } from '../dist/runtime/composables/useUnsavedChangesGuard.js';
|
|
6
6
|
export { greaterThan, isEmpty, isNoneOf, isNotEmpty, isOneOf, lessThan, matches, matchesField, when, whenAny, whenNot } from '../dist/runtime/composables/useProgressiveDisclosure.js';
|
|
7
7
|
export { FieldGroupDescriptor, flattenFieldGroups } from '../dist/runtime/utils/flattenFieldGroups.js';
|
|
8
|
-
export { AnyFieldDescriptor, BooleanDescriptor, ColorDescriptor, CustomDescriptor, DateDescriptor, EditorDescriptor, FieldAccessor, FieldComponentEntry, FieldDescriptor, FieldRegistry, FieldType, FileDescriptor, FksDescriptor, KnownFieldType, MapDescriptor, MediaDescriptor, NumberDescriptor, PartialFieldRegistry, RepeaterDescriptor, SelectDescriptor, SeoDescriptor, TextDescriptor } from '../dist/runtime/types/index.js';
|
|
8
|
+
export { AnyFieldDescriptor, BooleanDescriptor, ColorDescriptor, CustomDescriptor, DateDescriptor, EditorDescriptor, EnhancedMediaDescriptor, EnhancedMediaValue, FieldAccessor, FieldComponentEntry, FieldDescriptor, FieldRegistry, FieldType, FileDescriptor, FksDescriptor, KnownFieldType, MapDescriptor, MediaDescriptor, MediaItem, MediaLink, MediaM2mDescriptor, NumberDescriptor, PartialFieldRegistry, RepeaterDescriptor, SelectDescriptor, SeoDescriptor, TextDescriptor } from '../dist/runtime/types/index.js';
|
|
9
9
|
import '@nuxt/schema';
|
package/dist/index.d.ts
CHANGED
|
@@ -11,5 +11,5 @@ export { when, whenAny, whenNot, matchesField, isOneOf, isNoneOf, isNotEmpty, is
|
|
|
11
11
|
export { flattenFieldGroups } from "./runtime/utils/flattenFieldGroups.js";
|
|
12
12
|
export type { FieldGroupDescriptor } from "./runtime/utils/flattenFieldGroups.js";
|
|
13
13
|
export { KnownFieldType } from "./runtime/types/index.js";
|
|
14
|
-
export type { FieldType, FieldAccessor, FieldDescriptor, AnyFieldDescriptor, TextDescriptor, NumberDescriptor, BooleanDescriptor, SelectDescriptor, FksDescriptor, DateDescriptor, ColorDescriptor, FileDescriptor, EditorDescriptor, SeoDescriptor, MapDescriptor, RepeaterDescriptor, MediaDescriptor, CustomDescriptor, FieldComponentEntry, FieldRegistry, PartialFieldRegistry, } from "./runtime/types/index.js";
|
|
14
|
+
export type { FieldType, FieldAccessor, FieldDescriptor, AnyFieldDescriptor, TextDescriptor, NumberDescriptor, BooleanDescriptor, SelectDescriptor, FksDescriptor, DateDescriptor, ColorDescriptor, FileDescriptor, EditorDescriptor, SeoDescriptor, MapDescriptor, RepeaterDescriptor, MediaDescriptor, MediaM2mDescriptor, EnhancedMediaDescriptor, CustomDescriptor, MediaLink, MediaItem, EnhancedMediaValue, FieldComponentEntry, FieldRegistry, PartialFieldRegistry, } from "./runtime/types/index.js";
|
|
15
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAC/E,YAAY,EAAE,wBAAwB,EAAE,MAAM,4CAA4C,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,YAAY,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,YAAY,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iDAAiD,CAAC;AACzF,YAAY,EAAE,0BAA0B,EAAE,MAAM,iDAAiD,CAAC;AAElG,OAAO,EACL,IAAI,EACJ,OAAO,EACP,OAAO,EACP,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,UAAU,EACV,OAAO,EACP,WAAW,EACX,QAAQ,EACR,OAAO,GACR,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAC3E,YAAY,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,YAAY,EACV,SAAS,EACT,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,oBAAoB,GACrB,MAAM,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAC/E,YAAY,EAAE,wBAAwB,EAAE,MAAM,4CAA4C,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,YAAY,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,YAAY,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iDAAiD,CAAC;AACzF,YAAY,EAAE,0BAA0B,EAAE,MAAM,iDAAiD,CAAC;AAElG,OAAO,EACL,IAAI,EACJ,OAAO,EACP,OAAO,EACP,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,UAAU,EACV,OAAO,EACP,WAAW,EACX,QAAQ,EACR,OAAO,GACR,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAC3E,YAAY,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,YAAY,EACV,SAAS,EACT,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,gBAAgB,EAChB,SAAS,EACT,SAAS,EACT,kBAAkB,EAClB,mBAAmB,EACnB,aAAa,EACb,oBAAoB,GACrB,MAAM,0BAA0B,CAAC"}
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAErE,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;;AAED,wBAkOyC"}
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver, addPlugin, addTypeTemplate, extendPages, addComponent, addImports } from '@nuxt/kit';
|
|
1
|
+
import { defineNuxtModule, createResolver, hasNuxtModule, installModule, addPlugin, addTypeTemplate, extendPages, addComponent, addImports } from '@nuxt/kit';
|
|
2
2
|
|
|
3
3
|
const module$1 = defineNuxtModule({
|
|
4
4
|
meta: {
|
|
@@ -16,6 +16,9 @@ const module$1 = defineNuxtModule({
|
|
|
16
16
|
(nuxt.options.imports.transform.include ??= []).push(
|
|
17
17
|
/@mapomodule[\\/]form[\\/]/
|
|
18
18
|
);
|
|
19
|
+
if (!hasNuxtModule("@mapomodule/i18n")) {
|
|
20
|
+
await installModule(await resolver.resolvePath("@mapomodule/i18n"));
|
|
21
|
+
}
|
|
19
22
|
nuxt.options.runtimeConfig.public.mapoForm = {
|
|
20
23
|
groups: { expanded: options.groups?.expanded ?? true },
|
|
21
24
|
debounce: options.debounce ?? 300,
|
|
@@ -74,6 +77,18 @@ export {};
|
|
|
74
77
|
filePath: resolver.resolve(`./runtime/components/${name}.vue`)
|
|
75
78
|
});
|
|
76
79
|
}
|
|
80
|
+
const mediaFieldComponents = [
|
|
81
|
+
"MapoMediaField",
|
|
82
|
+
"MapoMediaM2mField",
|
|
83
|
+
"MapoEnhancedMediaField",
|
|
84
|
+
"MapoMediaUnavailable"
|
|
85
|
+
];
|
|
86
|
+
for (const name of mediaFieldComponents) {
|
|
87
|
+
addComponent({
|
|
88
|
+
name,
|
|
89
|
+
filePath: resolver.resolve(`./runtime/components/fields/${name}.vue`)
|
|
90
|
+
});
|
|
91
|
+
}
|
|
77
92
|
addImports([
|
|
78
93
|
{
|
|
79
94
|
name: "useMapoForm",
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, onMounted, onUnmounted } from "vue";
|
|
3
3
|
import { watch } from "vue";
|
|
4
|
+
import { useI18n } from "vue-i18n";
|
|
4
5
|
import { useFocusMode } from "../composables/useFocusMode.js";
|
|
5
6
|
import MapoForm from "./MapoForm.vue";
|
|
7
|
+
const { t } = useI18n();
|
|
6
8
|
const { focusTarget, exit } = useFocusMode();
|
|
7
9
|
const isOpen = computed(() => focusTarget.value !== null);
|
|
8
10
|
const canUseDocument = typeof document !== "undefined";
|
|
@@ -66,7 +68,7 @@ function onUpdate(val) {
|
|
|
66
68
|
variant="ghost"
|
|
67
69
|
color="neutral"
|
|
68
70
|
icon="i-lucide-x"
|
|
69
|
-
label="
|
|
71
|
+
:label="t('mapo.close')"
|
|
70
72
|
size="sm"
|
|
71
73
|
@click="exit"
|
|
72
74
|
/>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapoFocusPortal.vue.d.ts","sourceRoot":"","sources":["../../../src/runtime/components/MapoFocusPortal.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MapoFocusPortal.vue.d.ts","sourceRoot":"","sources":["../../../src/runtime/components/MapoFocusPortal.vue"],"names":[],"mappings":"AAudA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import { injectMapoForm } from "../composables/useMapoForm.js";
|
|
4
5
|
import MapoFormGroup from "./MapoFormGroup.vue";
|
|
5
6
|
import MapoFormFlatSection from "./MapoFormFlatSection.vue";
|
|
7
|
+
const { t } = useI18n();
|
|
6
8
|
const props = defineProps({
|
|
7
9
|
tabs: { type: Array, required: true }
|
|
8
10
|
});
|
|
@@ -21,7 +23,7 @@ const form = injectMapoForm();
|
|
|
21
23
|
const tabsWithErrors = computed(() => {
|
|
22
24
|
const errorKeys = new Set(Object.keys(form.errors.value));
|
|
23
25
|
return new Set(
|
|
24
|
-
props.tabs.filter((tab) => tabHasErrors(tab, errorKeys)).map((
|
|
26
|
+
props.tabs.filter((tab) => tabHasErrors(tab, errorKeys)).map((t2) => t2.name)
|
|
25
27
|
);
|
|
26
28
|
});
|
|
27
29
|
</script>
|
|
@@ -46,7 +48,7 @@ const tabsWithErrors = computed(() => {
|
|
|
46
48
|
<span
|
|
47
49
|
v-if="tabsWithErrors.has(tab.name)"
|
|
48
50
|
class="inline-block h-1.5 w-1.5 rounded-full bg-red-500"
|
|
49
|
-
aria-label="
|
|
51
|
+
:aria-label="t('mapo.form.tabErrors')"
|
|
50
52
|
/>
|
|
51
53
|
</button>
|
|
52
54
|
<!-- Extra content appended to the tab bar (actions, badges, …). -->
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapoFormTabs.vue.d.ts","sourceRoot":"","sources":["../../../src/runtime/components/MapoFormTabs.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MapoFormTabs.vue.d.ts","sourceRoot":"","sources":["../../../src/runtime/components/MapoFormTabs.vue"],"names":[],"mappings":"AA4HA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAOnD,KAAK,WAAW,GAAG;IAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAC;AAGxC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAsO3C,QAAA,MAAM,UAAU,kSAEd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import { parseDate, CalendarDate } from "@internationalized/date";
|
|
4
5
|
const props = defineProps({
|
|
5
6
|
modelValue: { type: null, required: true },
|
|
@@ -9,6 +10,7 @@ const props = defineProps({
|
|
|
9
10
|
disabled: { type: Boolean, required: false }
|
|
10
11
|
});
|
|
11
12
|
const emit = defineEmits(["update:modelValue"]);
|
|
13
|
+
const { t } = useI18n();
|
|
12
14
|
const open = ref(false);
|
|
13
15
|
const calendarValue = computed(() => {
|
|
14
16
|
const v = props.modelValue;
|
|
@@ -21,7 +23,7 @@ const calendarValue = computed(() => {
|
|
|
21
23
|
});
|
|
22
24
|
const displayLabel = computed(() => {
|
|
23
25
|
const v = calendarValue.value;
|
|
24
|
-
if (!v) return "
|
|
26
|
+
if (!v) return t("mapo.dateField.selectDate");
|
|
25
27
|
return `${String(v.day).padStart(2, "0")}/${String(v.month).padStart(2, "0")}/${v.year}`;
|
|
26
28
|
});
|
|
27
29
|
const attrs = computed(() => props.descriptor.attrs ?? {});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapoDateField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoDateField.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MapoDateField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoDateField.vue"],"names":[],"mappings":"AA0GA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,4EAA4E;AAC5E,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAyMF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
today,
|
|
7
7
|
getLocalTimeZone
|
|
8
8
|
} from "@internationalized/date";
|
|
9
|
+
import { useI18n } from "vue-i18n";
|
|
9
10
|
const props = defineProps({
|
|
10
11
|
modelValue: { type: null, required: true },
|
|
11
12
|
descriptor: { type: Object, required: true },
|
|
@@ -14,6 +15,7 @@ const props = defineProps({
|
|
|
14
15
|
disabled: { type: Boolean, required: false }
|
|
15
16
|
});
|
|
16
17
|
const emit = defineEmits(["update:modelValue"]);
|
|
18
|
+
const { t } = useI18n();
|
|
17
19
|
const open = ref(false);
|
|
18
20
|
const tzMode = computed(() => props.descriptor.attrs?.tz ?? "naive");
|
|
19
21
|
const calendarValue = computed(() => {
|
|
@@ -48,7 +50,7 @@ function emitFromCalendar(v) {
|
|
|
48
50
|
}
|
|
49
51
|
const displayLabel = computed(() => {
|
|
50
52
|
const v = calendarValue.value;
|
|
51
|
-
if (!v) return "
|
|
53
|
+
if (!v) return t("mapo.dateField.selectDateTime");
|
|
52
54
|
const date = `${String(v.day).padStart(2, "0")}/${String(v.month).padStart(2, "0")}/${v.year}`;
|
|
53
55
|
const time = `${String(v.hour).padStart(2, "0")}:${String(v.minute).padStart(2, "0")}`;
|
|
54
56
|
return `${date} ${time}`;
|
|
@@ -72,8 +74,8 @@ function onTimeChange(part, val) {
|
|
|
72
74
|
Math.min(part === "hour" ? 23 : 59, parseInt(val) || 0)
|
|
73
75
|
);
|
|
74
76
|
const base = calendarValue.value ?? (() => {
|
|
75
|
-
const
|
|
76
|
-
return new CalendarDateTime(
|
|
77
|
+
const t2 = today(getLocalTimeZone());
|
|
78
|
+
return new CalendarDateTime(t2.year, t2.month, t2.day, 0, 0);
|
|
77
79
|
})();
|
|
78
80
|
const merged = part === "hour" ? new CalendarDateTime(base.year, base.month, base.day, n, base.minute) : new CalendarDateTime(base.year, base.month, base.day, base.hour, n);
|
|
79
81
|
emitFromCalendar(merged);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapoDateTimeField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoDateTimeField.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MapoDateTimeField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoDateTimeField.vue"],"names":[],"mappings":"AA6MA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,0EAA0E;AAC1E,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAgWF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
4
|
+
import { useMediaManager } from "../../composables/useMediaManager.js";
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
modelValue: { type: null, required: true },
|
|
7
|
+
descriptor: { type: Object, required: true },
|
|
8
|
+
errors: { type: Array, required: false },
|
|
9
|
+
readonly: { type: Boolean, required: false },
|
|
10
|
+
disabled: { type: Boolean, required: false }
|
|
11
|
+
});
|
|
12
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
13
|
+
const { t } = useI18n();
|
|
14
|
+
const dialogOpen = ref(false);
|
|
15
|
+
const { MediaDialog, available } = useMediaManager();
|
|
16
|
+
const current = computed(
|
|
17
|
+
() => props.modelValue?.media ?? null
|
|
18
|
+
);
|
|
19
|
+
const altValue = computed({
|
|
20
|
+
get: () => props.modelValue?.alt ?? "",
|
|
21
|
+
set: (v) => emit("update:modelValue", {
|
|
22
|
+
...props.modelValue ?? { media: null },
|
|
23
|
+
alt: v
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
const captionValue = computed({
|
|
27
|
+
get: () => props.modelValue?.caption ?? "",
|
|
28
|
+
set: (v) => emit("update:modelValue", {
|
|
29
|
+
...props.modelValue ?? { media: null },
|
|
30
|
+
caption: v
|
|
31
|
+
})
|
|
32
|
+
});
|
|
33
|
+
const mime = computed(() => props.descriptor.attrs?.mime);
|
|
34
|
+
function onConfirm(selection) {
|
|
35
|
+
const media = Array.isArray(selection) ? selection[0] ?? null : selection;
|
|
36
|
+
emit("update:modelValue", {
|
|
37
|
+
...props.modelValue ?? {},
|
|
38
|
+
media,
|
|
39
|
+
alt: props.modelValue?.alt ?? media?.alt_text ?? "",
|
|
40
|
+
caption: props.modelValue?.caption ?? ""
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function clear() {
|
|
44
|
+
emit("update:modelValue", null);
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<div class="mapo-enhanced-media-field space-y-3">
|
|
50
|
+
<MapoMediaUnavailable v-if="!available" />
|
|
51
|
+
|
|
52
|
+
<template v-else>
|
|
53
|
+
<div class="flex gap-3">
|
|
54
|
+
<!-- Preview -->
|
|
55
|
+
<div class="shrink-0">
|
|
56
|
+
<div
|
|
57
|
+
class="flex size-24 cursor-pointer items-center justify-center overflow-hidden rounded-lg border-2 border-dashed transition-colors"
|
|
58
|
+
:class="
|
|
59
|
+
current ? 'border-default' : 'border-accented hover:border-primary'
|
|
60
|
+
"
|
|
61
|
+
@click="!readonly && !disabled && (dialogOpen = true)"
|
|
62
|
+
>
|
|
63
|
+
<MapoMediaPreview v-if="current" :media="current" size="md" />
|
|
64
|
+
<div v-else class="flex flex-col items-center gap-1 text-dimmed">
|
|
65
|
+
<UIcon name="i-lucide-image-plus" class="size-7" />
|
|
66
|
+
<span class="text-[10px]">{{ t("mapo.select") }}</span>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- Metadata fields -->
|
|
72
|
+
<div class="flex-1 space-y-2">
|
|
73
|
+
<div v-if="current" class="flex items-start justify-between gap-2">
|
|
74
|
+
<p class="truncate text-xs text-muted">
|
|
75
|
+
{{ current.title || current.file?.split("/").pop() }}
|
|
76
|
+
</p>
|
|
77
|
+
<div class="flex gap-1">
|
|
78
|
+
<UButton
|
|
79
|
+
v-if="!readonly && !disabled"
|
|
80
|
+
icon="i-lucide-refresh-cw"
|
|
81
|
+
size="xs"
|
|
82
|
+
variant="ghost"
|
|
83
|
+
color="neutral"
|
|
84
|
+
:title="t('mapo.change')"
|
|
85
|
+
@click="dialogOpen = true"
|
|
86
|
+
/>
|
|
87
|
+
<UButton
|
|
88
|
+
v-if="!readonly && !disabled"
|
|
89
|
+
icon="i-lucide-x"
|
|
90
|
+
size="xs"
|
|
91
|
+
variant="ghost"
|
|
92
|
+
color="neutral"
|
|
93
|
+
:title="t('mapo.remove')"
|
|
94
|
+
@click="clear"
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<UInput
|
|
100
|
+
v-model="altValue"
|
|
101
|
+
size="sm"
|
|
102
|
+
:placeholder="t('mapo.enhancedMediaField.altText')"
|
|
103
|
+
:disabled="readonly || disabled || !current"
|
|
104
|
+
/>
|
|
105
|
+
<UInput
|
|
106
|
+
v-model="captionValue"
|
|
107
|
+
size="sm"
|
|
108
|
+
:placeholder="t('mapo.enhancedMediaField.caption')"
|
|
109
|
+
:disabled="readonly || disabled || !current"
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<!-- Dialog -->
|
|
115
|
+
<component
|
|
116
|
+
:is="MediaDialog"
|
|
117
|
+
v-model="dialogOpen"
|
|
118
|
+
selection-mode="single"
|
|
119
|
+
:mime="mime"
|
|
120
|
+
:selected="current"
|
|
121
|
+
@confirm="onConfirm"
|
|
122
|
+
/>
|
|
123
|
+
</template>
|
|
124
|
+
|
|
125
|
+
<!-- Errors -->
|
|
126
|
+
<p v-for="err in errors" :key="err" class="text-xs text-error" role="alert">
|
|
127
|
+
{{ err }}
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
130
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EnhancedMediaDescriptor, EnhancedMediaValue } from "../../types/index.js";
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
modelValue: EnhancedMediaValue | null | undefined;
|
|
4
|
+
descriptor: EnhancedMediaDescriptor;
|
|
5
|
+
errors?: string[];
|
|
6
|
+
readonly?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
};
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
|
+
"update:modelValue": (value: EnhancedMediaValue | null) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
"onUpdate:modelValue"?: ((value: EnhancedMediaValue | null) => any) | undefined;
|
|
13
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
|
+
declare const _default: typeof __VLS_export;
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=MapoEnhancedMediaField.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapoEnhancedMediaField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoEnhancedMediaField.vue"],"names":[],"mappings":"AA8JA,OAAO,KAAK,EACV,uBAAuB,EACvB,kBAAkB,EAEnB,MAAM,sBAAsB,CAAC;AAG9B,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD,UAAU,EAAE,uBAAuB,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA+UF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
const props = defineProps({
|
|
4
5
|
modelValue: { type: null, required: true },
|
|
5
6
|
descriptor: { type: Object, required: true },
|
|
@@ -8,6 +9,7 @@ const props = defineProps({
|
|
|
8
9
|
disabled: { type: Boolean, required: false }
|
|
9
10
|
});
|
|
10
11
|
const emit = defineEmits(["update:modelValue"]);
|
|
12
|
+
const { t } = useI18n();
|
|
11
13
|
const inputRef = ref(null);
|
|
12
14
|
const currentFileName = computed(() => {
|
|
13
15
|
if (props.modelValue instanceof File) return props.modelValue.name;
|
|
@@ -69,7 +71,7 @@ const accept = computed(
|
|
|
69
71
|
variant="ghost"
|
|
70
72
|
color="neutral"
|
|
71
73
|
icon="i-lucide-x"
|
|
72
|
-
aria-label="
|
|
74
|
+
:aria-label="t('mapo.fileField.removeFile')"
|
|
73
75
|
@click="clear"
|
|
74
76
|
/>
|
|
75
77
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapoFileField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoFileField.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MapoFileField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoFileField.vue"],"names":[],"mappings":"AAiHA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA6MF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed, watch, onMounted } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import { useNuxtApp } from "#app";
|
|
4
5
|
import { debounce } from "@mapomodule/utils";
|
|
5
6
|
const props = defineProps({
|
|
@@ -10,6 +11,7 @@ const props = defineProps({
|
|
|
10
11
|
disabled: { type: Boolean, required: false }
|
|
11
12
|
});
|
|
12
13
|
const emit = defineEmits(["update:modelValue"]);
|
|
14
|
+
const { t } = useI18n();
|
|
13
15
|
const attrs = computed(() => props.descriptor.attrs);
|
|
14
16
|
const isMultiple = computed(
|
|
15
17
|
() => props.descriptor.type === "m2m" || !!attrs.value.multiple
|
|
@@ -43,7 +45,7 @@ const internalValue = computed({
|
|
|
43
45
|
set: (val) => emit("update:modelValue", val)
|
|
44
46
|
});
|
|
45
47
|
const placeholder = computed(
|
|
46
|
-
() => isMultiple.value ? "
|
|
48
|
+
() => isMultiple.value ? t("mapo.fksField.searchSelect") : t("mapo.search")
|
|
47
49
|
);
|
|
48
50
|
function getLabel(item) {
|
|
49
51
|
if (item !== null && item !== void 0 && typeof item !== "object") {
|
|
@@ -111,7 +113,9 @@ function getItemKey(item) {
|
|
|
111
113
|
<!-- Empty state -->
|
|
112
114
|
<template #empty>
|
|
113
115
|
<span class="text-sm text-gray-500">
|
|
114
|
-
{{
|
|
116
|
+
{{
|
|
117
|
+
searchTerm ? t("mapo.fksField.noResults") : t("mapo.fksField.noItems")
|
|
118
|
+
}}
|
|
115
119
|
</span>
|
|
116
120
|
</template>
|
|
117
121
|
</USelectMenu>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapoFksField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoFksField.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MapoFksField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoFksField.vue"],"names":[],"mappings":"AAmKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,aAAa,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAiQF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed, defineAsyncComponent } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
const LeafletMap = defineAsyncComponent(
|
|
4
5
|
() => import("./MapoMapFieldClient.vue")
|
|
5
6
|
);
|
|
7
|
+
const { t } = useI18n();
|
|
6
8
|
const props = defineProps({
|
|
7
9
|
modelValue: { type: null, required: true },
|
|
8
10
|
descriptor: { type: Object, required: true },
|
|
@@ -43,7 +45,7 @@ function clear() {
|
|
|
43
45
|
<div class="flex gap-2">
|
|
44
46
|
<UInput
|
|
45
47
|
v-model="latInput"
|
|
46
|
-
placeholder="
|
|
48
|
+
:placeholder="t('mapo.latitude')"
|
|
47
49
|
class="flex-1"
|
|
48
50
|
:readonly="readonly"
|
|
49
51
|
:disabled="disabled"
|
|
@@ -51,7 +53,7 @@ function clear() {
|
|
|
51
53
|
/>
|
|
52
54
|
<UInput
|
|
53
55
|
v-model="lngInput"
|
|
54
|
-
placeholder="
|
|
56
|
+
:placeholder="t('mapo.longitude')"
|
|
55
57
|
class="flex-1"
|
|
56
58
|
:readonly="readonly"
|
|
57
59
|
:disabled="disabled"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapoMapField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoMapField.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MapoMapField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoMapField.vue"],"names":[],"mappings":"AA6HA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAS1D,sDAAsD;AACtD,UAAU,MAAM;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,8EAA8E;AAC9E,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,aAAa,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAqOF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
4
|
+
import { useMediaManager } from "../../composables/useMediaManager.js";
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
modelValue: { type: null, required: true },
|
|
7
|
+
descriptor: { type: Object, required: true },
|
|
8
|
+
errors: { type: Array, required: false },
|
|
9
|
+
readonly: { type: Boolean, required: false },
|
|
10
|
+
disabled: { type: Boolean, required: false }
|
|
11
|
+
});
|
|
12
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
13
|
+
const { t } = useI18n();
|
|
14
|
+
const dialogOpen = ref(false);
|
|
15
|
+
const { MediaDialog, available } = useMediaManager();
|
|
16
|
+
const current = computed(() => {
|
|
17
|
+
if (!props.modelValue) return null;
|
|
18
|
+
if (typeof props.modelValue === "object") return props.modelValue;
|
|
19
|
+
return null;
|
|
20
|
+
});
|
|
21
|
+
const mime = computed(() => props.descriptor.attrs?.mime);
|
|
22
|
+
function onConfirm(selection) {
|
|
23
|
+
emit(
|
|
24
|
+
"update:modelValue",
|
|
25
|
+
Array.isArray(selection) ? selection[0] ?? null : selection
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
function clear() {
|
|
29
|
+
emit("update:modelValue", null);
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div class="mapo-media-field space-y-2">
|
|
35
|
+
<MapoMediaUnavailable v-if="!available" />
|
|
36
|
+
|
|
37
|
+
<template v-else>
|
|
38
|
+
<!-- Selected media preview -->
|
|
39
|
+
<div
|
|
40
|
+
v-if="current"
|
|
41
|
+
class="flex items-center gap-3 rounded-lg border border-default p-2"
|
|
42
|
+
>
|
|
43
|
+
<MapoMediaPreview :media="current" size="sm" />
|
|
44
|
+
<div class="flex-1 min-w-0">
|
|
45
|
+
<p class="truncate text-xs font-medium">
|
|
46
|
+
{{ current.title || current.file?.split("/").pop() }}
|
|
47
|
+
</p>
|
|
48
|
+
<p class="text-xs text-dimmed">
|
|
49
|
+
{{ current.mime_type }}
|
|
50
|
+
</p>
|
|
51
|
+
</div>
|
|
52
|
+
<UButton
|
|
53
|
+
v-if="!readonly && !disabled"
|
|
54
|
+
icon="i-lucide-x"
|
|
55
|
+
size="xs"
|
|
56
|
+
variant="ghost"
|
|
57
|
+
color="neutral"
|
|
58
|
+
@click="clear"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<!-- Trigger button -->
|
|
63
|
+
<UButton
|
|
64
|
+
v-if="!readonly && !disabled"
|
|
65
|
+
:icon="current ? 'i-lucide-refresh-cw' : 'i-lucide-image-plus'"
|
|
66
|
+
size="sm"
|
|
67
|
+
variant="soft"
|
|
68
|
+
color="neutral"
|
|
69
|
+
:block="!current"
|
|
70
|
+
@click="dialogOpen = true"
|
|
71
|
+
>
|
|
72
|
+
{{ current ? t("mapo.change") : t("mapo.mediaField.selectMedia") }}
|
|
73
|
+
</UButton>
|
|
74
|
+
|
|
75
|
+
<!-- Dialog -->
|
|
76
|
+
<component
|
|
77
|
+
:is="MediaDialog"
|
|
78
|
+
v-model="dialogOpen"
|
|
79
|
+
selection-mode="single"
|
|
80
|
+
:mime="mime"
|
|
81
|
+
:selected="current"
|
|
82
|
+
@confirm="onConfirm"
|
|
83
|
+
/>
|
|
84
|
+
</template>
|
|
85
|
+
|
|
86
|
+
<!-- Errors -->
|
|
87
|
+
<p v-for="err in errors" :key="err" class="text-xs text-error" role="alert">
|
|
88
|
+
{{ err }}
|
|
89
|
+
</p>
|
|
90
|
+
</div>
|
|
91
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MediaDescriptor, MediaItem } from "../../types/index.js";
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
modelValue: MediaItem | number | null | undefined;
|
|
4
|
+
descriptor: MediaDescriptor;
|
|
5
|
+
errors?: string[];
|
|
6
|
+
readonly?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
};
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
|
+
"update:modelValue": (value: MediaItem | null) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
"onUpdate:modelValue"?: ((value: MediaItem | null) => any) | undefined;
|
|
13
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
|
+
declare const _default: typeof __VLS_export;
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=MapoMediaField.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapoMediaField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoMediaField.vue"],"names":[],"mappings":"AA6GA,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAGvE,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD,UAAU,EAAE,eAAe,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAgOF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|