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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/dist/__tests__/__mocks__/nuxt-imports.d.ts +4 -0
  2. package/dist/__tests__/__mocks__/nuxt-imports.d.ts.map +1 -0
  3. package/dist/__tests__/registry.test.d.ts +2 -0
  4. package/dist/__tests__/registry.test.d.ts.map +1 -0
  5. package/dist/__tests__/useFormFromSchema.test.d.ts +2 -0
  6. package/dist/__tests__/useFormFromSchema.test.d.ts.map +1 -0
  7. package/dist/__tests__/useMapoForm.test.d.ts +2 -0
  8. package/dist/__tests__/useMapoForm.test.d.ts.map +1 -0
  9. package/dist/index.d.mts +9 -1
  10. package/dist/index.d.ts +15 -1
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.mjs +7 -34
  13. package/dist/module.d.mts +20 -2
  14. package/dist/module.d.ts +12 -2
  15. package/dist/module.d.ts.map +1 -0
  16. package/dist/module.json +1 -1
  17. package/dist/module.mjs +167 -13
  18. package/dist/runtime/components/MapoFocusPortal.vue +113 -0
  19. package/dist/runtime/components/MapoFocusPortal.vue.d.ts +4 -0
  20. package/dist/runtime/components/MapoFocusPortal.vue.d.ts.map +1 -0
  21. package/dist/runtime/components/MapoForm.vue +168 -0
  22. package/dist/runtime/components/MapoForm.vue.d.ts +47 -0
  23. package/dist/runtime/components/MapoForm.vue.d.ts.map +1 -0
  24. package/dist/runtime/components/MapoFormField.vue +269 -0
  25. package/dist/runtime/components/MapoFormField.vue.d.ts +46 -0
  26. package/dist/runtime/components/MapoFormField.vue.d.ts.map +1 -0
  27. package/dist/runtime/components/MapoFormFlatSection.vue +149 -0
  28. package/dist/runtime/components/MapoFormFlatSection.vue.d.ts +15 -0
  29. package/dist/runtime/components/MapoFormFlatSection.vue.d.ts.map +1 -0
  30. package/dist/runtime/components/MapoFormGroup.vue +247 -0
  31. package/dist/runtime/components/MapoFormGroup.vue.d.ts +44 -0
  32. package/dist/runtime/components/MapoFormGroup.vue.d.ts.map +1 -0
  33. package/dist/runtime/components/MapoFormTabs.vue +108 -0
  34. package/dist/runtime/components/MapoFormTabs.vue.d.ts +15 -0
  35. package/dist/runtime/components/MapoFormTabs.vue.d.ts.map +1 -0
  36. package/dist/runtime/components/MapoUnknownField.vue +62 -0
  37. package/dist/runtime/components/MapoUnknownField.vue.d.ts +16 -0
  38. package/dist/runtime/components/MapoUnknownField.vue.d.ts.map +1 -0
  39. package/dist/runtime/components/fields/MapoDateField.vue +86 -0
  40. package/dist/runtime/components/fields/MapoDateField.vue.d.ts +17 -0
  41. package/dist/runtime/components/fields/MapoDateField.vue.d.ts.map +1 -0
  42. package/dist/runtime/components/fields/MapoDateTimeField.vue +157 -0
  43. package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts +17 -0
  44. package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts.map +1 -0
  45. package/dist/runtime/components/fields/MapoFileField.vue +97 -0
  46. package/dist/runtime/components/fields/MapoFileField.vue.d.ts +16 -0
  47. package/dist/runtime/components/fields/MapoFileField.vue.d.ts.map +1 -0
  48. package/dist/runtime/components/fields/MapoFksField.vue +118 -0
  49. package/dist/runtime/components/fields/MapoFksField.vue.d.ts +16 -0
  50. package/dist/runtime/components/fields/MapoFksField.vue.d.ts.map +1 -0
  51. package/dist/runtime/components/fields/MapoMapField.vue +97 -0
  52. package/dist/runtime/components/fields/MapoMapField.vue.d.ts +22 -0
  53. package/dist/runtime/components/fields/MapoMapField.vue.d.ts.map +1 -0
  54. package/dist/runtime/components/fields/MapoMapFieldClient.vue +67 -0
  55. package/dist/runtime/components/fields/MapoMapFieldClient.vue.d.ts +20 -0
  56. package/dist/runtime/components/fields/MapoMapFieldClient.vue.d.ts.map +1 -0
  57. package/dist/runtime/components/fields/MapoRepeater.vue +449 -0
  58. package/dist/runtime/components/fields/MapoRepeater.vue.d.ts +16 -0
  59. package/dist/runtime/components/fields/MapoRepeater.vue.d.ts.map +1 -0
  60. package/dist/runtime/components/fields/MapoRepeaterItem.vue +341 -0
  61. package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts +55 -0
  62. package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts.map +1 -0
  63. package/dist/runtime/components/fields/MapoSeoPreview.vue +118 -0
  64. package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts +22 -0
  65. package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts.map +1 -0
  66. package/dist/runtime/components/fields/MapoTimeField.vue +44 -0
  67. package/dist/runtime/components/fields/MapoTimeField.vue.d.ts +17 -0
  68. package/dist/runtime/components/fields/MapoTimeField.vue.d.ts.map +1 -0
  69. package/dist/runtime/components/fields/MapoWygEditor.vue +227 -0
  70. package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts +16 -0
  71. package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts.map +1 -0
  72. package/dist/runtime/components/fields/nui/NuiCheckbox.vue +9 -0
  73. package/dist/runtime/components/fields/nui/NuiCheckbox.vue.d.ts +4 -0
  74. package/dist/runtime/components/fields/nui/NuiCheckbox.vue.d.ts.map +1 -0
  75. package/dist/runtime/components/fields/nui/NuiInput.vue +9 -0
  76. package/dist/runtime/components/fields/nui/NuiInput.vue.d.ts +4 -0
  77. package/dist/runtime/components/fields/nui/NuiInput.vue.d.ts.map +1 -0
  78. package/dist/runtime/components/fields/nui/NuiSelectMenu.vue +18 -0
  79. package/dist/runtime/components/fields/nui/NuiSelectMenu.vue.d.ts +4 -0
  80. package/dist/runtime/components/fields/nui/NuiSelectMenu.vue.d.ts.map +1 -0
  81. package/dist/runtime/components/fields/nui/NuiSlider.vue +9 -0
  82. package/dist/runtime/components/fields/nui/NuiSlider.vue.d.ts +4 -0
  83. package/dist/runtime/components/fields/nui/NuiSlider.vue.d.ts.map +1 -0
  84. package/dist/runtime/components/fields/nui/NuiSwitch.vue +9 -0
  85. package/dist/runtime/components/fields/nui/NuiSwitch.vue.d.ts +4 -0
  86. package/dist/runtime/components/fields/nui/NuiSwitch.vue.d.ts.map +1 -0
  87. package/dist/runtime/components/fields/nui/NuiTextarea.vue +9 -0
  88. package/dist/runtime/components/fields/nui/NuiTextarea.vue.d.ts +4 -0
  89. package/dist/runtime/components/fields/nui/NuiTextarea.vue.d.ts.map +1 -0
  90. package/dist/runtime/components/fields/nui/useNuiFieldAttrs.d.ts +13 -0
  91. package/dist/runtime/components/fields/nui/useNuiFieldAttrs.d.ts.map +1 -0
  92. package/dist/runtime/components/fields/nui/useNuiFieldAttrs.js +19 -0
  93. package/dist/runtime/composables/defineFormField.d.ts +25 -0
  94. package/dist/runtime/composables/defineFormField.d.ts.map +1 -0
  95. package/dist/runtime/composables/defineFormField.js +27 -0
  96. package/dist/runtime/composables/useCurrentLang.d.ts +6 -0
  97. package/dist/runtime/composables/useCurrentLang.d.ts.map +1 -0
  98. package/dist/runtime/composables/useCurrentLang.js +9 -0
  99. package/dist/runtime/composables/useFocusMode.d.ts +40 -0
  100. package/dist/runtime/composables/useFocusMode.d.ts.map +1 -0
  101. package/dist/runtime/composables/useFocusMode.js +31 -0
  102. package/dist/runtime/composables/useFormDraft.d.ts +60 -0
  103. package/dist/runtime/composables/useFormDraft.d.ts.map +1 -0
  104. package/dist/runtime/composables/useFormDraft.js +52 -0
  105. package/dist/runtime/composables/useFormFromSchema.d.ts +21 -0
  106. package/dist/runtime/composables/useFormFromSchema.d.ts.map +1 -0
  107. package/dist/runtime/composables/useFormFromSchema.js +142 -0
  108. package/dist/runtime/composables/useMapoForm.d.ts +96 -0
  109. package/dist/runtime/composables/useMapoForm.d.ts.map +1 -0
  110. package/dist/runtime/composables/useMapoForm.js +323 -0
  111. package/dist/runtime/composables/useProgressiveDisclosure.d.ts +44 -0
  112. package/dist/runtime/composables/useProgressiveDisclosure.d.ts.map +1 -0
  113. package/dist/runtime/composables/useProgressiveDisclosure.js +23 -0
  114. package/dist/runtime/composables/useUnsavedChangesGuard.d.ts +24 -0
  115. package/dist/runtime/composables/useUnsavedChangesGuard.d.ts.map +1 -0
  116. package/dist/runtime/composables/useUnsavedChangesGuard.js +27 -0
  117. package/dist/runtime/devtools/DevtoolsFormsPage.vue +235 -0
  118. package/dist/runtime/devtools/DevtoolsFormsPage.vue.d.ts +4 -0
  119. package/dist/runtime/devtools/DevtoolsFormsPage.vue.d.ts.map +1 -0
  120. package/dist/runtime/plugins/form-registry.d.ts +18 -0
  121. package/dist/runtime/plugins/form-registry.d.ts.map +1 -0
  122. package/dist/runtime/plugins/form-registry.js +19 -0
  123. package/dist/runtime/registry/defaults.d.ts +3 -0
  124. package/dist/runtime/registry/defaults.d.ts.map +1 -0
  125. package/dist/runtime/registry/defaults.js +87 -0
  126. package/dist/runtime/types/descriptor.d.ts +359 -0
  127. package/dist/runtime/types/descriptor.d.ts.map +1 -0
  128. package/dist/runtime/types/descriptor.js +26 -0
  129. package/dist/runtime/types/index.d.ts +11 -0
  130. package/dist/runtime/types/index.d.ts.map +1 -0
  131. package/dist/runtime/types/index.js +6 -0
  132. package/dist/runtime/types/layout.d.ts +21 -0
  133. package/dist/runtime/types/layout.d.ts.map +1 -0
  134. package/dist/runtime/types/layout.js +0 -0
  135. package/dist/runtime/types/registry.d.ts +62 -0
  136. package/dist/runtime/types/registry.d.ts.map +1 -0
  137. package/dist/runtime/types/registry.js +19 -0
  138. package/dist/runtime/utils/flattenFieldGroups.d.ts +50 -0
  139. package/dist/runtime/utils/flattenFieldGroups.d.ts.map +1 -0
  140. package/dist/runtime/utils/flattenFieldGroups.js +24 -0
  141. package/dist/types.d.mts +6 -10
  142. package/package.json +3 -3
  143. package/dist/index.d.cts +0 -1
  144. package/dist/module.d.cts +0 -2
