@moda-editor/vue2 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 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,4 @@
1
+ # @moda-editor/vue2
2
+
3
+ `@moda-editor/core`의 Vue 2.7 어댑터 — composable + SFC.
4
+ Vue 2.7의 composition API(`<script setup>`)를 사용한다.
@@ -0,0 +1,34 @@
1
+ import type { Component, PropType } from "vue";
2
+ import type { Editor, RenderNode } from "@moda-editor/core";
3
+ /**
4
+ * RenderNode 트리 → Vue2 vnode 재귀 번역.
5
+ * SFC 자기참조 대신 render 함수로 작성 — vite:vue2에서 확실하게 동작한다.
6
+ * data-me-path가 형제 간 유일하므로 key로 쓴다.
7
+ */
8
+ declare const EditorNode: import("vue").DefineComponent<{
9
+ node: {
10
+ type: PropType<RenderNode>;
11
+ required: true;
12
+ };
13
+ editor: {
14
+ type: PropType<Editor>;
15
+ required: true;
16
+ };
17
+ nodeViews: {
18
+ type: PropType<Record<string, Component>>;
19
+ };
20
+ }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, Readonly<import("vue").ExtractPropTypes<{
21
+ node: {
22
+ type: PropType<RenderNode>;
23
+ required: true;
24
+ };
25
+ editor: {
26
+ type: PropType<Editor>;
27
+ required: true;
28
+ };
29
+ nodeViews: {
30
+ type: PropType<Record<string, Component>>;
31
+ };
32
+ }>>, {}>;
33
+ export default EditorNode;
34
+ //# sourceMappingURL=EditorNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorNode.d.ts","sourceRoot":"","sources":["../src/EditorNode.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAS,MAAM,KAAK,CAAC;AAEtD,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;GAIG;AACH,QAAA,MAAM,UAAU;;cAGY,QAAQ,CAAC,UAAU,CAAC;;;;cAClB,QAAQ,CAAC,MAAM,CAAC;;;;cACb,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;;;;cAFxC,QAAQ,CAAC,UAAU,CAAC;;;;cAClB,QAAQ,CAAC,MAAM,CAAC;;;;cACb,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;;QAkElE,CAAC;AAEH,eAAe,UAAU,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { defineComponent, h } from "vue";
2
+ import { nodeViewEditable, nodeViewFor } from "@moda-editor/core";
3
+ /**
4
+ * RenderNode 트리 → Vue2 vnode 재귀 번역.
5
+ * SFC 자기참조 대신 render 함수로 작성 — vite:vue2에서 확실하게 동작한다.
6
+ * data-me-path가 형제 간 유일하므로 key로 쓴다.
7
+ */
8
+ const EditorNode = defineComponent({
9
+ name: "EditorNode",
10
+ props: {
11
+ node: { type: Object, required: true },
12
+ editor: { type: Object, required: true },
13
+ nodeViews: { type: Object },
14
+ },
15
+ render() {
16
+ const rn = this.node;
17
+ const editor = this.editor;
18
+ const view = nodeViewFor(this.nodeViews, rn.source.type);
19
+ if (view) {
20
+ const inline = editor.state.schema.isInline(rn.source.type);
21
+ const editable = nodeViewEditable(editor.state.schema, rn.source.type);
22
+ // 비-atom 노드 뷰는 "content" 슬롯으로 편집 아울렛을 받는다 —
23
+ // data-me-path는 래퍼가 아닌 .me-node-content가 가져 컨테이너
24
+ // 포인트가 아울렛 자식(AST 자식)과 1:1로 매핑된다
25
+ const outlet = editable
26
+ ? h("div", {
27
+ attrs: {
28
+ class: "me-node-content",
29
+ contenteditable: "true",
30
+ "data-me-path": rn.path.join(","),
31
+ },
32
+ slot: "content",
33
+ }, (rn.children ?? []).map((c) => h(EditorNode, {
34
+ props: { node: c, editor, nodeViews: this.nodeViews },
35
+ key: c.path.join(","),
36
+ })))
37
+ : null;
38
+ return h(inline ? "span" : "div", {
39
+ attrs: {
40
+ ...(editable ? {} : { "data-me-path": rn.path.join(",") }),
41
+ class: "me-node-view",
42
+ contenteditable: "false",
43
+ },
44
+ }, [
45
+ h(view, { props: { node: rn.source, editor } }, outlet ? [outlet] : undefined),
46
+ ]);
47
+ }
48
+ const children = rn.text != null
49
+ ? [rn.text]
50
+ : (rn.children ?? []).map((c) => h(EditorNode, {
51
+ props: { node: c, editor, nodeViews: this.nodeViews },
52
+ key: c.path.join(","),
53
+ }));
54
+ return h(rn.tag, { attrs: rn.attrs ?? {} }, children);
55
+ },
56
+ });
57
+ export default EditorNode;
58
+ //# sourceMappingURL=EditorNode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorNode.js","sourceRoot":"","sources":["../src/EditorNode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ <script setup lang="ts">
2
+ import { computed, onBeforeUnmount, onMounted, onUpdated, ref } from "vue";
3
+ import type { Component, PropType } from "vue";
4
+ import { bindEditorInput, DirtyKeyMap, renderChildren } from "@moda-editor/core";
5
+ import type {
6
+ DocNode,
7
+ Editor,
8
+ EditorInputBinding,
9
+ RenderNode,
10
+ } from "@moda-editor/core";
11
+ import { useEditorState } from "./useEditor.js";
12
+ import EditorNode from "./EditorNode.js";
13
+
14
+ const props = defineProps({
15
+ editor: { type: Object as PropType<Editor>, required: true },
16
+ /** 커스텀 노드 뷰 — nodeViews[노드타입] Vue2 컴포넌트 */
17
+ nodeViews: { type: Object as PropType<Record<string, Component>> },
18
+ });
19
+
20
+ const { state } = useEditorState(props.editor);
21
+
22
+ // doc 참조가 바뀔 때만 RenderNode 배열을 재생성 — selection-only
23
+ // 변경이면 같은 배열이라 자식 vnode가 그대로 스킵된다
24
+ let cachedDoc: DocNode | null = null;
25
+ let cachedNodes: RenderNode[] = [];
26
+ const nodes = computed(() => {
27
+ const s = state.value;
28
+ if (s.doc !== cachedDoc) {
29
+ cachedDoc = s.doc;
30
+ cachedNodes = renderChildren(s.doc, s.schema);
31
+ }
32
+ return cachedNodes;
33
+ });
34
+
35
+ const editableEl = ref<HTMLElement | null>(null);
36
+ let binding: EditorInputBinding | null = null;
37
+
38
+ // IME 등이 직접 바꾼 블록은 key를 올려 리컨실 대신 리마운트시킨다 —
39
+ // 브라우저가 변경한 DOM과 vnode가 어긋난 채 diff하면 크래시/유실이 난다
40
+ const dirtyKeys = new DirtyKeyMap();
41
+ const keyOf = (n: RenderNode): string =>
42
+ dirtyKeys.key(n.path.join(","), binding?.dirtyPaths);
43
+
44
+ onMounted(() => {
45
+ if (editableEl.value) binding = bindEditorInput(props.editor, editableEl.value);
46
+ });
47
+ // 리렌더 후 DOM에 doc 셀렉션 복원
48
+ onUpdated(() => binding?.restoreSelection());
49
+ onBeforeUnmount(() => binding?.unbind());
50
+ </script>
51
+
52
+ <template>
53
+ <div class="me-root">
54
+ <div
55
+ ref="editableEl"
56
+ class="me-editor"
57
+ data-me-path=""
58
+ :contenteditable="editor.editable"
59
+ >
60
+ <EditorNode
61
+ v-for="n in nodes"
62
+ :key="keyOf(n)"
63
+ :node="n"
64
+ :editor="editor"
65
+ :node-views="nodeViews"
66
+ />
67
+ </div>
68
+ </div>
69
+ </template>
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import type { PropType } from "vue";
3
+ import { WidgetCore } from "@moda-editor/core";
4
+ import type { RowData } from "@moda-editor/core";
5
+ import { useWidgetState } from "./useWidget.js";
6
+
7
+ type Item = RowData;
8
+
9
+ const props = defineProps({
10
+ widget: { type: Object as PropType<WidgetCore<Item>>, required: true },
11
+ getItemText: {
12
+ type: Function as PropType<(item: Item) => string>,
13
+ default: undefined,
14
+ },
15
+ searchPlaceholder: { type: String, default: "검색…" },
16
+ });
17
+
18
+ const { state } = useWidgetState(props.widget);
19
+
20
+ // Vue2 템플릿 이벤트 핸들러는 표현식만 받는다 — 캐스트/조건부는 메서드로.
21
+ function onSearchInput(e: Event): void {
22
+ props.widget.setSearch((e.target as HTMLInputElement).value);
23
+ }
24
+ function textOf(item: Item): string {
25
+ return props.getItemText?.(item) ?? String(item);
26
+ }
27
+ </script>
28
+
29
+ <template>
30
+ <div class="me-root">
31
+ <input
32
+ class="me-search"
33
+ :placeholder="searchPlaceholder"
34
+ :value="state.searchText"
35
+ @input="onSearchInput"
36
+ />
37
+ <ul class="me-list">
38
+ <li v-for="(item, i) in state.filteredItems" :key="i" class="me-item">
39
+ {{ textOf(item) }}
40
+ </li>
41
+ </ul>
42
+ <div class="me-count">
43
+ {{ state.filteredCount }} / {{ state.totalCount }}
44
+ </div>
45
+ </div>
46
+ </template>
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=conformance.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformance.test.d.ts","sourceRoot":"","sources":["../src/conformance.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,67 @@
1
+ import Vue 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 = Vue.extend({
8
+ props: { node: { type: Object, required: true } },
9
+ render(h) {
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 2.7 렌더러 — new Vue + $mount 마운트, nextTick으로 DOM 반영 대기.
18
+ runWidgetConformance("vue2", async (opts) => {
19
+ const el = document.createElement("div");
20
+ document.body.appendChild(el);
21
+ // $mount(el)은 마운트 대상 요소를 교체하므로 자식 래퍼에 마운트한다
22
+ const inner = document.createElement("div");
23
+ el.appendChild(inner);
24
+ const widget = new WidgetCore(opts);
25
+ const vm = new Vue({
26
+ render: (h) => h(WidgetView, { props: { widget, getItemText: opts.getItemText } }),
27
+ });
28
+ vm.$mount(inner);
29
+ await Vue.nextTick();
30
+ return {
31
+ el,
32
+ flush: () => Vue.nextTick(),
33
+ destroy: () => {
34
+ vm.$destroy();
35
+ // Vue 2의 $destroy는 DOM을 제거하지 않으므로 직접 제거한다
36
+ vm.$el.remove();
37
+ el.remove();
38
+ },
39
+ };
40
+ });
41
+ // Vue 2.7 에디터 — new Vue + $mount, nextTick으로 DOM 반영 대기.
42
+ runEditorConformance("vue2", async ({ editor, nodeViews }) => {
43
+ const el = document.createElement("div");
44
+ document.body.appendChild(el);
45
+ const inner = document.createElement("div");
46
+ el.appendChild(inner);
47
+ const views = nodeViews
48
+ ? Object.fromEntries(Object.keys(nodeViews)
49
+ .filter((k) => nodeViews[k])
50
+ .map((k) => [k, SpecNodeView]))
51
+ : undefined;
52
+ const vm = new Vue({
53
+ render: (h) => h(EditorView, { props: { editor, nodeViews: views } }),
54
+ });
55
+ vm.$mount(inner);
56
+ await Vue.nextTick();
57
+ return {
58
+ el,
59
+ flush: () => Vue.nextTick(),
60
+ destroy: () => {
61
+ vm.$destroy();
62
+ vm.$el.remove();
63
+ el.remove();
64
+ },
65
+ };
66
+ });
67
+ //# sourceMappingURL=conformance.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformance.test.js","sourceRoot":"","sources":["../src/conformance.test.ts"],"names":[],"mappings":"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"}
@@ -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"}
@@ -0,0 +1 @@
1
+ @import "@moda-editor/core/styles.css";
@@ -0,0 +1,20 @@
1
+ import type { Ref } 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: Ref<EditorState>;
8
+ unsubscribe(): void;
9
+ }
10
+ /**
11
+ * Vue 2.7 어댑터 composable — Editor 생성 + 불변 state를 shallowRef로 구독.
12
+ * 컴포넌트 스코프 안에서만 getCurrentScope 기반 자동 해제된다.
13
+ */
14
+ export declare function useEditor(options?: EditorOptions): UseEditorReturn;
15
+ /** 외부에서 만든 Editor의 state만 구독한다 */
16
+ export declare function useEditorState(editor: Editor): {
17
+ state: Ref<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,GAAG,EAAE,MAAM,KAAK,CAAC;AAE/B,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,GAAG,CAAC,WAAW,CAAC,CAAC;IACxB,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,GAAG,CAAC,WAAW,CAAC,CAAC;IACxB,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB,CAOA"}
@@ -0,0 +1,22 @@
1
+ import { getCurrentScope, onScopeDispose, shallowRef } from "vue";
2
+ import { createEditor } from "@moda-editor/core";
3
+ /**
4
+ * Vue 2.7 어댑터 composable — Editor 생성 + 불변 state를 shallowRef로 구독.
5
+ * 컴포넌트 스코프 안에서만 getCurrentScope 기반 자동 해제된다.
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
+ if (getCurrentScope())
19
+ onScopeDispose(unsubscribe);
20
+ return { state: state, unsubscribe };
21
+ }
22
+ //# sourceMappingURL=useEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEditor.js","sourceRoot":"","sources":["../src/useEditor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAElE,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,IAAI,eAAe,EAAE;QAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IACnD,OAAO,EAAE,KAAK,EAAE,KAAyB,EAAE,WAAW,EAAE,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { Ref } 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
+ widget: WidgetCore<TData>;
6
+ state: Ref<WidgetSnapshot<TData>>;
7
+ unsubscribe(): void;
8
+ }
9
+ /**
10
+ * Vue 2.7 어댑터 composable — 코어 생성 + 스냅샷을 shallowRef로 구독.
11
+ * 컴포넌트 스코프 안에서만 getCurrentScope 기반 자동 해제된다.
12
+ */
13
+ export declare function useWidget<TData extends object = RowData>(options: WidgetCoreOptions<TData>): UseWidgetReturn<TData>;
14
+ /** 외부에서 만든 WidgetCore의 스냅샷만 구독한다 */
15
+ export declare function useWidgetState<TData extends object = RowData>(widget: WidgetCore<TData>): {
16
+ state: Ref<WidgetSnapshot<TData>>;
17
+ unsubscribe: () => void;
18
+ };
19
+ //# 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,GAAG,EAAE,MAAM,KAAK,CAAC;AAC/B,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,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAC1B,KAAK,EAAE,GAAG,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IAClC,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,GAAG,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IAAC,WAAW,EAAE,MAAM,IAAI,CAAA;CAAE,CAOhE"}
@@ -0,0 +1,27 @@
1
+ import { getCurrentScope, onScopeDispose, shallowRef } from "vue";
2
+ import { WidgetCore } from "@moda-editor/core";
3
+ /**
4
+ * Vue 2.7 어댑터 composable — 코어 생성 + 스냅샷을 shallowRef로 구독.
5
+ * 컴포넌트 스코프 안에서만 getCurrentScope 기반 자동 해제된다.
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
+ if (getCurrentScope())
14
+ onScopeDispose(unsubscribe);
15
+ return { widget, state: state, unsubscribe };
16
+ }
17
+ /** 외부에서 만든 WidgetCore의 스냅샷만 구독한다 */
18
+ export function useWidgetState(widget) {
19
+ const state = shallowRef(widget.getSnapshot());
20
+ const unsubscribe = widget.subscribe(() => {
21
+ state.value = widget.getSnapshot();
22
+ });
23
+ if (getCurrentScope())
24
+ onScopeDispose(unsubscribe);
25
+ return { state: state, unsubscribe };
26
+ }
27
+ //# sourceMappingURL=useWidget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWidget.js","sourceRoot":"","sources":["../src/useWidget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAElE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAS/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,IAAI,eAAe,EAAE;QAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IACnD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAmC,EAAE,WAAW,EAAE,CAAC;AAC7E,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,IAAI,eAAe,EAAE;QAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IACnD,OAAO,EAAE,KAAK,EAAE,KAAmC,EAAE,WAAW,EAAE,CAAC;AACrE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@moda-editor/vue2",
3
+ "version": "0.1.1",
4
+ "description": "Vue 2.7 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
+ "vue2",
26
+ "adapter",
27
+ "headless",
28
+ "library"
29
+ ],
30
+ "dependencies": {
31
+ "@moda-editor/core": "0.1.1"
32
+ },
33
+ "peerDependencies": {
34
+ "vue": "^2.7.0"
35
+ },
36
+ "devDependencies": {
37
+ "@vitejs/plugin-vue2": "^2.3.4",
38
+ "happy-dom": "^20.14.5",
39
+ "typescript": "~5.9.2",
40
+ "vitest": "^3.2.7",
41
+ "vue": "2.7.16"
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
+ }