@cloudcannon/editable-regions 0.0.20-rc.3 → 0.0.21
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 +885 -0
- package/helpers/cloudcannon.mjs +20 -9
- package/helpers/hydrate-editable-regions.ts +36 -3
- package/integrations/eleventy/index.mjs +13 -3
- package/integrations/hugo/browser/errors.ts +8 -2
- package/integrations/hugo/renderer/main.go +3 -1
- package/integrations/react.mjs +63 -1
- package/integrations/svelte/EditableRegions.svelte +80 -0
- package/integrations/{svelte.mjs → svelte/index.mjs} +3 -1
- package/integrations/vue.mjs +97 -1
- package/nodes/editable-array-item.ts +5 -1
- package/nodes/editable-component.ts +10 -4
- package/nodes/editable-image.ts +80 -75
- package/nodes/editable.ts +28 -5
- package/package.json +20 -18
- package/types/react.d.ts +20 -6
- package/types/svelte.d.ts +10 -0
- package/types/vue.d.ts +14 -1
- package/integrations/liquid/README.md +0 -677
package/helpers/cloudcannon.mjs
CHANGED
|
@@ -14,24 +14,26 @@
|
|
|
14
14
|
* }} ExtendedWindow
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
/** @type {ExtendedWindow} */
|
|
18
|
-
const extendedWindow = /** @type {any} */ (
|
|
17
|
+
/** @type {ExtendedWindow | undefined} */
|
|
18
|
+
const extendedWindow = /** @type {any} */ (
|
|
19
|
+
typeof window === "undefined" ? undefined : window
|
|
20
|
+
);
|
|
19
21
|
|
|
20
|
-
/** @type {CloudCannonVisualEditorAPIV1} */
|
|
22
|
+
/** @type {CloudCannonVisualEditorAPIV1 | undefined} */
|
|
21
23
|
let _cloudcannon;
|
|
22
24
|
|
|
23
25
|
/** @type {Promise<void>} */
|
|
24
26
|
export const apiLoadedPromise = new Promise((resolve) => {
|
|
25
|
-
if (extendedWindow
|
|
27
|
+
if (extendedWindow?.CloudCannonAPI) {
|
|
26
28
|
_cloudcannon = /** @type {any} */ (
|
|
27
29
|
extendedWindow.CloudCannonAPI.useVersion("v1", true)
|
|
28
30
|
);
|
|
29
31
|
resolve();
|
|
30
|
-
} else {
|
|
32
|
+
} else if (typeof document !== "undefined") {
|
|
31
33
|
document.addEventListener(
|
|
32
34
|
"cloudcannon:load",
|
|
33
35
|
() => {
|
|
34
|
-
if (extendedWindow
|
|
36
|
+
if (extendedWindow?.CloudCannonAPI) {
|
|
35
37
|
_cloudcannon = /** @type {any} */ (
|
|
36
38
|
extendedWindow.CloudCannonAPI.useVersion("v1", true)
|
|
37
39
|
);
|
|
@@ -48,6 +50,9 @@ export const apiLoadedPromise = new Promise((resolve) => {
|
|
|
48
50
|
* @param {ComponentRenderer} renderer
|
|
49
51
|
*/
|
|
50
52
|
export const addEditableComponentRenderer = (key, renderer) => {
|
|
53
|
+
if (!extendedWindow) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
51
56
|
extendedWindow.cc_components = extendedWindow.cc_components || {};
|
|
52
57
|
extendedWindow.cc_components[key] = renderer;
|
|
53
58
|
};
|
|
@@ -57,15 +62,18 @@ export const addEditableComponentRenderer = (key, renderer) => {
|
|
|
57
62
|
* @param {ComponentRenderer} renderer
|
|
58
63
|
*/
|
|
59
64
|
export const addEditableSnippetRenderer = (key, renderer) => {
|
|
65
|
+
if (!extendedWindow) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
60
68
|
extendedWindow.cc_snippets = extendedWindow.cc_snippets || {};
|
|
61
69
|
extendedWindow.cc_snippets[key] = renderer;
|
|
62
70
|
};
|
|
63
71
|
|
|
64
72
|
export const getEditableComponentRenderers = () =>
|
|
65
|
-
extendedWindow
|
|
73
|
+
extendedWindow?.cc_components ?? {};
|
|
66
74
|
|
|
67
75
|
export const getEditableSnippetRenderers = () =>
|
|
68
|
-
extendedWindow
|
|
76
|
+
extendedWindow?.cc_snippets ?? {};
|
|
69
77
|
|
|
70
78
|
/**
|
|
71
79
|
* Resolves CloudCannon API objects (collections, files, datasets) to plain data.
|
|
@@ -91,13 +99,16 @@ export const realizeAPIValue = async (value) => {
|
|
|
91
99
|
};
|
|
92
100
|
|
|
93
101
|
export const addCustomEditableRegion = (key, region) => {
|
|
102
|
+
if (!extendedWindow || typeof document === "undefined") {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
94
105
|
extendedWindow.editableRegionMap ??= {};
|
|
95
106
|
extendedWindow.editableRegionMap[key] = region;
|
|
96
107
|
extendedWindow.hydrateDataEditableRegions(document.body);
|
|
97
108
|
};
|
|
98
109
|
|
|
99
110
|
export const getCustomEditableRegions = () => {
|
|
100
|
-
return extendedWindow
|
|
111
|
+
return extendedWindow?.editableRegionMap ?? {};
|
|
101
112
|
};
|
|
102
113
|
|
|
103
114
|
export { _cloudcannon as CloudCannon };
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
import { hasEditable } from "../nodes/editable.js";
|
|
11
11
|
import { isEditableWebcomponent } from "./checks";
|
|
12
12
|
|
|
13
|
+
const DYNAMIC_EDITABLE_TYPE = "_dynamic";
|
|
14
|
+
|
|
13
15
|
const baseEditableMap: Record<string, typeof Editable | undefined> = {
|
|
14
16
|
array: EditableArray,
|
|
15
17
|
"array-item": EditableArrayItem,
|
|
@@ -20,12 +22,23 @@ const baseEditableMap: Record<string, typeof Editable | undefined> = {
|
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
export const dehydrateDataEditableRegions = (root: Element) => {
|
|
23
|
-
if (
|
|
25
|
+
if (
|
|
26
|
+
root instanceof HTMLElement &&
|
|
27
|
+
hasEditable(root) &&
|
|
28
|
+
root.dataset.editable !== DYNAMIC_EDITABLE_TYPE
|
|
29
|
+
) {
|
|
24
30
|
root.editable.disconnect();
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
root.querySelectorAll("[data-editable]").forEach((element) => {
|
|
28
|
-
if (
|
|
34
|
+
if (
|
|
35
|
+
!(element instanceof HTMLElement) ||
|
|
36
|
+
element.dataset.editable === DYNAMIC_EDITABLE_TYPE
|
|
37
|
+
) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (hasEditable(element)) {
|
|
29
42
|
element.editable.disconnect();
|
|
30
43
|
}
|
|
31
44
|
});
|
|
@@ -39,6 +52,7 @@ export const hydrateDataEditableRegions = (root: Element) => {
|
|
|
39
52
|
if (
|
|
40
53
|
root instanceof HTMLElement &&
|
|
41
54
|
root.dataset.editable &&
|
|
55
|
+
root.dataset.editable !== DYNAMIC_EDITABLE_TYPE &&
|
|
42
56
|
!isEditableWebcomponent(root)
|
|
43
57
|
) {
|
|
44
58
|
if ("editable" in root && root.editable instanceof Editable) {
|
|
@@ -64,6 +78,10 @@ export const hydrateDataEditableRegions = (root: Element) => {
|
|
|
64
78
|
return;
|
|
65
79
|
}
|
|
66
80
|
|
|
81
|
+
if (element.dataset.editable === DYNAMIC_EDITABLE_TYPE) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
67
85
|
const Editable = editableMap[element.dataset.editable];
|
|
68
86
|
if (!Editable) {
|
|
69
87
|
const error = document.createElement("editable-region-error-card");
|
|
@@ -95,4 +113,19 @@ export const hydrateDataEditableRegions = (root: Element) => {
|
|
|
95
113
|
});
|
|
96
114
|
};
|
|
97
115
|
|
|
98
|
-
|
|
116
|
+
const extendedWindow = /** @type {any} */ (window);
|
|
117
|
+
|
|
118
|
+
Object.assign(extendedWindow, {
|
|
119
|
+
editableRegions: {
|
|
120
|
+
Editable,
|
|
121
|
+
EditableArray,
|
|
122
|
+
EditableArrayItem,
|
|
123
|
+
EditableComponent,
|
|
124
|
+
EditableImage,
|
|
125
|
+
EditableSource,
|
|
126
|
+
EditableText,
|
|
127
|
+
},
|
|
128
|
+
hydrateDataEditableRegions,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
document.dispatchEvent(new CustomEvent("editable-regions:load"));
|
|
@@ -368,9 +368,10 @@ async function generateLiveEditingSource(
|
|
|
368
368
|
|
|
369
369
|
for (const [i, filePath] of allLiquidFiles.entries()) {
|
|
370
370
|
const id = `liquidFile_${i}`;
|
|
371
|
-
|
|
371
|
+
const importPath = toPosixPath(filePath);
|
|
372
|
+
source += `import ${id} from ${JSON.stringify(`./${importPath}`)};
|
|
372
373
|
|
|
373
|
-
window.cc_liquid_files[
|
|
374
|
+
window.cc_liquid_files[${JSON.stringify(importPath)}] = ${id};
|
|
374
375
|
`;
|
|
375
376
|
}
|
|
376
377
|
|
|
@@ -537,6 +538,14 @@ async function findAllLiquidFiles(
|
|
|
537
538
|
return allFiles;
|
|
538
539
|
}
|
|
539
540
|
|
|
541
|
+
/**
|
|
542
|
+
* @param {string} filePath
|
|
543
|
+
* @returns {string}
|
|
544
|
+
*/
|
|
545
|
+
function toPosixPath(filePath) {
|
|
546
|
+
return filePath.split(path.sep).join("/");
|
|
547
|
+
}
|
|
548
|
+
|
|
540
549
|
/**
|
|
541
550
|
* @param {Object} options
|
|
542
551
|
* @param {string} options.directory
|
|
@@ -625,7 +634,8 @@ function emitImportRegistrations(liquidOptions) {
|
|
|
625
634
|
liquidOptions?.[optionKey] ?? {},
|
|
626
635
|
).entries()) {
|
|
627
636
|
const id = `${optionKey}_${i}`;
|
|
628
|
-
|
|
637
|
+
const importPath = toPosixPath(String(file));
|
|
638
|
+
imports += `\nimport ${id} from ${JSON.stringify(`./${importPath}`)};\n`;
|
|
629
639
|
body += `${registerFn}(${JSON.stringify(name)}, ${id});\n`;
|
|
630
640
|
}
|
|
631
641
|
}
|
|
@@ -43,7 +43,13 @@ export function parseHugoTemplateError(raw: string): ParsedHugoError {
|
|
|
43
43
|
return { frames, call, message: rest };
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
/** An error enhanced with an optional editor-context hint. */
|
|
47
|
+
export type HugoError = Error & { hint?: string };
|
|
48
|
+
|
|
49
|
+
export function enhanceHugoError(
|
|
50
|
+
message: string,
|
|
51
|
+
componentKey: string,
|
|
52
|
+
): HugoError {
|
|
47
53
|
const parsed = parseHugoTemplateError(message);
|
|
48
54
|
|
|
49
55
|
let hint = "";
|
|
@@ -76,7 +82,7 @@ export function enhanceHugoError(message: string, componentKey: string): Error {
|
|
|
76
82
|
|
|
77
83
|
const error = new Error(
|
|
78
84
|
`Failed to render Hugo component "${componentKey}": ${sentence}`,
|
|
79
|
-
) as
|
|
85
|
+
) as HugoError;
|
|
80
86
|
|
|
81
87
|
if (hint) {
|
|
82
88
|
error.hint = hint;
|
|
@@ -334,7 +334,9 @@ func (builder *editorSiteBuilder) targetPage(target string) page.Page {
|
|
|
334
334
|
if f == nil {
|
|
335
335
|
continue
|
|
336
336
|
}
|
|
337
|
-
|
|
337
|
+
// `target` is slash-normalized; `filepath.Join` uses the OS
|
|
338
|
+
// separator, so normalize it too or Windows never matches.
|
|
339
|
+
if filepath.ToSlash(filepath.Join(builder.Cfg.Base.ContentDir, f.Path())) != target {
|
|
338
340
|
continue
|
|
339
341
|
}
|
|
340
342
|
return p
|
package/integrations/react.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createElement, useEffect, useRef } from "react";
|
|
2
4
|
import { flushSync } from "react-dom";
|
|
3
5
|
import { createRoot } from "react-dom/client";
|
|
4
6
|
import { addEditableComponentRenderer } from "../helpers/cloudcannon.mjs";
|
|
@@ -26,3 +28,63 @@ export const registerReactComponent = (key, component) => {
|
|
|
26
28
|
|
|
27
29
|
addEditableComponentRenderer(key, wrappedComponent);
|
|
28
30
|
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Root editable region component for React (e.g. Next.js App Router pages).
|
|
34
|
+
* Keeps editable regions from mutating the DOM before frameworks like Next.js finish hydrating.
|
|
35
|
+
*
|
|
36
|
+
* @param {{ tag?: string } & Record<string, any>} props
|
|
37
|
+
* All other props and attributes are forwarded to the rendered element.
|
|
38
|
+
*/
|
|
39
|
+
export const EditableRegions = ({ tag: Tag = "div", ...attrs }) => {
|
|
40
|
+
const elementRef = useRef(null);
|
|
41
|
+
/** @type {{ current: any | null }} */
|
|
42
|
+
const editableRef = useRef(null);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
/** @param {any} regions */
|
|
46
|
+
const connectEditable = (regions) => {
|
|
47
|
+
if (!regions || editableRef.current) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
editableRef.current = new regions.Editable(elementRef.current);
|
|
52
|
+
editableRef.current.connect();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const onLoad = () => {
|
|
56
|
+
connectEditable(/** @type {any} */ (window).editableRegions);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (/** @type {any} */ (window).editableRegions) {
|
|
60
|
+
connectEditable(/** @type {any} */ (window).editableRegions);
|
|
61
|
+
} else {
|
|
62
|
+
document.addEventListener("editable-regions:load", onLoad, {
|
|
63
|
+
once: true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return () => {
|
|
68
|
+
document.removeEventListener("editable-regions:load", onLoad);
|
|
69
|
+
editableRef.current?.disconnect();
|
|
70
|
+
editableRef.current = null;
|
|
71
|
+
};
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
/** @type {Record<string, any>} */
|
|
75
|
+
const forwarded = {};
|
|
76
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
77
|
+
if (/^data-(prop|literal)/i.test(name)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
forwarded[name] = value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return createElement(Tag, {
|
|
85
|
+
...forwarded,
|
|
86
|
+
ref: elementRef,
|
|
87
|
+
"data-editable": "_dynamic",
|
|
88
|
+
"data-prop": "",
|
|
89
|
+
});
|
|
90
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { onDestroy, onMount } from "svelte";
|
|
3
|
+
|
|
4
|
+
// Element type to render. Defaults to "div".
|
|
5
|
+
/** @type {string | undefined} */
|
|
6
|
+
export let tag = "div";
|
|
7
|
+
|
|
8
|
+
/** @type {any} */
|
|
9
|
+
let editable = null;
|
|
10
|
+
/** @type {HTMLElement | undefined} */
|
|
11
|
+
let element;
|
|
12
|
+
/** @type {(() => void) | null} */
|
|
13
|
+
let pendingLoadHandler = null;
|
|
14
|
+
|
|
15
|
+
// data-prop and data-literal attributes are controlled by this component
|
|
16
|
+
// itself; user-supplied ones are ignored.
|
|
17
|
+
$: restProps = filterProps($$restProps);
|
|
18
|
+
|
|
19
|
+
function filterProps(props) {
|
|
20
|
+
const filtered = {};
|
|
21
|
+
for (const [name, value] of Object.entries(props)) {
|
|
22
|
+
if (/^data-(prop|literal)/i.test(name)) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
filtered[name] = value;
|
|
27
|
+
}
|
|
28
|
+
return filtered;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
onMount(() => {
|
|
32
|
+
/**
|
|
33
|
+
* @param {any} regions
|
|
34
|
+
*/
|
|
35
|
+
const connectEditable = (regions) => {
|
|
36
|
+
if (!regions || editable) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
editable = new regions.Editable(element);
|
|
41
|
+
editable.connect();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (window.editableRegions) {
|
|
45
|
+
connectEditable(window.editableRegions);
|
|
46
|
+
} else {
|
|
47
|
+
pendingLoadHandler = () => {
|
|
48
|
+
connectEditable(window.editableRegions);
|
|
49
|
+
};
|
|
50
|
+
document.addEventListener(
|
|
51
|
+
"editable-regions:load",
|
|
52
|
+
pendingLoadHandler,
|
|
53
|
+
{ once: true },
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
onDestroy(() => {
|
|
59
|
+
if (pendingLoadHandler) {
|
|
60
|
+
document.removeEventListener(
|
|
61
|
+
"editable-regions:load",
|
|
62
|
+
pendingLoadHandler,
|
|
63
|
+
);
|
|
64
|
+
pendingLoadHandler = null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
editable?.disconnect();
|
|
68
|
+
editable = null;
|
|
69
|
+
});
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<svelte:element
|
|
73
|
+
this={tag}
|
|
74
|
+
{...restProps}
|
|
75
|
+
bind:this={element}
|
|
76
|
+
data-editable="_dynamic"
|
|
77
|
+
data-prop=""
|
|
78
|
+
>
|
|
79
|
+
<slot />
|
|
80
|
+
</svelte:element>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
export { default as EditableRegions } from "./EditableRegions.svelte";
|
|
2
|
+
|
|
3
|
+
import { addEditableComponentRenderer } from "../../helpers/cloudcannon.mjs";
|
|
2
4
|
|
|
3
5
|
/** @type{((component: any, args: { target: HTMLElement, props: unknown }) => void) | undefined} */
|
|
4
6
|
let mount;
|
package/integrations/vue.mjs
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createApp,
|
|
3
|
+
defineComponent,
|
|
4
|
+
h,
|
|
5
|
+
onMounted,
|
|
6
|
+
onUnmounted,
|
|
7
|
+
ref,
|
|
8
|
+
} from "vue";
|
|
2
9
|
import { addEditableComponentRenderer } from "../helpers/cloudcannon.mjs";
|
|
3
10
|
|
|
4
11
|
/**
|
|
@@ -26,3 +33,92 @@ export const registerVueComponent = (key, component) => {
|
|
|
26
33
|
|
|
27
34
|
addEditableComponentRenderer(key, wrappedComponent);
|
|
28
35
|
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Root editable region component for Vue.
|
|
39
|
+
* Keeps editable regions from mutating the DOM before frameworks like Nuxt finish hydrating.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} [props.tag] - Element type to render. Defaults to "div".
|
|
42
|
+
* All other props and attributes are forwarded to the rendered element.
|
|
43
|
+
*/
|
|
44
|
+
export const EditableRegions = defineComponent({
|
|
45
|
+
name: "EditableRegions",
|
|
46
|
+
props: {
|
|
47
|
+
tag: {
|
|
48
|
+
type: String,
|
|
49
|
+
default: "div",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
inheritAttrs: false,
|
|
53
|
+
/**
|
|
54
|
+
* @param {{ tag: string }} props
|
|
55
|
+
* @param {{ attrs: any; slots: any }} context
|
|
56
|
+
*/
|
|
57
|
+
setup(props, { attrs, slots }) {
|
|
58
|
+
const element = ref(null);
|
|
59
|
+
/** @type {any} */
|
|
60
|
+
let editable = null;
|
|
61
|
+
/** @type {(() => void) | null} */
|
|
62
|
+
let pendingLoadHandler = null;
|
|
63
|
+
|
|
64
|
+
/** @param {any} regions */
|
|
65
|
+
const connectEditable = (regions) => {
|
|
66
|
+
if (!regions || editable) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
editable = new regions.Editable(element.value);
|
|
71
|
+
editable.connect();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
onMounted(() => {
|
|
75
|
+
const regions = /** @type {any} */ (window).editableRegions;
|
|
76
|
+
if (regions) {
|
|
77
|
+
connectEditable(regions);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pendingLoadHandler = () => {
|
|
82
|
+
connectEditable(/** @type {any} */ (window).editableRegions);
|
|
83
|
+
};
|
|
84
|
+
document.addEventListener("editable-regions:load", pendingLoadHandler, {
|
|
85
|
+
once: true,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
onUnmounted(() => {
|
|
90
|
+
if (pendingLoadHandler) {
|
|
91
|
+
document.removeEventListener(
|
|
92
|
+
"editable-regions:load",
|
|
93
|
+
pendingLoadHandler,
|
|
94
|
+
);
|
|
95
|
+
pendingLoadHandler = null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
editable?.disconnect();
|
|
99
|
+
editable = null;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return () => {
|
|
103
|
+
/** @type {Record<string, any>} */
|
|
104
|
+
const forwarded = {};
|
|
105
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
106
|
+
if (/^data-(prop|literal)/i.test(name)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
forwarded[name] = value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return h(
|
|
113
|
+
props.tag,
|
|
114
|
+
{
|
|
115
|
+
...forwarded,
|
|
116
|
+
ref: element,
|
|
117
|
+
"data-editable": "_dynamic",
|
|
118
|
+
"data-prop": "",
|
|
119
|
+
},
|
|
120
|
+
/** @type {any} */ (slots.default?.()),
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
});
|
|
@@ -18,12 +18,16 @@ export const hasEditableArrayItem = <T extends object>(
|
|
|
18
18
|
export default class EditableArrayItem extends EditableComponent {
|
|
19
19
|
parent: EditableArray | null = null;
|
|
20
20
|
|
|
21
|
-
protected
|
|
21
|
+
declare protected controlsElement?: EditableArrayItemControls;
|
|
22
22
|
|
|
23
23
|
private inputConfig?: any;
|
|
24
24
|
private structureStrings: string[] = [];
|
|
25
25
|
|
|
26
26
|
shouldMount(): boolean {
|
|
27
|
+
if (this.mounted) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
27
31
|
return this.value !== undefined;
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -25,13 +25,19 @@ export default class EditableComponent extends Editable {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
shouldMount(): boolean {
|
|
28
|
-
if (
|
|
29
|
-
return
|
|
28
|
+
if (this.mounted) {
|
|
29
|
+
return false;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
key.startsWith("prop"),
|
|
32
|
+
const hasNoProps = Object.keys(this.element.dataset).every(
|
|
33
|
+
(key) => !key.startsWith("prop"),
|
|
34
34
|
);
|
|
35
|
+
|
|
36
|
+
if (hasNoProps) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return super.shouldMount();
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
validateConfiguration(): boolean {
|