@@ -0,0 +1,449 @@
1
+ <script setup>
2
+ import { ref, computed, watchEffect } from "vue";
3
+ import { VueDraggable } from "vue-draggable-plus";
4
+ import { useConfirmStore } from "@mapomodule/store/runtime/stores/confirm";
5
+ import { injectMapoForm } from "../../composables/useMapoForm.js";
6
+ import MapoRepeaterItem from "./MapoRepeaterItem.vue";
7
+ const props = defineProps({
8
+ modelValue: { type: null, required: true },
9
+ descriptor: { type: Object, required: true },
10
+ errors: { type: Array, required: false },
11
+ readonly: { type: Boolean, required: false },
12
+ disabled: { type: Boolean, required: false }
13
+ });
14
+ const emit = defineEmits(["update:modelValue"]);
15
+ const form = injectMapoForm();
16
+ const attrs = computed(() => props.descriptor.attrs ?? {});
17
+ const items = computed({
18
+ get: () => Array.isArray(props.modelValue) ? props.modelValue : [],
19
+ set: (val) => emit("update:modelValue", val)
20
+ });
21
+ function emitItems(newItems) {
22
+ emit("update:modelValue", [...newItems]);
23
+ }
24
+ const itemUids = /* @__PURE__ */ new WeakMap();
25
+ let _uidSeq = 0;
26
+ function uidFor(item) {
27
+ let id = itemUids.get(item);
28
+ if (!id) {
29
+ id = `it_${++_uidSeq}`;
30
+ itemUids.set(item, id);
31
+ }
32
+ return id;
33
+ }
34
+ const undoStack = ref([]);
35
+ function pushUndo() {
36
+ undoStack.value.push(items.value.map((i) => ({ ...i })));
37
+ if (undoStack.value.length > 20) undoStack.value.shift();
38
+ }
39
+ function undo() {
40
+ const prev = undoStack.value.pop();
41
+ if (prev) emit("update:modelValue", prev);
42
+ }
43
+ const templates = computed(() => attrs.value.templates);
44
+ const templateKeys = computed(
45
+ () => templates.value ? Object.keys(templates.value) : []
46
+ );
47
+ const hasTemplates = computed(() => templateKeys.value.length > 0);
48
+ const selectedTemplate = ref("");
49
+ watchEffect(() => {
50
+ if (!selectedTemplate.value && templateKeys.value[0]) {
51
+ selectedTemplate.value = templateKeys.value[0];
52
+ }
53
+ });
54
+ function fieldsForItem(item) {
55
+ if (!templates.value) return props.descriptor.fields ?? [];
56
+ const key = item._template ?? selectedTemplate.value;
57
+ return templates.value[key] ?? props.descriptor.fields ?? [];
58
+ }
59
+ const canAdd = computed(
60
+ () => !props.readonly && (!attrs.value.maxItems || items.value.length < attrs.value.maxItems)
61
+ );
62
+ const canDelete = computed(
63
+ () => !props.readonly && (!attrs.value.minItems || items.value.length > attrs.value.minItems)
64
+ );
65
+ function remainingSlots() {
66
+ if (!attrs.value.maxItems) return Number.POSITIVE_INFINITY;
67
+ return Math.max(0, attrs.value.maxItems - items.value.length);
68
+ }
69
+ function minBound() {
70
+ return attrs.value.minItems ?? 0;
71
+ }
72
+ async function askConfirm(message, title = "Confirm") {
73
+ try {
74
+ return await useConfirmStore().ask({ title, message });
75
+ } catch {
76
+ return typeof globalThis.confirm === "function" ? globalThis.confirm(message) : false;
77
+ }
78
+ }
79
+ function addItem() {
80
+ if (!canAdd.value) return;
81
+ pushUndo();
82
+ const newItem = hasTemplates.value ? { _template: selectedTemplate.value } : {};
83
+ emitItems([...items.value, newItem]);
84
+ }
85
+ async function deleteItem(index) {
86
+ if (!canDelete.value) return;
87
+ if (attrs.value.confirmDelete !== false) {
88
+ const confirmed = await askConfirm(
89
+ "Are you sure you want to delete this item?",
90
+ "Delete item"
91
+ );
92
+ if (!confirmed) return;
93
+ }
94
+ pushUndo();
95
+ const next = [...items.value];
96
+ next.splice(index, 1);
97
+ emitItems(next);
98
+ }
99
+ function duplicateItem(index) {
100
+ if (remainingSlots() < 1) return;
101
+ const source = items.value[index];
102
+ if (!source) return;
103
+ pushUndo();
104
+ const copy = { ...source };
105
+ const next = [...items.value];
106
+ next.splice(index + 1, 0, copy);
107
+ emitItems(next);
108
+ }
109
+ function updateItem(index, val) {
110
+ const oldItem = items.value[index];
111
+ if (oldItem && oldItem !== val) {
112
+ const existingUid = itemUids.get(oldItem);
113
+ if (existingUid) itemUids.set(val, existingUid);
114
+ }
115
+ const next = [...items.value];
116
+ next[index] = val;
117
+ emitItems(next);
118
+ }
119
+ function moveUp(index) {
120
+ moveTo(index, index - 1);
121
+ }
122
+ function moveDown(index) {
123
+ moveTo(index, index + 1);
124
+ }
125
+ function moveTo(from, to) {
126
+ if (to < 0 || to >= items.value.length || from === to) return;
127
+ pushUndo();
128
+ const next = [...items.value];
129
+ const [item] = next.splice(from, 1);
130
+ if (item === void 0) return;
131
+ next.splice(to, 0, item);
132
+ emitItems(next);
133
+ }
134
+ const collapseVersion = ref(0);
135
+ const globalExpanded = ref(attrs.value.defaultExpanded ?? false);
136
+ function collapseAll() {
137
+ globalExpanded.value = false;
138
+ collapseVersion.value++;
139
+ }
140
+ function expandAll() {
141
+ globalExpanded.value = true;
142
+ collapseVersion.value++;
143
+ }
144
+ function onDragStart() {
145
+ pushUndo();
146
+ }
147
+ const selectionMode = ref(false);
148
+ const selectedUids = ref(/* @__PURE__ */ new Set());
149
+ const anchorUid = ref(null);
150
+ function toggleSelectionMode() {
151
+ selectionMode.value = !selectionMode.value;
152
+ if (!selectionMode.value) {
153
+ selectedUids.value = /* @__PURE__ */ new Set();
154
+ anchorUid.value = null;
155
+ collapseVersion.value++;
156
+ } else {
157
+ globalExpanded.value = false;
158
+ collapseVersion.value++;
159
+ }
160
+ }
161
+ function isSelected(uid) {
162
+ return selectedUids.value.has(uid);
163
+ }
164
+ function toggleSelect(uid, withShift = false) {
165
+ if (withShift && anchorUid.value && anchorUid.value !== uid) {
166
+ const uids = items.value.map(uidFor);
167
+ const a = uids.indexOf(anchorUid.value);
168
+ const b = uids.indexOf(uid);
169
+ if (a !== -1 && b !== -1) {
170
+ const [lo, hi] = a < b ? [a, b] : [b, a];
171
+ const next2 = new Set(selectedUids.value);
172
+ for (let i = lo; i <= hi; i++) {
173
+ const uid2 = uids[i];
174
+ if (uid2) next2.add(uid2);
175
+ }
176
+ selectedUids.value = next2;
177
+ return;
178
+ }
179
+ }
180
+ const next = new Set(selectedUids.value);
181
+ if (next.has(uid)) next.delete(uid);
182
+ else next.add(uid);
183
+ selectedUids.value = next;
184
+ anchorUid.value = uid;
185
+ }
186
+ function selectAll() {
187
+ selectedUids.value = new Set(items.value.map(uidFor));
188
+ }
189
+ function clearSelection() {
190
+ selectedUids.value = /* @__PURE__ */ new Set();
191
+ anchorUid.value = null;
192
+ }
193
+ function selectedItems() {
194
+ return items.value.filter((i) => selectedUids.value.has(uidFor(i)));
195
+ }
196
+ async function bulkDelete() {
197
+ const count = selectedUids.value.size;
198
+ if (count === 0) return;
199
+ if (items.value.length - count < minBound()) {
200
+ await askConfirm(
201
+ `Cannot delete: at least ${minBound()} items must remain.`,
202
+ "Action blocked"
203
+ );
204
+ return;
205
+ }
206
+ const confirmed = await askConfirm(
207
+ `Delete ${count} selected items?`,
208
+ "Delete items"
209
+ );
210
+ if (!confirmed) return;
211
+ pushUndo();
212
+ emitItems(items.value.filter((i) => !selectedUids.value.has(uidFor(i))));
213
+ clearSelection();
214
+ }
215
+ function bulkDuplicate() {
216
+ const count = selectedUids.value.size;
217
+ if (count === 0) return;
218
+ const slots = remainingSlots();
219
+ if (slots <= 0) return;
220
+ const toDup = selectedItems().slice(0, slots);
221
+ pushUndo();
222
+ const next = [...items.value];
223
+ const dupSet = new Set(toDup.map(uidFor));
224
+ for (let i = next.length - 1; i >= 0; i--) {
225
+ const item = next[i];
226
+ if (item && dupSet.has(uidFor(item))) {
227
+ next.splice(i + 1, 0, { ...item });
228
+ }
229
+ }
230
+ emitItems(next);
231
+ clearSelection();
232
+ }
233
+ function bulkMoveToTop() {
234
+ if (selectedUids.value.size === 0) return;
235
+ pushUndo();
236
+ const selected = items.value.filter((i) => selectedUids.value.has(uidFor(i)));
237
+ const rest = items.value.filter((i) => !selectedUids.value.has(uidFor(i)));
238
+ emitItems([...selected, ...rest]);
239
+ }
240
+ const hasSelection = computed(() => selectedUids.value.size > 0);
241
+ const selectionCount = computed(() => selectedUids.value.size);
242
+ const parentErrors = computed(() => form.errors.value);
243
+ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
244
+ </script>
245
+
246
+ <template>
247
+ <div class="mapo-repeater space-y-2">
248
+ <!-- ── Main toolbar ────────────────────────────────────────────────────── -->
249
+ <div
250
+ class="flex flex-wrap items-center gap-2 rounded border border-gray-200 bg-gray-50 px-3 py-2"
251
+ >
252
+ <span class="text-sm font-medium text-gray-700">
253
+ {{ items.length }} {{ items.length === 1 ? "item" : "items" }}
254
+ <span v-if="attrs.maxItems" class="text-gray-400"
255
+ >/ {{ attrs.maxItems }}</span
256
+ >
257
+ </span>
258
+
259
+ <div class="flex flex-1 flex-wrap items-center justify-end gap-2">
260
+ <UButton size="xs" variant="ghost" color="neutral" @click="collapseAll">
261
+ Collapse all
262
+ </UButton>
263
+ <UButton size="xs" variant="ghost" color="neutral" @click="expandAll">
264
+ Expand all
265
+ </UButton>
266
+ <UButton
267
+ v-if="undoStack.length"
268
+ size="xs"
269
+ variant="ghost"
270
+ color="neutral"
271
+ icon="i-lucide-undo-2"
272
+ title="Undo last action"
273
+ @click="undo"
274
+ />
275
+
276
+ <!-- Bulk selection mode -->
277
+ <UButton
278
+ size="xs"
279
+ :variant="selectionMode ? 'solid' : 'ghost'"
280
+ color="neutral"
281
+ icon="i-lucide-check-square"
282
+ title="Bulk selection"
283
+ :label="selectionMode ? 'Exit selection' : 'Select'"
284
+ @click="toggleSelectionMode"
285
+ />
286
+
287
+ <!-- Template selector -->
288
+ <USelectMenu
289
+ v-if="hasTemplates"
290
+ v-model="selectedTemplate"
291
+ :items="templateKeys"
292
+ size="xs"
293
+ class="w-32"
294
+ />
295
+
296
+ <UButton
297
+ size="sm"
298
+ color="primary"
299
+ icon="i-lucide-plus"
300
+ :disabled="!canAdd"
301
+ @click="addItem"
302
+ >
303
+ Add
304
+ </UButton>
305
+ </div>
306
+ </div>
307
+
308
+ <!-- ── Bulk actions bar (visible when there is a selection) ───────────── -->
309
+ <Transition name="slide-down">
310
+ <div
311
+ v-if="selectionMode && hasSelection"
312
+ class="flex items-center gap-2 rounded border border-primary-200 bg-primary-50 px-3 py-2"
313
+ >
314
+ <span class="text-sm font-medium text-primary-700">
315
+ {{ selectionCount }} selected
316
+ </span>
317
+ <div class="flex flex-1 flex-wrap items-center gap-2">
318
+ <UButton size="xs" variant="ghost" color="neutral" @click="selectAll">
319
+ All
320
+ </UButton>
321
+ <UButton
322
+ size="xs"
323
+ variant="ghost"
324
+ color="neutral"
325
+ @click="clearSelection"
326
+ >
327
+ Clear
328
+ </UButton>
329
+ <UButton
330
+ size="xs"
331
+ variant="outline"
332
+ color="neutral"
333
+ icon="i-lucide-copy"
334
+ :disabled="remainingSlots() <= 0"
335
+ :title="
336
+ remainingSlots() < selectionCount ? `Only ${remainingSlots()} of ${selectionCount} can be duplicated (maxItems reached)` : 'Duplicate selected'
337
+ "
338
+ @click="bulkDuplicate"
339
+ >
340
+ Duplicate ({{ Math.min(selectionCount, remainingSlots()) }})
341
+ </UButton>
342
+ <UButton
343
+ size="xs"
344
+ variant="outline"
345
+ color="neutral"
346
+ icon="i-lucide-arrow-up"
347
+ @click="bulkMoveToTop"
348
+ >
349
+ Move to top
350
+ </UButton>
351
+ <UButton
352
+ size="xs"
353
+ variant="outline"
354
+ color="error"
355
+ icon="i-lucide-trash-2"
356
+ :disabled="!canDelete"
357
+ @click="bulkDelete"
358
+ >
359
+ Delete ({{ selectionCount }})
360
+ </UButton>
361
+ </div>
362
+ </div>
363
+ </Transition>
364
+
365
+ <!-- ── Item list with drag&drop (ClientOnly for SSR) ───────────────────── -->
366
+ <ClientOnly>
367
+ <VueDraggable
368
+ v-model="items"
369
+ handle=".drag-handle"
370
+ :animation="150"
371
+ :disabled="readonly || selectionMode"
372
+ @start="onDragStart"
373
+ >
374
+ <MapoRepeaterItem
375
+ v-for="(item, index) in items"
376
+ :key="`${uidFor(item)}-${collapseVersion}`"
377
+ :uid="uidFor(item)"
378
+ :item="item"
379
+ :fields="fieldsForItem(item)"
380
+ :index="index"
381
+ :error-prefix="errorPrefix(index)"
382
+ :parent-errors="parentErrors"
383
+ :languages="form.languages"
384
+ :current-lang="form.currentLang.value"
385
+ :readonly="readonly ?? false"
386
+ :registry="form.registry"
387
+ :preview-label="attrs.previewLabel"
388
+ :default-expanded="globalExpanded"
389
+ :allow-duplicate="attrs.allowDuplicate"
390
+ :repeater-descriptor="descriptor"
391
+ :total-items="items.length"
392
+ :selected="isSelected(uidFor(item))"
393
+ :selection-mode="selectionMode"
394
+ @update:item="updateItem(index, $event)"
395
+ @delete="deleteItem(index)"
396
+ @duplicate="duplicateItem(index)"
397
+ @move-up="moveUp(index)"
398
+ @move-down="moveDown(index)"
399
+ @move-to="moveTo(index, $event)"
400
+ @toggle-select="toggleSelect(uidFor(item), $event)"
401
+ />
402
+ </VueDraggable>
403
+
404
+ <!-- SSR fallback: list without drag -->
405
+ <template #fallback>
406
+ <MapoRepeaterItem
407
+ v-for="(item, index) in items"
408
+ :key="`${uidFor(item)}-${collapseVersion}`"
409
+ :uid="uidFor(item)"
410
+ :item="item"
411
+ :fields="fieldsForItem(item)"
412
+ :index="index"
413
+ :error-prefix="errorPrefix(index)"
414
+ :parent-errors="parentErrors"
415
+ :languages="form.languages"
416
+ :current-lang="form.currentLang.value"
417
+ :readonly="readonly ?? false"
418
+ :registry="form.registry"
419
+ :preview-label="attrs.previewLabel"
420
+ :default-expanded="globalExpanded"
421
+ :allow-duplicate="attrs.allowDuplicate"
422
+ :repeater-descriptor="descriptor"
423
+ :total-items="items.length"
424
+ :selected="isSelected(uidFor(item))"
425
+ :selection-mode="selectionMode"
426
+ @update:item="updateItem(index, $event)"
427
+ @delete="deleteItem(index)"
428
+ @duplicate="duplicateItem(index)"
429
+ @move-up="moveUp(index)"
430
+ @move-down="moveDown(index)"
431
+ @move-to="moveTo(index, $event)"
432
+ @toggle-select="toggleSelect(uidFor(item), $event)"
433
+ />
434
+ </template>
435
+ </ClientOnly>
436
+
437
+ <!-- ── Empty state ─────────────────────────────────────────────────────── -->
438
+ <div
439
+ v-if="items.length === 0"
440
+ class="rounded border border-dashed border-gray-300 py-8 text-center text-sm text-gray-400"
441
+ >
442
+ No items. Click "Add" to start.
443
+ </div>
444
+ </div>
445
+ </template>
446
+
447
+ <style scoped>
448
+ .slide-down-enter-active,.slide-down-leave-active{display:grid;transition:grid-template-rows .25s ease,opacity .2s ease}.slide-down-enter-from,.slide-down-leave-to{grid-template-rows:0fr;opacity:0}.slide-down-enter-to,.slide-down-leave-from{grid-template-rows:1fr;opacity:1}
449
+ </style>
@@ -0,0 +1,16 @@
1
+ import type { RepeaterDescriptor } from "../../types/index.js";
2
+ type __VLS_Props = {
3
+ modelValue: unknown;
4
+ descriptor: RepeaterDescriptor;
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: unknown[]) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ "onUpdate:modelValue"?: ((value: unknown[]) => any) | undefined;
13
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const _default: typeof __VLS_export;
15
+ export default _default;
16
+ //# sourceMappingURL=MapoRepeater.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapoRepeater.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoRepeater.vue"],"names":[],"mappings":"AA+iBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAI/D,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA4hCF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}