@mapomodule/form 2.0.0-beta.1 → 2.0.0-beta.11
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 +37 -118
- 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__/tabVisibility.test.d.ts +2 -0
- package/dist/__tests__/tabVisibility.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 +177 -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 +122 -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 +93 -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 +111 -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 +258 -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/runtime/utils/tabVisibility.d.ts +18 -0
- package/dist/runtime/utils/tabVisibility.d.ts.map +1 -0
- package/dist/runtime/utils/tabVisibility.js +18 -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__/tabVisibility.test.ts +102 -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/MapoForm.vue +17 -4
- package/src/runtime/components/MapoFormTabs.vue +29 -6
- 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 +106 -0
- package/src/runtime/components/fields/MapoMediaM2mField.vue +131 -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 +55 -7
- 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/src/runtime/utils/tabVisibility.ts +44 -0
- package/dist/index.d.cts +0 -1
- package/dist/module.d.cts +0 -2
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, watch, onBeforeUnmount, resolveComponent } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
4
|
+
import { useEditor, EditorContent } from "@tiptap/vue-3";
|
|
5
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
6
|
+
import Link from "@tiptap/extension-link";
|
|
7
|
+
import Underline from "@tiptap/extension-underline";
|
|
8
|
+
import Image from "@tiptap/extension-image";
|
|
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 { t } = useI18n();
|
|
18
|
+
const extraExtensions = props.descriptor.attrs?.extensions ?? [];
|
|
19
|
+
const ALLOWED_LINK_PROTOCOLS = ["http", "https", "mailto", "tel"];
|
|
20
|
+
function isSafeUrl(url) {
|
|
21
|
+
try {
|
|
22
|
+
if (!/^[a-z][a-z0-9+.-]*:/i.test(url)) return true;
|
|
23
|
+
const proto = new URL(url, "https://placeholder.local").protocol.replace(":", "").toLowerCase();
|
|
24
|
+
return ALLOWED_LINK_PROTOCOLS.includes(proto);
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function sanitizeHtml(html) {
|
|
30
|
+
if (typeof window === "undefined" || !html) return html;
|
|
31
|
+
const tpl = document.createElement("template");
|
|
32
|
+
tpl.innerHTML = html;
|
|
33
|
+
const walker = document.createTreeWalker(
|
|
34
|
+
tpl.content,
|
|
35
|
+
NodeFilter.SHOW_ELEMENT
|
|
36
|
+
);
|
|
37
|
+
const toRemove = [];
|
|
38
|
+
let node = walker.nextNode();
|
|
39
|
+
while (node) {
|
|
40
|
+
if (node.tagName === "SCRIPT" || node.tagName === "STYLE") {
|
|
41
|
+
toRemove.push(node);
|
|
42
|
+
} else {
|
|
43
|
+
for (const attr of Array.from(node.attributes)) {
|
|
44
|
+
const name = attr.name.toLowerCase();
|
|
45
|
+
if (name.startsWith("on")) node.removeAttribute(attr.name);
|
|
46
|
+
if ((name === "href" || name === "src" || name === "xlink:href") && !isSafeUrl(attr.value)) {
|
|
47
|
+
node.removeAttribute(attr.name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
node = walker.nextNode();
|
|
52
|
+
}
|
|
53
|
+
toRemove.forEach((el) => el.remove());
|
|
54
|
+
return tpl.innerHTML;
|
|
55
|
+
}
|
|
56
|
+
const editor = useEditor({
|
|
57
|
+
content: sanitizeHtml(props.modelValue ?? ""),
|
|
58
|
+
editable: !props.readonly && !props.disabled,
|
|
59
|
+
extensions: [
|
|
60
|
+
StarterKit,
|
|
61
|
+
Link.configure({
|
|
62
|
+
openOnClick: false,
|
|
63
|
+
protocols: ALLOWED_LINK_PROTOCOLS,
|
|
64
|
+
validate: (href) => isSafeUrl(href)
|
|
65
|
+
}),
|
|
66
|
+
Underline,
|
|
67
|
+
Image.configure({ inline: false, allowBase64: false }),
|
|
68
|
+
...extraExtensions
|
|
69
|
+
],
|
|
70
|
+
onUpdate: ({ editor: e }) => {
|
|
71
|
+
emit("update:modelValue", e.getHTML());
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
watch(
|
|
75
|
+
() => [props.readonly, props.disabled],
|
|
76
|
+
([ro, dis]) => editor.value?.setEditable(!ro && !dis)
|
|
77
|
+
);
|
|
78
|
+
watch(
|
|
79
|
+
() => props.modelValue,
|
|
80
|
+
(val) => {
|
|
81
|
+
if (editor.value && val !== editor.value.getHTML()) {
|
|
82
|
+
editor.value.commands.setContent(
|
|
83
|
+
sanitizeHtml(val ?? ""),
|
|
84
|
+
false
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
onBeforeUnmount(() => editor.value?.destroy());
|
|
90
|
+
const imagePickerOpen = ref(false);
|
|
91
|
+
const MediaManagerDialog = resolveComponent("MapoMediaManagerDialog");
|
|
92
|
+
const hasMediaManager = typeof MediaManagerDialog !== "string";
|
|
93
|
+
function onImageConfirm(selection) {
|
|
94
|
+
const media = Array.isArray(selection) ? selection[0] : selection;
|
|
95
|
+
if (!media || !editor.value) return;
|
|
96
|
+
editor.value.chain().focus().setImage({ src: media.file, alt: media.alt_text ?? "" }).run();
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<!-- An input surface of its own, like the Nuxt UI inputs next to it
|
|
102
|
+
(`bg-default`, `accented` border): without it the text area takes the
|
|
103
|
+
page background and disappears on a tinted page. -->
|
|
104
|
+
<div
|
|
105
|
+
class="mapo-wyg-editor rounded-md border bg-default"
|
|
106
|
+
:class="[
|
|
107
|
+
errors?.length ? 'border-red-300' : 'border-accented',
|
|
108
|
+
readonly || disabled ? 'opacity-60' : ''
|
|
109
|
+
]"
|
|
110
|
+
>
|
|
111
|
+
<!-- Toolbar -->
|
|
112
|
+
<div
|
|
113
|
+
v-if="editor"
|
|
114
|
+
class="flex flex-wrap gap-1 border-b border-gray-200 bg-gray-50 px-2 py-1.5"
|
|
115
|
+
>
|
|
116
|
+
<UButton
|
|
117
|
+
size="xs"
|
|
118
|
+
variant="ghost"
|
|
119
|
+
color="neutral"
|
|
120
|
+
:disabled="readonly || disabled"
|
|
121
|
+
:class="{ 'bg-gray-200': editor.isActive('bold') }"
|
|
122
|
+
@click="editor.chain().focus().toggleBold().run()"
|
|
123
|
+
>
|
|
124
|
+
<strong>B</strong>
|
|
125
|
+
</UButton>
|
|
126
|
+
<UButton
|
|
127
|
+
size="xs"
|
|
128
|
+
variant="ghost"
|
|
129
|
+
color="neutral"
|
|
130
|
+
:disabled="readonly || disabled"
|
|
131
|
+
:class="{ 'bg-gray-200': editor.isActive('italic') }"
|
|
132
|
+
@click="editor.chain().focus().toggleItalic().run()"
|
|
133
|
+
>
|
|
134
|
+
<em>I</em>
|
|
135
|
+
</UButton>
|
|
136
|
+
<UButton
|
|
137
|
+
size="xs"
|
|
138
|
+
variant="ghost"
|
|
139
|
+
color="neutral"
|
|
140
|
+
:disabled="readonly || disabled"
|
|
141
|
+
:class="{ 'bg-gray-200': editor.isActive('underline') }"
|
|
142
|
+
@click="editor.chain().focus().toggleUnderline().run()"
|
|
143
|
+
>
|
|
144
|
+
<span class="underline">U</span>
|
|
145
|
+
</UButton>
|
|
146
|
+
|
|
147
|
+
<USeparator orientation="vertical" class="mx-1 h-5" />
|
|
148
|
+
|
|
149
|
+
<UButton
|
|
150
|
+
size="xs"
|
|
151
|
+
variant="ghost"
|
|
152
|
+
color="neutral"
|
|
153
|
+
:disabled="readonly || disabled"
|
|
154
|
+
:class="{ 'bg-gray-200': editor.isActive('heading', { level: 2 }) }"
|
|
155
|
+
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
|
|
156
|
+
>
|
|
157
|
+
H2
|
|
158
|
+
</UButton>
|
|
159
|
+
<UButton
|
|
160
|
+
size="xs"
|
|
161
|
+
variant="ghost"
|
|
162
|
+
color="neutral"
|
|
163
|
+
:disabled="readonly || disabled"
|
|
164
|
+
:class="{ 'bg-gray-200': editor.isActive('heading', { level: 3 }) }"
|
|
165
|
+
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
|
|
166
|
+
>
|
|
167
|
+
H3
|
|
168
|
+
</UButton>
|
|
169
|
+
|
|
170
|
+
<USeparator orientation="vertical" class="mx-1 h-5" />
|
|
171
|
+
|
|
172
|
+
<UButton
|
|
173
|
+
size="xs"
|
|
174
|
+
variant="ghost"
|
|
175
|
+
color="neutral"
|
|
176
|
+
:disabled="readonly || disabled"
|
|
177
|
+
:class="{ 'bg-gray-200': editor.isActive('bulletList') }"
|
|
178
|
+
icon="i-lucide-list"
|
|
179
|
+
@click="editor.chain().focus().toggleBulletList().run()"
|
|
180
|
+
/>
|
|
181
|
+
<UButton
|
|
182
|
+
size="xs"
|
|
183
|
+
variant="ghost"
|
|
184
|
+
color="neutral"
|
|
185
|
+
:disabled="readonly || disabled"
|
|
186
|
+
:class="{ 'bg-gray-200': editor.isActive('orderedList') }"
|
|
187
|
+
icon="i-lucide-list-ordered"
|
|
188
|
+
@click="editor.chain().focus().toggleOrderedList().run()"
|
|
189
|
+
/>
|
|
190
|
+
|
|
191
|
+
<USeparator orientation="vertical" class="mx-1 h-5" />
|
|
192
|
+
|
|
193
|
+
<UButton
|
|
194
|
+
size="xs"
|
|
195
|
+
variant="ghost"
|
|
196
|
+
color="neutral"
|
|
197
|
+
:disabled="readonly || disabled"
|
|
198
|
+
icon="i-lucide-undo"
|
|
199
|
+
@click="editor.chain().focus().undo().run()"
|
|
200
|
+
/>
|
|
201
|
+
<UButton
|
|
202
|
+
size="xs"
|
|
203
|
+
variant="ghost"
|
|
204
|
+
color="neutral"
|
|
205
|
+
:disabled="readonly || disabled"
|
|
206
|
+
icon="i-lucide-redo"
|
|
207
|
+
@click="editor.chain().focus().redo().run()"
|
|
208
|
+
/>
|
|
209
|
+
|
|
210
|
+
<USeparator orientation="vertical" class="mx-1 h-5" />
|
|
211
|
+
|
|
212
|
+
<!-- Insert Image via Media Manager (available when @mapomodule/uikit is installed) -->
|
|
213
|
+
<UTooltip
|
|
214
|
+
:text="
|
|
215
|
+
hasMediaManager ? t('mapo.wygEditor.insertImage') : t('mapo.wygEditor.requiresUikit')
|
|
216
|
+
"
|
|
217
|
+
:delay-open="300"
|
|
218
|
+
>
|
|
219
|
+
<UButton
|
|
220
|
+
size="xs"
|
|
221
|
+
variant="ghost"
|
|
222
|
+
color="neutral"
|
|
223
|
+
icon="i-lucide-image"
|
|
224
|
+
:disabled="readonly || disabled || !hasMediaManager"
|
|
225
|
+
@click="imagePickerOpen = true"
|
|
226
|
+
/>
|
|
227
|
+
</UTooltip>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<!-- Editor content -->
|
|
231
|
+
<EditorContent
|
|
232
|
+
:editor="editor"
|
|
233
|
+
class="prose prose-sm max-w-none px-3 py-2 focus-within:outline-none"
|
|
234
|
+
/>
|
|
235
|
+
|
|
236
|
+
<p
|
|
237
|
+
v-for="err in errors"
|
|
238
|
+
:key="err"
|
|
239
|
+
class="px-3 pb-2 text-sm text-red-500"
|
|
240
|
+
role="alert"
|
|
241
|
+
>
|
|
242
|
+
{{ err }}
|
|
243
|
+
</p>
|
|
244
|
+
|
|
245
|
+
<!-- Image picker dialog — only rendered when MapoMediaManagerDialog is
|
|
246
|
+
globally available. Inside the root on purpose: a second root would
|
|
247
|
+
drop what MapoFormField passes down (`flex-1`, `id`, `style`), and the
|
|
248
|
+
editor would shrink to its toolbar. As a modal it renders in <body>. -->
|
|
249
|
+
<component
|
|
250
|
+
:is="MediaManagerDialog"
|
|
251
|
+
v-if="hasMediaManager"
|
|
252
|
+
v-model="imagePickerOpen"
|
|
253
|
+
selection-mode="single"
|
|
254
|
+
mime="image/*"
|
|
255
|
+
@confirm="onImageConfirm"
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EditorDescriptor } from "../../types/index.js";
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
modelValue: unknown;
|
|
4
|
+
descriptor: EditorDescriptor;
|
|
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: string) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
13
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
|
+
declare const _default: typeof __VLS_export;
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=MapoWygEditor.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapoWygEditor.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoWygEditor.vue"],"names":[],"mappings":"AA2TA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA6pBF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
4
|
+
//# sourceMappingURL=NuiCheckbox.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NuiCheckbox.vue.d.ts","sourceRoot":"","sources":["../../../../../src/runtime/components/fields/nui/NuiCheckbox.vue"],"names":[],"mappings":"AA6CA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
4
|
+
//# sourceMappingURL=NuiInput.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NuiInput.vue.d.ts","sourceRoot":"","sources":["../../../../../src/runtime/components/fields/nui/NuiInput.vue"],"names":[],"mappings":"AA6CA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useNuiFieldAttrs } from "./useNuiFieldAttrs";
|
|
4
|
+
defineOptions({ inheritAttrs: false });
|
|
5
|
+
const rawAttrs = useNuiFieldAttrs();
|
|
6
|
+
const attrs = computed(() => {
|
|
7
|
+
const { options, items, ...rest } = rawAttrs.value;
|
|
8
|
+
const list = items ?? options;
|
|
9
|
+
const normalised = list?.map(
|
|
10
|
+
(it) => typeof it === "object" && it !== null ? { ...it, text: it.text ?? it.label ?? String(it.value) } : it
|
|
11
|
+
);
|
|
12
|
+
return { ...rest, items: normalised };
|
|
13
|
+
});
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<USelectMenu v-bind="attrs" />
|
|
18
|
+
</template>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
4
|
+
//# sourceMappingURL=NuiSelectMenu.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NuiSelectMenu.vue.d.ts","sourceRoot":"","sources":["../../../../../src/runtime/components/fields/nui/NuiSelectMenu.vue"],"names":[],"mappings":"AAmFA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
4
|
+
//# sourceMappingURL=NuiSlider.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NuiSlider.vue.d.ts","sourceRoot":"","sources":["../../../../../src/runtime/components/fields/nui/NuiSlider.vue"],"names":[],"mappings":"AA6CA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
4
|
+
//# sourceMappingURL=NuiSwitch.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NuiSwitch.vue.d.ts","sourceRoot":"","sources":["../../../../../src/runtime/components/fields/nui/NuiSwitch.vue"],"names":[],"mappings":"AA6CA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
4
|
+
//# sourceMappingURL=NuiTextarea.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NuiTextarea.vue.d.ts","sourceRoot":"","sources":["../../../../../src/runtime/components/fields/nui/NuiTextarea.vue"],"names":[],"mappings":"AA6CA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ComputedRef } from "vue";
|
|
2
|
+
/**
|
|
3
|
+
* Shared logic for NUI wrappers: strips internal props (`descriptor`, `errors`)
|
|
4
|
+
* from the binding passed to the underlying NUI component and derives visual
|
|
5
|
+
* error flags (`color: 'error'` + `aria-invalid`).
|
|
6
|
+
*
|
|
7
|
+
* Note: NUI v3 form components accept `color="error"` for the red border.
|
|
8
|
+
* We apply it here as a fallback when the caller has not set it already,
|
|
9
|
+
* so consumers that do not go through `MapoFormField` still get the correct
|
|
10
|
+
* color when `errors` is non-empty.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useNuiFieldAttrs(): ComputedRef<Record<string, unknown>>;
|
|
13
|
+
//# sourceMappingURL=useNuiFieldAttrs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useNuiFieldAttrs.d.ts","sourceRoot":"","sources":["../../../../../src/runtime/components/fields/nui/useNuiFieldAttrs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,KAAK,CAAC;AAE3D;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiBvE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { computed, useAttrs } from "vue";
|
|
2
|
+
export function useNuiFieldAttrs() {
|
|
3
|
+
const rawAttrs = useAttrs();
|
|
4
|
+
return computed(() => {
|
|
5
|
+
const {
|
|
6
|
+
descriptor: _d,
|
|
7
|
+
errors,
|
|
8
|
+
color,
|
|
9
|
+
...rest
|
|
10
|
+
} = rawAttrs;
|
|
11
|
+
const errs = errors ?? [];
|
|
12
|
+
const hasError = errs.length > 0;
|
|
13
|
+
return {
|
|
14
|
+
...rest,
|
|
15
|
+
color: color ?? (hasError ? "error" : void 0),
|
|
16
|
+
"aria-invalid": hasError ? "true" : void 0
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FieldComponentEntry, FieldAccessor } from "../types/index.js";
|
|
2
|
+
/** Options for registering or overriding a custom field type. */
|
|
3
|
+
export interface DefineFormFieldOptions {
|
|
4
|
+
/** Default attrs applied to all descriptors of this `type`. */
|
|
5
|
+
attrs?: Record<string, unknown>;
|
|
6
|
+
/** Default get/set accessor for this `type`. */
|
|
7
|
+
accessor?: FieldAccessor;
|
|
8
|
+
/** Replaces an existing registration without warning. */
|
|
9
|
+
override?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Typed API for registering a custom field component.
|
|
13
|
+
* Must be called from a Nuxt plugin (the load order guarantees that `$mapoFormRegistry`
|
|
14
|
+
* is already present — the `@mapomodule/form` plugin is loaded first).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // app/plugins/my-fields.ts
|
|
18
|
+
* export default defineNuxtPlugin(() => {
|
|
19
|
+
* defineFormField('rich-text', () => import('~/components/RichText.vue'), {
|
|
20
|
+
* attrs: { rows: 6 },
|
|
21
|
+
* })
|
|
22
|
+
* })
|
|
23
|
+
*/
|
|
24
|
+
export declare function defineFormField(type: string, entry: FieldComponentEntry, opts?: DefineFormFieldOptions): void;
|
|
25
|
+
//# sourceMappingURL=defineFormField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defineFormField.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/defineFormField.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EAEd,MAAM,mBAAmB,CAAC;AAE3B,iEAAiE;AACjE,MAAM,WAAW,sBAAsB;IACrC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,gDAAgD;IAChD,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,mBAAmB,EAC1B,IAAI,GAAE,sBAA2B,GAChC,IAAI,CA2BN"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useNuxtApp } from "#app";
|
|
2
|
+
export function defineFormField(type, entry, opts = {}) {
|
|
3
|
+
const nuxt = useNuxtApp();
|
|
4
|
+
const registry = nuxt.$mapoFormRegistry;
|
|
5
|
+
if (!registry) {
|
|
6
|
+
if (import.meta.dev) {
|
|
7
|
+
console.warn(
|
|
8
|
+
`[Mapo] defineFormField('${type}') called before $mapoFormRegistry is available. Make sure the plugin is loaded after @mapomodule/form.`
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (registry.mapping[type] && !opts.override) {
|
|
14
|
+
if (import.meta.dev) {
|
|
15
|
+
console.warn(
|
|
16
|
+
`[Mapo] defineFormField: type "${type}" already registered. Pass { override: true } to replace it intentionally.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
registry.mapping[type] = entry;
|
|
21
|
+
if (opts.attrs) {
|
|
22
|
+
registry.attrs[type] = { ...registry.attrs[type] ?? {}, ...opts.attrs };
|
|
23
|
+
}
|
|
24
|
+
if (opts.accessor) {
|
|
25
|
+
registry.accessor[type] = opts.accessor;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Ref } from "vue";
|
|
2
|
+
/** Provides the current form language to descendant components. */
|
|
3
|
+
export declare function provideCurrentLang(lang: Ref<string>): void;
|
|
4
|
+
/** Returns the current form language, falling back to the provided ref when needed. */
|
|
5
|
+
export declare function useCurrentLang(fallback?: Ref<string>): Ref<string>;
|
|
6
|
+
//# sourceMappingURL=useCurrentLang.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCurrentLang.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useCurrentLang.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAIzE,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,QAEnD;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAKlE"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getCurrentInstance, inject, provide, ref } from "vue";
|
|
2
|
+
const LANG_KEY = Symbol("mapoCurrentLang");
|
|
3
|
+
export function provideCurrentLang(lang) {
|
|
4
|
+
provide(LANG_KEY, lang);
|
|
5
|
+
}
|
|
6
|
+
export function useCurrentLang(fallback) {
|
|
7
|
+
if (!getCurrentInstance()) return fallback ?? ref("");
|
|
8
|
+
return inject(LANG_KEY, fallback ?? ref(""));
|
|
9
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Ref, type DeepReadonly, type ShallowRef } from "vue";
|
|
2
|
+
import type { AnyFieldDescriptor, FieldRegistry } from "../types/index.js";
|
|
3
|
+
/** Describes the currently focused repeater item in focus mode. */
|
|
4
|
+
export interface FocusTarget {
|
|
5
|
+
/** Descriptor of the repeater field that contains the focused item. */
|
|
6
|
+
descriptor: AnyFieldDescriptor;
|
|
7
|
+
/** Fields for the focused item, resolved from the template when needed. */
|
|
8
|
+
fields: AnyFieldDescriptor[];
|
|
9
|
+
/**
|
|
10
|
+
* Initial snapshot of the item model at the time the portal was opened.
|
|
11
|
+
* Stored as a plain object (not a Ref) to avoid Vue's auto-unwrap behaviour
|
|
12
|
+
* when placed inside Nuxt's useState reactive container.
|
|
13
|
+
*/
|
|
14
|
+
model: Record<string, unknown>;
|
|
15
|
+
/** Errors for this item (already filtered). */
|
|
16
|
+
errors: Ref<Record<string, string[]>>;
|
|
17
|
+
/** Callback invoked when the item model is updated. */
|
|
18
|
+
onUpdate: (val: Record<string, unknown>) => void;
|
|
19
|
+
/** Field component registry passed to the sub-form. */
|
|
20
|
+
registry: FieldRegistry;
|
|
21
|
+
/** Available language codes. */
|
|
22
|
+
languages: string[];
|
|
23
|
+
/** Currently active language code. */
|
|
24
|
+
currentLang: string;
|
|
25
|
+
/** Visual breadcrumb trail: ['Article', 'Blocks', 'Block 3'] */
|
|
26
|
+
breadcrumb: string[];
|
|
27
|
+
}
|
|
28
|
+
/** Shared focus-mode API used by nested repeater editors. */
|
|
29
|
+
export interface FocusModeReturn {
|
|
30
|
+
focusTarget: DeepReadonly<ShallowRef<FocusTarget | null>>;
|
|
31
|
+
isActive: Ref<boolean>;
|
|
32
|
+
enter: (target: FocusTarget) => void;
|
|
33
|
+
exit: () => void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Exposes a shared focus-mode state for nested repeater items.
|
|
37
|
+
* In Nuxt it uses `useState()` for SSR-safe request isolation.
|
|
38
|
+
*/
|
|
39
|
+
export declare function useFocusMode(): FocusModeReturn;
|
|
40
|
+
//# sourceMappingURL=useFocusMode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFocusMode.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useFocusMode.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,GAAG,EACR,KAAK,YAAY,EACjB,KAAK,UAAU,EAChB,MAAM,KAAK,CAAC;AACb,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE3E,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,UAAU,EAAE,kBAAkB,CAAC;IAC/B,2EAA2E;IAC3E,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,+CAA+C;IAC/C,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACtC,uDAAuD;IACvD,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACjD,uDAAuD;IACvD,QAAQ,EAAE,aAAa,CAAC;IACxB,gCAAgC;IAChC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,YAAY,CAAC,UAAU,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC;IAC1D,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACvB,KAAK,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACrC,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAqBD;;;GAGG;AACH,wBAAgB,YAAY,IAAI,eAAe,CAkB9C"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
readonly,
|
|
4
|
+
shallowRef
|
|
5
|
+
} from "vue";
|
|
6
|
+
const _clientFallback = shallowRef(null);
|
|
7
|
+
function getStateRef() {
|
|
8
|
+
const nuxtUseState = globalThis.useState;
|
|
9
|
+
if (typeof nuxtUseState === "function") {
|
|
10
|
+
return nuxtUseState(
|
|
11
|
+
"mapo:form:focusTarget",
|
|
12
|
+
() => null
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
return _clientFallback;
|
|
16
|
+
}
|
|
17
|
+
export function useFocusMode() {
|
|
18
|
+
const state = getStateRef();
|
|
19
|
+
function enter(target) {
|
|
20
|
+
state.value = target;
|
|
21
|
+
}
|
|
22
|
+
function exit() {
|
|
23
|
+
state.value = null;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
focusTarget: readonly(state),
|
|
27
|
+
isActive: computed(() => state.value !== null),
|
|
28
|
+
enter,
|
|
29
|
+
exit
|
|
30
|
+
};
|
|
31
|
+
}
|