@mapomodule/form 2.0.0-beta.5 → 2.0.0-beta.8

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 (54) hide show
  1. package/dist/module.d.ts.map +1 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +4 -1
  4. package/dist/runtime/components/MapoFocusPortal.vue +3 -1
  5. package/dist/runtime/components/MapoFocusPortal.vue.d.ts.map +1 -1
  6. package/dist/runtime/components/MapoFormTabs.vue +4 -2
  7. package/dist/runtime/components/MapoFormTabs.vue.d.ts.map +1 -1
  8. package/dist/runtime/components/fields/MapoDateField.vue +3 -1
  9. package/dist/runtime/components/fields/MapoDateField.vue.d.ts.map +1 -1
  10. package/dist/runtime/components/fields/MapoDateTimeField.vue +5 -3
  11. package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts.map +1 -1
  12. package/dist/runtime/components/fields/MapoEnhancedMediaField.vue +7 -5
  13. package/dist/runtime/components/fields/MapoEnhancedMediaField.vue.d.ts.map +1 -1
  14. package/dist/runtime/components/fields/MapoFileField.vue +3 -1
  15. package/dist/runtime/components/fields/MapoFileField.vue.d.ts.map +1 -1
  16. package/dist/runtime/components/fields/MapoFksField.vue +6 -2
  17. package/dist/runtime/components/fields/MapoFksField.vue.d.ts.map +1 -1
  18. package/dist/runtime/components/fields/MapoMapField.vue +4 -2
  19. package/dist/runtime/components/fields/MapoMapField.vue.d.ts.map +1 -1
  20. package/dist/runtime/components/fields/MapoMediaField.vue +3 -1
  21. package/dist/runtime/components/fields/MapoMediaField.vue.d.ts.map +1 -1
  22. package/dist/runtime/components/fields/MapoMediaM2mField.vue +5 -1
  23. package/dist/runtime/components/fields/MapoMediaM2mField.vue.d.ts.map +1 -1
  24. package/dist/runtime/components/fields/MapoRepeater.vue +32 -22
  25. package/dist/runtime/components/fields/MapoRepeater.vue.d.ts.map +1 -1
  26. package/dist/runtime/components/fields/MapoRepeaterItem.vue +13 -8
  27. package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts.map +1 -1
  28. package/dist/runtime/components/fields/MapoSeoPreview.vue +16 -12
  29. package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts.map +1 -1
  30. package/dist/runtime/components/fields/MapoWygEditor.vue +5 -1
  31. package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts.map +1 -1
  32. package/dist/runtime/composables/useMapoForm.d.ts.map +1 -1
  33. package/dist/runtime/composables/useMapoForm.js +4 -2
  34. package/dist/runtime/composables/useUnsavedChangesGuard.d.ts.map +1 -1
  35. package/dist/runtime/composables/useUnsavedChangesGuard.js +3 -2
  36. package/package.json +9 -8
  37. package/src/__tests__/useMapoForm.test.ts +5 -0
  38. package/src/module.ts +9 -0
  39. package/src/runtime/components/MapoFocusPortal.vue +3 -1
  40. package/src/runtime/components/MapoFormTabs.vue +4 -1
  41. package/src/runtime/components/fields/MapoDateField.vue +3 -1
  42. package/src/runtime/components/fields/MapoDateTimeField.vue +3 -1
  43. package/src/runtime/components/fields/MapoEnhancedMediaField.vue +8 -5
  44. package/src/runtime/components/fields/MapoFileField.vue +3 -1
  45. package/src/runtime/components/fields/MapoFksField.vue +7 -2
  46. package/src/runtime/components/fields/MapoMapField.vue +5 -2
  47. package/src/runtime/components/fields/MapoMediaField.vue +4 -1
  48. package/src/runtime/components/fields/MapoMediaM2mField.vue +8 -1
  49. package/src/runtime/components/fields/MapoRepeater.vue +33 -26
  50. package/src/runtime/components/fields/MapoRepeaterItem.vue +14 -8
  51. package/src/runtime/components/fields/MapoSeoPreview.vue +17 -13
  52. package/src/runtime/components/fields/MapoWygEditor.vue +8 -1
  53. package/src/runtime/composables/useMapoForm.ts +4 -2
  54. package/src/runtime/composables/useUnsavedChangesGuard.ts +3 -3
