@mapomodule/form 2.0.0-beta.1 → 2.0.0-beta.11
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 +37 -118
- 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__/tabVisibility.test.d.ts +2 -0
- package/dist/__tests__/tabVisibility.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 +177 -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 +122 -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 +93 -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 +111 -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 +258 -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/runtime/utils/tabVisibility.d.ts +18 -0
- package/dist/runtime/utils/tabVisibility.d.ts.map +1 -0
- package/dist/runtime/utils/tabVisibility.js +18 -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__/tabVisibility.test.ts +102 -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/MapoForm.vue +17 -4
- package/src/runtime/components/MapoFormTabs.vue +29 -6
- 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 +106 -0
- package/src/runtime/components/fields/MapoMediaM2mField.vue +131 -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 +55 -7
- 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/src/runtime/utils/tabVisibility.ts +44 -0
- package/dist/index.d.cts +0 -1
- package/dist/module.d.cts +0 -2
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed, watch, watchEffect } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
4
|
+
import { useMapoForm, injectMapoForm } from "../../composables/useMapoForm.js";
|
|
5
|
+
import { useFocusMode } from "../../composables/useFocusMode.js";
|
|
6
|
+
import MapoFormField from "../MapoFormField.vue";
|
|
7
|
+
const { t } = useI18n();
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
uid: { type: String, required: false },
|
|
10
|
+
item: { type: Object, required: true },
|
|
11
|
+
fields: { type: Array, required: true },
|
|
12
|
+
index: { type: Number, required: true },
|
|
13
|
+
errorPrefix: { type: String, required: true },
|
|
14
|
+
parentErrors: { type: Object, required: true },
|
|
15
|
+
languages: { type: Array, required: true },
|
|
16
|
+
currentLang: { type: String, required: true },
|
|
17
|
+
readonly: { type: Boolean, required: true },
|
|
18
|
+
registry: { type: Object, required: true },
|
|
19
|
+
previewLabel: { type: Function, required: false },
|
|
20
|
+
defaultExpanded: { type: Boolean, required: true },
|
|
21
|
+
allowDuplicate: { type: Boolean, required: false },
|
|
22
|
+
repeaterDescriptor: { type: Object, required: false },
|
|
23
|
+
totalItems: { type: Number, required: false },
|
|
24
|
+
selected: { type: Boolean, required: false },
|
|
25
|
+
selectionMode: { type: Boolean, required: false }
|
|
26
|
+
});
|
|
27
|
+
const emit = defineEmits(["update:item", "delete", "duplicate", "move-up", "move-down", "move-to", "toggle-select"]);
|
|
28
|
+
function toggleSelectFromEvent(e) {
|
|
29
|
+
emit("toggle-select", e.shiftKey === true);
|
|
30
|
+
}
|
|
31
|
+
const expanded = ref(props.defaultExpanded);
|
|
32
|
+
const localModel = ref({ ...props.item });
|
|
33
|
+
watch(
|
|
34
|
+
() => props.item,
|
|
35
|
+
(next) => {
|
|
36
|
+
if (next === localModel.value) return;
|
|
37
|
+
localModel.value = { ...next };
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
watch(
|
|
41
|
+
localModel,
|
|
42
|
+
(val) => {
|
|
43
|
+
emit("update:item", val);
|
|
44
|
+
},
|
|
45
|
+
{ deep: true }
|
|
46
|
+
);
|
|
47
|
+
const itemErrors = computed(() => {
|
|
48
|
+
const result = {};
|
|
49
|
+
const prefix = `${props.errorPrefix}.`;
|
|
50
|
+
for (const [k, v] of Object.entries(props.parentErrors)) {
|
|
51
|
+
if (k.startsWith(prefix)) result[k.slice(prefix.length)] = v;
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
});
|
|
55
|
+
const hasErrors = computed(() => Object.keys(itemErrors.value).length > 0);
|
|
56
|
+
const currentLangRef = computed(() => props.currentLang);
|
|
57
|
+
const readonly = computed(() => props.readonly);
|
|
58
|
+
const parentCtx = injectMapoForm();
|
|
59
|
+
const form = useMapoForm({
|
|
60
|
+
model: localModel,
|
|
61
|
+
fields: computed(() => props.fields),
|
|
62
|
+
errors: itemErrors,
|
|
63
|
+
languages: props.languages,
|
|
64
|
+
currentLang: currentLangRef,
|
|
65
|
+
immediate: parentCtx?.immediate ?? false,
|
|
66
|
+
debounce: parentCtx?.debounce ?? 300,
|
|
67
|
+
notifyErrors: false,
|
|
68
|
+
registry: props.registry
|
|
69
|
+
});
|
|
70
|
+
watchEffect(() => {
|
|
71
|
+
form.readonly.value = props.readonly;
|
|
72
|
+
if (parentCtx) form.ctx.submitted.value = parentCtx.submitted.value;
|
|
73
|
+
});
|
|
74
|
+
const label = computed(
|
|
75
|
+
() => props.previewLabel?.(localModel.value, props.index) ?? `Item ${props.index + 1}`
|
|
76
|
+
);
|
|
77
|
+
const miniCardAttrs = computed(() => props.repeaterDescriptor?.attrs);
|
|
78
|
+
const isCompressed = computed(() => {
|
|
79
|
+
if (expanded.value) return false;
|
|
80
|
+
const threshold = miniCardAttrs.value?.compressThreshold ?? 3;
|
|
81
|
+
const hasMiniCard = !!miniCardAttrs.value?.miniCard;
|
|
82
|
+
return hasMiniCard && (props.totalItems ?? 0) >= threshold;
|
|
83
|
+
});
|
|
84
|
+
const miniCard = computed(
|
|
85
|
+
() => miniCardAttrs.value?.miniCard?.(localModel.value, props.index)
|
|
86
|
+
);
|
|
87
|
+
const statusColorClass = computed(
|
|
88
|
+
() => ({
|
|
89
|
+
success: "bg-green-500",
|
|
90
|
+
info: "bg-blue-500",
|
|
91
|
+
warning: "bg-yellow-500",
|
|
92
|
+
error: "bg-red-500",
|
|
93
|
+
neutral: "bg-gray-400"
|
|
94
|
+
})[miniCard.value?.statusColor ?? "neutral"]
|
|
95
|
+
);
|
|
96
|
+
const focusMode = useFocusMode();
|
|
97
|
+
function enterFocusMode() {
|
|
98
|
+
focusMode.enter({
|
|
99
|
+
descriptor: props.repeaterDescriptor,
|
|
100
|
+
fields: props.fields,
|
|
101
|
+
model: { ...localModel.value },
|
|
102
|
+
errors: itemErrors,
|
|
103
|
+
onUpdate: (val) => {
|
|
104
|
+
localModel.value = { ...val };
|
|
105
|
+
emit("update:item", localModel.value);
|
|
106
|
+
},
|
|
107
|
+
registry: props.registry,
|
|
108
|
+
languages: props.languages,
|
|
109
|
+
currentLang: props.currentLang,
|
|
110
|
+
breadcrumb: [
|
|
111
|
+
props.repeaterDescriptor?.label ?? t("mapo.repeater.repeater"),
|
|
112
|
+
label.value
|
|
113
|
+
]
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<template>
|
|
119
|
+
<div
|
|
120
|
+
class="mapo-repeater-item rounded border transition-all"
|
|
121
|
+
:class="[
|
|
122
|
+
hasErrors ? 'border-red-300' : 'border-gray-200',
|
|
123
|
+
selected && selectionMode ? 'ring-2 ring-primary-500 ring-offset-1' : ''
|
|
124
|
+
]"
|
|
125
|
+
>
|
|
126
|
+
<!-- ── Mini-card (contextual scaling) ──────────────────────────────────── -->
|
|
127
|
+
<div
|
|
128
|
+
v-if="isCompressed && miniCard"
|
|
129
|
+
class="flex cursor-pointer items-center gap-3 px-3 py-2 transition-colors hover:bg-gray-50"
|
|
130
|
+
@click="selectionMode ? toggleSelectFromEvent($event) : expanded = true"
|
|
131
|
+
>
|
|
132
|
+
<!-- Bulk selection checkbox -->
|
|
133
|
+
<UCheckbox
|
|
134
|
+
v-if="selectionMode"
|
|
135
|
+
:model-value="selected"
|
|
136
|
+
@update:model-value="emit('toggle-select', false)"
|
|
137
|
+
@click.stop
|
|
138
|
+
/>
|
|
139
|
+
|
|
140
|
+
<!-- Optional thumbnail -->
|
|
141
|
+
<img
|
|
142
|
+
v-if="miniCard.thumbnail"
|
|
143
|
+
:src="miniCard.thumbnail"
|
|
144
|
+
class="h-9 w-9 rounded object-cover"
|
|
145
|
+
alt=""
|
|
146
|
+
/>
|
|
147
|
+
|
|
148
|
+
<!-- Status dot -->
|
|
149
|
+
<span
|
|
150
|
+
v-if="miniCard.statusColor"
|
|
151
|
+
class="h-2 w-2 shrink-0 rounded-full"
|
|
152
|
+
:class="statusColorClass"
|
|
153
|
+
/>
|
|
154
|
+
|
|
155
|
+
<!-- Drag handle (mousedown.stop prevents the parent click handler from firing on drag start) -->
|
|
156
|
+
<UIcon
|
|
157
|
+
name="i-lucide-grip-vertical"
|
|
158
|
+
class="drag-handle h-4 w-4 shrink-0 cursor-grab text-gray-300 active:cursor-grabbing"
|
|
159
|
+
@mousedown.stop
|
|
160
|
+
@click.stop
|
|
161
|
+
/>
|
|
162
|
+
|
|
163
|
+
<!-- Contenuto testuale -->
|
|
164
|
+
<div class="min-w-0 flex-1">
|
|
165
|
+
<p class="truncate text-sm font-medium text-gray-700">
|
|
166
|
+
{{ miniCard.title }}
|
|
167
|
+
</p>
|
|
168
|
+
<p v-if="miniCard.subtitle" class="truncate text-xs text-gray-400">
|
|
169
|
+
{{ miniCard.subtitle }}
|
|
170
|
+
</p>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<!-- Error badge -->
|
|
174
|
+
<UBadge v-if="hasErrors" color="error" variant="soft" size="xs">
|
|
175
|
+
errors
|
|
176
|
+
</UBadge>
|
|
177
|
+
|
|
178
|
+
<!-- Quick actions (delete only in mini mode) -->
|
|
179
|
+
<div class="flex items-center gap-1">
|
|
180
|
+
<UButton
|
|
181
|
+
size="xs"
|
|
182
|
+
variant="ghost"
|
|
183
|
+
color="error"
|
|
184
|
+
icon="i-lucide-trash-2"
|
|
185
|
+
:disabled="readonly"
|
|
186
|
+
:title="t('mapo.delete')"
|
|
187
|
+
@click.stop="$emit('delete')"
|
|
188
|
+
/>
|
|
189
|
+
<UIcon name="i-lucide-chevron-down" class="h-4 w-4 text-gray-400" />
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<!-- ── Item header (expanded mode) ─────────────────────────────────────── -->
|
|
194
|
+
<div
|
|
195
|
+
v-else
|
|
196
|
+
class="flex items-center gap-2 bg-gray-50 px-3 py-2"
|
|
197
|
+
:class="selectionMode ? 'cursor-pointer hover:bg-gray-100' : ''"
|
|
198
|
+
@click="selectionMode && toggleSelectFromEvent($event)"
|
|
199
|
+
>
|
|
200
|
+
<!-- Bulk selection checkbox -->
|
|
201
|
+
<UCheckbox
|
|
202
|
+
v-if="selectionMode"
|
|
203
|
+
:model-value="selected"
|
|
204
|
+
@update:model-value="emit('toggle-select', false)"
|
|
205
|
+
@click.stop
|
|
206
|
+
/>
|
|
207
|
+
|
|
208
|
+
<!-- Drag handle (mousedown.stop avoids triggering row-click selection on drag start) -->
|
|
209
|
+
<UIcon
|
|
210
|
+
name="i-lucide-grip-vertical"
|
|
211
|
+
class="drag-handle h-4 w-4 cursor-grab text-gray-400 active:cursor-grabbing"
|
|
212
|
+
@mousedown.stop
|
|
213
|
+
@click.stop
|
|
214
|
+
/>
|
|
215
|
+
|
|
216
|
+
<!-- Collapsed label / error badge. In selection mode the row click handles selection,
|
|
217
|
+
so this button only toggles expansion when not selecting. -->
|
|
218
|
+
<button
|
|
219
|
+
type="button"
|
|
220
|
+
class="flex flex-1 items-center gap-2 text-left text-sm font-medium text-gray-700"
|
|
221
|
+
@click.stop="
|
|
222
|
+
selectionMode ? toggleSelectFromEvent($event) : expanded = !expanded
|
|
223
|
+
"
|
|
224
|
+
>
|
|
225
|
+
<span class="truncate">{{ label }}</span>
|
|
226
|
+
<UBadge v-if="hasErrors" color="error" variant="soft" size="xs">
|
|
227
|
+
errors
|
|
228
|
+
</UBadge>
|
|
229
|
+
</button>
|
|
230
|
+
|
|
231
|
+
<!-- Action buttons. All buttons stop propagation so the row-click selection handler
|
|
232
|
+
does not also toggle selection when the user clicks an action. -->
|
|
233
|
+
<div class="flex items-center gap-1" @click.stop>
|
|
234
|
+
<slot name="actions" />
|
|
235
|
+
|
|
236
|
+
<!-- Numeric position field (enabled via repeaterDescriptor.attrs.showPositionField) -->
|
|
237
|
+
<template
|
|
238
|
+
v-if="repeaterDescriptor?.attrs?.showPositionField && !readonly"
|
|
239
|
+
>
|
|
240
|
+
<UInput
|
|
241
|
+
:model-value="index + 1"
|
|
242
|
+
type="number"
|
|
243
|
+
:min="1"
|
|
244
|
+
:max="totalItems"
|
|
245
|
+
size="xs"
|
|
246
|
+
class="w-14 text-center"
|
|
247
|
+
:title="t('mapo.repeater.position')"
|
|
248
|
+
@change="
|
|
249
|
+
$emit(
|
|
250
|
+
'move-to',
|
|
251
|
+
Math.max(
|
|
252
|
+
0,
|
|
253
|
+
Math.min(
|
|
254
|
+
totalItems - 1,
|
|
255
|
+
Number($event.target.value) - 1
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
"
|
|
260
|
+
/>
|
|
261
|
+
</template>
|
|
262
|
+
|
|
263
|
+
<!-- Focus mode -->
|
|
264
|
+
<UButton
|
|
265
|
+
size="xs"
|
|
266
|
+
variant="ghost"
|
|
267
|
+
color="neutral"
|
|
268
|
+
icon="i-lucide-maximize-2"
|
|
269
|
+
:title="t('mapo.repeater.editFullScreen')"
|
|
270
|
+
@click="enterFocusMode"
|
|
271
|
+
/>
|
|
272
|
+
|
|
273
|
+
<UButton
|
|
274
|
+
size="xs"
|
|
275
|
+
variant="ghost"
|
|
276
|
+
color="neutral"
|
|
277
|
+
icon="i-lucide-chevron-up"
|
|
278
|
+
:disabled="readonly"
|
|
279
|
+
:title="t('mapo.repeater.moveUp')"
|
|
280
|
+
@click="$emit('move-up')"
|
|
281
|
+
/>
|
|
282
|
+
<UButton
|
|
283
|
+
size="xs"
|
|
284
|
+
variant="ghost"
|
|
285
|
+
color="neutral"
|
|
286
|
+
icon="i-lucide-chevron-down"
|
|
287
|
+
:disabled="readonly"
|
|
288
|
+
:title="t('mapo.repeater.moveDown')"
|
|
289
|
+
@click="$emit('move-down')"
|
|
290
|
+
/>
|
|
291
|
+
|
|
292
|
+
<UButton
|
|
293
|
+
v-if="allowDuplicate"
|
|
294
|
+
size="xs"
|
|
295
|
+
variant="ghost"
|
|
296
|
+
color="neutral"
|
|
297
|
+
icon="i-lucide-copy"
|
|
298
|
+
:disabled="readonly"
|
|
299
|
+
:title="t('mapo.duplicate')"
|
|
300
|
+
@click="$emit('duplicate')"
|
|
301
|
+
/>
|
|
302
|
+
|
|
303
|
+
<UButton
|
|
304
|
+
size="xs"
|
|
305
|
+
variant="ghost"
|
|
306
|
+
color="error"
|
|
307
|
+
icon="i-lucide-trash-2"
|
|
308
|
+
:disabled="readonly"
|
|
309
|
+
:title="t('mapo.delete')"
|
|
310
|
+
@click="$emit('delete')"
|
|
311
|
+
/>
|
|
312
|
+
|
|
313
|
+
<UIcon
|
|
314
|
+
:name="expanded ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
|
|
315
|
+
class="h-4 w-4 text-gray-400"
|
|
316
|
+
@click="expanded = !expanded"
|
|
317
|
+
/>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<!-- ── Item body (fields) ──────────────────────────────────────────────── -->
|
|
322
|
+
<!-- COL_SPAN: Tailwind v4 requires static classes — do not use dynamic template literals. -->
|
|
323
|
+
<div v-if="expanded && !isCompressed" class="grid grid-cols-12 gap-5 p-4">
|
|
324
|
+
<div
|
|
325
|
+
v-for="field in fields"
|
|
326
|
+
:key="field.key"
|
|
327
|
+
:class="{
|
|
328
|
+
'col-span-1': field.cols === 1,
|
|
329
|
+
'col-span-2': field.cols === 2,
|
|
330
|
+
'col-span-3': field.cols === 3,
|
|
331
|
+
'col-span-4': field.cols === 4,
|
|
332
|
+
'col-span-5': field.cols === 5,
|
|
333
|
+
'col-span-6': field.cols === 6,
|
|
334
|
+
'col-span-7': field.cols === 7,
|
|
335
|
+
'col-span-8': field.cols === 8,
|
|
336
|
+
'col-span-9': field.cols === 9,
|
|
337
|
+
'col-span-10': field.cols === 10,
|
|
338
|
+
'col-span-11': field.cols === 11,
|
|
339
|
+
'col-span-12': !field.cols || field.cols === 12
|
|
340
|
+
}"
|
|
341
|
+
>
|
|
342
|
+
<MapoFormField :descriptor="field" />
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
</template>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AnyFieldDescriptor, FieldRegistry, RepeaterDescriptor } from "../../types/index.js";
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Stable identity-based UID assigned by the parent repeater. */
|
|
4
|
+
uid?: string;
|
|
5
|
+
item: Record<string, unknown>;
|
|
6
|
+
fields: AnyFieldDescriptor[];
|
|
7
|
+
index: number;
|
|
8
|
+
errorPrefix: string;
|
|
9
|
+
parentErrors: Record<string, string[]>;
|
|
10
|
+
languages: string[];
|
|
11
|
+
currentLang: string;
|
|
12
|
+
readonly: boolean;
|
|
13
|
+
registry: FieldRegistry;
|
|
14
|
+
previewLabel?: (item: unknown, index: number) => string;
|
|
15
|
+
defaultExpanded: boolean;
|
|
16
|
+
allowDuplicate?: boolean;
|
|
17
|
+
/** Descriptor of the parent repeater (for miniCard and other attrs). */
|
|
18
|
+
repeaterDescriptor?: RepeaterDescriptor;
|
|
19
|
+
/** Total number of items in the repeater (used to compute contextual scaling). */
|
|
20
|
+
totalItems?: number;
|
|
21
|
+
/** true = this item is selected in bulk mode. */
|
|
22
|
+
selected?: boolean;
|
|
23
|
+
/** true = bulk selection mode is active in the parent repeater. */
|
|
24
|
+
selectionMode?: boolean;
|
|
25
|
+
};
|
|
26
|
+
declare var __VLS_53: {};
|
|
27
|
+
type __VLS_Slots = {} & {
|
|
28
|
+
actions?: (props: typeof __VLS_53) => any;
|
|
29
|
+
};
|
|
30
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
|
+
delete: () => any;
|
|
32
|
+
"update:item": (value: Record<string, unknown>) => any;
|
|
33
|
+
duplicate: () => any;
|
|
34
|
+
"move-up": () => any;
|
|
35
|
+
"move-down": () => any;
|
|
36
|
+
"move-to": (index: number) => any;
|
|
37
|
+
"toggle-select": (withShift: boolean) => any;
|
|
38
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
39
|
+
onDelete?: (() => any) | undefined;
|
|
40
|
+
"onUpdate:item"?: ((value: Record<string, unknown>) => any) | undefined;
|
|
41
|
+
onDuplicate?: (() => any) | undefined;
|
|
42
|
+
"onMove-up"?: (() => any) | undefined;
|
|
43
|
+
"onMove-down"?: (() => any) | undefined;
|
|
44
|
+
"onMove-to"?: ((index: number) => any) | undefined;
|
|
45
|
+
"onToggle-select"?: ((withShift: boolean) => any) | undefined;
|
|
46
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
47
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
48
|
+
declare const _default: typeof __VLS_export;
|
|
49
|
+
export default _default;
|
|
50
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
51
|
+
new (): {
|
|
52
|
+
$slots: S;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=MapoRepeaterItem.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapoRepeaterItem.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoRepeaterItem.vue"],"names":[],"mappings":"AAoaA,OAAO,KAAK,EACV,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAO9B,KAAK,WAAW,GAAG;IACjB,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,aAAa,CAAC;IACxB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxD,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAszBF,QAAA,IAAI,QAAQ,IAAY,CAAE;AAC1B,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,QAAQ,KAAK,GAAG,CAAA;CAAE,CAAC;AAKhD,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;kFAGd,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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
4
|
+
import { useRequestURL } from "#app";
|
|
5
|
+
const { t } = useI18n();
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
modelValue: { type: null, required: true },
|
|
8
|
+
descriptor: { type: Object, required: true },
|
|
9
|
+
errors: { type: Array, required: false },
|
|
10
|
+
readonly: { type: Boolean, required: false },
|
|
11
|
+
disabled: { type: Boolean, required: false }
|
|
12
|
+
});
|
|
13
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
14
|
+
const value = computed(() => {
|
|
15
|
+
const v = props.modelValue;
|
|
16
|
+
if (!v || typeof v !== "object")
|
|
17
|
+
return { title: "", description: "", permalink: "" };
|
|
18
|
+
return v;
|
|
19
|
+
});
|
|
20
|
+
function update(field, val) {
|
|
21
|
+
emit("update:modelValue", { ...value.value, [field]: val });
|
|
22
|
+
}
|
|
23
|
+
const titleLen = computed(() => (value.value.title ?? "").length);
|
|
24
|
+
const descLen = computed(() => (value.value.description ?? "").length);
|
|
25
|
+
const titleColor = computed(() => {
|
|
26
|
+
if (titleLen.value > 60) return "text-red-500";
|
|
27
|
+
if (titleLen.value > 50) return "text-amber-500";
|
|
28
|
+
return "text-green-600";
|
|
29
|
+
});
|
|
30
|
+
const descColor = computed(() => {
|
|
31
|
+
if (descLen.value > 155) return "text-red-500";
|
|
32
|
+
if (descLen.value > 130) return "text-amber-500";
|
|
33
|
+
return "text-green-600";
|
|
34
|
+
});
|
|
35
|
+
const requestUrl = useRequestURL();
|
|
36
|
+
const displayUrl = computed(() => value.value.permalink || requestUrl.host);
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<div class="mapo-seo-preview space-y-4">
|
|
41
|
+
<!-- Input fields -->
|
|
42
|
+
<div class="space-y-3">
|
|
43
|
+
<div>
|
|
44
|
+
<div class="mb-1 flex items-center justify-between">
|
|
45
|
+
<label class="text-sm font-medium text-gray-700">{{
|
|
46
|
+
t("mapo.url")
|
|
47
|
+
}}</label>
|
|
48
|
+
</div>
|
|
49
|
+
<UInput
|
|
50
|
+
:model-value="value.permalink"
|
|
51
|
+
:readonly="readonly"
|
|
52
|
+
:disabled="disabled"
|
|
53
|
+
placeholder="https://example.com/slug"
|
|
54
|
+
class="w-full"
|
|
55
|
+
@update:model-value="update('permalink', $event)"
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div>
|
|
60
|
+
<div class="mb-1 flex items-center justify-between">
|
|
61
|
+
<label class="text-sm font-medium text-gray-700">{{
|
|
62
|
+
t("mapo.seoPreview.seoTitle")
|
|
63
|
+
}}</label>
|
|
64
|
+
<span class="text-xs" :class="titleColor">{{ titleLen }} / 60</span>
|
|
65
|
+
</div>
|
|
66
|
+
<UInput
|
|
67
|
+
:model-value="value.title"
|
|
68
|
+
:readonly="readonly"
|
|
69
|
+
:disabled="disabled"
|
|
70
|
+
:placeholder="t('mapo.seoPreview.titlePlaceholder')"
|
|
71
|
+
class="w-full"
|
|
72
|
+
@update:model-value="update('title', $event)"
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div>
|
|
77
|
+
<div class="mb-1 flex items-center justify-between">
|
|
78
|
+
<label class="text-sm font-medium text-gray-700">{{
|
|
79
|
+
t("mapo.seoPreview.metaDescription")
|
|
80
|
+
}}</label>
|
|
81
|
+
<span class="text-xs" :class="descColor">{{ descLen }} / 155</span>
|
|
82
|
+
</div>
|
|
83
|
+
<UTextarea
|
|
84
|
+
:model-value="value.description"
|
|
85
|
+
:rows="3"
|
|
86
|
+
:readonly="readonly"
|
|
87
|
+
:disabled="disabled"
|
|
88
|
+
:placeholder="t('mapo.seoPreview.descriptionPlaceholder')"
|
|
89
|
+
class="w-full"
|
|
90
|
+
@update:model-value="update('description', $event)"
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- SERP preview -->
|
|
96
|
+
<div class="rounded border border-gray-200 bg-gray-50 p-4">
|
|
97
|
+
<p class="mb-1 text-xs font-medium uppercase tracking-wide text-gray-400">
|
|
98
|
+
{{ t("mapo.seoPreview.googlePreview") }}
|
|
99
|
+
</p>
|
|
100
|
+
<div class="max-w-lg">
|
|
101
|
+
<p class="truncate text-sm text-green-700">
|
|
102
|
+
{{ displayUrl }}
|
|
103
|
+
</p>
|
|
104
|
+
<p class="truncate text-lg text-blue-700 underline">
|
|
105
|
+
{{ value.title || t("mapo.seoPreview.defaultTitle") }}
|
|
106
|
+
</p>
|
|
107
|
+
<p class="mt-1 line-clamp-2 text-sm text-gray-600">
|
|
108
|
+
{{ value.description || t("mapo.seoPreview.defaultDescription") }}
|
|
109
|
+
</p>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<p
|
|
114
|
+
v-for="err in errors"
|
|
115
|
+
:key="err"
|
|
116
|
+
class="text-sm text-red-500"
|
|
117
|
+
role="alert"
|
|
118
|
+
>
|
|
119
|
+
{{ err }}
|
|
120
|
+
</p>
|
|
121
|
+
</div>
|
|
122
|
+
</template>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SeoDescriptor } from "../../types/index.js";
|
|
2
|
+
interface SeoValue {
|
|
3
|
+
title?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
/** Matches v1 key. `url` is accepted as alias via the registry accessor. */
|
|
6
|
+
permalink?: string;
|
|
7
|
+
}
|
|
8
|
+
type __VLS_Props = {
|
|
9
|
+
modelValue: unknown;
|
|
10
|
+
descriptor: SeoDescriptor;
|
|
11
|
+
errors?: string[];
|
|
12
|
+
readonly?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
};
|
|
15
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
16
|
+
"update:modelValue": (value: SeoValue) => any;
|
|
17
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
18
|
+
"onUpdate:modelValue"?: ((value: SeoValue) => any) | undefined;
|
|
19
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
20
|
+
declare const _default: typeof __VLS_export;
|
|
21
|
+
export default _default;
|
|
22
|
+
//# sourceMappingURL=MapoSeoPreview.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapoSeoPreview.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoSeoPreview.vue"],"names":[],"mappings":"AAmJA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,UAAU,QAAQ;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,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;AAySF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { parseDateTime, CalendarDateTime } from "@internationalized/date";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
modelValue: { type: null, required: true },
|
|
6
|
+
descriptor: { type: Object, required: true },
|
|
7
|
+
errors: { type: Array, required: false },
|
|
8
|
+
readonly: { type: Boolean, required: false },
|
|
9
|
+
disabled: { type: Boolean, required: false }
|
|
10
|
+
});
|
|
11
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
12
|
+
const calendarValue = computed(() => {
|
|
13
|
+
const v = props.modelValue;
|
|
14
|
+
if (!v) return void 0;
|
|
15
|
+
const normalized = v.includes("T") ? v : `1970-01-01T${v}`;
|
|
16
|
+
try {
|
|
17
|
+
return parseDateTime(normalized);
|
|
18
|
+
} catch {
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
function onUpdate(val) {
|
|
23
|
+
const time = val;
|
|
24
|
+
if (!time || typeof time.hour !== "number" || typeof time.minute !== "number") {
|
|
25
|
+
emit("update:modelValue", null);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const h = String(time.hour).padStart(2, "0");
|
|
29
|
+
const m = String(time.minute).padStart(2, "0");
|
|
30
|
+
emit("update:modelValue", `${h}:${m}`);
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<UInputDate
|
|
36
|
+
:model-value="calendarValue"
|
|
37
|
+
granularity="minute"
|
|
38
|
+
hide-time-zone
|
|
39
|
+
:readonly="readonly"
|
|
40
|
+
:disabled="disabled"
|
|
41
|
+
:highlight="!!errors?.length"
|
|
42
|
+
@update:model-value="onUpdate"
|
|
43
|
+
/>
|
|
44
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DateDescriptor } from "../../types/index.js";
|
|
2
|
+
/** Time-only field backed by `UInputDate` with minute granularity. */
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
modelValue: unknown;
|
|
5
|
+
descriptor: DateDescriptor;
|
|
6
|
+
errors?: string[];
|
|
7
|
+
readonly?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
};
|
|
10
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:modelValue": (value: string | null) => any;
|
|
12
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
13
|
+
"onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
|
|
14
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
17
|
+
//# sourceMappingURL=MapoTimeField.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapoTimeField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoTimeField.vue"],"names":[],"mappings":"AAgEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,sEAAsE;AACtE,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;AA2FF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|