209 lines
6.6 KiB
Svelte
209 lines
6.6 KiB
Svelte
<svelte:options immutable />
|
|
|
|
<script lang="ts">
|
|
import { setContext, onDestroy } from 'svelte';
|
|
import { get, writable } from 'svelte/store';
|
|
import cc from 'classcat';
|
|
import { errorMessages, Position } from '@xyflow/system';
|
|
|
|
import drag from '$lib/actions/drag';
|
|
import { useStore } from '$lib/store';
|
|
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
|
import type { NodeWrapperProps } from './types';
|
|
import { getNodeInlineStyleDimensions } from './utils';
|
|
import { createNodeEventDispatcher } from '$lib';
|
|
|
|
interface $$Props extends NodeWrapperProps {}
|
|
|
|
export let node: $$Props['node'];
|
|
export let id: $$Props['id'];
|
|
export let data: $$Props['data'] = {};
|
|
export let selected: $$Props['selected'] = false;
|
|
export let draggable: $$Props['draggable'] = undefined;
|
|
export let selectable: $$Props['selectable'] = undefined;
|
|
export let connectable: $$Props['connectable'] = true;
|
|
export let hidden: $$Props['hidden'] = false;
|
|
export let dragging: boolean = false;
|
|
export let resizeObserver: $$Props['resizeObserver'] = null;
|
|
export let style: $$Props['style'] = undefined;
|
|
export let type: $$Props['type'] = 'default';
|
|
export let isParent: $$Props['isParent'] = false;
|
|
export let positionX: $$Props['positionX'];
|
|
export let positionY: $$Props['positionY'];
|
|
export let positionOriginX: $$Props['positionOriginX'];
|
|
export let positionOriginY: $$Props['positionOriginY'];
|
|
export let sourcePosition: $$Props['sourcePosition'] = undefined;
|
|
export let targetPosition: $$Props['targetPosition'] = undefined;
|
|
export let zIndex: $$Props['zIndex'];
|
|
export let measuredWidth: $$Props['measuredWidth'] = undefined;
|
|
export let measuredHeight: $$Props['measuredHeight'] = undefined;
|
|
export let initialWidth: $$Props['initialWidth'] = undefined;
|
|
export let initialHeight: $$Props['initialHeight'] = undefined;
|
|
export let width: $$Props['width'] = undefined;
|
|
export let height: $$Props['height'] = undefined;
|
|
export let dragHandle: $$Props['dragHandle'] = undefined;
|
|
export let initialized: $$Props['initialized'] = false;
|
|
let className: string = '';
|
|
export { className as class };
|
|
|
|
const store = useStore();
|
|
const {
|
|
nodeTypes,
|
|
nodeDragThreshold,
|
|
selectNodesOnDrag,
|
|
handleNodeSelection,
|
|
updateNodeInternals
|
|
} = store;
|
|
|
|
let nodeRef: HTMLDivElement;
|
|
let prevNodeRef: HTMLDivElement | null = null;
|
|
|
|
const dispatchNodeEvent = createNodeEventDispatcher();
|
|
const connectableStore = writable(connectable);
|
|
let prevType: string | undefined = undefined;
|
|
let prevSourcePosition: Position | undefined = undefined;
|
|
let prevTargetPosition: Position | undefined = undefined;
|
|
|
|
$: nodeType = type || 'default';
|
|
$: nodeTypeValid = !!$nodeTypes[nodeType];
|
|
$: nodeComponent = $nodeTypes[nodeType] || DefaultNode;
|
|
|
|
$: {
|
|
if (!nodeTypeValid) {
|
|
console.warn('003', errorMessages['error003'](type!));
|
|
}
|
|
}
|
|
|
|
$: inlineStyleDimensions = getNodeInlineStyleDimensions({
|
|
width,
|
|
height,
|
|
initialWidth,
|
|
initialHeight,
|
|
measuredWidth,
|
|
measuredHeight
|
|
});
|
|
|
|
$: {
|
|
connectableStore.set(!!connectable);
|
|
}
|
|
|
|
$: {
|
|
// if type, sourcePosition or targetPosition changes,
|
|
// we need to re-calculate the handle positions
|
|
const doUpdate =
|
|
(prevType && nodeType !== prevType) ||
|
|
(prevSourcePosition && sourcePosition !== prevSourcePosition) ||
|
|
(prevTargetPosition && targetPosition !== prevTargetPosition);
|
|
|
|
if (doUpdate) {
|
|
requestAnimationFrame(() =>
|
|
updateNodeInternals(
|
|
new Map([
|
|
[
|
|
id,
|
|
{
|
|
id,
|
|
nodeElement: nodeRef,
|
|
forceUpdate: true
|
|
}
|
|
]
|
|
])
|
|
)
|
|
);
|
|
}
|
|
|
|
prevType = nodeType;
|
|
prevSourcePosition = sourcePosition;
|
|
prevTargetPosition = targetPosition;
|
|
}
|
|
|
|
setContext('svelteflow__node_id', id);
|
|
setContext('svelteflow__node_connectable', connectableStore);
|
|
|
|
$: {
|
|
if (resizeObserver && nodeRef !== prevNodeRef) {
|
|
prevNodeRef && resizeObserver.unobserve(prevNodeRef);
|
|
nodeRef && resizeObserver.observe(nodeRef);
|
|
prevNodeRef = nodeRef;
|
|
}
|
|
}
|
|
|
|
onDestroy(() => {
|
|
if (prevNodeRef) {
|
|
resizeObserver?.unobserve(prevNodeRef);
|
|
}
|
|
});
|
|
|
|
function onSelectNodeHandler(event: MouseEvent | TouchEvent) {
|
|
if (selectable && (!get(selectNodesOnDrag) || !draggable || get(nodeDragThreshold) > 0)) {
|
|
// this handler gets called by XYDrag on drag start when selectNodesOnDrag=true
|
|
// here we only need to call it when selectNodesOnDrag=false
|
|
handleNodeSelection(id);
|
|
}
|
|
|
|
dispatchNodeEvent('nodeclick', { node, event });
|
|
}
|
|
</script>
|
|
|
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
{#if !hidden}
|
|
<div
|
|
use:drag={{
|
|
nodeId: id,
|
|
isSelectable: selectable,
|
|
disabled: false,
|
|
handleSelector: dragHandle,
|
|
noDragClass: 'nodrag',
|
|
onNodeMouseDown: handleNodeSelection,
|
|
onDrag: (event, _, targetNode, nodes) => {
|
|
dispatchNodeEvent('nodedrag', { event, targetNode, nodes });
|
|
},
|
|
onDragStart: (event, _, targetNode, nodes) => {
|
|
dispatchNodeEvent('nodedragstart', { event, targetNode, nodes });
|
|
},
|
|
onDragStop: (event, _, targetNode, nodes) => {
|
|
dispatchNodeEvent('nodedragstop', { event, targetNode, nodes });
|
|
},
|
|
store
|
|
}}
|
|
bind:this={nodeRef}
|
|
data-id={id}
|
|
class={cc(['svelte-flow__node', `svelte-flow__node-${nodeType}`, className])}
|
|
class:dragging
|
|
class:selected
|
|
class:draggable
|
|
class:connectable
|
|
class:selectable
|
|
class:nopan={draggable}
|
|
class:parent={isParent}
|
|
style:z-index={zIndex}
|
|
style:transform="translate({positionOriginX}px, {positionOriginY}px)"
|
|
style:visibility={initialized ? 'visible' : 'hidden'}
|
|
style="{style ?? ''};{inlineStyleDimensions.width}{inlineStyleDimensions.height}"
|
|
on:click={onSelectNodeHandler}
|
|
on:mouseenter={(event) => dispatchNodeEvent('nodemouseenter', { node, event })}
|
|
on:mouseleave={(event) => dispatchNodeEvent('nodemouseleave', { node, event })}
|
|
on:mousemove={(event) => dispatchNodeEvent('nodemousemove', { node, event })}
|
|
on:contextmenu={(event) => dispatchNodeEvent('nodecontextmenu', { node, event })}
|
|
>
|
|
<svelte:component
|
|
this={nodeComponent}
|
|
{data}
|
|
{id}
|
|
{selected}
|
|
{sourcePosition}
|
|
{targetPosition}
|
|
{zIndex}
|
|
{dragging}
|
|
{dragHandle}
|
|
type={nodeType}
|
|
isConnectable={$connectableStore}
|
|
positionAbsoluteX={positionX}
|
|
positionAbsoluteY={positionY}
|
|
{width}
|
|
{height}
|
|
/>
|
|
</div>
|
|
{/if}
|