@@ -1,8 +1,10 @@
1
1
  <script setup>
2
2
  import { ref, computed, watch, watchEffect } from "vue";
3
+ import { useI18n } from "vue-i18n";
3
4
  import { useMapoForm, injectMapoForm } from "../../composables/useMapoForm.js";
4
5
  import { useFocusMode } from "../../composables/useFocusMode.js";
5
6
  import MapoFormField from "../MapoFormField.vue";
7
+ const { t } = useI18n();
6
8
  const props = defineProps({
7
9
  uid: { type: String, required: false },
8
10
  item: { type: Object, required: true },
@@ -105,7 +107,10 @@ function enterFocusMode() {
105
107
  registry: props.registry,
106
108
  languages: props.languages,
107
109
  currentLang: props.currentLang,
108
- breadcrumb: [props.repeaterDescriptor?.label ?? "Repeater", label.value]
110
+ breadcrumb: [
111
+ props.repeaterDescriptor?.label ?? t("mapo.repeater.repeater"),
112
+ label.value
113
+ ]
109
114
  });
110
115
  }
111
116
  </script>
@@ -178,7 +183,7 @@ function enterFocusMode() {
178
183
  color="error"
179
184
  icon="i-lucide-trash-2"
180
185
  :disabled="readonly"
181
- title="Delete"
186
+ :title="t('mapo.delete')"
182
187
  @click.stop="$emit('delete')"
183
188
  />
184
189
  <UIcon name="i-lucide-chevron-down" class="h-4 w-4 text-gray-400" />
@@ -239,7 +244,7 @@ function enterFocusMode() {
239
244
  :max="totalItems"
240
245
  size="xs"
241
246
  class="w-14 text-center"
242
- title="Position"
247
+ :title="t('mapo.repeater.position')"
243
248
  @change="
244
249
  $emit(
245
250
  'move-to',
@@ -261,7 +266,7 @@ function enterFocusMode() {
261
266
  variant="ghost"
262
267
  color="neutral"
263
268
  icon="i-lucide-maximize-2"
264
- title="Edit full screen"
269
+ :title="t('mapo.repeater.editFullScreen')"
265
270
  @click="enterFocusMode"
266
271
  />
267
272
 
@@ -271,7 +276,7 @@ function enterFocusMode() {
271
276
  color="neutral"
272
277
  icon="i-lucide-chevron-up"
273
278
  :disabled="readonly"
274
- title="Move up"
279
+ :title="t('mapo.repeater.moveUp')"
275
280
  @click="$emit('move-up')"
276
281
  />
277
282
  <UButton
@@ -280,7 +285,7 @@ function enterFocusMode() {
280
285
  color="neutral"
281
286
  icon="i-lucide-chevron-down"
282
287
  :disabled="readonly"
283
- title="Move down"
288
+ :title="t('mapo.repeater.moveDown')"
284
289
  @click="$emit('move-down')"
285
290
  />
286
291
 
@@ -291,7 +296,7 @@ function enterFocusMode() {
291
296
  color="neutral"
292
297
  icon="i-lucide-copy"
293
298
  :disabled="readonly"
294
- title="Duplicate"
299
+ :title="t('mapo.duplicate')"
295
300
  @click="$emit('duplicate')"
296
301
  />
297
302
 
@@ -301,7 +306,7 @@ function enterFocusMode() {
301
306
  color="error"
302
307
  icon="i-lucide-trash-2"
303
308
  :disabled="readonly"
304
- title="Delete"
309
+ :title="t('mapo.delete')"
305
310
  @click="$emit('delete')"
306
311
  />
307
312
 
@@ -1 +1 @@
1
- {"version":3,"file":"MapoRepeaterItem.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoRepeaterItem.vue"],"names":[],"mappings":"AA6ZA,OAAO,KAAK,EACV,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAK9B,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;AAkzBF,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"}
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"}
@@ -1,6 +1,8 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
+ import { useI18n } from "vue-i18n";
3
4
  import { useRequestURL } from "#app";
5
+ const { t } = useI18n();
4
6
  const props = defineProps({
5
7
  modelValue: { type: null, required: true },
6
8
  descriptor: { type: Object, required: true },
@@ -40,7 +42,9 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
40
42
  <div class="space-y-3">
41
43
  <div>
42
44
  <div class="mb-1 flex items-center justify-between">
43
- <label class="text-sm font-medium text-gray-700">URL</label>
45
+ <label class="text-sm font-medium text-gray-700">{{
46
+ t("mapo.url")
47
+ }}</label>
44
48
  </div>
45
49
  <UInput
46
50
  :model-value="value.permalink"
@@ -54,14 +58,16 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
54
58
 
55
59
  <div>
56
60
  <div class="mb-1 flex items-center justify-between">
57
- <label class="text-sm font-medium text-gray-700">SEO title</label>
61
+ <label class="text-sm font-medium text-gray-700">{{
62
+ t("mapo.seoPreview.seoTitle")
63
+ }}</label>
58
64
  <span class="text-xs" :class="titleColor">{{ titleLen }} / 60</span>
59
65
  </div>
60
66
  <UInput
61
67
  :model-value="value.title"
62
68
  :readonly="readonly"
63
69
  :disabled="disabled"
64
- placeholder="Page title..."
70
+ :placeholder="t('mapo.seoPreview.titlePlaceholder')"
65
71
  class="w-full"
66
72
  @update:model-value="update('title', $event)"
67
73
  />
@@ -69,9 +75,9 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
69
75
 
70
76
  <div>
71
77
  <div class="mb-1 flex items-center justify-between">
72
- <label class="text-sm font-medium text-gray-700"
73
- >Meta description</label
74
- >
78
+ <label class="text-sm font-medium text-gray-700">{{
79
+ t("mapo.seoPreview.metaDescription")
80
+ }}</label>
75
81
  <span class="text-xs" :class="descColor">{{ descLen }} / 155</span>
76
82
  </div>
77
83
  <UTextarea
@@ -79,7 +85,7 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
79
85
  :rows="3"
80
86
  :readonly="readonly"
81
87
  :disabled="disabled"
82
- placeholder="Page description..."
88
+ :placeholder="t('mapo.seoPreview.descriptionPlaceholder')"
83
89
  class="w-full"
84
90
  @update:model-value="update('description', $event)"
85
91
  />
@@ -89,19 +95,17 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
89
95
  <!-- SERP preview -->
90
96
  <div class="rounded border border-gray-200 bg-gray-50 p-4">
91
97
  <p class="mb-1 text-xs font-medium uppercase tracking-wide text-gray-400">
92
- Google preview
98
+ {{ t("mapo.seoPreview.googlePreview") }}
93
99
  </p>
94
100
  <div class="max-w-lg">
95
101
  <p class="truncate text-sm text-green-700">
96
102
  {{ displayUrl }}
97
103
  </p>
98
104
  <p class="truncate text-lg text-blue-700 underline">
99
- {{ value.title || "Page title" }}
105
+ {{ value.title || t("mapo.seoPreview.defaultTitle") }}
100
106
  </p>
101
107
  <p class="mt-1 line-clamp-2 text-sm text-gray-600">
102
- {{
103
- value.description || "Page description that will appear in Google search results."
104
- }}
108
+ {{ value.description || t("mapo.seoPreview.defaultDescription") }}
105
109
  </p>
106
110
  </div>
107
111
  </div>
@@ -1 +1 @@
1
- {"version":3,"file":"MapoSeoPreview.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoSeoPreview.vue"],"names":[],"mappings":"AA8IA,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;AAED,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;AAiSF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
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"}
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { ref, watch, onBeforeUnmount, resolveComponent } from "vue";
3
+ import { useI18n } from "vue-i18n";
3
4
  import { useEditor, EditorContent } from "@tiptap/vue-3";
4
5
  import StarterKit from "@tiptap/starter-kit";
5
6
  import Link from "@tiptap/extension-link";
@@ -13,6 +14,7 @@ const props = defineProps({
13
14
  disabled: { type: Boolean, required: false }
14
15
  });
15
16
  const emit = defineEmits(["update:modelValue"]);
17
+ const { t } = useI18n();
16
18
  const extraExtensions = props.descriptor.attrs?.extensions ?? [];
17
19
  const ALLOWED_LINK_PROTOCOLS = ["http", "https", "mailto", "tel"];
18
20
  function isSafeUrl(url) {
@@ -206,7 +208,9 @@ function onImageConfirm(selection) {
206
208
 
207
209
  <!-- Insert Image via Media Manager (available when @mapomodule/uikit is installed) -->
208
210
  <UTooltip
209
- :text="hasMediaManager ? 'Insert image' : 'Requires @mapomodule/uikit'"
211
+ :text="
212
+ hasMediaManager ? t('mapo.wygEditor.insertImage') : t('mapo.wygEditor.requiresUikit')
213
+ "
210
214
  :delay-open="300"
211
215
  >
212
216
  <UButton
@@ -1 +1 @@
1
- {"version":3,"file":"MapoWygEditor.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoWygEditor.vue"],"names":[],"mappings":"AA6SA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA+oBF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"MapoWygEditor.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoWygEditor.vue"],"names":[],"mappings":"AAqTA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA0pBF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useMapoForm.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useMapoForm.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useMapoForm.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useMapoForm.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,7 @@ import {
16
16
  import { resolveFieldAccessor } from "../types/index.js";
17
17
  import { useCurrentLang } from "./useCurrentLang.js";
18
18
  import { useSnackStore } from "@mapomodule/store/runtime/stores/snack";
19
+ import { useMapoT } from "@mapomodule/i18n/runtime/composables/useMapoT";
19
20
  import { useAuthStore } from "@mapomodule/store/runtime/stores/auth";
20
21
  function safeClone(v) {
21
22
  const raw = toRaw(v);
@@ -48,6 +49,7 @@ export function useMapoForm(options) {
48
49
  } = options;
49
50
  const currentLang = useCurrentLang(currentLangProp);
50
51
  const snack = useSnackStore();
52
+ const t = useMapoT();
51
53
  const authStore = useAuthStore();
52
54
  const backup = ref(safeClone(model.value));
53
55
  const clientErrors = ref({});
@@ -198,7 +200,7 @@ export function useMapoForm(options) {
198
200
  function checkRequired(descriptor, val) {
199
201
  if (!descriptor.required) return null;
200
202
  if (val === null || val === void 0 || val === "")
201
- return "This field is required.";
203
+ return t("mapo.form.required");
202
204
  return null;
203
205
  }
204
206
  function getClientError(descriptor) {
@@ -236,7 +238,7 @@ export function useMapoForm(options) {
236
238
  submitted.value = true;
237
239
  const { valid } = validateClient();
238
240
  if (!valid) {
239
- snack.show("Please fix the errors before saving.", "error");
241
+ snack.show(t("mapo.fixErrors"), "error");
240
242
  return;
241
243
  }
242
244
  isLoading.value = true;
@@ -1 +1 @@
1
- {"version":3,"file":"useUnsavedChangesGuard.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useUnsavedChangesGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAGhD,uEAAuE;AACvE,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,EACrB,OAAO,GAAE,0BAA+B,QA8BzC"}
1
+ {"version":3,"file":"useUnsavedChangesGuard.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useUnsavedChangesGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAIhD,uEAAuE;AACvE,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,EACrB,OAAO,GAAE,0BAA+B,QA6BzC"}
@@ -1,7 +1,8 @@
1
1
  import { onBeforeUnmount } from "vue";
2
- import { onBeforeRouteLeave } from "vue-router";
2
+ import { onBeforeRouteLeave } from "#app";
3
+ import { useMapoT } from "@mapomodule/i18n/runtime/composables/useMapoT";
3
4
  export function useUnsavedChangesGuard(isDirty, options = {}) {
4
- const message = options.message ?? "You have unsaved changes. Do you really want to leave?";
5
+ const message = options.message ?? useMapoT()("mapo.confirmLeaveChanges");
5
6
  const canUseWindow = typeof window !== "undefined";
6
7
  const isEnabled = () => {
7
8
  if (typeof options.enabled === "function") return options.enabled();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mapomodule/form",
3
- "version": "2.0.0-beta.5",
3
+ "version": "2.0.0-beta.8",
4
4
  "description": "Mapo declarative typed form engine and field components",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,7 +40,6 @@
40
40
  "scripts": {
41
41
  "build": "nuxt-module-build build && vue-tsc -p tsconfig.build-types.json && node ../../../scripts/clean-empty-vue-dts.mjs dist",
42
42
  "dev": "nuxt-module-build build --stub --watch",
43
- "prepack": "pnpm build",
44
43
  "dev:prepare": "nuxt-module-build build --stub",
45
44
  "typecheck": "vue-tsc --noEmit",
46
45
  "lint": "eslint src",
@@ -48,15 +47,17 @@
48
47
  },
49
48
  "dependencies": {
50
49
  "@internationalized/date": "^3.7.0",
51
- "@mapomodule/store": "2.0.0-beta.4",
52
- "@mapomodule/utils": "2.0.0-beta.2",
50
+ "@mapomodule/i18n": "2.0.0-beta.2",
51
+ "@mapomodule/store": "2.0.0-beta.5",
52
+ "@mapomodule/utils": "2.0.0-beta.3",
53
53
  "@tiptap/extension-image": "^2.11.5",
54
54
  "@tiptap/extension-link": "^2.11.5",
55
55
  "@tiptap/extension-underline": "^2.11.5",
56
56
  "@tiptap/starter-kit": "^2.11.5",
57
57
  "@tiptap/vue-3": "^2.11.5",
58
58
  "leaflet": "^1.9.4",
59
- "vue-draggable-plus": "^0.6.0"
59
+ "vue-draggable-plus": "^0.6.0",
60
+ "vue-i18n": "^11.4.0"
60
61
  },
61
62
  "optionalDependencies": {
62
63
  "@types/leaflet": "^1.9.18"
@@ -69,10 +70,10 @@
69
70
  "@nuxt/kit": "4.4.2",
70
71
  "@nuxt/module-builder": "^1.0.2",
71
72
  "@nuxt/schema": "4.4.2",
72
- "nuxt": "^4.4.2",
73
+ "nuxt": "^4.5.1",
73
74
  "vitest": "^3.2.0",
74
- "vue": "^3.5.32",
75
- "vue-router": "^5.0.4",
75
+ "vue": "^3.5.40",
76
+ "vue-router": "^5.2.0",
76
77
  "vue-tsc": "^3.3.4",
77
78
  "zod": "^3.23.8"
78
79
  }
@@ -10,6 +10,11 @@ vi.mock("@mapomodule/store/runtime/stores/auth", () => ({
10
10
  vi.mock("@mapomodule/store/runtime/stores/snack", () => ({
11
11
  useSnackStore: () => ({ show: vi.fn() }),
12
12
  }));
13
+ // Pulls in `nuxt/app`, which cannot resolve outside a Nuxt build. Assertions
14
+ // below match on message keys, so echoing the key back is the useful stub.
15
+ vi.mock("@mapomodule/i18n/runtime/composables/useMapoT", () => ({
16
+ useMapoT: () => (key: string) => key,
17
+ }));
13
18
 
14
19
  // provide/inject called outside setup() emits a warning; silence it in tests.
15
20
  beforeEach(() => {
package/src/module.ts CHANGED
@@ -6,6 +6,8 @@ import {
6
6
  createResolver,
7
7
  addImports,
8
8
  extendPages,
9
+ hasNuxtModule,
10
+ installModule,
9
11
  } from "@nuxt/kit";
10
12
  import type { NuxtModule } from "@nuxt/schema";
11
13
  import type { PartialFieldRegistry } from "./runtime/types/index.js";
@@ -40,6 +42,13 @@ export default defineNuxtModule<MapoFormOptions>({
40
42
  /@mapomodule[\\/]form[\\/]/,
41
43
  );
42
44
 
45
+ // Form components render user-facing strings through vue-i18n
46
+ // (`useI18n().t("mapo...")`). @mapomodule/i18n provides the catalogs and
47
+ // installs @nuxtjs/i18n unless the app manages it itself.
48
+ if (!hasNuxtModule("@mapomodule/i18n")) {
49
+ await installModule(await resolver.resolvePath("@mapomodule/i18n"));
50
+ }
51
+
43
52
  // Only serializable values can go into runtimeConfig.public.
44
53
  // `mapping` and `accessor` contain functions → not SSR-serializable.
45
54
  // To extend mapping/accessor, use an app plugin that accesses $mapoFormRegistry:
@@ -11,9 +11,11 @@
11
11
  */
12
12
  import { computed, ref, onMounted, onUnmounted } from "vue";
13
13
  import { watch } from "vue";
14
+ import { useI18n } from "vue-i18n";
14
15
  import { useFocusMode } from "../composables/useFocusMode.js";
15
16
  import MapoForm from "./MapoForm.vue";
16
17
 
18
+ const { t } = useI18n();
17
19
  const { focusTarget, exit } = useFocusMode();
18
20
 
19
21
  const isOpen = computed(() => focusTarget.value !== null);
@@ -88,7 +90,7 @@ function onUpdate(val: Record<string, unknown>) {
88
90
  variant="ghost"
89
91
  color="neutral"
90
92
  icon="i-lucide-x"
91
- label="Close"
93
+ :label="t('mapo.close')"
92
94
  size="sm"
93
95
  @click="exit"
94
96
  />
@@ -1,10 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed } from "vue";
3
+ import { useI18n } from "vue-i18n";
3
4
  import type { TabEntry } from "../types/layout.js";
4
5
  import { injectMapoForm } from "../composables/useMapoForm.js";
5
6
  import MapoFormGroup from "./MapoFormGroup.vue";
6
7
  import MapoFormFlatSection from "./MapoFormFlatSection.vue";
7
8
 
9
+ const { t } = useI18n();
10
+
8
11
  const props = defineProps<{ tabs: TabEntry[] }>();
9
12
 
10
13
  defineSlots<Record<string, unknown>>();
@@ -54,7 +57,7 @@ const tabsWithErrors = computed(() => {
54
57
  <span
55
58
  v-if="tabsWithErrors.has(tab.name)"
56
59
  class="inline-block h-1.5 w-1.5 rounded-full bg-red-500"
57
- aria-label="This tab contains errors"
60
+ :aria-label="t('mapo.form.tabErrors')"
58
61
  />
59
62
  </button>
60
63
  <!-- Extra content appended to the tab bar (actions, badges, …). -->
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref } from "vue";
3
+ import { useI18n } from "vue-i18n";
3
4
  import { parseDate, CalendarDate } from "@internationalized/date";
4
5
  import type { DateDescriptor } from "../../types/index.js";
5
6
 
@@ -14,6 +15,7 @@ const props = defineProps<{
14
15
 
15
16
  const emit = defineEmits<{ "update:modelValue": [value: string | null] }>();
16
17
 
18
+ const { t } = useI18n();
17
19
  const open = ref(false);
18
20
 
19
21
  /** Parsed calendar value derived from the current ISO-like model string. */
@@ -30,7 +32,7 @@ const calendarValue = computed<CalendarDate | undefined>(() => {
30
32
 
31
33
  const displayLabel = computed(() => {
32
34
  const v = calendarValue.value;
33
- if (!v) return "Select date";
35
+ if (!v) return t("mapo.dateField.selectDate");
34
36
  return `${String(v.day).padStart(2, "0")}/${String(v.month).padStart(2, "0")}/${v.year}`;
35
37
  });
36
38
 
@@ -6,6 +6,7 @@ import {
6
6
  today,
7
7
  getLocalTimeZone,
8
8
  } from "@internationalized/date";
9
+ import { useI18n } from "vue-i18n";
9
10
  import type { DateDescriptor } from "../../types/index.js";
10
11
 
11
12
  /** Datetime field with configurable naive/UTC serialisation semantics. */
@@ -19,6 +20,7 @@ const props = defineProps<{
19
20
 
20
21
  const emit = defineEmits<{ "update:modelValue": [value: string | null] }>();
21
22
 
23
+ const { t } = useI18n();
22
24
  const open = ref(false);
23
25
 
24
26
  // Timezone strategy configurable through `descriptor.attrs.tz`:
@@ -72,7 +74,7 @@ function emitFromCalendar(v: CalendarDateTime) {
72
74
  // Displayed label in the trigger button
73
75
  const displayLabel = computed(() => {
74
76
  const v = calendarValue.value;
75
- if (!v) return "Select date and time";
77
+ if (!v) return t("mapo.dateField.selectDateTime");
76
78
  const date = `${String(v.day).padStart(2, "0")}/${String(v.month).padStart(2, "0")}/${v.year}`;
77
79
  const time = `${String(v.hour).padStart(2, "0")}:${String(v.minute).padStart(2, "0")}`;
78
80
  return `${date} ${time}`;
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed } from "vue";
3
+ import { useI18n } from "vue-i18n";
3
4
  import type {
4
5
  EnhancedMediaDescriptor,
5
6
  EnhancedMediaValue,
@@ -19,6 +20,8 @@ const emit = defineEmits<{
19
20
  "update:modelValue": [value: EnhancedMediaValue | null];
20
21
  }>();
21
22
 
23
+ const { t } = useI18n();
24
+
22
25
  const dialogOpen = ref(false);
23
26
 
24
27
  // Picker dialog comes from @mapomodule/uikit, resolved at runtime.
@@ -83,7 +86,7 @@ function clear() {
83
86
  <MapoMediaPreview v-if="current" :media="current" size="md" />
84
87
  <div v-else class="flex flex-col items-center gap-1 text-dimmed">
85
88
  <UIcon name="i-lucide-image-plus" class="size-7" />
86
- <span class="text-[10px]">Select</span>
89
+ <span class="text-[10px]">{{ t("mapo.select") }}</span>
87
90
  </div>
88
91
  </div>
89
92
  </div>
@@ -101,7 +104,7 @@ function clear() {
101
104
  size="xs"
102
105
  variant="ghost"
103
106
  color="neutral"
104
- title="Change"
107
+ :title="t('mapo.change')"
105
108
  @click="dialogOpen = true"
106
109
  />
107
110
  <UButton
@@ -110,7 +113,7 @@ function clear() {
110
113
  size="xs"
111
114
  variant="ghost"
112
115
  color="neutral"
113
- title="Remove"
116
+ :title="t('mapo.remove')"
114
117
  @click="clear"
115
118
  />
116
119
  </div>
@@ -119,13 +122,13 @@ function clear() {
119
122
  <UInput
120
123
  v-model="altValue"
121
124
  size="sm"
122
- placeholder="Alt text"
125
+ :placeholder="t('mapo.enhancedMediaField.altText')"
123
126
  :disabled="readonly || disabled || !current"
124
127
  />
125
128
  <UInput
126
129
  v-model="captionValue"
127
130
  size="sm"
128
- placeholder="Caption"
131
+ :placeholder="t('mapo.enhancedMediaField.caption')"
129
132
  :disabled="readonly || disabled || !current"
130
133
  />
131
134
  </div>
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed } from "vue";
3
+ import { useI18n } from "vue-i18n";
3
4
  import type { FileDescriptor } from "../../types/index.js";
4
5
 
5
6
  const props = defineProps<{
@@ -12,6 +13,7 @@ const props = defineProps<{
12
13
 
13
14
  const emit = defineEmits<{ "update:modelValue": [value: File | null] }>();
14
15
 
16
+ const { t } = useI18n();
15
17
  const inputRef = ref<HTMLInputElement | null>(null);
16
18
 
17
19
  const currentFileName = computed<string | null>(() => {
@@ -78,7 +80,7 @@ const accept = computed(
78
80
  variant="ghost"
79
81
  color="neutral"
80
82
  icon="i-lucide-x"
81
- aria-label="Remove file"
83
+ :aria-label="t('mapo.fileField.removeFile')"
82
84
  @click="clear"
83
85
  />
84
86
  </div>
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
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
  import type { FksDescriptor } from "../../types/index.js";
@@ -14,6 +15,8 @@ const props = defineProps<{
14
15
 
15
16
  const emit = defineEmits<{ "update:modelValue": [value: unknown] }>();
16
17
 
18
+ const { t } = useI18n();
19
+
17
20
  const attrs = computed(() => props.descriptor.attrs);
18
21
  const isMultiple = computed(
19
22
  () => props.descriptor.type === "m2m" || !!attrs.value.multiple,
@@ -70,7 +73,7 @@ const internalValue = computed({
70
73
 
71
74
  // Placeholder
72
75
  const placeholder = computed(() =>
73
- isMultiple.value ? "Search and select..." : "Search...",
76
+ isMultiple.value ? t("mapo.fksField.searchSelect") : t("mapo.search"),
74
77
  );
75
78
 
76
79
  // Chip text shown in the trigger.
@@ -144,7 +147,9 @@ function getItemKey(item: unknown): unknown {
144
147
  <!-- Empty state -->
145
148
  <template #empty>
146
149
  <span class="text-sm text-gray-500">
147
- {{ searchTerm ? "No results found" : "No items available" }}
150
+ {{
151
+ searchTerm ? t("mapo.fksField.noResults") : t("mapo.fksField.noItems")
152
+ }}
148
153
  </span>
149
154
  </template>
150
155
  </USelectMenu>
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, defineAsyncComponent } from "vue";
3
+ import { useI18n } from "vue-i18n";
3
4
  import type { MapDescriptor } from "../../types/index.js";
4
5
 
5
6
  // Leaflet is loaded only on the client because it is not SSR-safe.
@@ -7,6 +8,8 @@ const LeafletMap = defineAsyncComponent(
7
8
  () => import("./MapoMapFieldClient.vue"),
8
9
  );
9
10
 
11
+ const { t } = useI18n();
12
+
10
13
  /** Geographic coordinates stored by the map field. */
11
14
  interface LatLng {
12
15
  lat: number;
@@ -63,7 +66,7 @@ function clear() {
63
66
  <div class="flex gap-2">
64
67
  <UInput
65
68
  v-model="latInput"
66
- placeholder="Latitude"
69
+ :placeholder="t('mapo.latitude')"
67
70
  class="flex-1"
68
71
  :readonly="readonly"
69
72
  :disabled="disabled"
@@ -71,7 +74,7 @@ function clear() {
71
74
  />
72
75
  <UInput
73
76
  v-model="lngInput"
74
- placeholder="Longitude"
77
+ :placeholder="t('mapo.longitude')"
75
78
  class="flex-1"
76
79
  :readonly="readonly"
77
80
  :disabled="disabled"
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed } from "vue";
3
+ import { useI18n } from "vue-i18n";
3
4
  import type { MediaDescriptor, MediaItem } from "../../types/index.js";
4
5
  import { useMediaManager } from "../../composables/useMediaManager.js";
5
6
 
@@ -15,6 +16,8 @@ const emit = defineEmits<{
15
16
  "update:modelValue": [value: MediaItem | null];
16
17
  }>();
17
18
 
19
+ const { t } = useI18n();
20
+
18
21
  const dialogOpen = ref(false);
19
22
 
20
23
  // Picker dialog comes from @mapomodule/uikit, resolved at runtime.
@@ -79,7 +82,7 @@ function clear() {
79
82
  :block="!current"
80
83
  @click="dialogOpen = true"
81
84
  >
82
- {{ current ? "Change" : "Select media" }}
85
+ {{ current ? t("mapo.change") : t("mapo.mediaField.selectMedia") }}
83
86
  </UButton>
84
87
 
85
88
  <!-- Dialog -->