From 7972da73131d6bc2e6eb429d909f08b378e6a27a Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:08:38 +0800 Subject: [PATCH 1/8] feat: support adding custom konva nodes --- index.d.ts | 5 ++-- src/components/KonvaNode.ts | 42 +++++++++---------------------- src/components/Stage.ts | 2 +- src/index.ts | 48 ++++++++++++++++++++++++++++++------ src/types.ts | 49 ------------------------------------- src/utils/applyNodeProps.ts | 13 +++------- src/utils/index.ts | 10 +++----- src/utils/types.ts | 3 +++ 8 files changed, 67 insertions(+), 105 deletions(-) delete mode 100644 src/types.ts create mode 100644 src/utils/types.ts diff --git a/index.d.ts b/index.d.ts index b7c1e36..26dca0f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,6 @@ import type Konva from 'konva'; import type { KonvaNodes } from './src/types'; +import { Node } from 'konva/lib/Node'; declare global { interface Window { @@ -12,10 +13,10 @@ declare module 'vue' { import { ComponentInternalInstance, VNode } from 'vue'; export interface ComponentInternalInstance { - __konvaNode?: KonvaNodes; + __konvaNode?: Node; } export interface VNode { - __konvaNode?: KonvaNodes; + __konvaNode?: Node; } } diff --git a/src/components/KonvaNode.ts b/src/components/KonvaNode.ts index 5769ee9..c763de1 100644 --- a/src/components/KonvaNode.ts +++ b/src/components/KonvaNode.ts @@ -9,13 +9,9 @@ import { defineComponent, VNode, } from 'vue'; -import { - applyNodeProps, - findParentKonva, - updatePicture, - checkOrder, -} from '../utils'; -import { KONVA_NODES } from '../types'; +import { applyNodeProps, findParentKonva, updatePicture, checkOrder } from '../utils'; +import Konva from 'konva'; +import { KonvaNodeConstructor } from '../utils/types'; const EVENTS_NAMESPACE = '.vue-konva-event'; @@ -26,13 +22,13 @@ const CONTAINERS = { Label: true, }; -export default function(nameNode: typeof KONVA_NODES[number]) { +export default function (componentName: string, NodeConstructor: KonvaNodeConstructor) { return defineComponent({ - name: nameNode, + name: componentName, props: { config: { type: Object, - default: function() { + default: function () { return {}; }, }, @@ -46,15 +42,7 @@ export default function(nameNode: typeof KONVA_NODES[number]) { if (!instance) return; const oldProps = reactive({}); - const NodeClass = window.Konva[nameNode]; - - if (!NodeClass) { - console.error('vue-konva error: Can not find node ' + nameNode); - return; - } - - // @ts-ignore - const __konvaNode = new NodeClass(); + const __konvaNode = new NodeConstructor(); instance.__konvaNode = __konvaNode; instance.vnode.__konvaNode = __konvaNode; uploadKonva(); @@ -81,18 +69,14 @@ export default function(nameNode: typeof KONVA_NODES[number]) { ...props.config, ...events, }; - applyNodeProps( - instance, - newProps, - existingProps, - props.__useStrictMode, - ); + applyNodeProps(instance, newProps, existingProps, props.__useStrictMode); Object.assign(oldProps, newProps); } onMounted(() => { const parentKonvaNode = findParentKonva(instance)?.__konvaNode; - if (parentKonvaNode && 'add' in parentKonvaNode) parentKonvaNode.add(__konvaNode); + if (parentKonvaNode && 'add' in parentKonvaNode) + (parentKonvaNode as { add: (node: Konva.Node) => void }).add(__konvaNode); updatePicture(__konvaNode); }); @@ -114,10 +98,8 @@ export default function(nameNode: typeof KONVA_NODES[number]) { getNode, }); - const isContainer = CONTAINERS.hasOwnProperty(nameNode); - return () => isContainer - ? h('template', {}, slots.default?.()) - : null; + const isContainer = CONTAINERS.hasOwnProperty(componentName); + return () => (isContainer ? h('template', {}, slots.default?.()) : null); }, }); } diff --git a/src/components/Stage.ts b/src/components/Stage.ts index 38ab00f..cc8ece8 100644 --- a/src/components/Stage.ts +++ b/src/components/Stage.ts @@ -18,7 +18,7 @@ export default defineComponent({ props: { config: { type: Object as PropType, - default: function() { + default: function () { return {}; }, }, diff --git a/src/index.ts b/src/index.ts index a71e92c..3c2a7be 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,53 @@ -import type { App } from 'vue'; +import type { App, Component } from 'vue'; import Stage from './components/Stage'; -import KonvaNode from './components/KonvaNode'; import { componentPrefix } from './utils'; -import { KONVA_NODES } from './types'; +import Konva from 'konva'; +import { Node } from 'konva/lib/Node'; +import KonvaNode from './components/KonvaNode'; +import { KonvaNodeConstructor } from './utils/types'; if (typeof window !== 'undefined' && !window.Konva) { require('konva'); } + const VueKonva = { - install: (app: App, options?: { prefix?: string }) => { - let prefixToUse = options?.prefix || componentPrefix; + install: (app: App, options?: { prefix?: string; customNodes: KonvaNodeConstructor[] }) => { + const prefixToUse = options?.prefix || componentPrefix; + + const konvaNodeConstructors: KonvaNodeConstructor[] = [ + Konva.Arrow, + Konva.Arc, + Konva.Circle, + Konva.Ellipse, + Konva.FastLayer, + Konva.Image, + Konva.Label, + Konva.Line, + Konva.Path, + Konva.Rect, + Konva.RegularPolygon, + Konva.Ring, + Konva.Shape, + Konva.Sprite, + Konva.Stage, + Konva.Star, + Konva.Tag, + Konva.Text, + Konva.TextPath, + Konva.Transformer, + Konva.Wedge, + ...(options?.customNodes || []), + ]; - [Stage, ...KONVA_NODES.map(KonvaNode)].map((k) => { - app.component(`${prefixToUse}${k.name}`, k); + const components: Component[] = [ + Stage, + ...konvaNodeConstructors.map((konvaNodeConstructor) => + KonvaNode(konvaNodeConstructor.name, konvaNodeConstructor), + ), + ]; + components.forEach((component) => { + app.component(`${prefixToUse}${component.name}`, component); }); }, }; diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index b192a79..0000000 --- a/src/types.ts +++ /dev/null @@ -1,49 +0,0 @@ -import Konva from 'konva'; - -export const KONVA_NODES = [ - 'Arc', - 'Arrow', - 'Circle', - 'Ellipse', - 'FastLayer', - 'Group', - 'Image', - 'Label', - 'Layer', - 'Line', - 'Path', - 'Rect', - 'RegularPolygon', - 'Ring', - 'Shape', - 'Sprite', - 'Star', - 'Tag', - 'Text', - 'TextPath', - 'Transformer', - 'Wedge', -] as const; - -export type KonvaNodes = - Konva.Arrow | - Konva.Arc | - Konva.Circle | - Konva.Ellipse | - Konva.FastLayer | - Konva.Image | - Konva.Label | - Konva.Line | - Konva.Path | - Konva.Rect | - Konva.RegularPolygon | - Konva.Ring | - Konva.Shape | - Konva.Sprite | - Konva.Stage | - Konva.Star | - Konva.Tag | - Konva.Text | - Konva.TextPath | - Konva.Transformer | - Konva.Wedge diff --git a/src/utils/applyNodeProps.ts b/src/utils/applyNodeProps.ts index 1f2bc65..be100a6 100644 --- a/src/utils/applyNodeProps.ts +++ b/src/utils/applyNodeProps.ts @@ -24,10 +24,7 @@ export default function applyNodeProps( if (isEvent && propChanged) { let eventName = key.slice(2).toLowerCase(); if (eventName.slice(0, 7) === 'content') { - eventName = - 'content' + - eventName.slice(7, 1).toUpperCase() + - eventName.slice(8); + eventName = 'content' + eventName.slice(7, 1).toUpperCase() + eventName.slice(8); } instance?.off(eventName + EVENTS_NAMESPACE, oldProps[key]); } @@ -46,10 +43,7 @@ export default function applyNodeProps( if (isEvent && toAdd) { let eventName = key.slice(2).toLowerCase(); if (eventName.slice(0, 7) === 'content') { - eventName = - 'content' + - eventName.slice(7, 1).toUpperCase() + - eventName.slice(8); + eventName = 'content' + eventName.slice(7, 1).toUpperCase() + eventName.slice(8); } if (props[key]) { instance?.off(eventName + EVENTS_NAMESPACE); @@ -58,8 +52,7 @@ export default function applyNodeProps( } if ( !isEvent && - (props[key] !== oldProps[key] || - (useStrict && props[key] !== instance?.getAttr(key))) + (props[key] !== oldProps[key] || (useStrict && props[key] !== instance?.getAttr(key))) ) { hasUpdates = true; updatedProps[key] = props[key]; diff --git a/src/utils/index.ts b/src/utils/index.ts index 40dabf3..9fd7905 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -30,10 +30,7 @@ export function findParentKonva(instance: ComponentInternalInstance) { export function findKonvaNode(instance: VNode): Konva.Node | null { if (!instance.component) return null; - return ( - instance.component.__konvaNode || - findKonvaNode(instance.component.subTree) - ); + return instance.component.__konvaNode || findKonvaNode(instance.component.subTree); } function checkTagAndGetNode(instance: VNode): Konva.Node | null { @@ -54,8 +51,9 @@ function checkTagAndGetNode(instance: VNode): Konva.Node | null { function getChildren(instance: VNode) { const isVNode = (value: VNodeChild | VNodeNormalizedChildren): value is VNode => !!value?.hasOwnProperty('component'); - const isVNodeArrayChildren = (value: VNodeChild | VNodeNormalizedChildren): value is VNodeArrayChildren => - Array.isArray(value); + const isVNodeArrayChildren = ( + value: VNodeChild | VNodeNormalizedChildren, + ): value is VNodeArrayChildren => Array.isArray(value); const recursivelyFindChildren = (item: VNodeChild | VNodeNormalizedChildren): VNode[] => { if (isVNode(item)) return [item, ...recursivelyFindChildren(item.children)]; diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..d4d9877 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,3 @@ +import { Node } from 'konva/lib/Node'; + +export type KonvaNodeConstructor = new (...args: any) => Node; From 05bad3cba8dbf54088b5cf8471174b73187ca497 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:16:50 +0800 Subject: [PATCH 2/8] make customNodes optional --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 3c2a7be..29c01f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ if (typeof window !== 'undefined' && !window.Konva) { const VueKonva = { - install: (app: App, options?: { prefix?: string; customNodes: KonvaNodeConstructor[] }) => { + install: (app: App, options?: { prefix?: string; customNodes?: KonvaNodeConstructor[] }) => { const prefixToUse = options?.prefix || componentPrefix; const konvaNodeConstructors: KonvaNodeConstructor[] = [ From 129ff70491738b8ad1ea40e5fee0ae93c4380611 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:20:40 +0800 Subject: [PATCH 3/8] move types.ts --- src/components/KonvaNode.ts | 2 +- src/index.ts | 3 +-- src/{utils => }/types.ts | 0 3 files changed, 2 insertions(+), 3 deletions(-) rename src/{utils => }/types.ts (100%) diff --git a/src/components/KonvaNode.ts b/src/components/KonvaNode.ts index c763de1..3c62829 100644 --- a/src/components/KonvaNode.ts +++ b/src/components/KonvaNode.ts @@ -11,7 +11,7 @@ import { } from 'vue'; import { applyNodeProps, findParentKonva, updatePicture, checkOrder } from '../utils'; import Konva from 'konva'; -import { KonvaNodeConstructor } from '../utils/types'; +import { KonvaNodeConstructor } from '../types'; const EVENTS_NAMESPACE = '.vue-konva-event'; diff --git a/src/index.ts b/src/index.ts index 29c01f7..e09b0fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,8 @@ import type { App, Component } from 'vue'; import Stage from './components/Stage'; import { componentPrefix } from './utils'; import Konva from 'konva'; -import { Node } from 'konva/lib/Node'; import KonvaNode from './components/KonvaNode'; -import { KonvaNodeConstructor } from './utils/types'; +import { KonvaNodeConstructor } from './types'; if (typeof window !== 'undefined' && !window.Konva) { require('konva'); diff --git a/src/utils/types.ts b/src/types.ts similarity index 100% rename from src/utils/types.ts rename to src/types.ts From 4808b998b8f44f48bcd156733d8e770e00db011e Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:11:02 +0800 Subject: [PATCH 4/8] use named constructors --- src/components/Stage.ts | 4 +-- src/index.ts | 59 ++++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/components/Stage.ts b/src/components/Stage.ts index cc8ece8..15142fa 100644 --- a/src/components/Stage.ts +++ b/src/components/Stage.ts @@ -10,7 +10,7 @@ import { defineComponent, PropType, } from 'vue'; -import type Konva from 'konva'; +import Konva from 'konva'; import { applyNodeProps, checkOrder } from '../utils'; export default defineComponent({ @@ -36,7 +36,7 @@ export default defineComponent({ const container = ref(null); - const __konvaNode = new window.Konva.Stage({ + const __konvaNode = new Konva.Stage({ width: props.config.width, height: props.config.height, container: document.createElement('div'), // Fake container. Will be replaced diff --git a/src/index.ts b/src/index.ts index e09b0fb..6bd76dd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,40 +9,43 @@ if (typeof window !== 'undefined' && !window.Konva) { require('konva'); } - const VueKonva = { - install: (app: App, options?: { prefix?: string; customNodes?: KonvaNodeConstructor[] }) => { + install: ( + app: App, + options?: { prefix?: string; customNodes?: Record }, + ) => { const prefixToUse = options?.prefix || componentPrefix; - const konvaNodeConstructors: KonvaNodeConstructor[] = [ - Konva.Arrow, - Konva.Arc, - Konva.Circle, - Konva.Ellipse, - Konva.FastLayer, - Konva.Image, - Konva.Label, - Konva.Line, - Konva.Path, - Konva.Rect, - Konva.RegularPolygon, - Konva.Ring, - Konva.Shape, - Konva.Sprite, - Konva.Stage, - Konva.Star, - Konva.Tag, - Konva.Text, - Konva.TextPath, - Konva.Transformer, - Konva.Wedge, - ...(options?.customNodes || []), - ]; + const konvaNodeConstructors: Record = { + Arc: Konva.Arc, + Arrow: Konva.Arrow, + Circle: Konva.Circle, + Ellipse: Konva.Ellipse, + FastLayer: Konva.FastLayer, + Group: Konva.Group, + Image: Konva.Image, + Label: Konva.Label, + Layer: Konva.Layer, + Line: Konva.Line, + Path: Konva.Path, + Rect: Konva.Rect, + RegularPolygon: Konva.RegularPolygon, + Ring: Konva.Ring, + Shape: Konva.Shape, + Sprite: Konva.Sprite, + Star: Konva.Star, + Tag: Konva.Tag, + Text: Konva.Text, + TextPath: Konva.TextPath, + Transformer: Konva.Transformer, + Wedge: Konva.Wedge, + ...options?.customNodes, + }; const components: Component[] = [ Stage, - ...konvaNodeConstructors.map((konvaNodeConstructor) => - KonvaNode(konvaNodeConstructor.name, konvaNodeConstructor), + ...Object.entries(konvaNodeConstructors).map(([name, constructor]) => + KonvaNode(name, constructor), ), ]; components.forEach((component) => { From ca810d31e1d50904d3fd1b43df00bc680de85aca Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:11:25 +0800 Subject: [PATCH 5/8] support module export --- package.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index dfb1a1e..63db144 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,13 @@ ], "homepage": "https://github.com/konvajs/vue-konva#readme", "author": "Rafael Escala ", - "main": "dist/vue-konva.umd.js", "types": "dist/index.d.ts", + "main": "dist/vue-konva.umd.js", + "module": "dist/vue-konva.mjs", + "exports": { + "import": "./dist/vue-konva.mjs", + "require": "./dist/vue-konva.umd.js" + }, "vetur": { "tags": "vetur/tags.json", "attributes": "vetur/attributes.json" From 9a52000fc02b478d44eddf8ed79c7e1e7fdcaa4b Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:22:49 +0800 Subject: [PATCH 6/8] add docs --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 347b7e4..ac0c386 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,30 @@ Vue.use(VueKonva, { prefix: 'Konva'}); ``` +## Custom Konva Nodes + +By passing a `Record Node>` object to `customNodes` in options, you can use your own konva node classes in Vue Konva. + +```js +import Vue from 'vue'; +import VueKonva from 'vue-konva' + +class MyRect extends Konva.Rect { + constructor() { + super() + console.log('MyRect') + } +} + +Vue.use(VueKonva, { + // The keys are used as component names. + customNodes: { MyRect } +}) + +// in template: + +``` + ## Change log The change log can be found on the [Releases page](https://github.com/konvajs/vue-konva/releases). From 854fc72120f6cbd21f6228c9b5eee420ef34f22a Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Tue, 6 Aug 2024 23:58:57 +0800 Subject: [PATCH 7/8] fix customNodes install type --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 6bd76dd..ecbd74a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ if (typeof window !== 'undefined' && !window.Konva) { const VueKonva = { install: ( app: App, - options?: { prefix?: string; customNodes?: Record }, + options?: { prefix?: string; customNodes?: Record }, ) => { const prefixToUse = options?.prefix || componentPrefix; From ac1b37d5e8dfd1006ff944531a7261465a8f8876 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Fri, 9 Aug 2024 09:47:02 +0800 Subject: [PATCH 8/8] fix types --- package.json | 3 ++- src/index.ts | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 63db144..f54ab86 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "module": "dist/vue-konva.mjs", "exports": { "import": "./dist/vue-konva.mjs", - "require": "./dist/vue-konva.umd.js" + "require": "./dist/vue-konva.umd.js", + "types": "./dist/index.d.ts" }, "vetur": { "tags": "vetur/tags.json", diff --git a/src/index.ts b/src/index.ts index ecbd74a..1a785be 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import type { App, Component } from 'vue'; +import type { Component } from 'vue'; import Stage from './components/Stage'; import { componentPrefix } from './utils'; import Konva from 'konva'; @@ -11,7 +11,9 @@ if (typeof window !== 'undefined' && !window.Konva) { const VueKonva = { install: ( - app: App, + app: any, + // We use any here as it seems TypeScript will complain + // if the user uses a different version of Vue. options?: { prefix?: string; customNodes?: Record }, ) => { const prefixToUse = options?.prefix || componentPrefix;