@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,47 @@
1
+ import type { Ref } from "vue";
2
+ import type { AnyFieldDescriptor, FieldRegistry } from "../types/index.js";
3
+ declare const __VLS_export: <T extends object>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
4
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<{
5
+ modelValue: T;
6
+ fields: AnyFieldDescriptor<T>[];
7
+ errors?: Record<string, string[]>;
8
+ languages?: string[];
9
+ currentLang?: string;
10
+ readonly?: boolean;
11
+ immediate?: boolean;
12
+ /**
13
+ * Field component registry. When omitted, it is auto-injected from
14
+ * `$mapoFormRegistry` (provided by the `@mapomodule/form` plugin).
15
+ * Pass it explicitly only for headless forms or one-off registry swaps.
16
+ */
17
+ registry?: FieldRegistry;
18
+ } & {
19
+ "onUpdate:modelValue"?: ((value: T) => any) | undefined;
20
+ }> & (typeof globalThis extends {
21
+ __VLS_PROPS_FALLBACK: infer P;
22
+ } ? P : {});
23
+ expose: (exposed: import("vue").ShallowUnwrapRef<{
24
+ submit: (handler: (model: T | Partial<T>, isNew: boolean) => Promise<void>, isNew?: boolean) => Promise<void>;
25
+ validateClient: () => {
26
+ valid: boolean;
27
+ errors: Record<string, string>;
28
+ };
29
+ resetDirty: () => void;
30
+ getPatch: () => Partial<T>;
31
+ isDirty: import("vue").ComputedRef<boolean>;
32
+ isLoading: Ref<boolean, boolean>;
33
+ }>) => void;
34
+ attrs: any;
35
+ slots: Record<string, (props: Record<string, any>) => unknown>;
36
+ emit: (evt: "update:modelValue", value: T) => void;
37
+ }>) => import("vue").VNode & {
38
+ __ctx?: Awaited<typeof __VLS_setup>;
39
+ };
40
+ declare const _default: typeof __VLS_export;
41
+ export default _default;
42
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
43
+ [K in keyof T]: T[K];
44
+ } : {
45
+ [K in keyof T as K]: T[K];
46
+ }) & {};
47
+ //# sourceMappingURL=MapoForm.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapoForm.vue.d.ts","sourceRoot":"","sources":["../../../src/runtime/components/MapoForm.vue"],"names":[],"mappings":"AA2PA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE/B,OAAO,KAAK,EACV,kBAAkB,EAClB,aAAa,EAEd,MAAM,mBAAmB,CAAC;AAQ3B,QAAA,MAAM,YAAY,GAAK,CAAC,SAAS,MAAM,EACtC,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,gBAAgB,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EAClE;WAmUO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;oBAjUxC,CAAC;gBACL,kBAAkB,CAAC,CAAC,CAAC,EAAE;iBACtB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;oBACrB,MAAM,EAAE;sBACN,MAAM;mBACT,OAAO;oBACN,OAAO;QACnB;;;;WAIG;mBACQ,aAAa;;;KAqT+D,CAAC,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5J,CAAC,OAAO,EAAE,OAAO,KAAK,EAAE,gBAAgB;;;;;;;;;;MAAsB,KAAK,IAAI;WACxE,GAAG;kCAnR+B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO;;EAuRrE,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,WAAW,CAAC,CAAA;CAAI,CAAC;wBACvD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,CAAC,GAAG,EAAE,CAAC"}
@@ -0,0 +1,269 @@
1
+ <script setup>
2
+ import { computed, defineAsyncComponent, inject, ref, watch } from "vue";
3
+ import {
4
+ resolveFieldComponent,
5
+ resolveFieldAttrs,
6
+ resolveFieldAccessor
7
+ } from "../types/index.js";
8
+ import { injectMapoForm } from "../composables/useMapoForm.js";
9
+ import { getNestedValue, debounce } from "@mapomodule/utils";
10
+ import MapoUnknownField from "./MapoUnknownField.vue";
11
+ const props = defineProps({
12
+ descriptor: { type: Object, required: true }
13
+ });
14
+ const form = injectMapoForm();
15
+ const registry = form.registry;
16
+ const currentLang = form.currentLang;
17
+ const model = form.model;
18
+ const isVisible = computed(() => {
19
+ if (props.descriptor.visible && !props.descriptor.visible({ model: model.value }))
20
+ return false;
21
+ return true;
22
+ });
23
+ const showField = computed(() => {
24
+ if (props.descriptor.show && !props.descriptor.show({ model: model.value }))
25
+ return false;
26
+ return true;
27
+ });
28
+ const path = computed(() => {
29
+ const lang = currentLang.value;
30
+ if (props.descriptor.translatable && lang) {
31
+ return `translations.${lang}.${props.descriptor.key}`;
32
+ }
33
+ return props.descriptor.key;
34
+ });
35
+ const accessor = computed(
36
+ () => resolveFieldAccessor(props.descriptor, registry)
37
+ );
38
+ const modelValue = computed(() => {
39
+ const raw = getNestedValue(model.value, path.value);
40
+ return accessor.value.get ? accessor.value.get({
41
+ model: model.value,
42
+ val: raw,
43
+ lang: currentLang.value
44
+ }) : raw;
45
+ });
46
+ function doUpdate(val) {
47
+ form.setFieldValue(props.descriptor.key, val);
48
+ }
49
+ const debounceMs = form.immediate ? 0 : props.descriptor.debounce ?? form.debounce;
50
+ const onUpdate = debounceMs > 0 ? debounce(doUpdate, debounceMs) : doUpdate;
51
+ const asyncState = ref("idle");
52
+ const asyncError = ref(null);
53
+ const asyncCache = /* @__PURE__ */ new Map();
54
+ const asyncValidationDebounceMs = props.descriptor.validateAsync ? props.descriptor.validateAsyncDebounce ?? 600 : 0;
55
+ const runAsyncValidation = props.descriptor.validateAsync ? debounce(async (val) => {
56
+ const cacheKey = JSON.stringify(val);
57
+ if (asyncCache.has(cacheKey)) {
58
+ asyncError.value = asyncCache.get(cacheKey);
59
+ asyncState.value = asyncError.value ? "invalid" : "valid";
60
+ return;
61
+ }
62
+ asyncState.value = "pending";
63
+ try {
64
+ const result = await props.descriptor.validateAsync(val, {
65
+ model: model.value
66
+ });
67
+ asyncCache.set(cacheKey, result);
68
+ asyncError.value = result;
69
+ asyncState.value = result ? "invalid" : "valid";
70
+ } catch {
71
+ asyncState.value = "idle";
72
+ }
73
+ }, asyncValidationDebounceMs) : null;
74
+ if (runAsyncValidation) {
75
+ watch(modelValue, (val, old) => {
76
+ if (val === old) return;
77
+ runAsyncValidation(val);
78
+ });
79
+ }
80
+ function onBlur() {
81
+ form.markTouched(props.descriptor.key);
82
+ }
83
+ const asyncTrailingIcon = computed(
84
+ () => ({
85
+ idle: void 0,
86
+ pending: "i-lucide-loader-2",
87
+ valid: "i-lucide-check-circle",
88
+ invalid: "i-lucide-x-circle"
89
+ })[asyncState.value]
90
+ );
91
+ const asyncTrailingIconClass = computed(
92
+ () => ({
93
+ idle: "",
94
+ pending: "text-gray-400 animate-spin",
95
+ valid: "text-green-500",
96
+ invalid: "text-red-500"
97
+ })[asyncState.value]
98
+ );
99
+ const fieldComponent = computed(() => {
100
+ const entry = resolveFieldComponent(props.descriptor, registry);
101
+ if (!entry) return MapoUnknownField;
102
+ if (typeof entry === "function")
103
+ return defineAsyncComponent(entry);
104
+ return entry;
105
+ });
106
+ const fieldAttrs = computed(
107
+ () => resolveFieldAttrs(props.descriptor, registry)
108
+ );
109
+ function resolveErrors(errorsMap, dotPath) {
110
+ const segments = dotPath.split(".");
111
+ const result = [];
112
+ let current = "";
113
+ for (const seg of segments) {
114
+ current = current ? `${current}.${seg}` : seg;
115
+ const errs = errorsMap[current];
116
+ if (errs) result.push(...errs);
117
+ }
118
+ return result;
119
+ }
120
+ const errors = computed(() => resolveErrors(form.errors.value, path.value));
121
+ const clientError = computed(
122
+ () => form.getClientError(props.descriptor.key)
123
+ );
124
+ const allErrors = computed(() => {
125
+ const errs = [...errors.value];
126
+ if (clientError.value) errs.unshift(clientError.value);
127
+ if (asyncError.value && asyncState.value === "invalid")
128
+ errs.push(asyncError.value);
129
+ return errs;
130
+ });
131
+ const hasError = computed(() => allErrors.value.length > 0);
132
+ const errorColor = computed(() => hasError.value ? "error" : void 0);
133
+ function autoLabel(key) {
134
+ const stripped = key.replace(/^translations\.[^.]+\./, "");
135
+ return stripped.replace(/[_.-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
136
+ }
137
+ const label = computed(
138
+ () => props.descriptor.label ?? autoLabel(props.descriptor.key)
139
+ );
140
+ const fieldId = computed(() => `field-${props.descriptor.key}`);
141
+ const groupExpand = inject("mapoGroupExpand", null);
142
+ const isExpanded = computed(
143
+ () => groupExpand ? groupExpand.expandedFieldKey.value === props.descriptor.key : false
144
+ );
145
+ function toggleExpand() {
146
+ if (!groupExpand) return;
147
+ if (isExpanded.value) groupExpand.collapseField();
148
+ else groupExpand.expandField(props.descriptor.key);
149
+ }
150
+ </script>
151
+
152
+ <template>
153
+ <div
154
+ v-if="isVisible"
155
+ v-show="showField"
156
+ class="mapo-form-field group flex flex-col gap-1"
157
+ :class="[
158
+ descriptor.class,
159
+ hasError && 'mapo-form-field--error',
160
+ isExpanded && 'flex-1 min-h-0'
161
+ ]"
162
+ >
163
+ <!-- Label -->
164
+ <slot
165
+ :name="`field.${descriptor.key}.label`"
166
+ :descriptor="descriptor"
167
+ :label="label"
168
+ >
169
+ <label
170
+ :for="fieldId"
171
+ class="flex shrink-0 items-center gap-1 text-sm font-medium text-gray-700"
172
+ >
173
+ <span>{{ label }}</span>
174
+ <span v-if="descriptor.required" class="text-red-500" aria-hidden="true"
175
+ >*</span
176
+ >
177
+ <UIcon
178
+ v-if="asyncTrailingIcon"
179
+ :name="asyncTrailingIcon"
180
+ class="ml-auto h-3.5 w-3.5"
181
+ :class="asyncTrailingIconClass"
182
+ />
183
+ <!-- Expand button — visible on hover or while expanded -->
184
+ <button
185
+ v-if="descriptor.expandable && groupExpand"
186
+ type="button"
187
+ :class="
188
+ isExpanded ? 'ml-auto text-primary-500' : 'ml-auto text-gray-300 opacity-0 group-hover:opacity-100 focus:opacity-100'
189
+ "
190
+ class="rounded p-0.5 transition-all hover:bg-gray-100 hover:text-gray-600"
191
+ :title="isExpanded ? 'Collapse field' : 'Expand field'"
192
+ @click.prevent="toggleExpand"
193
+ >
194
+ <UIcon
195
+ :name="isExpanded ? 'i-lucide-minimize-2' : 'i-lucide-expand'"
196
+ class="h-3.5 w-3.5"
197
+ />
198
+ </button>
199
+ </label>
200
+ </slot>
201
+
202
+ <!-- Slot before -->
203
+ <slot
204
+ :name="`field.${descriptor.key}.before`"
205
+ :descriptor="descriptor"
206
+ :model="model"
207
+ />
208
+
209
+ <div
210
+ class="flex items-start gap-2"
211
+ :class="isExpanded && 'flex-1 min-h-0'"
212
+ :style="isExpanded ? 'height: 100%; overflow: hidden;' : ''"
213
+ >
214
+ <!-- Slot prepend -->
215
+ <slot
216
+ :name="`field.${descriptor.key}.prepend`"
217
+ :descriptor="descriptor"
218
+ :model="model"
219
+ />
220
+
221
+ <component
222
+ :is="fieldComponent"
223
+ :id="fieldId"
224
+ v-bind="fieldAttrs"
225
+ :class="isExpanded ? 'flex-1 min-h-0' : 'flex-1'"
226
+ :style="isExpanded ? 'height: 100%;' : ''"
227
+ :model-value="modelValue"
228
+ :readonly="descriptor.readonly || form.readonly.value"
229
+ :disabled="descriptor.readonly || form.readonly.value"
230
+ :descriptor="descriptor"
231
+ :errors="allErrors"
232
+ :color="errorColor"
233
+ @update:model-value="onUpdate"
234
+ @blur="onBlur"
235
+ />
236
+
237
+ <!-- Slot append -->
238
+ <slot
239
+ :name="`field.${descriptor.key}.append`"
240
+ :descriptor="descriptor"
241
+ :model="model"
242
+ />
243
+ </div>
244
+
245
+ <!-- Slot hint -->
246
+ <slot
247
+ :name="`field.${descriptor.key}.hint`"
248
+ :descriptor="descriptor"
249
+ :model="model"
250
+ />
251
+
252
+ <!-- Errori -->
253
+ <p
254
+ v-for="err in allErrors"
255
+ :key="err"
256
+ class="text-xs text-red-500"
257
+ role="alert"
258
+ >
259
+ {{ err }}
260
+ </p>
261
+
262
+ <!-- Slot after -->
263
+ <slot
264
+ :name="`field.${descriptor.key}.after`"
265
+ :descriptor="descriptor"
266
+ :model="model"
267
+ />
268
+ </div>
269
+ </template>
@@ -0,0 +1,46 @@
1
+ import type { AnyFieldDescriptor } from "../types/index.js";
2
+ type __VLS_Props = {
3
+ descriptor: AnyFieldDescriptor<any>;
4
+ };
5
+ declare var __VLS_2: `field.${string}.label`, __VLS_3: {
6
+ descriptor: AnyFieldDescriptor<any>;
7
+ label: string;
8
+ }, __VLS_16: `field.${string}.before`, __VLS_17: {
9
+ descriptor: AnyFieldDescriptor<any>;
10
+ model: Record<string, unknown>;
11
+ }, __VLS_20: `field.${string}.prepend`, __VLS_21: {
12
+ descriptor: AnyFieldDescriptor<any>;
13
+ model: Record<string, unknown>;
14
+ }, __VLS_31: `field.${string}.append`, __VLS_32: {
15
+ descriptor: AnyFieldDescriptor<any>;
16
+ model: Record<string, unknown>;
17
+ }, __VLS_35: `field.${string}.hint`, __VLS_36: {
18
+ descriptor: AnyFieldDescriptor<any>;
19
+ model: Record<string, unknown>;
20
+ }, __VLS_39: `field.${string}.after`, __VLS_40: {
21
+ descriptor: AnyFieldDescriptor<any>;
22
+ model: Record<string, unknown>;
23
+ };
24
+ type __VLS_Slots = {} & {
25
+ [K in NonNullable<typeof __VLS_2>]?: (props: typeof __VLS_3) => any;
26
+ } & {
27
+ [K in NonNullable<typeof __VLS_16>]?: (props: typeof __VLS_17) => any;
28
+ } & {
29
+ [K in NonNullable<typeof __VLS_20>]?: (props: typeof __VLS_21) => any;
30
+ } & {
31
+ [K in NonNullable<typeof __VLS_31>]?: (props: typeof __VLS_32) => any;
32
+ } & {
33
+ [K in NonNullable<typeof __VLS_35>]?: (props: typeof __VLS_36) => any;
34
+ } & {
35
+ [K in NonNullable<typeof __VLS_39>]?: (props: typeof __VLS_40) => any;
36
+ };
37
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
38
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
39
+ declare const _default: typeof __VLS_export;
40
+ export default _default;
41
+ type __VLS_WithSlots<T, S> = T & {
42
+ new (): {
43
+ $slots: S;
44
+ };
45
+ };
46
+ //# sourceMappingURL=MapoFormField.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapoFormField.vue.d.ts","sourceRoot":"","sources":["../../../src/runtime/components/MapoFormField.vue"],"names":[],"mappings":"AAmWA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAa5D,KAAK,WAAW,GAAG;IAAE,UAAU,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAA;CAAE,CAAC;AA2a3D,QAAA,IAAI,OAAO,yBAAU,EAAE,OAAO;;;CAAU,EAAE,QAAQ,0BAAW,EAAE,QAAQ;;;CAAW,EAAE,QAAQ,2BAAW,EAAE,QAAQ;;;CAAW,EAAE,QAAQ,0BAAW,EAAE,QAAQ;;;CAAW,EAAE,QAAQ,wBAAW,EAAE,QAAQ;;;CAAW,EAAE,QAAQ,yBAAW,EAAE,QAAQ;;;CAAY,CAAE;AAC7P,KAAK,WAAW,GAAG,EAAE,GACnB;KAAG,CAAC,IAAI,WAAW,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG;CAAE,GACvE;KAAG,CAAC,IAAI,WAAW,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,QAAQ,KAAK,GAAG;CAAE,GACzE;KAAG,CAAC,IAAI,WAAW,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,QAAQ,KAAK,GAAG;CAAE,GACzE;KAAG,CAAC,IAAI,WAAW,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,QAAQ,KAAK,GAAG;CAAE,GACzE;KAAG,CAAC,IAAI,WAAW,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,QAAQ,KAAK,GAAG;CAAE,GACzE;KAAG,CAAC,IAAI,WAAW,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,QAAQ,KAAK,GAAG;CAAE,CAAC;AAK5E,QAAA,MAAM,UAAU,kSAEd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
@@ -0,0 +1,149 @@
1
+ <script setup>
2
+ import { ref, computed, provide, onMounted, onUnmounted } from "vue";
3
+ import { injectMapoForm } from "../composables/useMapoForm.js";
4
+ import MapoFormField from "./MapoFormField.vue";
5
+ const props = defineProps({
6
+ fields: { type: Array, required: true }
7
+ });
8
+ defineSlots();
9
+ const form = injectMapoForm();
10
+ const model = computed(() => form?.model.value);
11
+ const currentLang = computed(() => form?.currentLang.value ?? "");
12
+ const expandedFieldKey = ref(null);
13
+ const expandedFieldLabel = computed(() => {
14
+ if (!expandedFieldKey.value) return "";
15
+ const field = props.fields.find(
16
+ (f) => f.key === expandedFieldKey.value
17
+ );
18
+ return field?.label ?? expandedFieldKey.value;
19
+ });
20
+ function expandField(key) {
21
+ expandedFieldKey.value = key;
22
+ document.body.style.overflow = "hidden";
23
+ }
24
+ function collapseField() {
25
+ expandedFieldKey.value = null;
26
+ document.body.style.overflow = "";
27
+ }
28
+ function onKeydown(e) {
29
+ if (e.key === "Escape" && expandedFieldKey.value) collapseField();
30
+ }
31
+ onMounted(() => document.addEventListener("keydown", onKeydown));
32
+ onUnmounted(() => {
33
+ document.removeEventListener("keydown", onKeydown);
34
+ document.body.style.overflow = "";
35
+ });
36
+ provide("mapoGroupExpand", { expandField, collapseField, expandedFieldKey });
37
+ const COL_SPAN = {
38
+ 1: "col-span-1",
39
+ 2: "col-span-2",
40
+ 3: "col-span-3",
41
+ 4: "col-span-4",
42
+ 5: "col-span-5",
43
+ 6: "col-span-6",
44
+ 7: "col-span-7",
45
+ 8: "col-span-8",
46
+ 9: "col-span-9",
47
+ 10: "col-span-10",
48
+ 11: "col-span-11",
49
+ 12: "col-span-12"
50
+ };
51
+ function colClass(cols) {
52
+ if (!cols || typeof cols !== "number") return "col-span-12";
53
+ return COL_SPAN[cols] ?? "col-span-12";
54
+ }
55
+ </script>
56
+
57
+ <template>
58
+ <!-- Expanded panel: teleported to body -->
59
+ <Teleport to="body">
60
+ <Transition name="mapo-expand">
61
+ <div
62
+ v-if="expandedFieldKey"
63
+ class="fixed inset-0 z-50 flex flex-col"
64
+ style="padding: 1rem"
65
+ >
66
+ <div
67
+ class="absolute inset-0 bg-black/50 backdrop-blur-sm"
68
+ @click="collapseField"
69
+ />
70
+ <div
71
+ class="relative z-10 flex h-full flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-2xl"
72
+ >
73
+ <div
74
+ class="flex shrink-0 items-center justify-between border-b border-gray-100 px-5 py-3.5"
75
+ >
76
+ <span class="text-sm font-semibold tracking-wide text-gray-700">{{
77
+ expandedFieldLabel
78
+ }}</span>
79
+ <button
80
+ type="button"
81
+ class="rounded p-1 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600"
82
+ @click="collapseField"
83
+ >
84
+ <UIcon name="i-lucide-minimize-2" class="h-4 w-4" />
85
+ </button>
86
+ </div>
87
+ <div
88
+ class="mapo-group-expand-body flex min-h-0 flex-1 flex-col p-5"
89
+ style="overflow: hidden"
90
+ >
91
+ <template v-for="field in fields" :key="field.key">
92
+ <div
93
+ v-if="expandedFieldKey === field.key"
94
+ style="
95
+ display: flex;
96
+ flex-direction: column;
97
+ flex: 1;
98
+ min-height: 0;
99
+ height: 100%;
100
+ "
101
+ >
102
+ <slot
103
+ :name="`field.${field.key}`"
104
+ :field="field"
105
+ :model="model"
106
+ :current-lang="currentLang"
107
+ >
108
+ <MapoFormField
109
+ :descriptor="field"
110
+ style="flex: 1; min-height: 0; height: 100%"
111
+ >
112
+ <template
113
+ v-for="(_, slotName) in $slots"
114
+ #[slotName]="slotProps"
115
+ >
116
+ <slot :name="slotName" v-bind="slotProps ?? {}" />
117
+ </template>
118
+ </MapoFormField>
119
+ </slot>
120
+ </div>
121
+ </template>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </Transition>
126
+ </Teleport>
127
+
128
+ <!-- Flat grid -->
129
+ <div class="grid grid-cols-12 gap-5">
130
+ <div
131
+ v-for="field in fields"
132
+ :key="field.key"
133
+ :class="colClass(field.cols)"
134
+ >
135
+ <slot
136
+ :name="`field.${field.key}`"
137
+ :field="field"
138
+ :model="model"
139
+ :current-lang="currentLang"
140
+ >
141
+ <MapoFormField :descriptor="field">
142
+ <template v-for="(_, slotName) in $slots" #[slotName]="slotProps">
143
+ <slot :name="slotName" v-bind="slotProps ?? {}" />
144
+ </template>
145
+ </MapoFormField>
146
+ </slot>
147
+ </div>
148
+ </div>
149
+ </template>
@@ -0,0 +1,15 @@
1
+ import type { AnyFieldDescriptor } from "../types/index.js";
2
+ type __VLS_Props = {
3
+ fields: AnyFieldDescriptor[];
4
+ };
5
+ type __VLS_Slots = Record<string, unknown>;
6
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
10
+ type __VLS_WithSlots<T, S> = T & {
11
+ new (): {
12
+ $slots: S;
13
+ };
14
+ };
15
+ //# sourceMappingURL=MapoFormFlatSection.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapoFormFlatSection.vue.d.ts","sourceRoot":"","sources":["../../../src/runtime/components/MapoFormFlatSection.vue"],"names":[],"mappings":"AAuKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAI5D,KAAK,WAAW,GAAG;IAAE,MAAM,EAAE,kBAAkB,EAAE,CAAA;CAAE,CAAC;AAGpD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAmS3C,QAAA,MAAM,UAAU,kSAEd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}