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

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,157 @@
1
+ <script setup>
2
+ import { computed, ref } from "vue";
3
+ import {
4
+ parseDateTime,
5
+ CalendarDateTime,
6
+ today,
7
+ getLocalTimeZone
8
+ } from "@internationalized/date";
9
+ const props = defineProps({
10
+ modelValue: { type: null, required: true },
11
+ descriptor: { type: Object, required: true },
12
+ errors: { type: Array, required: false },
13
+ readonly: { type: Boolean, required: false },
14
+ disabled: { type: Boolean, required: false }
15
+ });
16
+ const emit = defineEmits(["update:modelValue"]);
17
+ const open = ref(false);
18
+ const tzMode = computed(() => props.descriptor.attrs?.tz ?? "naive");
19
+ const calendarValue = computed(() => {
20
+ const v = props.modelValue;
21
+ if (!v) return void 0;
22
+ try {
23
+ if (tzMode.value === "utc") {
24
+ const d = new Date(v);
25
+ if (Number.isNaN(d.getTime())) return void 0;
26
+ return new CalendarDateTime(
27
+ d.getFullYear(),
28
+ d.getMonth() + 1,
29
+ d.getDate(),
30
+ d.getHours(),
31
+ d.getMinutes(),
32
+ d.getSeconds()
33
+ );
34
+ }
35
+ const clean = v.replace(/Z$/, "").replace(/[+-]\d{2}:\d{2}$/, "").split(".")[0];
36
+ return parseDateTime(clean);
37
+ } catch {
38
+ return void 0;
39
+ }
40
+ });
41
+ function emitFromCalendar(v) {
42
+ if (tzMode.value === "utc") {
43
+ const d = new Date(v.year, v.month - 1, v.day, v.hour, v.minute, 0, 0);
44
+ emit("update:modelValue", d.toISOString());
45
+ return;
46
+ }
47
+ emit("update:modelValue", v.toString());
48
+ }
49
+ const displayLabel = computed(() => {
50
+ const v = calendarValue.value;
51
+ if (!v) return "Select date and time";
52
+ const date = `${String(v.day).padStart(2, "0")}/${String(v.month).padStart(2, "0")}/${v.year}`;
53
+ const time = `${String(v.hour).padStart(2, "0")}:${String(v.minute).padStart(2, "0")}`;
54
+ return `${date} ${time}`;
55
+ });
56
+ const timeHour = computed(() => calendarValue.value?.hour ?? 0);
57
+ const timeMinute = computed(() => calendarValue.value?.minute ?? 0);
58
+ function onDateSelect(date) {
59
+ const d = date;
60
+ if (!d) {
61
+ emit("update:modelValue", null);
62
+ return;
63
+ }
64
+ const h = calendarValue.value?.hour ?? 0;
65
+ const m = calendarValue.value?.minute ?? 0;
66
+ const merged = new CalendarDateTime(d.year, d.month, d.day, h, m);
67
+ emitFromCalendar(merged);
68
+ }
69
+ function onTimeChange(part, val) {
70
+ const n = Math.max(
71
+ 0,
72
+ Math.min(part === "hour" ? 23 : 59, parseInt(val) || 0)
73
+ );
74
+ const base = calendarValue.value ?? (() => {
75
+ const t = today(getLocalTimeZone());
76
+ return new CalendarDateTime(t.year, t.month, t.day, 0, 0);
77
+ })();
78
+ const merged = part === "hour" ? new CalendarDateTime(base.year, base.month, base.day, n, base.minute) : new CalendarDateTime(base.year, base.month, base.day, base.hour, n);
79
+ emitFromCalendar(merged);
80
+ }
81
+ function clear() {
82
+ emit("update:modelValue", null);
83
+ open.value = false;
84
+ }
85
+ </script>
86
+
87
+ <template>
88
+ <UPopover v-model:open="open" :disabled="readonly || disabled">
89
+ <UButton
90
+ variant="outline"
91
+ color="neutral"
92
+ icon="i-lucide-calendar"
93
+ trailing
94
+ class="w-full justify-between font-normal"
95
+ :class="{
96
+ 'opacity-50 cursor-not-allowed': readonly || disabled,
97
+ 'ring-2 ring-red-500': errors?.length
98
+ }"
99
+ :disabled="readonly || disabled"
100
+ >
101
+ <span
102
+ :class="
103
+ calendarValue ? 'text-gray-900 dark:text-white' : 'text-gray-400'
104
+ "
105
+ >
106
+ {{ displayLabel }}
107
+ </span>
108
+ </UButton>
109
+
110
+ <template #content>
111
+ <div class="p-3 space-y-3">
112
+ <UCalendar
113
+ :model-value="calendarValue"
114
+ @update:model-value="onDateSelect"
115
+ />
116
+
117
+ <!-- Time picker -->
118
+ <div
119
+ class="flex items-center gap-2 border-t border-gray-200 dark:border-gray-700 pt-3"
120
+ >
121
+ <UIcon name="i-lucide-clock" class="text-gray-400 shrink-0" />
122
+ <div class="flex items-center gap-1">
123
+ <UInput
124
+ type="number"
125
+ :model-value="timeHour"
126
+ min="0"
127
+ max="23"
128
+ class="w-14 text-center"
129
+ @update:model-value="
130
+ (v) => onTimeChange('hour', String(v))
131
+ "
132
+ />
133
+ <span class="text-gray-500 font-medium">:</span>
134
+ <UInput
135
+ type="number"
136
+ :model-value="timeMinute"
137
+ min="0"
138
+ max="59"
139
+ class="w-14 text-center"
140
+ @update:model-value="
141
+ (v) => onTimeChange('minute', String(v))
142
+ "
143
+ />
144
+ </div>
145
+ <UButton
146
+ variant="ghost"
147
+ color="neutral"
148
+ size="xs"
149
+ icon="i-lucide-x"
150
+ class="ml-auto"
151
+ @click="clear"
152
+ />
153
+ </div>
154
+ </div>
155
+ </template>
156
+ </UPopover>
157
+ </template>
@@ -0,0 +1,17 @@
1
+ import type { DateDescriptor } from "../../types/index.js";
2
+ /** Datetime field with configurable naive/UTC serialisation semantics. */
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=MapoDateTimeField.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapoDateTimeField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoDateTimeField.vue"],"names":[],"mappings":"AA0MA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,0EAA0E;AAC1E,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA+VF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -0,0 +1,97 @@
1
+ <script setup>
2
+ import { ref, computed } from "vue";
3
+ const props = defineProps({
4
+ modelValue: { type: null, required: true },
5
+ descriptor: { type: Object, required: true },
6
+ errors: { type: Array, required: false },
7
+ readonly: { type: Boolean, required: false },
8
+ disabled: { type: Boolean, required: false }
9
+ });
10
+ const emit = defineEmits(["update:modelValue"]);
11
+ const inputRef = ref(null);
12
+ const currentFileName = computed(() => {
13
+ if (props.modelValue instanceof File) return props.modelValue.name;
14
+ if (typeof props.modelValue === "string" && props.modelValue)
15
+ return props.modelValue.split("/").pop() ?? props.modelValue;
16
+ return null;
17
+ });
18
+ const previewUrl = computed(() => {
19
+ if (props.modelValue instanceof File) {
20
+ if (props.modelValue.type.startsWith("image/"))
21
+ return URL.createObjectURL(props.modelValue);
22
+ }
23
+ if (typeof props.modelValue === "string" && props.modelValue) {
24
+ const ext = props.modelValue.split(".").pop()?.toLowerCase() ?? "";
25
+ if (["jpg", "jpeg", "png", "gif", "webp", "svg", "avif"].includes(ext))
26
+ return props.modelValue;
27
+ }
28
+ return null;
29
+ });
30
+ function onFileChange(e) {
31
+ const file = e.target.files?.[0] ?? null;
32
+ emit("update:modelValue", file);
33
+ }
34
+ function clear() {
35
+ emit("update:modelValue", null);
36
+ if (inputRef.value) inputRef.value.value = "";
37
+ }
38
+ const hasError = computed(() => (props.errors?.length ?? 0) > 0);
39
+ const accept = computed(
40
+ () => props.descriptor.attrs?.accept ?? void 0
41
+ );
42
+ </script>
43
+
44
+ <template>
45
+ <div class="mapo-file-field space-y-2">
46
+ <!-- Current file preview -->
47
+ <div
48
+ v-if="currentFileName"
49
+ class="flex items-center gap-2 rounded border px-3 py-2 text-sm"
50
+ :class="
51
+ hasError ? 'border-red-300 bg-red-50' : 'border-gray-200 bg-gray-50'
52
+ "
53
+ >
54
+ <img
55
+ v-if="previewUrl"
56
+ :src="previewUrl"
57
+ class="h-8 w-8 rounded object-cover shrink-0"
58
+ alt=""
59
+ />
60
+ <UIcon
61
+ v-else
62
+ name="i-lucide-file"
63
+ class="size-4 shrink-0 text-gray-400"
64
+ />
65
+ <span class="flex-1 truncate text-gray-700">{{ currentFileName }}</span>
66
+ <UButton
67
+ v-if="!readonly && !disabled"
68
+ size="xs"
69
+ variant="ghost"
70
+ color="neutral"
71
+ icon="i-lucide-x"
72
+ aria-label="Remove file"
73
+ @click="clear"
74
+ />
75
+ </div>
76
+
77
+ <!-- File input -->
78
+ <UInput
79
+ v-if="!readonly && !disabled"
80
+ ref="inputRef"
81
+ type="file"
82
+ :accept="accept"
83
+ :color="hasError ? 'error' : void 0"
84
+ :aria-invalid="hasError ? 'true' : void 0"
85
+ @change="onFileChange"
86
+ />
87
+
88
+ <p
89
+ v-for="err in errors"
90
+ :key="err"
91
+ class="text-sm text-red-500"
92
+ role="alert"
93
+ >
94
+ {{ err }}
95
+ </p>
96
+ </div>
97
+ </template>
@@ -0,0 +1,16 @@
1
+ import type { FileDescriptor } from "../../types/index.js";
2
+ type __VLS_Props = {
3
+ modelValue: unknown;
4
+ descriptor: FileDescriptor;
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: File | null) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ "onUpdate:modelValue"?: ((value: File | null) => any) | undefined;
13
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const _default: typeof __VLS_export;
15
+ export default _default;
16
+ //# sourceMappingURL=MapoFileField.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapoFileField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoFileField.vue"],"names":[],"mappings":"AA8GA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA2MF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -0,0 +1,118 @@
1
+ <script setup>
2
+ import { ref, computed, watch, onMounted } from "vue";
3
+ import { useNuxtApp } from "#app";
4
+ import { debounce } from "@mapomodule/utils";
5
+ const props = defineProps({
6
+ modelValue: { type: null, required: true },
7
+ descriptor: { type: Object, required: true },
8
+ errors: { type: Array, required: false },
9
+ readonly: { type: Boolean, required: false },
10
+ disabled: { type: Boolean, required: false }
11
+ });
12
+ const emit = defineEmits(["update:modelValue"]);
13
+ const attrs = computed(() => props.descriptor.attrs);
14
+ const isMultiple = computed(
15
+ () => props.descriptor.type === "m2m" || !!attrs.value.multiple
16
+ );
17
+ const itemText = computed(() => attrs.value.itemText ?? "name");
18
+ const itemValue = computed(() => attrs.value.itemValue ?? "id");
19
+ const items = ref([]);
20
+ const isLoading = ref(false);
21
+ const searchTerm = ref("");
22
+ const $mapoFetch = useNuxtApp().$mapoFetch;
23
+ async function fetchItems(query) {
24
+ isLoading.value = true;
25
+ try {
26
+ const rawEndpoint = attrs.value.endpoint;
27
+ const endpoint = rawEndpoint.startsWith("/") || rawEndpoint.startsWith("http") ? rawEndpoint : `/${rawEndpoint}`;
28
+ const url = query ? `${endpoint}${endpoint.includes("?") ? "&" : "?"}search=${encodeURIComponent(query)}` : endpoint;
29
+ const data = await ($mapoFetch ?? $fetch)(url);
30
+ const raw = Array.isArray(data) ? data : data.results ?? [];
31
+ items.value = raw;
32
+ } catch {
33
+ items.value = [];
34
+ } finally {
35
+ isLoading.value = false;
36
+ }
37
+ }
38
+ const debouncedFetch = debounce(fetchItems, 300);
39
+ onMounted(() => fetchItems(""));
40
+ watch(searchTerm, (q) => debouncedFetch(q));
41
+ const internalValue = computed({
42
+ get: () => props.modelValue,
43
+ set: (val) => emit("update:modelValue", val)
44
+ });
45
+ const placeholder = computed(
46
+ () => isMultiple.value ? "Search and select..." : "Search..."
47
+ );
48
+ function getLabel(item) {
49
+ if (item !== null && item !== void 0 && typeof item !== "object") {
50
+ const found = items.value.find((i) => i[itemValue.value] === item);
51
+ return found ? String(found[itemText.value] ?? item) : String(item);
52
+ }
53
+ if (!item || typeof item !== "object") return "";
54
+ return String(item[itemText.value] ?? "");
55
+ }
56
+ function getItemKey(item) {
57
+ if (item !== null && item !== void 0 && typeof item !== "object")
58
+ return item;
59
+ return item?.[itemValue.value];
60
+ }
61
+ </script>
62
+
63
+ <template>
64
+ <USelectMenu
65
+ v-model="internalValue"
66
+ v-model:search-term="searchTerm"
67
+ :items="items"
68
+ :label-key="itemText"
69
+ :value-key="attrs.returnObject === false ? itemValue : void 0"
70
+ :by="attrs.returnObject !== false ? itemValue : void 0"
71
+ :multiple="isMultiple"
72
+ :loading="isLoading"
73
+ :ignore-filter="true"
74
+ :placeholder="placeholder"
75
+ :readonly="readonly"
76
+ :disabled="disabled"
77
+ :highlight="!!errors?.length"
78
+ clear
79
+ >
80
+ <!-- Chips for multi-selection -->
81
+ <template
82
+ v-if="isMultiple && Array.isArray(internalValue) && internalValue.length"
83
+ #default
84
+ >
85
+ <div class="flex flex-wrap gap-1 py-0.5">
86
+ <UBadge
87
+ v-for="item in internalValue"
88
+ :key="String(getItemKey(item))"
89
+ variant="soft"
90
+ color="primary"
91
+ class="cursor-default"
92
+ >
93
+ {{ getLabel(item) }}
94
+ <UButton
95
+ v-if="!readonly && !disabled"
96
+ size="xs"
97
+ variant="ghost"
98
+ color="primary"
99
+ icon="i-lucide-x"
100
+ class="-mr-1 ml-0.5 h-3.5 w-3.5"
101
+ @click.stop="
102
+ internalValue = internalValue.filter(
103
+ (i) => getItemKey(i) !== getItemKey(item)
104
+ )
105
+ "
106
+ />
107
+ </UBadge>
108
+ </div>
109
+ </template>
110
+
111
+ <!-- Empty state -->
112
+ <template #empty>
113
+ <span class="text-sm text-gray-500">
114
+ {{ searchTerm ? "No results found" : "No items available" }}
115
+ </span>
116
+ </template>
117
+ </USelectMenu>
118
+ </template>
@@ -0,0 +1,16 @@
1
+ import type { FksDescriptor } from "../../types/index.js";
2
+ type __VLS_Props = {
3
+ modelValue: unknown;
4
+ descriptor: FksDescriptor;
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=MapoFksField.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapoFksField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoFksField.vue"],"names":[],"mappings":"AA6JA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,aAAa,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA4PF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -0,0 +1,97 @@
1
+ <script setup>
2
+ import { ref, computed, defineAsyncComponent } from "vue";
3
+ const LeafletMap = defineAsyncComponent(
4
+ () => import("./MapoMapFieldClient.vue")
5
+ );
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 attrs = computed(() => props.descriptor.attrs ?? {});
15
+ const value = computed(() => {
16
+ const v = props.modelValue;
17
+ if (!v || typeof v !== "object") return null;
18
+ const { lat, lng } = v;
19
+ return typeof lat === "number" && typeof lng === "number" ? { lat, lng } : null;
20
+ });
21
+ const latInput = ref(value.value?.lat?.toString() ?? "");
22
+ const lngInput = ref(value.value?.lng?.toString() ?? "");
23
+ function onMapClick(coords) {
24
+ latInput.value = coords.lat.toFixed(6);
25
+ lngInput.value = coords.lng.toFixed(6);
26
+ emit("update:modelValue", coords);
27
+ }
28
+ function onManualInput() {
29
+ const lat = parseFloat(latInput.value);
30
+ const lng = parseFloat(lngInput.value);
31
+ if (!isNaN(lat) && !isNaN(lng)) emit("update:modelValue", { lat, lng });
32
+ }
33
+ function clear() {
34
+ latInput.value = "";
35
+ lngInput.value = "";
36
+ emit("update:modelValue", null);
37
+ }
38
+ </script>
39
+
40
+ <template>
41
+ <div class="mapo-map-field space-y-2">
42
+ <!-- Manual coordinate inputs -->
43
+ <div class="flex gap-2">
44
+ <UInput
45
+ v-model="latInput"
46
+ placeholder="Latitude"
47
+ class="flex-1"
48
+ :readonly="readonly"
49
+ :disabled="disabled"
50
+ @blur="onManualInput"
51
+ />
52
+ <UInput
53
+ v-model="lngInput"
54
+ placeholder="Longitude"
55
+ class="flex-1"
56
+ :readonly="readonly"
57
+ :disabled="disabled"
58
+ @blur="onManualInput"
59
+ />
60
+ <UButton
61
+ v-if="value && !readonly && !disabled"
62
+ variant="ghost"
63
+ color="neutral"
64
+ icon="i-lucide-x"
65
+ @click="clear"
66
+ />
67
+ </div>
68
+
69
+ <!-- Leaflet map (client only) -->
70
+ <ClientOnly>
71
+ <LeafletMap
72
+ :value="value"
73
+ :default-lat="attrs.defaultLat ?? 41.9"
74
+ :default-lng="attrs.defaultLng ?? 12.5"
75
+ :zoom="attrs.zoom ?? 6"
76
+ :readonly="readonly ?? false"
77
+ @click="onMapClick"
78
+ />
79
+ <template #fallback>
80
+ <div
81
+ class="flex h-48 items-center justify-center rounded border border-gray-200 bg-gray-50 text-sm text-gray-400"
82
+ >
83
+ Loading map...
84
+ </div>
85
+ </template>
86
+ </ClientOnly>
87
+
88
+ <p
89
+ v-for="err in errors"
90
+ :key="err"
91
+ class="text-sm text-red-500"
92
+ role="alert"
93
+ >
94
+ {{ err }}
95
+ </p>
96
+ </div>
97
+ </template>
@@ -0,0 +1,22 @@
1
+ import type { MapDescriptor } from "../../types/index.js";
2
+ /** Geographic coordinates stored by the map field. */
3
+ interface LatLng {
4
+ lat: number;
5
+ lng: number;
6
+ }
7
+ /** Map field with manual lat/lng inputs plus a client-only Leaflet picker. */
8
+ type __VLS_Props = {
9
+ modelValue: unknown;
10
+ descriptor: MapDescriptor;
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: LatLng | null) => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
+ "onUpdate:modelValue"?: ((value: LatLng | null) => any) | undefined;
19
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
+ declare const _default: typeof __VLS_export;
21
+ export default _default;
22
+ //# sourceMappingURL=MapoMapField.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapoMapField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoMapField.vue"],"names":[],"mappings":"AAyHA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAO1D,sDAAsD;AACtD,UAAU,MAAM;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,8EAA8E;AAC9E,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,aAAa,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAoOF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -0,0 +1,67 @@
1
+ <script setup>
2
+ import { ref, watch, onMounted, onBeforeUnmount } from "vue";
3
+ const props = defineProps({
4
+ value: { type: [Object, null], required: true },
5
+ defaultLat: { type: Number, required: true },
6
+ defaultLng: { type: Number, required: true },
7
+ zoom: { type: Number, required: true },
8
+ readonly: { type: Boolean, required: true }
9
+ });
10
+ const emit = defineEmits(["click"]);
11
+ const mapEl = ref(null);
12
+ let L = null;
13
+ let map = null;
14
+ let marker = null;
15
+ onMounted(async () => {
16
+ L = (await import("leaflet")).default;
17
+ await import("leaflet/dist/leaflet.css");
18
+ const [iconRetinaUrl, iconUrl, shadowUrl] = await Promise.all([
19
+ import("leaflet/dist/images/marker-icon-2x.png?url").then((m) => m.default),
20
+ import("leaflet/dist/images/marker-icon.png?url").then((m) => m.default),
21
+ import("leaflet/dist/images/marker-shadow.png?url").then((m) => m.default)
22
+ ]);
23
+ delete L.Icon.Default.prototype._getIconUrl;
24
+ L.Icon.Default.mergeOptions({ iconRetinaUrl, iconUrl, shadowUrl });
25
+ if (!mapEl.value) return;
26
+ map = L.map(mapEl.value).setView(
27
+ [
28
+ props.value?.lat ?? props.defaultLat,
29
+ props.value?.lng ?? props.defaultLng
30
+ ],
31
+ props.zoom
32
+ );
33
+ L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
34
+ attribution: '\xA9 <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
35
+ }).addTo(map);
36
+ if (props.value) {
37
+ marker = L.marker([props.value.lat, props.value.lng]).addTo(map);
38
+ }
39
+ if (!props.readonly) {
40
+ map.on("click", (e) => {
41
+ const { lat, lng } = e.latlng;
42
+ if (marker) marker.setLatLng([lat, lng]);
43
+ else if (L && map) marker = L.marker([lat, lng]).addTo(map);
44
+ emit("click", { lat, lng });
45
+ });
46
+ }
47
+ });
48
+ watch(
49
+ () => props.value,
50
+ (val) => {
51
+ if (!map || !L) return;
52
+ if (val) {
53
+ if (marker) marker.setLatLng([val.lat, val.lng]);
54
+ else marker = L.marker([val.lat, val.lng]).addTo(map);
55
+ map.panTo([val.lat, val.lng]);
56
+ } else {
57
+ marker?.remove();
58
+ marker = null;
59
+ }
60
+ }
61
+ );
62
+ onBeforeUnmount(() => map?.remove());
63
+ </script>
64
+
65
+ <template>
66
+ <div ref="mapEl" class="h-64 w-full rounded border border-gray-200" />
67
+ </template>
@@ -0,0 +1,20 @@
1
+ /** Geographic coordinates emitted by the interactive Leaflet client. */
2
+ interface LatLng {
3
+ lat: number;
4
+ lng: number;
5
+ }
6
+ type __VLS_Props = {
7
+ value: LatLng | null;
8
+ defaultLat: number;
9
+ defaultLng: number;
10
+ zoom: number;
11
+ readonly: boolean;
12
+ };
13
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
+ click: (coords: LatLng) => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
+ onClick?: ((coords: LatLng) => any) | undefined;
17
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
20
+ //# sourceMappingURL=MapoMapFieldClient.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapoMapFieldClient.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoMapFieldClient.vue"],"names":[],"mappings":"AAiGA,wEAAwE;AACxE,UAAU,MAAM;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,KAAK,WAAW,GAAG;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAuGF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}