diff --git a/code/CHANGELOG.md b/code/CHANGELOG.md index 7aa64828a..5b61c80de 100644 --- a/code/CHANGELOG.md +++ b/code/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- [#693](https://github.com/InditexTech/weavejs/issues/693) Clone elements on ALT + Click & Drag + ## [0.73.1] - 2025-10-09 ### Fixed @@ -1104,283 +1108,143 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#18](https://github.com/InditexTech/weavejs/issues/18) Fix awareness not working on store-azure-web-pubsub [Unreleased]: https://github.com/InditexTech/weavejs/compare/0.73.1...HEAD - [0.73.1]: https://github.com/InditexTech/weavejs/compare/0.73.0...0.73.1 - [0.73.0]: https://github.com/InditexTech/weavejs/compare/0.72.1...0.73.0 - [0.72.1]: https://github.com/InditexTech/weavejs/compare/0.72.0...0.72.1 - [0.72.0]: https://github.com/InditexTech/weavejs/compare/0.71.0...0.72.0 - [0.71.0]: https://github.com/InditexTech/weavejs/compare/0.70.0...0.71.0 - [0.70.0]: https://github.com/InditexTech/weavejs/compare/0.69.2...0.70.0 - [0.69.2]: https://github.com/InditexTech/weavejs/compare/0.69.1...0.69.2 - [0.69.1]: https://github.com/InditexTech/weavejs/compare/0.69.0...0.69.1 - [0.69.0]: https://github.com/InditexTech/weavejs/compare/0.68.1...0.69.0 - [0.68.1]: https://github.com/InditexTech/weavejs/compare/0.68.0...0.68.1 - [0.68.0]: https://github.com/InditexTech/weavejs/compare/0.67.5...0.68.0 - [0.67.5]: https://github.com/InditexTech/weavejs/compare/0.67.4...0.67.5 - [0.67.4]: https://github.com/InditexTech/weavejs/compare/0.67.3...0.67.4 - [0.67.3]: https://github.com/InditexTech/weavejs/compare/0.67.2...0.67.3 - [0.67.2]: https://github.com/InditexTech/weavejs/compare/0.67.1...0.67.2 - [0.67.1]: https://github.com/InditexTech/weavejs/compare/0.67.0...0.67.1 - [0.67.0]: https://github.com/InditexTech/weavejs/compare/0.66.0...0.67.0 - [0.66.0]: https://github.com/InditexTech/weavejs/compare/0.64.0...0.66.0 - [0.64.0]: https://github.com/InditexTech/weavejs/compare/0.62.4...0.64.0 - [0.62.4]: https://github.com/InditexTech/weavejs/compare/0.62.3...0.62.4 - [0.62.3]: https://github.com/InditexTech/weavejs/compare/0.62.2...0.62.3 - [0.62.2]: https://github.com/InditexTech/weavejs/compare/0.62.1...0.62.2 - [0.62.1]: https://github.com/InditexTech/weavejs/compare/0.62.0...0.62.1 - [0.62.0]: https://github.com/InditexTech/weavejs/compare/0.61.0...0.62.0 - [0.61.0]: https://github.com/InditexTech/weavejs/compare/0.60.0...0.61.0 - [0.60.0]: https://github.com/InditexTech/weavejs/compare/0.59.0...0.60.0 - [0.59.0]: https://github.com/InditexTech/weavejs/compare/0.58.0...0.59.0 - [0.58.0]: https://github.com/InditexTech/weavejs/compare/0.57.1...0.58.0 - [0.57.1]: https://github.com/InditexTech/weavejs/compare/0.57.0...0.57.1 - [0.57.0]: https://github.com/InditexTech/weavejs/compare/0.56.2...0.57.0 - [0.56.2]: https://github.com/InditexTech/weavejs/compare/0.56.1...0.56.2 - [0.56.1]: https://github.com/InditexTech/weavejs/compare/0.56.0...0.56.1 - [0.56.0]: https://github.com/InditexTech/weavejs/compare/0.55.2...0.56.0 - [0.55.2]: https://github.com/InditexTech/weavejs/compare/0.55.1...0.55.2 - [0.55.1]: https://github.com/InditexTech/weavejs/compare/0.55.0...0.55.1 - [0.55.0]: https://github.com/InditexTech/weavejs/compare/0.54.1...0.55.0 - [0.54.1]: https://github.com/InditexTech/weavejs/compare/0.54.0...0.54.1 - [0.54.0]: https://github.com/InditexTech/weavejs/compare/0.53.0...0.54.0 - [0.53.0]: https://github.com/InditexTech/weavejs/compare/0.52.3...0.53.0 - [0.52.3]: https://github.com/InditexTech/weavejs/compare/0.52.2...0.52.3 - [0.52.2]: https://github.com/InditexTech/weavejs/compare/0.52.1...0.52.2 - [0.52.1]: https://github.com/InditexTech/weavejs/compare/0.52.0...0.52.1 - [0.52.0]: https://github.com/InditexTech/weavejs/compare/0.51.0...0.52.0 - [0.51.0]: https://github.com/InditexTech/weavejs/compare/0.50.0...0.51.0 - [0.50.0]: https://github.com/InditexTech/weavejs/compare/0.49.0...0.50.0 - [0.49.0]: https://github.com/InditexTech/weavejs/compare/0.48.0...0.49.0 - [0.48.0]: https://github.com/InditexTech/weavejs/compare/0.47.1...0.48.0 - [0.47.1]: https://github.com/InditexTech/weavejs/compare/0.47.0...0.47.1 - [0.47.0]: https://github.com/InditexTech/weavejs/compare/0.46.1...0.47.0 - [0.46.1]: https://github.com/InditexTech/weavejs/compare/0.46.0...0.46.1 - [0.46.0]: https://github.com/InditexTech/weavejs/compare/0.45.0...0.46.0 - [0.45.0]: https://github.com/InditexTech/weavejs/compare/0.44.0...0.45.0 - [0.44.0]: https://github.com/InditexTech/weavejs/compare/0.43.0...0.44.0 - [0.43.0]: https://github.com/InditexTech/weavejs/compare/0.42.2...0.43.0 - [0.42.2]: https://github.com/InditexTech/weavejs/compare/0.42.1...0.42.2 - [0.42.1]: https://github.com/InditexTech/weavejs/compare/0.42.0...0.42.1 - [0.42.0]: https://github.com/InditexTech/weavejs/compare/0.41.0...0.42.0 - [0.41.0]: https://github.com/InditexTech/weavejs/compare/0.40.2...0.41.0 - [0.40.2]: https://github.com/InditexTech/weavejs/compare/0.40.1...0.40.2 - [0.40.1]: https://github.com/InditexTech/weavejs/compare/0.40.0...0.40.1 - [0.40.0]: https://github.com/InditexTech/weavejs/compare/0.39.3...0.40.0 - [0.39.3]: https://github.com/InditexTech/weavejs/compare/0.39.2...0.39.3 - [0.39.2]: https://github.com/InditexTech/weavejs/compare/0.39.1...0.39.2 - [0.39.1]: https://github.com/InditexTech/weavejs/compare/0.39.0...0.39.1 - [0.39.0]: https://github.com/InditexTech/weavejs/compare/0.38.0...0.39.0 - [0.38.0]: https://github.com/InditexTech/weavejs/compare/0.37.0...0.38.0 - [0.37.0]: https://github.com/InditexTech/weavejs/compare/0.36.0...0.37.0 - [0.36.0]: https://github.com/InditexTech/weavejs/compare/0.35.0...0.36.0 - [0.35.0]: https://github.com/InditexTech/weavejs/compare/0.34.0...0.35.0 - [0.34.0]: https://github.com/InditexTech/weavejs/compare/0.33.0...0.34.0 - [0.33.0]: https://github.com/InditexTech/weavejs/compare/0.32.0...0.33.0 - [0.32.0]: https://github.com/InditexTech/weavejs/compare/0.31.1...0.32.0 - [0.31.1]: https://github.com/InditexTech/weavejs/compare/0.31.0...0.31.1 - [0.31.0]: https://github.com/InditexTech/weavejs/compare/0.30.1...0.31.0 - [0.30.1]: https://github.com/InditexTech/weavejs/compare/0.30.0...0.30.1 - [0.30.0]: https://github.com/InditexTech/weavejs/compare/0.29.1...0.30.0 - [0.29.1]: https://github.com/InditexTech/weavejs/compare/0.29.0...0.29.1 - [0.29.0]: https://github.com/InditexTech/weavejs/compare/0.28.0...0.29.0 - [0.28.0]: https://github.com/InditexTech/weavejs/compare/0.27.4...0.28.0 - [0.27.4]: https://github.com/InditexTech/weavejs/compare/0.27.3...0.27.4 - [0.27.3]: https://github.com/InditexTech/weavejs/compare/0.27.2...0.27.3 - [0.27.2]: https://github.com/InditexTech/weavejs/compare/0.27.1...0.27.2 - [0.27.1]: https://github.com/InditexTech/weavejs/compare/0.27.0...0.27.1 - [0.27.0]: https://github.com/InditexTech/weavejs/compare/0.26.2...0.27.0 - [0.26.2]: https://github.com/InditexTech/weavejs/compare/0.26.1...0.26.2 - [0.26.1]: https://github.com/InditexTech/weavejs/compare/0.26.0...0.26.1 - [0.26.0]: https://github.com/InditexTech/weavejs/compare/0.25.0...0.26.0 - [0.25.0]: https://github.com/InditexTech/weavejs/compare/0.24.1...0.25.0 - [0.24.1]: https://github.com/InditexTech/weavejs/compare/0.24.0...0.24.1 - [0.24.0]: https://github.com/InditexTech/weavejs/compare/0.23.1...0.24.0 - [0.23.1]: https://github.com/InditexTech/weavejs/compare/0.23.0...0.23.1 - [0.23.0]: https://github.com/InditexTech/weavejs/compare/0.22.1...0.23.0 - [0.22.1]: https://github.com/InditexTech/weavejs/compare/0.22.0...0.22.1 - [0.22.0]: https://github.com/InditexTech/weavejs/compare/0.21.2...0.22.0 - [0.21.2]: https://github.com/InditexTech/weavejs/compare/0.21.1...0.21.2 - [0.21.1]: https://github.com/InditexTech/weavejs/compare/0.21.0...0.21.1 - [0.21.0]: https://github.com/InditexTech/weavejs/compare/0.20.4...0.21.0 - [0.20.4]: https://github.com/InditexTech/weavejs/compare/0.20.3...0.20.4 - [0.20.3]: https://github.com/InditexTech/weavejs/compare/0.20.2...0.20.3 - [0.20.2]: https://github.com/InditexTech/weavejs/compare/0.20.1...0.20.2 - [0.20.1]: https://github.com/InditexTech/weavejs/compare/0.20.0...0.20.1 - [0.20.0]: https://github.com/InditexTech/weavejs/compare/0.19.0...0.20.0 - [0.19.0]: https://github.com/InditexTech/weavejs/compare/0.18.0...0.19.0 - [0.18.0]: https://github.com/InditexTech/weavejs/compare/0.17.0...0.18.0 - [0.17.0]: https://github.com/InditexTech/weavejs/compare/0.16.2...0.17.0 - [0.16.2]: https://github.com/InditexTech/weavejs/compare/0.16.1...0.16.2 - [0.16.1]: https://github.com/InditexTech/weavejs/compare/0.16.0...0.16.1 - [0.16.0]: https://github.com/InditexTech/weavejs/compare/0.15.0...0.16.0 - [0.15.0]: https://github.com/InditexTech/weavejs/compare/0.14.3...0.15.0 - [0.14.3]: https://github.com/InditexTech/weavejs/compare/0.14.2...0.14.3 - [0.14.2]: https://github.com/InditexTech/weavejs/compare/0.14.1...0.14.2 - [0.14.1]: https://github.com/InditexTech/weavejs/compare/0.14.0...0.14.1 - [0.14.0]: https://github.com/InditexTech/weavejs/compare/0.13.1...0.14.0 - [0.13.1]: https://github.com/InditexTech/weavejs/compare/0.13.0...0.13.1 - [0.13.0]: https://github.com/InditexTech/weavejs/compare/0.12.1...0.13.0 - [0.12.1]: https://github.com/InditexTech/weavejs/compare/0.12.0...0.12.1 - [0.12.0]: https://github.com/InditexTech/weavejs/compare/0.11.0...0.12.0 - [0.11.0]: https://github.com/InditexTech/weavejs/compare/0.10.3...0.11.0 - [0.10.3]: https://github.com/InditexTech/weavejs/compare/0.10.2...0.10.3 - [0.10.2]: https://github.com/InditexTech/weavejs/compare/0.10.1...0.10.2 - [0.10.1]: https://github.com/InditexTech/weavejs/compare/0.10.0...0.10.1 - [0.10.0]: https://github.com/InditexTech/weavejs/compare/0.9.3...0.10.0 - [0.9.3]: https://github.com/InditexTech/weavejs/compare/0.9.2...0.9.3 - [0.9.2]: https://github.com/InditexTech/weavejs/compare/0.9.1...0.9.2 - [0.9.1]: https://github.com/InditexTech/weavejs/compare/0.9.0...0.9.1 - [0.9.0]: https://github.com/InditexTech/weavejs/compare/0.8.0...0.9.0 - [0.8.0]: https://github.com/InditexTech/weavejs/compare/0.7.1...0.8.0 - [0.7.1]: https://github.com/InditexTech/weavejs/compare/0.7.0...0.7.1 - [0.7.0]: https://github.com/InditexTech/weavejs/compare/0.6.0...0.7.0 - [0.6.0]: https://github.com/InditexTech/weavejs/compare/0.5.0...0.6.0 - [0.5.0]: https://github.com/InditexTech/weavejs/compare/0.4.0...0.5.0 - [0.4.0]: https://github.com/InditexTech/weavejs/compare/0.3.3...0.4.0 - [0.3.3]: https://github.com/InditexTech/weavejs/compare/0.3.2...0.3.3 - [0.3.2]: https://github.com/InditexTech/weavejs/compare/0.3.1...0.3.2 - [0.3.1]: https://github.com/InditexTech/weavejs/compare/0.3.0...0.3.1 - [0.3.0]: https://github.com/InditexTech/weavejs/compare/0.2.1...0.3.0 - [0.2.1]: https://github.com/InditexTech/weavejs/compare/0.2.0...0.2.1 - [0.2.0]: https://github.com/InditexTech/weavejs/compare/0.1.1...0.2.0 - [0.1.1]: https://github.com/InditexTech/weavejs/compare/0.1.0...0.1.1 - [0.1.0]: https://github.com/InditexTech/weavejs/releases/tag/0.1.0 diff --git a/code/packages/sdk/src/index.ts b/code/packages/sdk/src/index.ts index 0a7039092..dd486d100 100644 --- a/code/packages/sdk/src/index.ts +++ b/code/packages/sdk/src/index.ts @@ -123,6 +123,8 @@ export { WeaveStageGridPlugin } from './plugins/stage-grid/stage-grid'; export * from './plugins/stage-grid/constants'; export * from './plugins/stage-grid/types'; export { WeaveStagePanningPlugin } from './plugins/stage-panning/stage-panning'; +export * from './plugins/stage-panning/constants'; +export * from './plugins/stage-panning/types'; export { WeaveStageMinimapPlugin } from './plugins/stage-minimap/stage-minimap'; export { WeaveStageResizePlugin } from './plugins/stage-resize/stage-resize'; export { WeaveStageZoomPlugin } from './plugins/stage-zoom/stage-zoom'; diff --git a/code/packages/sdk/src/managers/cloning.ts b/code/packages/sdk/src/managers/cloning.ts index 57773a588..72f0363e0 100644 --- a/code/packages/sdk/src/managers/cloning.ts +++ b/code/packages/sdk/src/managers/cloning.ts @@ -8,12 +8,16 @@ import { v4 as uuidv4 } from 'uuid'; import { Weave } from '@/weave'; import { type Vector2d } from 'konva/lib/types'; import { type Logger } from 'pino'; -import { type WeaveStateElement } from '@inditextech/weave-types'; +import { + type WeaveElementInstance, + type WeaveStateElement, +} from '@inditextech/weave-types'; import type { WeaveNode } from '@/nodes/node'; export class WeaveCloningManager { private instance: Weave; private logger: Logger; + private clones: Konva.Node[] = []; constructor(instance: Weave) { this.instance = instance; @@ -272,4 +276,63 @@ export class WeaveCloningManager { newGroup.destroy(); } + + private recursivelyUpdateKeys(nodes: WeaveStateElement[]) { + for (const child of nodes) { + const newNodeId = uuidv4(); + child.key = newNodeId; + child.props.id = newNodeId; + if (child.props.children) { + this.recursivelyUpdateKeys(child.props.children); + } + } + } + + cloneNode(targetNode: Konva.Node): Konva.Node | undefined { + const nodeHandler = this.instance.getNodeHandler( + targetNode.getAttrs().nodeType + ); + + if (!nodeHandler) { + return undefined; + } + + const parent: Konva.Container = targetNode.getParent() as Konva.Container; + + const serializedNode = nodeHandler.serialize( + targetNode as WeaveElementInstance + ); + + this.recursivelyUpdateKeys(serializedNode.props.children ?? []); + + const newNodeId = uuidv4(); + serializedNode.key = newNodeId; + serializedNode.props.id = newNodeId; + + const realParent = this.instance.getInstanceRecursive(parent); + + this.instance.addNode(serializedNode, realParent?.getAttrs().id); + + return this.instance.getStage().findOne(`#${newNodeId}`); + } + + addClone(node: Konva.Node) { + this.clones.push(node); + } + + removeClone(node: Konva.Node) { + this.clones = this.clones.filter((c) => c !== node); + } + + getClones() { + return this.clones; + } + + isClone(node: Konva.Node) { + return this.clones.find((c) => c === node); + } + + cleanupClones() { + this.clones = []; + } } diff --git a/code/packages/sdk/src/nodes/frame/constants.ts b/code/packages/sdk/src/nodes/frame/constants.ts index b13347b9e..b15d502af 100644 --- a/code/packages/sdk/src/nodes/frame/constants.ts +++ b/code/packages/sdk/src/nodes/frame/constants.ts @@ -8,17 +8,13 @@ export const WEAVE_FRAME_NODE_DEFAULT_CONFIG = { fontFamily: 'Arial', fontStyle: 'bold', fontSize: 20, - fontColor: '#000000ff', + fontColor: '#000000', titleMargin: 20, - borderColor: '#000000ff', + borderColor: '#000000', borderWidth: 1, - onTargetLeave: { - borderColor: '#000000ff', - fill: '#ffffffff', - }, onTargetEnter: { - borderColor: '#ff6863ff', - fill: '#ffffffff', + borderColor: '#FF6863FF', + fill: '#FFFFFFFF', }, transform: { rotateEnabled: false, @@ -29,8 +25,11 @@ export const WEAVE_FRAME_NODE_DEFAULT_CONFIG = { }, }; +export const WEAVE_FRAME_DEFAULT_BACKGROUND_COLOR = '#FFFFFFFF'; + export const WEAVE_FRAME_NODE_DEFAULT_PROPS = { title: 'Frame XXX', frameWidth: 1920, frameHeight: 1080, + frameBackground: WEAVE_FRAME_DEFAULT_BACKGROUND_COLOR, }; diff --git a/code/packages/sdk/src/nodes/frame/frame.ts b/code/packages/sdk/src/nodes/frame/frame.ts index 5bca682af..bf7389978 100644 --- a/code/packages/sdk/src/nodes/frame/frame.ts +++ b/code/packages/sdk/src/nodes/frame/frame.ts @@ -12,6 +12,7 @@ import { WEAVE_NODE_CUSTOM_EVENTS, } from '@inditextech/weave-types'; import { + WEAVE_FRAME_DEFAULT_BACKGROUND_COLOR, WEAVE_FRAME_NODE_DEFAULT_CONFIG, WEAVE_FRAME_NODE_DEFAULT_PROPS, WEAVE_FRAME_NODE_TYPE, @@ -48,6 +49,9 @@ export class WeaveFrameNode extends WeaveNode { ...(props.title && { title: props.title }), ...(props.frameWidth && { frameWidth: props.frameWidth }), ...(props.frameHeight && { frameHeight: props.frameHeight }), + ...(props.frameBackground && { + frameBackground: props.frameBackground, + }), children: [], }, }; @@ -73,10 +77,6 @@ export class WeaveFrameNode extends WeaveNode { borderColor: onTargetEnterBorderColor, fill: onTargetEnterFill, }, - onTargetLeave: { - borderColor: onTargetLeaveBorderColor, - fill: onTargetLeaveFill, - }, } = this.config; const frameParams = { @@ -119,13 +119,14 @@ export class WeaveFrameNode extends WeaveNode { nodeId: id, x: 0, y: 0, + onTargetEnter: false, width: props.frameWidth, stroke: borderColor, strokeWidth: borderWidth, strokeScaleEnabled: true, shadowForStrokeEnabled: false, height: props.frameHeight, - fill: frameParams.frameBackground ?? '#ffffffff', + fill: frameParams.frameBackground ?? WEAVE_FRAME_DEFAULT_BACKGROUND_COLOR, listening: false, draggable: false, }); @@ -138,7 +139,7 @@ export class WeaveFrameNode extends WeaveNode { id: `${id}-title`, x: 0, width: props.frameWidth, - fontSize: fontSize / stage.scaleX(), + fontSize: fontSize, fontFamily, fontStyle, verticalAlign: 'middle', @@ -208,7 +209,9 @@ export class WeaveFrameNode extends WeaveNode { ctx.rect(0, -textHeight, props.frameWidth, textHeight); ctx.fillStrokeShape(shape); }, + strokeWidth: 0, fill: 'transparent', + nodeId: id, id: `${id}-selection-area`, listening: true, draggable: true, @@ -226,6 +229,7 @@ export class WeaveFrameNode extends WeaveNode { ctx.rect(0, 0, props.frameWidth, props.frameHeight); ctx.fillStrokeShape(shape); }, + strokeWidth: 0, fill: 'transparent', nodeId: id, id: `${id}-container-area`, @@ -264,15 +268,6 @@ export class WeaveFrameNode extends WeaveNode { this.setupDefaultNodeEvents(frame); this.instance.addEventListener('onZoomChange', () => { - const stage = this.instance.getStage(); - text.fontSize(fontSize / stage.scaleX()); - text.width(props.frameWidth); - const textMeasures = text.measureSize(text.getAttrs().text ?? ''); - const textHeight = - textMeasures.height + (2 * titleMargin) / stage.scaleX(); - text.y(-textHeight); - text.height(textHeight); - selectionArea.hitFunc(function (ctx, shape) { ctx.beginPath(); ctx.rect(0, -textHeight, props.frameWidth, textHeight); @@ -284,14 +279,17 @@ export class WeaveFrameNode extends WeaveNode { frame.on(WEAVE_NODE_CUSTOM_EVENTS.onTargetLeave, () => { background.setAttrs({ - stroke: onTargetLeaveBorderColor, + onTargetEnter: false, + stroke: borderColor, strokeWidth: borderWidth, - fill: onTargetLeaveFill, + fill: + frameParams.frameBackground ?? WEAVE_FRAME_DEFAULT_BACKGROUND_COLOR, }); }); frame.on(WEAVE_NODE_CUSTOM_EVENTS.onTargetEnter, () => { background.setAttrs({ + onTargetEnter: true, stroke: onTargetEnterBorderColor, strokeWidth: borderWidth, fill: onTargetEnterFill, @@ -331,9 +329,9 @@ export class WeaveFrameNode extends WeaveNode { `#${newProps.id}-selection-area` ); - if (background) { + if (background && !newProps.onTargetEnter) { background.setAttrs({ - fill: newProps.frameBackground ?? '#ffffffff', + fill: newProps.frameBackground ?? WEAVE_FRAME_DEFAULT_BACKGROUND_COLOR, }); } @@ -341,8 +339,7 @@ export class WeaveFrameNode extends WeaveNode { title.text(newProps.title); const textMeasures = title.measureSize(title.getAttrs().text ?? ''); - const textHeight = - textMeasures.height + (2 * titleMargin) / stage.scaleX(); + const textHeight = textMeasures.height + 2 * titleMargin; title.y(-textHeight); title.height(textHeight); @@ -391,6 +388,7 @@ export class WeaveFrameNode extends WeaveNode { const cleanedAttrs = { ...realAttrs }; delete cleanedAttrs.draggable; + delete cleanedAttrs.onTargetEnter; return { key: realAttrs?.id ?? '', diff --git a/code/packages/sdk/src/nodes/frame/types.ts b/code/packages/sdk/src/nodes/frame/types.ts index e6b1500b1..c3b089fee 100644 --- a/code/packages/sdk/src/nodes/frame/types.ts +++ b/code/packages/sdk/src/nodes/frame/types.ts @@ -15,10 +15,6 @@ export type WeaveFrameProperties = { titleMargin: number; borderWidth: number; borderColor: string; - onTargetLeave: { - borderColor: string; - fill: string; - }; onTargetEnter: { borderColor: string; fill: string; @@ -30,6 +26,7 @@ export type WeaveFrameAttributes = WeaveElementAttributes & { title: string; frameWidth: number; frameHeight: number; + frameBackground: string; }; export type WeaveFrameNodeParams = { diff --git a/code/packages/sdk/src/nodes/node.ts b/code/packages/sdk/src/nodes/node.ts index 5710a2cbf..e3f412ef5 100644 --- a/code/packages/sdk/src/nodes/node.ts +++ b/code/packages/sdk/src/nodes/node.ts @@ -283,11 +283,15 @@ export abstract class WeaveNode implements WeaveNodeBase { } }); + let clone: Konva.Node | undefined = undefined; + node.on('dragstart', (e) => { + const nodeTarget = e.target; + this.didMove = false; if (e.evt?.buttons === 0) { - e.target.stopDrag(); + nodeTarget.stopDrag(); return; } @@ -296,21 +300,56 @@ export abstract class WeaveNode implements WeaveNodeBase { const isErasing = this.instance.getActiveAction() === 'eraseTool'; if (isErasing) { - e.target.stopDrag(); + nodeTarget.stopDrag(); return; } - this.instance.emitEvent('onDrag', e.target); + this.instance.emitEvent('onDrag', nodeTarget); if (stage.isMouseWheelPressed()) { e.cancelBubble = true; - e.target.stopDrag(); + nodeTarget.stopDrag(); + } + + const realNodeTarget: Konva.Node = this.getRealSelectedNode(nodeTarget); + + if (realNodeTarget.getAttrs().isCloned) { + return; + } + + if (e.evt?.altKey) { + nodeTarget.stopDrag(e.evt); + + e.cancelBubble = true; + + clone = this.instance.getCloningManager().cloneNode(realNodeTarget); + + if (clone && !this.instance.getCloningManager().isClone(clone)) { + clone.setAttrs({ isCloned: true }); + this.instance.getCloningManager().addClone(clone); + } + + stage.setPointersPositions(e.evt); + + const nodesSelectionPlugin = this.getNodesSelectionPlugin(); + nodesSelectionPlugin?.setSelectedNodes([]); + + setTimeout(() => { + nodesSelectionPlugin?.setSelectedNodes( + this.instance.getCloningManager().getClones() + ); + clone?.startDrag(e.evt); + }, 0); } }); const handleDragMove = (e: KonvaEventObject) => { + const nodeTarget = e.target; + + e.cancelBubble = true; + if (e.evt?.buttons === 0) { - e.target.stopDrag(); + nodeTarget.stopDrag(); return; } @@ -321,26 +360,34 @@ export abstract class WeaveNode implements WeaveNodeBase { const isErasing = this.instance.getActiveAction() === 'eraseTool'; if (isErasing) { - e.target.stopDrag(); + nodeTarget.stopDrag(); return; } if (stage.isMouseWheelPressed()) { e.cancelBubble = true; - e.target.stopDrag(); + nodeTarget.stopDrag(); return; } + const realNodeTarget: Konva.Node = this.getRealSelectedNode(nodeTarget); + if ( this.isSelecting() && - this.isNodeSelected(node) && + // this.isNodeSelected(node) && this.getSelectionPlugin()?.getSelectedNodes().length === 1 ) { clearContainerTargets(this.instance); - const layerToMove = containerOverCursor(this.instance, [node]); + const layerToMove = containerOverCursor(this.instance, [ + realNodeTarget, + ]); - if (layerToMove && !hasFrames(node) && node.isDragging()) { + if ( + layerToMove && + !hasFrames(realNodeTarget) && + realNodeTarget.isDragging() + ) { layerToMove.fire(WEAVE_NODE_CUSTOM_EVENTS.onTargetEnter, { bubbles: true, }); @@ -351,6 +398,14 @@ export abstract class WeaveNode implements WeaveNodeBase { node.on('dragmove', throttle(handleDragMove, 100)); node.on('dragend', (e) => { + const nodeTarget = e.target; + + e.cancelBubble = true; + + if (clone) { + clone = undefined; + } + if (!this.didMove) { return; } @@ -358,15 +413,16 @@ export abstract class WeaveNode implements WeaveNodeBase { const isErasing = this.instance.getActiveAction() === 'eraseTool'; if (isErasing) { - e.target.stopDrag(); + nodeTarget.stopDrag(); return; } this.instance.emitEvent('onDrag', null); + const realNodeTarget: Konva.Node = this.getRealSelectedNode(nodeTarget); + if ( this.isSelecting() && - this.isNodeSelected(node) && this.getSelectionPlugin()?.getSelectedNodes().length === 1 ) { clearContainerTargets(this.instance); @@ -384,7 +440,9 @@ export abstract class WeaveNode implements WeaveNodeBase { nodesDistanceSnappingPlugin.cleanupGuidelines(); } - const layerToMove = containerOverCursor(this.instance, [node]); + const layerToMove = containerOverCursor(this.instance, [ + realNodeTarget, + ]); let containerToMove: Konva.Layer | Konva.Node | undefined = this.instance.getMainLayer(); @@ -394,14 +452,24 @@ export abstract class WeaveNode implements WeaveNodeBase { } let moved = false; - if (containerToMove && !hasFrames(node)) { + if ( + containerToMove && + !hasFrames(node) && + !realNodeTarget.getAttrs().isCloned + ) { moved = moveNodeToContainer( this.instance, - e.target, + realNodeTarget, containerToMove ); } + if (realNodeTarget.getAttrs().isCloned) { + this.instance.getCloningManager().removeClone(realNodeTarget); + } + + realNodeTarget?.setAttrs({ isCloned: undefined }); + if (containerToMove) { containerToMove.fire(WEAVE_NODE_CUSTOM_EVENTS.onTargetLeave, { bubbles: true, @@ -410,7 +478,7 @@ export abstract class WeaveNode implements WeaveNodeBase { if (!moved) { this.instance.updateNode( - this.serialize(node as WeaveElementInstance) + this.serialize(realNodeTarget as WeaveElementInstance) ); } } @@ -719,6 +787,13 @@ export abstract class WeaveNode implements WeaveNodeBase { return merge(transformProperties, nodeTransformConfig ?? {}); } + private getNodesSelectionPlugin() { + const nodesSelectionPlugin = + this.instance.getPlugin('nodesSelection'); + + return nodesSelectionPlugin; + } + getNodesEdgeSnappingPlugin() { const snappingPlugin = this.instance.getPlugin( @@ -742,4 +817,26 @@ export abstract class WeaveNode implements WeaveNodeBase { y: 0, }; } + + private getRealSelectedNode(nodeTarget: Konva.Node) { + const stage = this.instance.getStage(); + + let realNodeTarget: Konva.Node = nodeTarget; + + if (nodeTarget.getParent() instanceof Konva.Transformer) { + const mousePos = stage.getPointerPosition(); + const intersections = stage.getAllIntersections(mousePos); + const nodesIntersected = intersections.filter( + (ele) => ele.getAttrs().nodeType + ); + + if (nodesIntersected.length > 0) { + realNodeTarget = this.instance.getInstanceRecursive( + nodesIntersected[nodesIntersected.length - 1] + ); + } + } + + return realNodeTarget; + } } diff --git a/code/packages/sdk/src/nodes/video/video.ts b/code/packages/sdk/src/nodes/video/video.ts index 4c11146ab..dadb3402c 100644 --- a/code/packages/sdk/src/nodes/video/video.ts +++ b/code/packages/sdk/src/nodes/video/video.ts @@ -292,6 +292,7 @@ export class WeaveVideoNode extends WeaveNode { fill: this.config.style.background.color, stroke: this.config.style.background.strokeColor, strokeWidth: this.config.style.background.strokeWidth, + nodeId: id, }); videoGroup.add(bg); @@ -306,6 +307,7 @@ export class WeaveVideoNode extends WeaveNode { draggable: false, image: undefined, name: undefined, + nodeId: id, }); video.hide(); @@ -322,6 +324,7 @@ export class WeaveVideoNode extends WeaveNode { strokeWidth: 0, fill: this.config.style.track.color, name: undefined, + nodeId: id, }); this.instance.addEventListener( @@ -348,6 +351,7 @@ export class WeaveVideoNode extends WeaveNode { draggable: false, image: undefined, name: undefined, + nodeId: id, }); videoPlaceholder.show(); @@ -373,6 +377,7 @@ export class WeaveVideoNode extends WeaveNode { strokeWidth: this.config.style.iconBackground.strokeWidth, stroke: this.config.style.iconBackground.strokeColor, fill: this.config.style.iconBackground.color, + nodeId: id, }); videoIconGroup.add(videoIconBg); @@ -386,6 +391,7 @@ export class WeaveVideoNode extends WeaveNode { height: this.config.style.icon.height, fill: 'transparent', image: this.videoIconImage, + nodeId: id, }); videoIconGroup.add(videoIcon); diff --git a/code/packages/sdk/src/plugins/nodes-distance-snapping/nodes-distance-snapping.ts b/code/packages/sdk/src/plugins/nodes-distance-snapping/nodes-distance-snapping.ts index 35c4fe9d3..58d808c0b 100644 --- a/code/packages/sdk/src/plugins/nodes-distance-snapping/nodes-distance-snapping.ts +++ b/code/packages/sdk/src/plugins/nodes-distance-snapping/nodes-distance-snapping.ts @@ -1110,6 +1110,7 @@ export class WeaveNodesDistanceSnappingPlugin extends WeavePlugin { } disable(): void { + this.cleanupGuidelines(); this.enabled = false; } } diff --git a/code/packages/sdk/src/plugins/nodes-edge-snapping/nodes-edge-snapping.ts b/code/packages/sdk/src/plugins/nodes-edge-snapping/nodes-edge-snapping.ts index 0731c9d0a..f4a2ba4fb 100644 --- a/code/packages/sdk/src/plugins/nodes-edge-snapping/nodes-edge-snapping.ts +++ b/code/packages/sdk/src/plugins/nodes-edge-snapping/nodes-edge-snapping.ts @@ -505,6 +505,7 @@ export class WeaveNodesEdgeSnappingPlugin extends WeavePlugin { } disable(): void { + this.cleanupGuidelines(); this.enabled = false; } } diff --git a/code/packages/sdk/src/plugins/nodes-selection/nodes-selection.ts b/code/packages/sdk/src/plugins/nodes-selection/nodes-selection.ts index 76c4a6e39..c4d73fa64 100644 --- a/code/packages/sdk/src/plugins/nodes-selection/nodes-selection.ts +++ b/code/packages/sdk/src/plugins/nodes-selection/nodes-selection.ts @@ -50,6 +50,8 @@ import type { WeaveNodesDistanceSnappingPlugin } from '../nodes-distance-snappin import { WEAVE_NODES_DISTANCE_SNAPPING_PLUGIN_KEY } from '../nodes-distance-snapping/constants'; import { WEAVE_STAGE_GRID_PLUGIN_KEY } from '../stage-grid/constants'; import type { WeaveStageGridPlugin } from '../stage-grid/stage-grid'; +import type { WeaveStagePanningPlugin } from '../stage-panning/stage-panning'; +import { WEAVE_STAGE_PANNING_KEY } from '../stage-panning/constants'; export class WeaveNodesSelectionPlugin extends WeavePlugin { private tr!: Konva.Transformer; @@ -313,6 +315,12 @@ export class WeaveNodesSelectionPlugin extends WeavePlugin { node.updatePosition(node.getAbsolutePosition()); } + if (e.evt?.altKey) { + tr.stopDrag(e.evt); + + e.cancelBubble = true; + } + tr.forceUpdate(); }); @@ -321,6 +329,8 @@ export class WeaveNodesSelectionPlugin extends WeavePlugin { ) => { const actualPos = { x: e.target.x(), y: e.target.y() }; + e.cancelBubble = true; + if (initialPos) { const moved = this.checkMovedDrag(initialPos, actualPos); if (moved) { @@ -338,8 +348,6 @@ export class WeaveNodesSelectionPlugin extends WeavePlugin { this.didMove = true; - e.cancelBubble = true; - const selectedNodes = tr.nodes(); let selectionContainsFrames = false; for (let i = 0; i < selectedNodes.length; i++) { @@ -372,12 +380,17 @@ export class WeaveNodesSelectionPlugin extends WeavePlugin { e.cancelBubble = true; + this.instance.getCloningManager().cleanupClones(); + + this.getStagePanningPlugin()?.cleanupEdgeMoveIntervals(); + const selectedNodes = tr.nodes(); let selectionContainsFrames = false; for (let i = 0; i < selectedNodes.length; i++) { const node = selectedNodes[i]; selectionContainsFrames = selectionContainsFrames || hasFrames(node); node.updatePosition(node.getAbsolutePosition()); + node.setAttrs({ isCloned: undefined }); } if (this.isSelecting() && tr.nodes().length > 1) { @@ -779,7 +792,9 @@ export class WeaveNodesSelectionPlugin extends WeavePlugin { (e.code === 'Backspace' || e.code === 'Delete') && Object.keys(window.weaveTextEditing).length === 0 ) { - this.removeSelectedNodes(); + Promise.resolve().then(() => { + this.removeSelectedNodes(); + }); return; } }); @@ -1450,6 +1465,13 @@ export class WeaveNodesSelectionPlugin extends WeavePlugin { return snappingPlugin; } + getStagePanningPlugin() { + const stagePanning = this.instance.getPlugin( + WEAVE_STAGE_PANNING_KEY + ); + return stagePanning; + } + getSelectorConfig(): TransformerConfig { return this.config.selection; } diff --git a/code/packages/sdk/src/plugins/stage-panning/constants.ts b/code/packages/sdk/src/plugins/stage-panning/constants.ts index 37c14f89d..3f3ae2bbe 100644 --- a/code/packages/sdk/src/plugins/stage-panning/constants.ts +++ b/code/packages/sdk/src/plugins/stage-panning/constants.ts @@ -3,3 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 export const WEAVE_STAGE_PANNING_KEY = 'stagePanning'; + +export const WEAVE_STAGE_PANNING_DEFAULT_CONFIG = { + edgePanOffset: 50, + edgePanSpeed: 10, +}; diff --git a/code/packages/sdk/src/plugins/stage-panning/stage-panning.ts b/code/packages/sdk/src/plugins/stage-panning/stage-panning.ts index c3c1f1356..911d6b74b 100644 --- a/code/packages/sdk/src/plugins/stage-panning/stage-panning.ts +++ b/code/packages/sdk/src/plugins/stage-panning/stage-panning.ts @@ -3,18 +3,17 @@ // SPDX-License-Identifier: Apache-2.0 import { WeavePlugin } from '@/plugins/plugin'; -import { WEAVE_STAGE_PANNING_KEY } from './constants'; +import { + WEAVE_STAGE_PANNING_DEFAULT_CONFIG, + WEAVE_STAGE_PANNING_KEY, +} from './constants'; import type { KonvaEventObject } from 'konva/lib/Node'; import type { Stage } from 'konva/lib/Stage'; import type { Vector2d } from 'konva/lib/types'; import type { WeaveStageZoomPlugin } from '../stage-zoom/stage-zoom'; import type { WeaveContextMenuPlugin } from '../context-menu/context-menu'; import { MOVE_TOOL_ACTION_NAME } from '@/actions/move-tool/constants'; -import { - getTopmostShadowHost, - // getTopmostShadowHost, - isInShadowDOM, -} from '@/utils'; +import { getTopmostShadowHost, isInShadowDOM } from '@/utils'; import type { WeaveNodesEdgeSnappingPlugin } from '../nodes-edge-snapping/nodes-edge-snapping'; import type { WeaveNodesDistanceSnappingPlugin } from '../nodes-distance-snapping/nodes-distance-snapping'; import type { WeaveNodesSelectionPlugin } from '../nodes-selection/nodes-selection'; @@ -22,8 +21,16 @@ import { WEAVE_NODES_EDGE_SNAPPING_PLUGIN_KEY } from '../nodes-edge-snapping/con import { WEAVE_NODES_DISTANCE_SNAPPING_PLUGIN_KEY } from '../nodes-distance-snapping/constants'; import { WEAVE_NODES_SELECTION_KEY } from '../nodes-selection/constants'; import { WEAVE_CONTEXT_MENU_PLUGIN_KEY } from '../context-menu/constants'; +import type { WeaveStageGridPlugin } from '../stage-grid/stage-grid'; +import type Konva from 'konva'; +import merge from 'lodash/merge'; +import type { + WeaveStagePanningPluginConfig, + WeaveStagePanningPluginParams, +} from './types'; export class WeaveStagePanningPlugin extends WeavePlugin { + private readonly config!: WeaveStagePanningPluginConfig; private moveToolActive: boolean; private isMouseLeftButtonPressed: boolean; private isMouseMiddleButtonPressed: boolean; @@ -34,13 +41,23 @@ export class WeaveStagePanningPlugin extends WeavePlugin { private pointers: Map; private panning: boolean = false; protected previousPointer!: string | null; + protected currentPointer: Konva.Vector2d | null = null; + protected stageScrollInterval: NodeJS.Timeout | undefined = undefined; + protected targetScrollIntervals: Record = + {}; + getLayerName = undefined; initLayer = undefined; onRender: undefined; - constructor() { + constructor(params?: WeaveStagePanningPluginParams) { super(); + this.config = merge( + WEAVE_STAGE_PANNING_DEFAULT_CONFIG, + params?.config ?? {} + ); + this.pointers = new Map(); this.panning = false; this.isDragging = false; @@ -165,6 +182,9 @@ export class WeaveStagePanningPlugin extends WeavePlugin { }); const handleMouseMove = (e: KonvaEventObject) => { + const pos = stage.getPointerPosition(); + if (pos) this.currentPointer = pos; + if (['touch', 'pen'].includes(e.evt.pointerType) && e.evt.buttons !== 1) { return; } @@ -186,8 +206,6 @@ export class WeaveStagePanningPlugin extends WeavePlugin { this.getContextMenuPlugin()?.cancelLongPressTimer(); - const pos = stage.getPointerPosition(); - if (pos && lastPos) { const dx = pos.x - lastPos.x; const dy = pos.y - lastPos.y; @@ -251,6 +269,102 @@ export class WeaveStagePanningPlugin extends WeavePlugin { window.addEventListener('wheel', handleWheel, { passive: true }); + stage.on('dragstart', (e) => { + const duration = 1000 / 60; + + if ( + this.targetScrollIntervals[e.target.getAttrs().id ?? ''] !== undefined + ) { + return; + } + + this.targetScrollIntervals[e.target.getAttrs().id ?? ''] = setInterval( + () => { + const pos = stage.getPointerPosition(); + const offset = this.config.edgePanOffset; + const speed = this.config.edgePanSpeed; + + if (!pos) return; + + const isNearLeft = pos.x < offset / stage.scaleX(); + if (isNearLeft) { + e.target.x(e.target.x() - speed / stage.scaleX()); + } + + const isNearRight = pos.x > stage.width() - offset / stage.scaleX(); + if (isNearRight) { + e.target.x(e.target.x() + speed / stage.scaleX()); + } + + const isNearTop = pos.y < offset / stage.scaleY(); + if (isNearTop) { + e.target.y(e.target.y() - speed / stage.scaleX()); + } + + const isNearBottom = pos.y > stage.height() - offset / stage.scaleY(); + if (isNearBottom) { + e.target.y(e.target.y() + speed / stage.scaleX()); + } + + this.getStageGridPlugin()?.renderGrid(); + }, + duration + ); + + if (this.stageScrollInterval !== undefined) { + return; + } + + this.stageScrollInterval = setInterval(() => { + const pos = stage.getPointerPosition(); + const offset = this.config.edgePanOffset; + const speed = this.config.edgePanSpeed; + + if (!pos) return; + + let isOnBorder = false; + + const isNearLeft = pos.x < offset; + if (isNearLeft) { + stage.x(stage.x() + speed); + isOnBorder = true; + } + + const isNearRight = pos.x > stage.width() - offset; + if (isNearRight) { + stage.x(stage.x() - speed); + isOnBorder = true; + } + + const isNearTop = pos.y < offset; + if (isNearTop) { + stage.y(stage.y() + speed); + isOnBorder = true; + } + + const isNearBottom = pos.y > stage.height() - offset; + if (isNearBottom) { + stage.y(stage.y() - speed); + isOnBorder = true; + } + + if (isOnBorder) { + this.getNodesEdgeSnappingPlugin()?.disable(); + this.getNodesDistanceSnappingPlugin()?.disable(); + } + if (!isOnBorder) { + this.getNodesEdgeSnappingPlugin()?.enable(); + this.getNodesDistanceSnappingPlugin()?.enable(); + } + + this.getStageGridPlugin()?.renderGrid(); + }, duration); + }); + + stage.on('dragend', () => { + this.cleanupEdgeMoveIntervals(); + }); + stage.container().style.touchAction = 'none'; stage.container().style.userSelect = 'none'; stage.container().style.setProperty('-webkit-user-drag', 'none'); @@ -321,6 +435,27 @@ export class WeaveStagePanningPlugin extends WeavePlugin { return snappingPlugin; } + getStageGridPlugin() { + const gridPlugin = + this.instance.getPlugin('stageGrid'); + return gridPlugin; + } + + getCurrentPointer() { + return this.currentPointer; + } + + cleanupEdgeMoveIntervals() { + const intervals = Object.keys(this.targetScrollIntervals); + for (const key of intervals) { + clearInterval(this.targetScrollIntervals[key]); + this.targetScrollIntervals[key] = undefined; + } + + clearInterval(this.stageScrollInterval); + this.stageScrollInterval = undefined; + } + enable(): void { this.enabled = true; } diff --git a/code/packages/sdk/src/plugins/stage-panning/types.ts b/code/packages/sdk/src/plugins/stage-panning/types.ts new file mode 100644 index 000000000..d0f0076df --- /dev/null +++ b/code/packages/sdk/src/plugins/stage-panning/types.ts @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +// +// SPDX-License-Identifier: Apache-2.0 + +export type WeaveStagePanningPluginParams = { + config?: Partial; +}; + +export type WeaveStagePanningPluginConfig = { + edgePanOffset: number; + edgePanSpeed: number; +}; diff --git a/code/packages/sdk/src/weave.ts b/code/packages/sdk/src/weave.ts index 720fac662..330a80c1e 100644 --- a/code/packages/sdk/src/weave.ts +++ b/code/packages/sdk/src/weave.ts @@ -580,7 +580,7 @@ export class Weave { const stage = this.getStage(); let nodeContainer = node.getParent()?.getAttrs().id ?? ''; - if (typeof node.getParent()?.getAttrs().nodeId !== 'undefined') { + if (typeof node?.getParent()?.getAttrs().nodeId !== 'undefined') { const realContainer = stage.findOne( `#${node.getParent()?.getAttrs().nodeId}` ); @@ -594,9 +594,9 @@ export class Weave { getNodeContainer(node: WeaveElementInstance | Konva.Node): Konva.Node | null { const stage = this.getStage(); - let nodeContainer: Konva.Node | null = node.getParent(); + let nodeContainer: Konva.Node | null = node?.getParent(); - if (typeof node.getParent()?.getAttrs().nodeId !== 'undefined') { + if (typeof node?.getParent()?.getAttrs().nodeId !== 'undefined') { const realContainer = stage.findOne( `#${node.getParent()?.getAttrs().nodeId}` ); @@ -708,6 +708,10 @@ export class Weave { // CLONING MANAGEMENT METHODS PROXIES + getCloningManager(): WeaveCloningManager { + return this.cloningManager; + } + nodesToGroupSerialized(instancesToClone: Konva.Node[]): WeaveSerializedGroup { return this.cloningManager.nodesToGroupSerialized(instancesToClone); } diff --git a/docs/content/docs/main/changelog/index.mdx b/docs/content/docs/main/changelog/index.mdx index 7bcee9dea..e7421e997 100644 --- a/docs/content/docs/main/changelog/index.mdx +++ b/docs/content/docs/main/changelog/index.mdx @@ -5,6 +5,7 @@ description: Check out the latest changes to Weave.js. ## Pre-release versions +- [**0.74.0**](/docs/main/changelog/prerelease/0.74.0) - [**0.73.1**](/docs/main/changelog/prerelease/0.73.1) - [**0.73.0**](/docs/main/changelog/prerelease/0.73.0) - [**0.72.0**](/docs/main/changelog/prerelease/0.72.0) diff --git a/docs/content/docs/main/changelog/prerelease/0.74.0.mdx b/docs/content/docs/main/changelog/prerelease/0.74.0.mdx new file mode 100644 index 000000000..87b918f6b --- /dev/null +++ b/docs/content/docs/main/changelog/prerelease/0.74.0.mdx @@ -0,0 +1,12 @@ +--- +title: v0.74.0 +description: Allow to clone nodes with click + alt and drag +--- + +## Metadata + +- **Release date**: 2025-10-09 + +### Added + +- [#693](https://github.com/InditexTech/weavejs/issues/693) Clone elements on ALT + Click & Drag diff --git a/docs/content/docs/main/changelog/prerelease/meta.json b/docs/content/docs/main/changelog/prerelease/meta.json index d2c1cf31b..bc64b9990 100644 --- a/docs/content/docs/main/changelog/prerelease/meta.json +++ b/docs/content/docs/main/changelog/prerelease/meta.json @@ -2,6 +2,7 @@ "title": "Prerelease versions", "description": "Detailed changelog for Weave.js pre-release versions", "pages": [ + "0.74.0", "0.73.1", "0.73.0", "0.72.1", diff --git a/docs/content/docs/sdk/api-reference/plugins/stage-panning.mdx b/docs/content/docs/sdk/api-reference/plugins/stage-panning.mdx index 24dfd903d..a55a80921 100644 --- a/docs/content/docs/sdk/api-reference/plugins/stage-panning.mdx +++ b/docs/content/docs/sdk/api-reference/plugins/stage-panning.mdx @@ -30,5 +30,50 @@ import { WeaveStagePanningPlugin } from "@inditextech/weave-sdk"; ## Instantiation ```ts -new WeaveStagePanningPlugin(); +new WeaveStagePanningPlugin(params?: WeaveStagePanningPluginParams); ``` + +## TypeScript types + +```ts +type WeaveStagePanningPluginParams = { + config?: Partial; +}; + +type WeaveStagePanningPluginConfig = { + edgePanOffset: number; + edgePanSpeed: number; +}; +``` + +## Parameters + +For `WeaveStagePanningPluginParams`: + + + +For `WeaveStagePanningPluginConfig`: + +