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). 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/package.json b/package.json index dfb1a1e..f54ab86 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,14 @@ ], "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", + "types": "./dist/index.d.ts" + }, "vetur": { "tags": "vetur/tags.json", "attributes": "vetur/attributes.json" diff --git a/src/components/KonvaNode.ts b/src/components/KonvaNode.ts index 5769ee9..3c62829 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 '../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..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({ @@ -18,7 +18,7 @@ export default defineComponent({ props: { config: { type: Object as PropType, - default: function() { + default: function () { return {}; }, }, @@ -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 a71e92c..1a785be 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,57 @@ -import type { App } from 'vue'; +import type { 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 KonvaNode from './components/KonvaNode'; +import { KonvaNodeConstructor } from './types'; if (typeof window !== 'undefined' && !window.Konva) { require('konva'); } const VueKonva = { - install: (app: App, options?: { prefix?: string }) => { - let prefixToUse = options?.prefix || componentPrefix; + install: ( + 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; + + 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, + }; - [Stage, ...KONVA_NODES.map(KonvaNode)].map((k) => { - app.component(`${prefixToUse}${k.name}`, k); + const components: Component[] = [ + Stage, + ...Object.entries(konvaNodeConstructors).map(([name, constructor]) => + KonvaNode(name, constructor), + ), + ]; + components.forEach((component) => { + app.component(`${prefixToUse}${component.name}`, component); }); }, }; diff --git a/src/types.ts b/src/types.ts index b192a79..d4d9877 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,49 +1,3 @@ -import Konva from 'konva'; +import { Node } from 'konva/lib/Node'; -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 +export type KonvaNodeConstructor = new (...args: any) => Node; 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)];