@mapomodule/form 2.0.0-beta.1 → 2.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/__tests__/__mocks__/nuxt-imports.d.ts +4 -0
- package/dist/__tests__/__mocks__/nuxt-imports.d.ts.map +1 -0
- package/dist/__tests__/camomillaRoundTrip.test.d.ts +2 -0
- package/dist/__tests__/camomillaRoundTrip.test.d.ts.map +1 -0
- package/dist/__tests__/registry.test.d.ts +2 -0
- package/dist/__tests__/registry.test.d.ts.map +1 -0
- package/dist/__tests__/useFormFromSchema.test.d.ts +2 -0
- package/dist/__tests__/useFormFromSchema.test.d.ts.map +1 -0
- package/dist/__tests__/useMapoForm.test.d.ts +2 -0
- package/dist/__tests__/useMapoForm.test.d.ts.map +1 -0
- package/dist/index.d.mts +9 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +7 -34
- package/dist/module.d.mts +20 -2
- package/dist/module.d.ts +12 -2
- package/dist/module.d.ts.map +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +187 -13
- package/dist/runtime/components/MapoFocusPortal.vue +115 -0
- package/dist/runtime/components/MapoFocusPortal.vue.d.ts +4 -0
- package/dist/runtime/components/MapoFocusPortal.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoForm.vue +168 -0
- package/dist/runtime/components/MapoForm.vue.d.ts +47 -0
- package/dist/runtime/components/MapoForm.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormField.vue +269 -0
- package/dist/runtime/components/MapoFormField.vue.d.ts +46 -0
- package/dist/runtime/components/MapoFormField.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormFlatSection.vue +149 -0
- package/dist/runtime/components/MapoFormFlatSection.vue.d.ts +15 -0
- package/dist/runtime/components/MapoFormFlatSection.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormGroup.vue +247 -0
- package/dist/runtime/components/MapoFormGroup.vue.d.ts +44 -0
- package/dist/runtime/components/MapoFormGroup.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormTabs.vue +110 -0
- package/dist/runtime/components/MapoFormTabs.vue.d.ts +15 -0
- package/dist/runtime/components/MapoFormTabs.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoUnknownField.vue +62 -0
- package/dist/runtime/components/MapoUnknownField.vue.d.ts +16 -0
- package/dist/runtime/components/MapoUnknownField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoDateField.vue +88 -0
- package/dist/runtime/components/fields/MapoDateField.vue.d.ts +17 -0
- package/dist/runtime/components/fields/MapoDateField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoDateTimeField.vue +159 -0
- package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts +17 -0
- package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoEnhancedMediaField.vue +130 -0
- package/dist/runtime/components/fields/MapoEnhancedMediaField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoEnhancedMediaField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoFileField.vue +99 -0
- package/dist/runtime/components/fields/MapoFileField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoFileField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoFksField.vue +122 -0
- package/dist/runtime/components/fields/MapoFksField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoFksField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMapField.vue +99 -0
- package/dist/runtime/components/fields/MapoMapField.vue.d.ts +22 -0
- package/dist/runtime/components/fields/MapoMapField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMapFieldClient.vue +67 -0
- package/dist/runtime/components/fields/MapoMapFieldClient.vue.d.ts +20 -0
- package/dist/runtime/components/fields/MapoMapFieldClient.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMediaField.vue +91 -0
- package/dist/runtime/components/fields/MapoMediaField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoMediaField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMediaM2mField.vue +110 -0
- package/dist/runtime/components/fields/MapoMediaM2mField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoMediaM2mField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMediaUnavailable.vue +15 -0
- package/dist/runtime/components/fields/MapoMediaUnavailable.vue.d.ts +4 -0
- package/dist/runtime/components/fields/MapoMediaUnavailable.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoRepeater.vue +459 -0
- package/dist/runtime/components/fields/MapoRepeater.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoRepeater.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoRepeaterItem.vue +346 -0
- package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts +55 -0
- package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoSeoPreview.vue +122 -0
- package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts +22 -0
- package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoTimeField.vue +44 -0
- package/dist/runtime/components/fields/MapoTimeField.vue.d.ts +17 -0
- package/dist/runtime/components/fields/MapoTimeField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoWygEditor.vue +252 -0
- package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiCheckbox.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiCheckbox.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiCheckbox.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiInput.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiInput.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiInput.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiSelectMenu.vue +18 -0
- package/dist/runtime/components/fields/nui/NuiSelectMenu.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiSelectMenu.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiSlider.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiSlider.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiSlider.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiSwitch.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiSwitch.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiSwitch.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiTextarea.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiTextarea.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiTextarea.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/useNuiFieldAttrs.d.ts +13 -0
- package/dist/runtime/components/fields/nui/useNuiFieldAttrs.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/useNuiFieldAttrs.js +19 -0
- package/dist/runtime/composables/defineFormField.d.ts +25 -0
- package/dist/runtime/composables/defineFormField.d.ts.map +1 -0
- package/dist/runtime/composables/defineFormField.js +27 -0
- package/dist/runtime/composables/useCurrentLang.d.ts +6 -0
- package/dist/runtime/composables/useCurrentLang.d.ts.map +1 -0
- package/dist/runtime/composables/useCurrentLang.js +9 -0
- package/dist/runtime/composables/useFocusMode.d.ts +40 -0
- package/dist/runtime/composables/useFocusMode.d.ts.map +1 -0
- package/dist/runtime/composables/useFocusMode.js +31 -0
- package/dist/runtime/composables/useFormDraft.d.ts +60 -0
- package/dist/runtime/composables/useFormDraft.d.ts.map +1 -0
- package/dist/runtime/composables/useFormDraft.js +52 -0
- package/dist/runtime/composables/useFormFromSchema.d.ts +21 -0
- package/dist/runtime/composables/useFormFromSchema.d.ts.map +1 -0
- package/dist/runtime/composables/useFormFromSchema.js +145 -0
- package/dist/runtime/composables/useMapoForm.d.ts +96 -0
- package/dist/runtime/composables/useMapoForm.d.ts.map +1 -0
- package/dist/runtime/composables/useMapoForm.js +325 -0
- package/dist/runtime/composables/useMediaManager.d.ts +17 -0
- package/dist/runtime/composables/useMediaManager.d.ts.map +1 -0
- package/dist/runtime/composables/useMediaManager.js +6 -0
- package/dist/runtime/composables/useProgressiveDisclosure.d.ts +44 -0
- package/dist/runtime/composables/useProgressiveDisclosure.d.ts.map +1 -0
- package/dist/runtime/composables/useProgressiveDisclosure.js +23 -0
- package/dist/runtime/composables/useUnsavedChangesGuard.d.ts +24 -0
- package/dist/runtime/composables/useUnsavedChangesGuard.d.ts.map +1 -0
- package/dist/runtime/composables/useUnsavedChangesGuard.js +28 -0
- package/dist/runtime/devtools/DevtoolsFormsPage.vue +235 -0
- package/dist/runtime/devtools/DevtoolsFormsPage.vue.d.ts +4 -0
- package/dist/runtime/devtools/DevtoolsFormsPage.vue.d.ts.map +1 -0
- package/dist/runtime/plugins/form-registry.d.ts +18 -0
- package/dist/runtime/plugins/form-registry.d.ts.map +1 -0
- package/dist/runtime/plugins/form-registry.js +19 -0
- package/dist/runtime/registry/defaults.d.ts +3 -0
- package/dist/runtime/registry/defaults.d.ts.map +1 -0
- package/dist/runtime/registry/defaults.js +93 -0
- package/dist/runtime/types/descriptor.d.ts +373 -0
- package/dist/runtime/types/descriptor.d.ts.map +1 -0
- package/dist/runtime/types/descriptor.js +26 -0
- package/dist/runtime/types/index.d.ts +12 -0
- package/dist/runtime/types/index.d.ts.map +1 -0
- package/dist/runtime/types/index.js +6 -0
- package/dist/runtime/types/layout.d.ts +21 -0
- package/dist/runtime/types/layout.d.ts.map +1 -0
- package/dist/runtime/types/layout.js +0 -0
- package/dist/runtime/types/media.d.ts +43 -0
- package/dist/runtime/types/media.d.ts.map +1 -0
- package/dist/runtime/types/media.js +0 -0
- package/dist/runtime/types/registry.d.ts +62 -0
- package/dist/runtime/types/registry.d.ts.map +1 -0
- package/dist/runtime/types/registry.js +19 -0
- package/dist/runtime/utils/flattenFieldGroups.d.ts +50 -0
- package/dist/runtime/utils/flattenFieldGroups.d.ts.map +1 -0
- package/dist/runtime/utils/flattenFieldGroups.js +24 -0
- package/dist/types.d.mts +6 -10
- package/package.json +10 -8
- package/src/__tests__/article.schema.json +193 -0
- package/src/__tests__/camomillaRoundTrip.test.ts +73 -0
- package/src/__tests__/useFormFromSchema.test.ts +24 -0
- package/src/__tests__/useMapoForm.test.ts +5 -0
- package/src/index.ts +5 -0
- package/src/module.ts +34 -0
- package/src/runtime/components/MapoFocusPortal.vue +3 -1
- package/src/runtime/components/MapoFormTabs.vue +4 -1
- package/src/runtime/components/fields/MapoDateField.vue +3 -1
- package/src/runtime/components/fields/MapoDateTimeField.vue +3 -1
- package/src/runtime/components/fields/MapoEnhancedMediaField.vue +153 -0
- package/src/runtime/components/fields/MapoFileField.vue +3 -1
- package/src/runtime/components/fields/MapoFksField.vue +7 -2
- package/src/runtime/components/fields/MapoMapField.vue +5 -2
- package/src/runtime/components/fields/MapoMediaField.vue +104 -0
- package/src/runtime/components/fields/MapoMediaM2mField.vue +130 -0
- package/src/runtime/components/fields/MapoMediaUnavailable.vue +19 -0
- package/src/runtime/components/fields/MapoRepeater.vue +33 -26
- package/src/runtime/components/fields/MapoRepeaterItem.vue +14 -8
- package/src/runtime/components/fields/MapoSeoPreview.vue +17 -13
- package/src/runtime/components/fields/MapoWygEditor.vue +47 -5
- package/src/runtime/composables/useFormFromSchema.ts +4 -1
- package/src/runtime/composables/useMapoForm.ts +4 -2
- package/src/runtime/composables/useMediaManager.ts +21 -0
- package/src/runtime/composables/useUnsavedChangesGuard.ts +3 -3
- package/src/runtime/registry/defaults.ts +9 -1
- package/src/runtime/types/descriptor.ts +22 -7
- package/src/runtime/types/index.ts +4 -0
- package/src/runtime/types/media.ts +44 -0
- package/dist/index.d.cts +0 -1
- package/dist/module.d.cts +0 -2
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type Ref } from "vue";
|
|
2
|
+
interface DraftEntry<T> {
|
|
3
|
+
model: T;
|
|
4
|
+
savedAt: number;
|
|
5
|
+
}
|
|
6
|
+
/** Options for draft persistence and restoration. */
|
|
7
|
+
export interface UseFormDraftOptions<T> {
|
|
8
|
+
model: Ref<T>;
|
|
9
|
+
isDirty: Ref<boolean>;
|
|
10
|
+
/**
|
|
11
|
+
* Unique draft key. It is prefixed with `mapo:draft:{userId}:` when `userId` is provided,
|
|
12
|
+
* otherwise `mapo:draft:`. Example: `article:42` → `mapo:draft:1:article:42`.
|
|
13
|
+
*/
|
|
14
|
+
key: string;
|
|
15
|
+
/**
|
|
16
|
+
* User identifier used to scope the draft so it is never shared across accounts.
|
|
17
|
+
* Without this, a draft survives logout and may be seen by the next user on the same browser.
|
|
18
|
+
* Pass `useAuthStore().user?.id` (or equivalent) from the calling composable.
|
|
19
|
+
*/
|
|
20
|
+
userId?: string | number;
|
|
21
|
+
/** Milliseconds between debounced writes to localStorage. Default: `2000`. */
|
|
22
|
+
debounce?: number;
|
|
23
|
+
/** TTL in milliseconds before the draft is discarded. Default: `86_400_000` (24h). */
|
|
24
|
+
ttl?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Called on mount when a valid, non-expired draft exists.
|
|
27
|
+
* Show a notification here and decide whether to restore it.
|
|
28
|
+
*/
|
|
29
|
+
onRestore?: (draft: T, savedAt: Date) => void;
|
|
30
|
+
/** Called after each successful write to localStorage. */
|
|
31
|
+
onSave?: (savedAt: Date) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Persists form drafts to `localStorage` and restores them on mount when available.
|
|
35
|
+
*
|
|
36
|
+
* The draft is written debounced while `isDirty` is `true`. On mount, if a valid
|
|
37
|
+
* non-expired draft is found, `onRestore` is called so the caller can decide
|
|
38
|
+
* whether to apply it (e.g. show a snack notification first).
|
|
39
|
+
*
|
|
40
|
+
* @param options - Configuration for key, TTL, debounce interval and lifecycle callbacks.
|
|
41
|
+
* @returns An object with `{ clearDraft, getDraft, hasDraft }` for manual control.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* useFormDraft({
|
|
45
|
+
* model,
|
|
46
|
+
* isDirty,
|
|
47
|
+
* key: `article:${route.params.id}`,
|
|
48
|
+
* onRestore: (draft, savedAt) => {
|
|
49
|
+
* snack.show(`Draft from ${savedAt.toLocaleString()} restored`)
|
|
50
|
+
* Object.assign(model.value, draft)
|
|
51
|
+
* },
|
|
52
|
+
* })
|
|
53
|
+
*/
|
|
54
|
+
export declare function useFormDraft<T>(options: UseFormDraftOptions<T>): {
|
|
55
|
+
clearDraft: () => void;
|
|
56
|
+
getDraft: () => DraftEntry<T> | null;
|
|
57
|
+
hasDraft: () => boolean;
|
|
58
|
+
};
|
|
59
|
+
export {};
|
|
60
|
+
//# sourceMappingURL=useFormDraft.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFormDraft.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useFormDraft.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAG9D,UAAU,UAAU,CAAC,CAAC;IACpB,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qDAAqD;AACrD,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACd,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACtB;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC;IAC9C,0DAA0D;IAC1D,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC;;oBAmCxC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;oBAqBpB,OAAO;EAK7B"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { watch, onMounted, onUnmounted } from "vue";
|
|
2
|
+
import { debounce } from "@mapomodule/utils";
|
|
3
|
+
export function useFormDraft(options) {
|
|
4
|
+
const storageKey = options.userId != null ? `mapo:draft:${options.userId}:${options.key}` : `mapo:draft:${options.key}`;
|
|
5
|
+
const ttl = options.ttl ?? 864e5;
|
|
6
|
+
const canUseStorage = typeof localStorage !== "undefined";
|
|
7
|
+
const debouncedSave = debounce(() => {
|
|
8
|
+
if (!canUseStorage) return;
|
|
9
|
+
if (!options.isDirty.value) return;
|
|
10
|
+
const entry = {
|
|
11
|
+
model: options.model.value,
|
|
12
|
+
savedAt: Date.now()
|
|
13
|
+
};
|
|
14
|
+
try {
|
|
15
|
+
localStorage.setItem(storageKey, JSON.stringify(entry));
|
|
16
|
+
options.onSave?.(new Date(entry.savedAt));
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
}, options.debounce ?? 2e3);
|
|
20
|
+
const stopWatch = watch(options.model, debouncedSave, { deep: true });
|
|
21
|
+
onMounted(() => {
|
|
22
|
+
const draft = getDraft();
|
|
23
|
+
if (!draft) return;
|
|
24
|
+
options.onRestore?.(draft.model, new Date(draft.savedAt));
|
|
25
|
+
});
|
|
26
|
+
onUnmounted(() => {
|
|
27
|
+
stopWatch();
|
|
28
|
+
});
|
|
29
|
+
function getDraft() {
|
|
30
|
+
if (!canUseStorage) return null;
|
|
31
|
+
try {
|
|
32
|
+
const raw = localStorage.getItem(storageKey);
|
|
33
|
+
if (!raw) return null;
|
|
34
|
+
const entry = JSON.parse(raw);
|
|
35
|
+
if (Date.now() - entry.savedAt > ttl) {
|
|
36
|
+
localStorage.removeItem(storageKey);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return entry;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function clearDraft() {
|
|
45
|
+
if (!canUseStorage) return;
|
|
46
|
+
localStorage.removeItem(storageKey);
|
|
47
|
+
}
|
|
48
|
+
function hasDraft() {
|
|
49
|
+
return getDraft() !== null;
|
|
50
|
+
}
|
|
51
|
+
return { clearDraft, getDraft, hasDraft };
|
|
52
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { JSONSchema } from "@mapomodule/utils";
|
|
2
|
+
import type { AnyFieldDescriptor } from "../types/index.js";
|
|
3
|
+
/** Options for generating field descriptors from a JSON Schema document. */
|
|
4
|
+
export interface UseFormFromSchemaOptions {
|
|
5
|
+
/** Keys to exclude from generation, for example `id` or `created_at`. */
|
|
6
|
+
exclude?: string[];
|
|
7
|
+
/** Per-field overrides that shallow-merge onto the generated descriptor. */
|
|
8
|
+
overrides?: Record<string, Partial<AnyFieldDescriptor>>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Builds a `AnyFieldDescriptor[]` array from a JSON Schema.
|
|
12
|
+
* Supports Pydantic v2 (`$defs`, nullable `anyOf`), DRF Spectacular, and `if/then/else` conditionals.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const fields = useFormFromSchema(myApiSchema, {
|
|
16
|
+
* exclude: ['id', 'created_at'],
|
|
17
|
+
* overrides: { body: { type: 'editor', translatable: true } },
|
|
18
|
+
* })
|
|
19
|
+
*/
|
|
20
|
+
export declare function useFormFromSchema(schema: JSONSchema, options?: UseFormFromSchemaOptions): AnyFieldDescriptor[];
|
|
21
|
+
//# sourceMappingURL=useFormFromSchema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFormFromSchema.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useFormFromSchema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAa,MAAM,mBAAmB,CAAC;AAyMvE,4EAA4E;AAC5E,MAAM,WAAW,wBAAwB;IACvC,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC;CACzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,wBAA6B,GACrC,kBAAkB,EAAE,CA+BtB"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { resolveSchema, extractDefs, matchesSchema } from "@mapomodule/utils";
|
|
2
|
+
function mapType(schema, key) {
|
|
3
|
+
if (schema.enum) return "select";
|
|
4
|
+
const t = Array.isArray(schema.type) ? schema.type.find((v) => v !== "null") : schema.type;
|
|
5
|
+
const fmt = schema.format;
|
|
6
|
+
if (t === "string") {
|
|
7
|
+
if (fmt === "textarea") return "textarea";
|
|
8
|
+
if (fmt === "date") return "date";
|
|
9
|
+
if (fmt === "date-time") return "datetime";
|
|
10
|
+
if (fmt === "time") return "time";
|
|
11
|
+
if (fmt === "color") return "color";
|
|
12
|
+
if (typeof schema.maxLength === "number" && schema.maxLength > 255)
|
|
13
|
+
return "textarea";
|
|
14
|
+
return "text";
|
|
15
|
+
}
|
|
16
|
+
if (t === "integer" || t === "number") return "number";
|
|
17
|
+
if (t === "boolean") return "boolean";
|
|
18
|
+
if (t === "relation") {
|
|
19
|
+
return schema.multiple ? "m2m" : "fks";
|
|
20
|
+
}
|
|
21
|
+
if (t === "array") {
|
|
22
|
+
const items = schema.items;
|
|
23
|
+
if (items?.type === "relation") return "m2m";
|
|
24
|
+
if (items?.type === "object" || items?.properties) return "repeater";
|
|
25
|
+
return "select";
|
|
26
|
+
}
|
|
27
|
+
if (t === "object") {
|
|
28
|
+
const props = Object.keys(schema.properties ?? {});
|
|
29
|
+
if (props.includes("title") && props.includes("description") && (props.includes("url") || props.includes("slug")))
|
|
30
|
+
return "seo";
|
|
31
|
+
return `object:${key}`;
|
|
32
|
+
}
|
|
33
|
+
return "text";
|
|
34
|
+
}
|
|
35
|
+
function buildAttrs(schema, type) {
|
|
36
|
+
const attrs = {};
|
|
37
|
+
if (type === "select") {
|
|
38
|
+
if (schema.enum) {
|
|
39
|
+
attrs.options = schema.enum.map((v) => ({
|
|
40
|
+
text: String(v),
|
|
41
|
+
value: v
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (type === "number") {
|
|
46
|
+
if (schema.minimum !== void 0) attrs.min = schema.minimum;
|
|
47
|
+
if (schema.maximum !== void 0) attrs.max = schema.maximum;
|
|
48
|
+
}
|
|
49
|
+
if (type === "text" || type === "textarea") {
|
|
50
|
+
if (schema.maxLength !== void 0) attrs.maxLength = schema.maxLength;
|
|
51
|
+
if (schema.minLength !== void 0) attrs.minLength = schema.minLength;
|
|
52
|
+
}
|
|
53
|
+
if (type === "fks" || type === "m2m") {
|
|
54
|
+
const ext = schema;
|
|
55
|
+
attrs.endpoint = ext.endpoint ?? ext.relation ?? "";
|
|
56
|
+
attrs.returnObject = true;
|
|
57
|
+
if (type === "m2m") attrs.multiple = true;
|
|
58
|
+
}
|
|
59
|
+
if (type === "date" || type === "datetime") {
|
|
60
|
+
if (schema.minimum) attrs.min = schema.minimum;
|
|
61
|
+
if (schema.maximum) attrs.max = schema.maximum;
|
|
62
|
+
}
|
|
63
|
+
return attrs;
|
|
64
|
+
}
|
|
65
|
+
function collectRules(schema) {
|
|
66
|
+
const rules = [];
|
|
67
|
+
function addRule(ifSchema, then, elseBranch) {
|
|
68
|
+
rules.push({
|
|
69
|
+
ifSchema,
|
|
70
|
+
thenKeys: new Set(Object.keys(then?.properties ?? {})),
|
|
71
|
+
elseKeys: new Set(Object.keys(elseBranch?.properties ?? {}))
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (schema.if) addRule(schema.if, schema.then, schema.else);
|
|
75
|
+
if (Array.isArray(schema.allOf)) {
|
|
76
|
+
for (const entry of schema.allOf) {
|
|
77
|
+
if (entry?.if) addRule(entry.if, entry.then, entry.else);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return rules;
|
|
81
|
+
}
|
|
82
|
+
function makeVisible(ifSchema, side) {
|
|
83
|
+
return ({ model }) => {
|
|
84
|
+
const matches = matchesSchema(model, ifSchema);
|
|
85
|
+
return side === "then" ? matches : !matches;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function propertyToDescriptor(key, rawSchema, defs, requiredKeys, rules) {
|
|
89
|
+
const schema = resolveSchema(rawSchema, defs);
|
|
90
|
+
const type = mapType(schema, key);
|
|
91
|
+
const attrs = buildAttrs(schema, type);
|
|
92
|
+
let visible;
|
|
93
|
+
for (const rule of rules) {
|
|
94
|
+
if (rule.thenKeys.has(key) && !rule.elseKeys.has(key)) {
|
|
95
|
+
visible = makeVisible(rule.ifSchema, "then");
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
if (rule.elseKeys.has(key) && !rule.thenKeys.has(key)) {
|
|
99
|
+
visible = makeVisible(rule.ifSchema, "else");
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const base = {
|
|
104
|
+
key,
|
|
105
|
+
type,
|
|
106
|
+
label: schema.title ?? void 0,
|
|
107
|
+
required: requiredKeys.has(key),
|
|
108
|
+
...schema.readOnly ? { readonly: true } : {},
|
|
109
|
+
...schema.translatable ? { translatable: true } : {},
|
|
110
|
+
...visible ? { visible } : {},
|
|
111
|
+
...Object.keys(attrs).length ? { attrs } : {}
|
|
112
|
+
};
|
|
113
|
+
if (type === "repeater" && schema.items) {
|
|
114
|
+
const itemSchema = resolveSchema(schema.items, defs);
|
|
115
|
+
const childRequired = new Set(itemSchema.required ?? []);
|
|
116
|
+
const childProps = itemSchema.properties ?? {};
|
|
117
|
+
const childRules = collectRules(itemSchema);
|
|
118
|
+
base.fields = Object.entries(childProps).map(
|
|
119
|
+
([k, v]) => propertyToDescriptor(k, v, defs, childRequired, childRules)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return base;
|
|
123
|
+
}
|
|
124
|
+
export function useFormFromSchema(schema, options = {}) {
|
|
125
|
+
const { exclude = [], overrides = {} } = options;
|
|
126
|
+
const defs = extractDefs(schema);
|
|
127
|
+
const properties = schema.properties ?? {};
|
|
128
|
+
const requiredKeys = new Set(schema.required ?? []);
|
|
129
|
+
const rules = collectRules(schema);
|
|
130
|
+
const descriptors = [];
|
|
131
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
132
|
+
if (exclude.includes(key)) continue;
|
|
133
|
+
const descriptor = propertyToDescriptor(
|
|
134
|
+
key,
|
|
135
|
+
rawProp,
|
|
136
|
+
defs,
|
|
137
|
+
requiredKeys,
|
|
138
|
+
rules
|
|
139
|
+
);
|
|
140
|
+
const override = overrides[key];
|
|
141
|
+
const final = override ? { ...descriptor, ...override } : descriptor;
|
|
142
|
+
descriptors.push(final);
|
|
143
|
+
}
|
|
144
|
+
return descriptors;
|
|
145
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type Ref, type MaybeRef } from "vue";
|
|
2
|
+
import type { AnyFieldDescriptor, FieldRegistry } from "../types/index.js";
|
|
3
|
+
/** Options accepted by `useMapoForm()`. */
|
|
4
|
+
export interface UseMapoFormOptions<T extends object> {
|
|
5
|
+
model: Ref<T>;
|
|
6
|
+
fields: MaybeRef<AnyFieldDescriptor<T>[]>;
|
|
7
|
+
errors?: Ref<Record<string, string[]>>;
|
|
8
|
+
languages?: string[];
|
|
9
|
+
currentLang?: Ref<string>;
|
|
10
|
+
immediate?: boolean;
|
|
11
|
+
/** Global debounce in milliseconds. Override per field with `descriptor.debounce`. Default: `300`. */
|
|
12
|
+
debounce?: number;
|
|
13
|
+
/**
|
|
14
|
+
* When `false`, suppresses the toast that lists server-side field errors.
|
|
15
|
+
* Set this for sub-forms (e.g. each repeater item) so they don't duplicate the
|
|
16
|
+
* root form's error toast. Default: `true`.
|
|
17
|
+
*/
|
|
18
|
+
notifyErrors?: boolean;
|
|
19
|
+
registry: FieldRegistry;
|
|
20
|
+
/**
|
|
21
|
+
* When set, enables automatic draft persistence to localStorage.
|
|
22
|
+
* Key is prefixed with `mapo:draft:`. Drafts expire after 24 h.
|
|
23
|
+
* Call the returned `checkDraft()` after the model is populated to offer restore.
|
|
24
|
+
* Example: `"article:42"` → `mapo:draft:article:42`.
|
|
25
|
+
*/
|
|
26
|
+
draftKey?: MaybeRef<string | undefined>;
|
|
27
|
+
}
|
|
28
|
+
/** State exposed when a pending draft is found in localStorage. */
|
|
29
|
+
export interface MapoDraftBanner {
|
|
30
|
+
savedAt: Date;
|
|
31
|
+
restore: () => void;
|
|
32
|
+
discard: () => void;
|
|
33
|
+
}
|
|
34
|
+
/** Reactive context shared with child form fields via provide/inject. */
|
|
35
|
+
export interface MapoFormContext<T extends object> {
|
|
36
|
+
model: Ref<T>;
|
|
37
|
+
fields: MaybeRef<AnyFieldDescriptor<T>[]>;
|
|
38
|
+
errors: Ref<Record<string, string[]>>;
|
|
39
|
+
languages: string[];
|
|
40
|
+
currentLang: Ref<string>;
|
|
41
|
+
readonly: Ref<boolean>;
|
|
42
|
+
immediate: boolean;
|
|
43
|
+
debounce: number;
|
|
44
|
+
registry: FieldRegistry;
|
|
45
|
+
setFieldValue: (key: string, val: unknown) => void;
|
|
46
|
+
getClientError: (key: string) => string | null;
|
|
47
|
+
markTouched: (key: string) => void;
|
|
48
|
+
isTouched: (key: string) => boolean;
|
|
49
|
+
submitted: Ref<boolean>;
|
|
50
|
+
}
|
|
51
|
+
/** Injection key for the current Mapo form context. */
|
|
52
|
+
export declare const FORM_KEY: unique symbol;
|
|
53
|
+
/** Injects the nearest form context, if present. */
|
|
54
|
+
export declare function injectMapoForm<T extends object>(): MapoFormContext<T> | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Creates the core reactive state and helpers for a Mapo form.
|
|
57
|
+
* Handles dirty tracking, field accessors, sync validation, and submit flows.
|
|
58
|
+
*/
|
|
59
|
+
export declare function useMapoForm<T extends object>(options: UseMapoFormOptions<T>): {
|
|
60
|
+
state: import("vue").ComputedRef<{
|
|
61
|
+
model: T;
|
|
62
|
+
errors: Record<string, string[]>;
|
|
63
|
+
isDirty: boolean;
|
|
64
|
+
currentLang: string;
|
|
65
|
+
isLoading: boolean;
|
|
66
|
+
}>;
|
|
67
|
+
isDirty: import("vue").ComputedRef<boolean>;
|
|
68
|
+
isLoading: Ref<boolean, boolean>;
|
|
69
|
+
readonly: Ref<boolean, boolean>;
|
|
70
|
+
currentLang: Ref<string, string>;
|
|
71
|
+
getFieldValue: (descriptor: AnyFieldDescriptor<T>) => unknown;
|
|
72
|
+
setFieldValue: (keyOrDescriptor: string | AnyFieldDescriptor<T>, val: unknown) => void;
|
|
73
|
+
getClientError: (keyOrDescriptor: string | AnyFieldDescriptor<T>) => string | null;
|
|
74
|
+
markTouched: (key: string) => void;
|
|
75
|
+
isTouched: (key: string) => boolean;
|
|
76
|
+
validateClient: () => {
|
|
77
|
+
valid: boolean;
|
|
78
|
+
errors: Record<string, string>;
|
|
79
|
+
};
|
|
80
|
+
resetDirty: () => void;
|
|
81
|
+
getPatch: () => Partial<T>;
|
|
82
|
+
submit: (handler: (model: T | Partial<T>, isNew: boolean) => Promise<void>, isNew?: boolean) => Promise<void>;
|
|
83
|
+
ctx: MapoFormContext<T>;
|
|
84
|
+
draftBanner: Ref<{
|
|
85
|
+
savedAt: Date;
|
|
86
|
+
restore: () => void;
|
|
87
|
+
discard: () => void;
|
|
88
|
+
} | null, MapoDraftBanner | {
|
|
89
|
+
savedAt: Date;
|
|
90
|
+
restore: () => void;
|
|
91
|
+
discard: () => void;
|
|
92
|
+
} | null>;
|
|
93
|
+
checkDraft: () => void;
|
|
94
|
+
clearDraft: () => void;
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=useMapoForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMapoForm.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useMapoForm.ts"],"names":[],"mappings":"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"}
|