@moda-editor/vue 0.1.1
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/LICENSE +21 -0
- package/README.md +12 -0
- package/dist/EditorNode.vue +76 -0
- package/dist/EditorView.vue +80 -0
- package/dist/WidgetView.vue +36 -0
- package/dist/conformance.test.d.ts +2 -0
- package/dist/conformance.test.d.ts.map +1 -0
- package/dist/conformance.test.js +56 -0
- package/dist/conformance.test.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +1 -0
- package/dist/useEditor.d.ts +20 -0
- package/dist/useEditor.d.ts.map +1 -0
- package/dist/useEditor.js +21 -0
- package/dist/useEditor.js.map +1 -0
- package/dist/useWidget.d.ts +21 -0
- package/dist/useWidget.d.ts.map +1 -0
- package/dist/useWidget.js +25 -0
- package/dist/useWidget.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 park-taejoon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @moda-editor/vue
|
|
2
|
+
|
|
3
|
+
`@moda-editor/core`의 Vue 3 어댑터 — composable + SFC.
|
|
4
|
+
|
|
5
|
+
```vue
|
|
6
|
+
<script setup>
|
|
7
|
+
import { WidgetView, useWidget } from "@moda-editor/vue";
|
|
8
|
+
import "@moda-editor/vue/styles.css";
|
|
9
|
+
const { widget } = useWidget({ items, getItemText });
|
|
10
|
+
</script>
|
|
11
|
+
<template><WidgetView :widget="widget" :get-item-text="getItemText" /></template>
|
|
12
|
+
```
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import type { Component } from "vue";
|
|
4
|
+
import { nodeViewEditable, nodeViewFor } from "@moda-editor/core";
|
|
5
|
+
import type { Editor, RenderNode } from "@moda-editor/core";
|
|
6
|
+
|
|
7
|
+
// 자기 참조 재귀 컴포넌트 — RenderNode 트리를 DOM으로 번역한다.
|
|
8
|
+
// data-me-path가 형제 간 유일하므로 key로 쓴다.
|
|
9
|
+
const props = defineProps<{
|
|
10
|
+
node: RenderNode;
|
|
11
|
+
editor: Editor;
|
|
12
|
+
nodeViews?: Record<string, Component>;
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
// 커스텀 노드 뷰 — .me-node-view 래퍼가 data-me-path 계약을 유지하고
|
|
16
|
+
// 컴포넌트는 NodeViewProps만 받는다
|
|
17
|
+
const view = computed(
|
|
18
|
+
() =>
|
|
19
|
+
nodeViewFor(props.nodeViews, props.node.source.type) as
|
|
20
|
+
| Component
|
|
21
|
+
| undefined,
|
|
22
|
+
);
|
|
23
|
+
const wrapperTag = computed(() =>
|
|
24
|
+
props.editor.state.schema.isInline(props.node.source.type) ? "span" : "div",
|
|
25
|
+
);
|
|
26
|
+
// 비-atom 노드 뷰는 편집 가능한 콘텐츠 아울렛을 받는다 — data-me-path는
|
|
27
|
+
// 래퍼가 아닌 .me-node-content가 가져 컨테이너 포인트 매핑이 맞는다
|
|
28
|
+
const editable = computed(() =>
|
|
29
|
+
nodeViewEditable(props.editor.state.schema, props.node.source.type),
|
|
30
|
+
);
|
|
31
|
+
const wrapperAttrs = computed(() => ({
|
|
32
|
+
...(editable.value ? {} : { "data-me-path": props.node.path.join(",") }),
|
|
33
|
+
class: "me-node-view",
|
|
34
|
+
contenteditable: "false",
|
|
35
|
+
}));
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<component
|
|
40
|
+
:is="view ? wrapperTag : node.tag"
|
|
41
|
+
v-bind="view ? wrapperAttrs : (node.attrs ?? {})"
|
|
42
|
+
>
|
|
43
|
+
<component
|
|
44
|
+
v-if="view"
|
|
45
|
+
:is="view"
|
|
46
|
+
:node="node.source"
|
|
47
|
+
:editor="editor"
|
|
48
|
+
>
|
|
49
|
+
<template v-if="editable" #content>
|
|
50
|
+
<div
|
|
51
|
+
class="me-node-content"
|
|
52
|
+
contenteditable="true"
|
|
53
|
+
:data-me-path="node.path.join(',')"
|
|
54
|
+
>
|
|
55
|
+
<EditorNode
|
|
56
|
+
v-for="c in node.children ?? []"
|
|
57
|
+
:key="c.path.join(',')"
|
|
58
|
+
:node="c"
|
|
59
|
+
:editor="editor"
|
|
60
|
+
:node-views="nodeViews"
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
64
|
+
</component>
|
|
65
|
+
<template v-else-if="node.text != null">{{ node.text }}</template>
|
|
66
|
+
<template v-else>
|
|
67
|
+
<EditorNode
|
|
68
|
+
v-for="c in node.children ?? []"
|
|
69
|
+
:key="c.path.join(',')"
|
|
70
|
+
:node="c"
|
|
71
|
+
:editor="editor"
|
|
72
|
+
:node-views="nodeViews"
|
|
73
|
+
/>
|
|
74
|
+
</template>
|
|
75
|
+
</component>
|
|
76
|
+
</template>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
computed,
|
|
4
|
+
onBeforeUnmount,
|
|
5
|
+
onMounted,
|
|
6
|
+
onUpdated,
|
|
7
|
+
ref,
|
|
8
|
+
watch,
|
|
9
|
+
} from "vue";
|
|
10
|
+
import type { Component, PropType } from "vue";
|
|
11
|
+
import { bindEditorInput, DirtyKeyMap, renderChildren } from "@moda-editor/core";
|
|
12
|
+
import type {
|
|
13
|
+
DocNode,
|
|
14
|
+
Editor,
|
|
15
|
+
EditorInputBinding,
|
|
16
|
+
RenderNode,
|
|
17
|
+
} from "@moda-editor/core";
|
|
18
|
+
import { useEditorState } from "./useEditor.js";
|
|
19
|
+
import EditorNode from "./EditorNode.vue";
|
|
20
|
+
|
|
21
|
+
const props = defineProps({
|
|
22
|
+
editor: { type: Object as PropType<Editor>, required: true },
|
|
23
|
+
/** 커스텀 노드 뷰 — nodeViews[노드타입] Vue 컴포넌트 */
|
|
24
|
+
nodeViews: { type: Object as PropType<Record<string, Component>> },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const { state } = useEditorState(props.editor);
|
|
28
|
+
|
|
29
|
+
// doc 참조가 바뀔 때만 RenderNode 배열을 재생성 — selection-only
|
|
30
|
+
// 변경이면 같은 배열이라 자식 vnode가 그대로 스킵된다
|
|
31
|
+
let cachedDoc: DocNode | null = null;
|
|
32
|
+
let cachedNodes: RenderNode[] = [];
|
|
33
|
+
const nodes = computed(() => {
|
|
34
|
+
const s = state.value;
|
|
35
|
+
if (s.doc !== cachedDoc) {
|
|
36
|
+
cachedDoc = s.doc;
|
|
37
|
+
cachedNodes = renderChildren(s.doc, s.schema);
|
|
38
|
+
}
|
|
39
|
+
return cachedNodes;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const editableEl = ref<HTMLElement | null>(null);
|
|
43
|
+
let binding: EditorInputBinding | null = null;
|
|
44
|
+
|
|
45
|
+
// IME 등이 직접 바꾼 블록은 key를 올려 리컨실 대신 리마운트시킨다 —
|
|
46
|
+
// 브라우저가 변경한 DOM과 vnode가 어긋난 채 diff하면 크래시/유실이 난다
|
|
47
|
+
const dirtyKeys = new DirtyKeyMap();
|
|
48
|
+
const keyOf = (n: RenderNode): string =>
|
|
49
|
+
dirtyKeys.key(n.path.join(","), binding?.dirtyPaths);
|
|
50
|
+
|
|
51
|
+
onMounted(() => {
|
|
52
|
+
if (editableEl.value) binding = bindEditorInput(props.editor, editableEl.value);
|
|
53
|
+
});
|
|
54
|
+
// 리렌더 후 DOM에 doc 셀렉션 복원.
|
|
55
|
+
// 셀렉션만 바뀐 디스패치(Tab 셀 이동 등)는 nodes가 같은 배열이라
|
|
56
|
+
// computed가 리렌더를 생략하고 onUpdated도 안 뜬다 — flush:post watcher로
|
|
57
|
+
// 리렌더 유무와 무관하게 패치 후 복원을 보장한다.
|
|
58
|
+
onUpdated(() => binding?.restoreSelection());
|
|
59
|
+
watch(state, () => binding?.restoreSelection(), { flush: "post" });
|
|
60
|
+
onBeforeUnmount(() => binding?.unbind());
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<div class="me-root">
|
|
65
|
+
<div
|
|
66
|
+
ref="editableEl"
|
|
67
|
+
class="me-editor"
|
|
68
|
+
data-me-path=""
|
|
69
|
+
:contenteditable="editor.editable"
|
|
70
|
+
>
|
|
71
|
+
<EditorNode
|
|
72
|
+
v-for="n in nodes"
|
|
73
|
+
:key="keyOf(n)"
|
|
74
|
+
:node="n"
|
|
75
|
+
:editor="editor"
|
|
76
|
+
:node-views="nodeViews"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts" generic="TData extends object = RowData">
|
|
2
|
+
import { WidgetCore } from "@moda-editor/core";
|
|
3
|
+
import type { RowData } from "@moda-editor/core";
|
|
4
|
+
import { useWidgetState } from "./useWidget.js";
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(
|
|
7
|
+
defineProps<{
|
|
8
|
+
widget: WidgetCore<TData>;
|
|
9
|
+
getItemText?: (item: TData) => string;
|
|
10
|
+
searchPlaceholder?: string;
|
|
11
|
+
}>(),
|
|
12
|
+
{ searchPlaceholder: "검색…" },
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const { state } = useWidgetState(props.widget);
|
|
16
|
+
const textOf = (item: TData) => props.getItemText?.(item) ?? String(item);
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<div class="me-root">
|
|
21
|
+
<input
|
|
22
|
+
class="me-search"
|
|
23
|
+
:placeholder="searchPlaceholder"
|
|
24
|
+
:value="state.searchText"
|
|
25
|
+
@input="widget.setSearch(($event.target as HTMLInputElement).value)"
|
|
26
|
+
/>
|
|
27
|
+
<ul class="me-list">
|
|
28
|
+
<li v-for="(item, i) in state.filteredItems" :key="i" class="me-item">
|
|
29
|
+
{{ textOf(item) }}
|
|
30
|
+
</li>
|
|
31
|
+
</ul>
|
|
32
|
+
<div class="me-count">
|
|
33
|
+
{{ state.filteredCount }} / {{ state.totalCount }}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.test.d.ts","sourceRoot":"","sources":["../src/conformance.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createApp, defineComponent, h, nextTick } from "vue";
|
|
2
|
+
import { runEditorConformance, runWidgetConformance } from "@moda-editor/core/conformance";
|
|
3
|
+
import { WidgetCore } from "@moda-editor/core";
|
|
4
|
+
import WidgetView from "./WidgetView.vue";
|
|
5
|
+
import EditorView from "./EditorView.vue";
|
|
6
|
+
// 컨포먼스 표준 노드 뷰 — .me-custom-view에 node.attrs.label을 출력
|
|
7
|
+
const SpecNodeView = defineComponent({
|
|
8
|
+
props: { node: { type: Object, required: true } },
|
|
9
|
+
render() {
|
|
10
|
+
return h("div", { class: "me-custom-view" }, [
|
|
11
|
+
String(this.node.attrs?.["label"] ?? ""),
|
|
12
|
+
// 비-atom이면 "content" 슬롯으로 편집 아울렛을 받는다
|
|
13
|
+
...(this.$slots["content"]?.() ?? []),
|
|
14
|
+
]);
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
// Vue 3 렌더러 — createApp 마운트 후 nextTick으로 DOM 반영을 기다린다.
|
|
18
|
+
runWidgetConformance("vue3", async (opts) => {
|
|
19
|
+
const el = document.createElement("div");
|
|
20
|
+
document.body.appendChild(el);
|
|
21
|
+
const widget = new WidgetCore(opts);
|
|
22
|
+
const app = createApp(WidgetView, {
|
|
23
|
+
widget,
|
|
24
|
+
getItemText: opts.getItemText,
|
|
25
|
+
});
|
|
26
|
+
app.mount(el);
|
|
27
|
+
return {
|
|
28
|
+
el,
|
|
29
|
+
flush: () => nextTick(),
|
|
30
|
+
destroy: () => {
|
|
31
|
+
app.unmount();
|
|
32
|
+
el.remove();
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
// Vue 3 에디터 — createApp 마운트 후 nextTick으로 DOM 반영 대기.
|
|
37
|
+
runEditorConformance("vue3", async ({ editor, nodeViews }) => {
|
|
38
|
+
const el = document.createElement("div");
|
|
39
|
+
document.body.appendChild(el);
|
|
40
|
+
const views = nodeViews
|
|
41
|
+
? Object.fromEntries(Object.keys(nodeViews)
|
|
42
|
+
.filter((k) => nodeViews[k])
|
|
43
|
+
.map((k) => [k, SpecNodeView]))
|
|
44
|
+
: undefined;
|
|
45
|
+
const app = createApp(EditorView, { editor, nodeViews: views });
|
|
46
|
+
app.mount(el);
|
|
47
|
+
return {
|
|
48
|
+
el,
|
|
49
|
+
flush: () => nextTick(),
|
|
50
|
+
destroy: () => {
|
|
51
|
+
app.unmount();
|
|
52
|
+
el.remove();
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=conformance.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.test.js","sourceRoot":"","sources":["../src/conformance.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,UAAU,MAAM,kBAAkB,CAAC;AAC1C,OAAO,UAAU,MAAM,kBAAkB,CAAC;AAE1C,uDAAuD;AACvD,MAAM,YAAY,GAAG,eAAe,CAAC;IACnC,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IACjD,MAAM;QACJ,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE;YAC3C,MAAM,CAAE,IAAI,CAAC,IAAyC,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAC9E,sCAAsC;YACtC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;SACtC,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC;AAEH,uDAAuD;AACvD,oBAAoB,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;IAC1C,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IACzC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,SAAS,CAAC,UAAU,EAAE;QAChC,MAAM;QACN,WAAW,EAAE,IAAI,CAAC,WAAW;KACrB,CAAC,CAAC;IACZ,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACd,OAAO;QACL,EAAE;QACF,KAAK,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE;QACvB,OAAO,EAAE,GAAG,EAAE;YACZ,GAAG,CAAC,OAAO,EAAE,CAAC;YACd,EAAE,CAAC,MAAM,EAAE,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,oDAAoD;AACpD,oBAAoB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE;IAC3D,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IACzC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,SAAS;QACrB,CAAC,CAAC,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;aACnB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;aAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CACjC;QACH,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,GAAG,GAAG,SAAS,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAW,CAAC,CAAC;IACzE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACd,OAAO;QACL,EAAE;QACF,KAAK,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE;QACvB,OAAO,EAAE,GAAG,EAAE;YACZ,GAAG,CAAC,OAAO,EAAE,CAAC;YACd,EAAE,CAAC,MAAM,EAAE,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { default as EditorView } from "./EditorView.vue";
|
|
2
|
+
export { useEditor, useEditorState } from "./useEditor.js";
|
|
3
|
+
export type { UseEditorReturn } from "./useEditor.js";
|
|
4
|
+
export { default as WidgetView } from "./WidgetView.vue";
|
|
5
|
+
export { useWidget, useWidgetState } from "./useWidget.js";
|
|
6
|
+
export type { UseWidgetReturn } from "./useWidget.js";
|
|
7
|
+
export type { Widget, WidgetCoreOptions, WidgetEventHandlers, WidgetEventMap, WidgetEventType, WidgetSnapshot, RowData, } from "@moda-editor/core";
|
|
8
|
+
export { WidgetCore } from "@moda-editor/core";
|
|
9
|
+
export type { DocNode, Editor, EditorNode, EditorOptions, EditorState, Extension, InputRule, Mark, NodeViewProps, } from "@moda-editor/core";
|
|
10
|
+
export { blockRule, blockTextOffset, canRedo, canUndo, createDoc, createText, isBlockActive, isInNode, isMarkActive, leafPoint, codeBracketAt, codeCaret, codeTextAt, duplicateCodeLines, moveCodeLines, toggleCodeComment, linkHrefAt, linkRangeAt, markRule, nodeAt, normalizedRange, pointEq, starterExtensions, textSelection, } from "@moda-editor/core";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC3D,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC3D,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,YAAY,EACV,MAAM,EACN,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,cAAc,EACd,OAAO,GACR,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,YAAY,EACV,OAAO,EACP,MAAM,EACN,UAAU,EACV,aAAa,EACb,WAAW,EACX,SAAS,EACT,SAAS,EACT,IAAI,EACJ,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,SAAS,EACT,eAAe,EACf,OAAO,EACP,OAAO,EACP,SAAS,EACT,UAAU,EACV,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,aAAa,EACb,SAAS,EACT,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,MAAM,EACN,eAAe,EACf,OAAO,EACP,iBAAiB,EACjB,aAAa,GACd,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as EditorView } from "./EditorView.vue";
|
|
2
|
+
export { useEditor, useEditorState } from "./useEditor.js";
|
|
3
|
+
export { default as WidgetView } from "./WidgetView.vue";
|
|
4
|
+
export { useWidget, useWidgetState } from "./useWidget.js";
|
|
5
|
+
export { WidgetCore } from "@moda-editor/core";
|
|
6
|
+
export { blockRule, blockTextOffset, canRedo, canUndo, createDoc, createText, isBlockActive, isInNode, isMarkActive, leafPoint, codeBracketAt, codeCaret, codeTextAt, duplicateCodeLines, moveCodeLines, toggleCodeComment, linkHrefAt, linkRangeAt, markRule, nodeAt, normalizedRange, pointEq, starterExtensions, textSelection, } from "@moda-editor/core";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE3D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAW3D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAY/C,OAAO,EACL,SAAS,EACT,eAAe,EACf,OAAO,EACP,OAAO,EACP,SAAS,EACT,UAAU,EACV,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,aAAa,EACb,SAAS,EACT,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,MAAM,EACN,eAAe,EACf,OAAO,EACP,iBAAiB,EACjB,aAAa,GACd,MAAM,mBAAmB,CAAC"}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "@moda-editor/core/styles.css";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ShallowRef } from "vue";
|
|
2
|
+
import type { Editor, EditorOptions, EditorState } from "@moda-editor/core";
|
|
3
|
+
export interface UseEditorReturn {
|
|
4
|
+
/** 헤드리스 에디터 — commands/dispatch 호출용 */
|
|
5
|
+
editor: Editor;
|
|
6
|
+
/** 렌더용 불변 스냅샷 (ShallowRef — 통째 교체 구조) */
|
|
7
|
+
state: ShallowRef<EditorState>;
|
|
8
|
+
unsubscribe(): void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Vue 3 어댑터 composable — Editor 생성 + 불변 state를 ShallowRef로 구독.
|
|
12
|
+
* 컴포넌트/이펙트 스코프 안에서 호출하면 onScopeDispose로 자동 해제된다.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useEditor(options?: EditorOptions): UseEditorReturn;
|
|
15
|
+
/** 외부에서 만든 Editor의 state만 구독한다 */
|
|
16
|
+
export declare function useEditorState(editor: Editor): {
|
|
17
|
+
state: ShallowRef<EditorState>;
|
|
18
|
+
unsubscribe: () => void;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=useEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEditor.d.ts","sourceRoot":"","sources":["../src/useEditor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAEtC,OAAO,KAAK,EACV,MAAM,EACN,aAAa,EACb,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,eAAe;IAC9B,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAC/B,WAAW,IAAI,IAAI,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,aAAkB,GAAG,eAAe,CAItE;AAED,kCAAkC;AAClC,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAC9C,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAC/B,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB,CAOA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { onScopeDispose, shallowRef } from "vue";
|
|
2
|
+
import { createEditor } from "@moda-editor/core";
|
|
3
|
+
/**
|
|
4
|
+
* Vue 3 어댑터 composable — Editor 생성 + 불변 state를 ShallowRef로 구독.
|
|
5
|
+
* 컴포넌트/이펙트 스코프 안에서 호출하면 onScopeDispose로 자동 해제된다.
|
|
6
|
+
*/
|
|
7
|
+
export function useEditor(options = {}) {
|
|
8
|
+
const editor = createEditor(options);
|
|
9
|
+
const { state, unsubscribe } = useEditorState(editor);
|
|
10
|
+
return { editor, state, unsubscribe };
|
|
11
|
+
}
|
|
12
|
+
/** 외부에서 만든 Editor의 state만 구독한다 */
|
|
13
|
+
export function useEditorState(editor) {
|
|
14
|
+
const state = shallowRef(editor.state);
|
|
15
|
+
const unsubscribe = editor.subscribe(() => {
|
|
16
|
+
state.value = editor.state;
|
|
17
|
+
});
|
|
18
|
+
onScopeDispose(unsubscribe, true);
|
|
19
|
+
return { state: state, unsubscribe };
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=useEditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEditor.js","sourceRoot":"","sources":["../src/useEditor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAejD;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,UAAyB,EAAE;IACnD,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACtD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AACxC,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,cAAc,CAAC,MAAc;IAI3C,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE;QACxC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC7B,CAAC,CAAC,CAAC;IACH,cAAc,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,EAAE,KAAK,EAAE,KAAgC,EAAE,WAAW,EAAE,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ShallowRef } from "vue";
|
|
2
|
+
import { WidgetCore } from "@moda-editor/core";
|
|
3
|
+
import type { WidgetCoreOptions, WidgetSnapshot, RowData } from "@moda-editor/core";
|
|
4
|
+
export interface UseWidgetReturn<TData extends object = RowData> {
|
|
5
|
+
/** 헤드리스 코어 — 액션 호출용 */
|
|
6
|
+
widget: WidgetCore<TData>;
|
|
7
|
+
/** 렌더용 스냅샷 (ShallowRef — 통째 교체 구조) */
|
|
8
|
+
state: ShallowRef<WidgetSnapshot<TData>>;
|
|
9
|
+
unsubscribe(): void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Vue 3 어댑터 composable — 코어 생성 + 스냅샷을 ShallowRef로 구독.
|
|
13
|
+
* 컴포넌트/이펙트 스코프 안에서 호출하면 onScopeDispose로 자동 해제된다.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useWidget<TData extends object = RowData>(options: WidgetCoreOptions<TData>): UseWidgetReturn<TData>;
|
|
16
|
+
/** 외부에서 만든 WidgetCore의 스냅샷만 구독한다 */
|
|
17
|
+
export declare function useWidgetState<TData extends object = RowData>(widget: WidgetCore<TData>): {
|
|
18
|
+
state: ShallowRef<WidgetSnapshot<TData>>;
|
|
19
|
+
unsubscribe: () => void;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=useWidget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWidget.d.ts","sourceRoot":"","sources":["../src/useWidget.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEpF,MAAM,WAAW,eAAe,CAAC,KAAK,SAAS,MAAM,GAAG,OAAO;IAC7D,uBAAuB;IACvB,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAC1B,sCAAsC;IACtC,KAAK,EAAE,UAAU,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IACzC,WAAW,IAAI,IAAI,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,SAAS,MAAM,GAAG,OAAO,EACtD,OAAO,EAAE,iBAAiB,CAAC,KAAK,CAAC,GAChC,eAAe,CAAC,KAAK,CAAC,CAQxB;AAED,oCAAoC;AACpC,wBAAgB,cAAc,CAAC,KAAK,SAAS,MAAM,GAAG,OAAO,EAC3D,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,GACxB;IAAE,KAAK,EAAE,UAAU,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IAAC,WAAW,EAAE,MAAM,IAAI,CAAA;CAAE,CAOvE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { onScopeDispose, shallowRef } from "vue";
|
|
2
|
+
import { WidgetCore } from "@moda-editor/core";
|
|
3
|
+
/**
|
|
4
|
+
* Vue 3 어댑터 composable — 코어 생성 + 스냅샷을 ShallowRef로 구독.
|
|
5
|
+
* 컴포넌트/이펙트 스코프 안에서 호출하면 onScopeDispose로 자동 해제된다.
|
|
6
|
+
*/
|
|
7
|
+
export function useWidget(options) {
|
|
8
|
+
const widget = new WidgetCore(options);
|
|
9
|
+
const state = shallowRef(widget.getSnapshot());
|
|
10
|
+
const unsubscribe = widget.subscribe(() => {
|
|
11
|
+
state.value = widget.getSnapshot();
|
|
12
|
+
});
|
|
13
|
+
onScopeDispose(unsubscribe, true);
|
|
14
|
+
return { widget, state: state, unsubscribe };
|
|
15
|
+
}
|
|
16
|
+
/** 외부에서 만든 WidgetCore의 스냅샷만 구독한다 */
|
|
17
|
+
export function useWidgetState(widget) {
|
|
18
|
+
const state = shallowRef(widget.getSnapshot());
|
|
19
|
+
const unsubscribe = widget.subscribe(() => {
|
|
20
|
+
state.value = widget.getSnapshot();
|
|
21
|
+
});
|
|
22
|
+
onScopeDispose(unsubscribe, true);
|
|
23
|
+
return { state: state, unsubscribe };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=useWidget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWidget.js","sourceRoot":"","sources":["../src/useWidget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAEjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAW/C;;;GAGG;AACH,MAAM,UAAU,SAAS,CACvB,OAAiC;IAEjC,MAAM,MAAM,GAAG,IAAI,UAAU,CAAQ,OAAO,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE;QACxC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC,CAAC,CAAC;IACH,cAAc,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAA0C,EAAE,WAAW,EAAE,CAAC;AACpF,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,cAAc,CAC5B,MAAyB;IAEzB,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE;QACxC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC,CAAC,CAAC;IACH,cAAc,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,EAAE,KAAK,EAAE,KAA0C,EAAE,WAAW,EAAE,CAAC;AAC5E,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moda-editor/vue",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Vue 3 adapter for @moda-editor — useWidget + WidgetView",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./styles.css": "./dist/styles.css"
|
|
13
|
+
},
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": [
|
|
19
|
+
"**/*.css"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"vue",
|
|
26
|
+
"adapter",
|
|
27
|
+
"headless",
|
|
28
|
+
"library"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@moda-editor/core": "0.1.1"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"vue": ">=3.3.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@vitejs/plugin-vue": "^5.2.4",
|
|
38
|
+
"happy-dom": "^20.14.5",
|
|
39
|
+
"typescript": "~5.9.2",
|
|
40
|
+
"vitest": "^3.2.7",
|
|
41
|
+
"vue": "^3.5.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc -p tsconfig.json && cp src/styles.css src/*.vue dist/",
|
|
45
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
46
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
47
|
+
"test": "vitest run"
|
|
48
|
+
},
|
|
49
|
+
"main": "./dist/index.js"
|
|
50
|
+
}
|