@@ -237,7 +237,7 @@ const OverviewFlow = () => {
|
|||||||
onPaneMouseMove={onPaneMouseMove}
|
onPaneMouseMove={onPaneMouseMove}
|
||||||
>
|
>
|
||||||
<MiniMap nodeBorderRadius={2} />
|
<MiniMap nodeBorderRadius={2} />
|
||||||
<Controls />
|
<Controls orientation="horizontal" />
|
||||||
<Background gap={25} />
|
<Background gap={25} />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -189,8 +189,9 @@
|
|||||||
autoPanOnNodeDrag
|
autoPanOnNodeDrag
|
||||||
connectionMode={ConnectionMode.Strict}
|
connectionMode={ConnectionMode.Strict}
|
||||||
attributionPosition={'top-center'}
|
attributionPosition={'top-center'}
|
||||||
|
deleteKey={['Backspace', 'd']}
|
||||||
>
|
>
|
||||||
<Controls>
|
<Controls orientation="horizontal">
|
||||||
<ControlButton slot="before">xy</ControlButton>
|
<ControlButton slot="before">xy</ControlButton>
|
||||||
<ControlButton aria-label="log" on:click={() => console.log('control button')}
|
<ControlButton aria-label="log" on:click={() => console.log('control button')}
|
||||||
>log</ControlButton
|
>log</ControlButton
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ function ControlsComponent({
|
|||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
position = 'bottom-left',
|
position = 'bottom-left',
|
||||||
|
orientation = 'vertical',
|
||||||
'aria-label': ariaLabel = 'React Flow controls',
|
'aria-label': ariaLabel = 'React Flow controls',
|
||||||
}: ControlProps) {
|
}: ControlProps) {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
@@ -65,9 +66,11 @@ function ControlsComponent({
|
|||||||
onInteractiveChange?.(!isInteractive);
|
onInteractiveChange?.(!isInteractive);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const orientationClass = orientation === 'horizontal' ? 'horizontal' : 'vertical';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
className={cc(['react-flow__controls', className])}
|
className={cc(['react-flow__controls', orientationClass, className])}
|
||||||
position={position}
|
position={position}
|
||||||
style={style}
|
style={style}
|
||||||
data-testid="rf__controls"
|
data-testid="rf__controls"
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export type ControlProps = {
|
|||||||
/** ClassName applied to container */
|
/** ClassName applied to container */
|
||||||
className?: string;
|
className?: string;
|
||||||
'aria-label'?: string;
|
'aria-label'?: string;
|
||||||
|
orientation?: 'horizontal' | 'vertical';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ControlButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
|
export type ControlButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
|
||||||
|
|||||||
@@ -109,11 +109,13 @@ export function useKeyPress(
|
|||||||
target?.addEventListener('keydown', downHandler as EventListenerOrEventListenerObject);
|
target?.addEventListener('keydown', downHandler as EventListenerOrEventListenerObject);
|
||||||
target?.addEventListener('keyup', upHandler as EventListenerOrEventListenerObject);
|
target?.addEventListener('keyup', upHandler as EventListenerOrEventListenerObject);
|
||||||
window.addEventListener('blur', resetHandler);
|
window.addEventListener('blur', resetHandler);
|
||||||
|
window.addEventListener('contextmenu', resetHandler);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
target?.removeEventListener('keydown', downHandler as EventListenerOrEventListenerObject);
|
target?.removeEventListener('keydown', downHandler as EventListenerOrEventListenerObject);
|
||||||
target?.removeEventListener('keyup', upHandler as EventListenerOrEventListenerObject);
|
target?.removeEventListener('keyup', upHandler as EventListenerOrEventListenerObject);
|
||||||
window.removeEventListener('blur', resetHandler);
|
window.removeEventListener('blur', resetHandler);
|
||||||
|
window.removeEventListener('contextmenu', resetHandler);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [keyCode, setKeyPressed]);
|
}, [keyCode, setKeyPressed]);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
node_modules
|
node_modules
|
||||||
/build
|
/build
|
||||||
/.svelte-kit
|
/.svelte-kit
|
||||||
|
dist
|
||||||
/package
|
/package
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
|||||||
@@ -3,6 +3,5 @@
|
|||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"plugins": ["prettier-plugin-svelte"],
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
"pluginSearchDirs": ["."],
|
|
||||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# @xyflow/svelte
|
# @xyflow/svelte
|
||||||
|
|
||||||
|
## 0.0.40
|
||||||
|
|
||||||
|
- add `orientation` ('horizontal' or 'vertical'), `style` and `class` prop for `Controls` component
|
||||||
|
- allow multiple keys for `deleteKey`, `selectionKey`, `multiSelectionKey`, `panActivationKey` and `zoomActivationKey`
|
||||||
|
- fix node observe / unobserve
|
||||||
|
- fix edge and node types
|
||||||
|
- active context menu releases pressed keys to reset selection
|
||||||
|
|
||||||
## 0.0.39
|
## 0.0.39
|
||||||
|
|
||||||
## ⚠️ Breaking changes
|
## ⚠️ Breaking changes
|
||||||
|
|||||||
@@ -38,17 +38,11 @@ You only need a few lines to get a fully interactive (e.g. select and drag nodes
|
|||||||
```svelte
|
```svelte
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import {
|
import { SvelteFlow, Controls, Background, BackgroundVariant, MiniMap } from '@xyflow/svelte';
|
||||||
SvelteFlow,
|
|
||||||
Controls,
|
|
||||||
Background,
|
|
||||||
BackgroundVariant,
|
|
||||||
MiniMap,
|
|
||||||
} from '@xyflow/svelte';
|
|
||||||
|
|
||||||
// you need to import the styles for Svelte Flow to work
|
// you need to import the styles for Svelte Flow to work
|
||||||
// if you just want to load the basic styleds, you can import '@xyflow/svelte/dist/base.css'
|
// if you just want to load the basic styleds, you can import '@xyflow/svelte/dist/base.css'
|
||||||
import '@xyflow/svelte/dist/style.css'
|
import '@xyflow/svelte/dist/style.css';
|
||||||
|
|
||||||
// We are using writables for the nodes and edges to sync them easily. When a user drags a node for example, Svelte Flow updates its position. This also makes it easier to update nodes in user land.
|
// We are using writables for the nodes and edges to sync them easily. When a user drags a node for example, Svelte Flow updates its position. This also makes it easier to update nodes in user land.
|
||||||
const nodes = writable([
|
const nodes = writable([
|
||||||
@@ -78,12 +72,7 @@ You only need a few lines to get a fully interactive (e.g. select and drag nodes
|
|||||||
]);
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SvelteFlow
|
<SvelteFlow {nodes} {edges} fitView on:nodeclick={(event) => console.log('on node click', event)}>
|
||||||
{nodes}
|
|
||||||
{edges}
|
|
||||||
fitView
|
|
||||||
on:nodeclick={(event) => console.log('on node click', event)}
|
|
||||||
>
|
|
||||||
<Controls />
|
<Controls />
|
||||||
<Background variant={BackgroundVariant.Dots} />
|
<Background variant={BackgroundVariant.Dots} />
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
@@ -102,7 +91,6 @@ You only need a few lines to get a fully interactive (e.g. select and drag nodes
|
|||||||
|
|
||||||
And of course, we love Github stars ⭐
|
And of course, we love Github stars ⭐
|
||||||
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
If you want to check out the current version you need to run the following command from the root directory:
|
If you want to check out the current version you need to run the following command from the root directory:
|
||||||
@@ -113,7 +101,6 @@ If you want to check out the current version you need to run the following comma
|
|||||||
|
|
||||||
You can now access the examples under http://127.0.0.1:5173
|
You can now access the examples under http://127.0.0.1:5173
|
||||||
|
|
||||||
|
|
||||||
## Maintainers
|
## Maintainers
|
||||||
|
|
||||||
Svelte Flow is maintained by the team behind [xyflow](https://xyflow.com). If you need help or want to talk to us about a collaboration, reach out through our [contact form](https://xyflow.com/contact) or by joining our [Discord Server](https://discord.gg/Bqt6xrs).
|
Svelte Flow is maintained by the team behind [xyflow](https://xyflow.com). If you need help or want to talk to us about a collaboration, reach out through our [contact form](https://xyflow.com/contact) or by joining our [Discord Server](https://discord.gg/Bqt6xrs).
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
type $$Props = AttributionProps;
|
type $$Props = AttributionProps;
|
||||||
|
|
||||||
export let proOptions: AttributionProps['proOptions'] = undefined;
|
export let proOptions: $$Props['proOptions'] = undefined;
|
||||||
export let position: AttributionProps['position'] = 'bottom-right';
|
export let position: $$Props['position'] = 'bottom-right';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !proOptions?.hideAttribution}
|
{#if !proOptions?.hideAttribution}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
import portal from '$lib/actions/portal';
|
import portal from '$lib/actions/portal';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
type $$Props = {};
|
|
||||||
|
|
||||||
const { domNode } = useStore();
|
const { domNode } = useStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
edgecontextmenu: { edge: Edge; event: MouseEvent };
|
edgecontextmenu: { edge: Edge; event: MouseEvent };
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const edgeType = type || 'default';
|
$: edgeType = type || 'default';
|
||||||
$: edgeComponent = $edgeTypes[edgeType] || BezierEdgeInternal;
|
$: edgeComponent = $edgeTypes[edgeType] || BezierEdgeInternal;
|
||||||
$: markerStartUrl = markerStart ? `url(#${getMarkerId(markerStart, $flowId)})` : undefined;
|
$: markerStartUrl = markerStart ? `url(#${getMarkerId(markerStart, $flowId)})` : undefined;
|
||||||
$: markerEndUrl = markerEnd ? `url(#${getMarkerId(markerEnd, $flowId)})` : undefined;
|
$: markerEndUrl = markerEnd ? `url(#${getMarkerId(markerEnd, $flowId)})` : undefined;
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
{#if !hidden}
|
{#if !hidden}
|
||||||
<svg style:zIndex>
|
<svg style:z-index={zIndex}>
|
||||||
<g
|
<g
|
||||||
class={cc(['svelte-flow__edge', className])}
|
class={cc(['svelte-flow__edge', className])}
|
||||||
class:animated
|
class:animated
|
||||||
@@ -92,8 +92,8 @@
|
|||||||
aria-label={ariaLabel === null
|
aria-label={ariaLabel === null
|
||||||
? undefined
|
? undefined
|
||||||
: ariaLabel
|
: ariaLabel
|
||||||
? ariaLabel
|
? ariaLabel
|
||||||
: `Edge from ${source} to ${target}`}
|
: `Edge from ${source} to ${target}`}
|
||||||
role="img"
|
role="img"
|
||||||
>
|
>
|
||||||
<svelte:component
|
<svelte:component
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { shortcut, type ShortcutModifierDefinition } from '@svelte-put/shortcut';
|
import {
|
||||||
|
shortcut,
|
||||||
|
type ShortcutEventDetail,
|
||||||
|
type ShortcutModifierDefinition
|
||||||
|
} from '@svelte-put/shortcut';
|
||||||
import { isInputDOMNode, isMacOs } from '@xyflow/system';
|
import { isInputDOMNode, isMacOs } from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
@@ -19,7 +23,8 @@
|
|||||||
multiselectionKeyPressed,
|
multiselectionKeyPressed,
|
||||||
deleteKeyPressed,
|
deleteKeyPressed,
|
||||||
panActivationKeyPressed,
|
panActivationKeyPressed,
|
||||||
zoomActivationKeyPressed
|
zoomActivationKeyPressed,
|
||||||
|
selectionRect
|
||||||
} = useStore();
|
} = useStore();
|
||||||
|
|
||||||
function isKeyObject(key?: KeyDefinition | null): key is KeyDefinitionObject {
|
function isKeyObject(key?: KeyDefinition | null): key is KeyDefinitionObject {
|
||||||
@@ -40,140 +45,81 @@
|
|||||||
return isKeyObject(key) ? key.key : key;
|
return isKeyObject(key) ? key.key : key;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: selectionKeyDefinition = {
|
function getShortcutTrigger(
|
||||||
key: getKeyString(selectionKey),
|
key: KeyDefinition | KeyDefinition[] | null | undefined,
|
||||||
modifier: getModifier(selectionKey)
|
callback: (detail: ShortcutEventDetail) => void
|
||||||
};
|
) {
|
||||||
|
const keys = Array.isArray(key) ? key : [key];
|
||||||
|
return keys.map((_key) => {
|
||||||
|
const keyString = getKeyString(_key);
|
||||||
|
return {
|
||||||
|
key: keyString,
|
||||||
|
modifier: getModifier(_key),
|
||||||
|
enabled: keyString !== null,
|
||||||
|
callback
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$: multiSelectionKeyDefinition = {
|
function resetKeysAndSelection() {
|
||||||
key: getKeyString(multiSelectionKey),
|
selectionRect.set(null);
|
||||||
modifier: getModifier(multiSelectionKey)
|
selectionKeyPressed.set(false);
|
||||||
};
|
multiselectionKeyPressed.set(false);
|
||||||
|
deleteKeyPressed.set(false);
|
||||||
$: deleteKeyDefinition = {
|
panActivationKeyPressed.set(false);
|
||||||
key: getKeyString(deleteKey),
|
zoomActivationKeyPressed.set(false);
|
||||||
modifier: getModifier(deleteKey)
|
}
|
||||||
};
|
|
||||||
|
|
||||||
$: panActivationKeyDefinition = {
|
|
||||||
key: getKeyString(panActivationKey),
|
|
||||||
modifier: getModifier(panActivationKey)
|
|
||||||
};
|
|
||||||
|
|
||||||
$: zoomActivationKeyDefinition = {
|
|
||||||
key: getKeyString(zoomActivationKey),
|
|
||||||
modifier: getModifier(zoomActivationKey)
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window
|
<svelte:window
|
||||||
|
on:blur={resetKeysAndSelection}
|
||||||
|
on:contextmenu={resetKeysAndSelection}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [
|
trigger: getShortcutTrigger(selectionKey, () => selectionKeyPressed.set(true)),
|
||||||
{
|
|
||||||
...selectionKeyDefinition,
|
|
||||||
enabled: selectionKeyDefinition.key !== null,
|
|
||||||
callback: () => selectionKeyPressed.set(true)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [
|
trigger: getShortcutTrigger(selectionKey, () => selectionKeyPressed.set(false)),
|
||||||
{
|
|
||||||
...selectionKeyDefinition,
|
|
||||||
enabled: selectionKeyDefinition.key !== null,
|
|
||||||
callback: () => selectionKeyPressed.set(false)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [
|
trigger: getShortcutTrigger(multiSelectionKey, () => multiselectionKeyPressed.set(true)),
|
||||||
{
|
|
||||||
...multiSelectionKeyDefinition,
|
|
||||||
enabled: multiSelectionKeyDefinition.key !== null,
|
|
||||||
callback: () => multiselectionKeyPressed.set(true)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [
|
trigger: getShortcutTrigger(multiSelectionKey, () => multiselectionKeyPressed.set(false)),
|
||||||
{
|
|
||||||
...multiSelectionKeyDefinition,
|
|
||||||
enabled: multiSelectionKeyDefinition.key !== null,
|
|
||||||
callback: () => multiselectionKeyPressed.set(false)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [
|
trigger: getShortcutTrigger(deleteKey, (detail) => {
|
||||||
{
|
const isModifierKey =
|
||||||
...deleteKeyDefinition,
|
detail.originalEvent.ctrlKey ||
|
||||||
enabled: deleteKeyDefinition.key !== null,
|
detail.originalEvent.metaKey ||
|
||||||
callback: (detail) => {
|
detail.originalEvent.shiftKey;
|
||||||
const isModifierKey =
|
if (!isModifierKey && !isInputDOMNode(detail.originalEvent)) {
|
||||||
detail.originalEvent.ctrlKey ||
|
deleteKeyPressed.set(true);
|
||||||
detail.originalEvent.metaKey ||
|
|
||||||
detail.originalEvent.shiftKey;
|
|
||||||
if (!isModifierKey && !isInputDOMNode(detail.originalEvent)) {
|
|
||||||
deleteKeyPressed.set(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
}),
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [
|
trigger: getShortcutTrigger(deleteKey, () => deleteKeyPressed.set(false)),
|
||||||
{
|
|
||||||
...deleteKeyDefinition,
|
|
||||||
enabled: deleteKeyDefinition.key !== null,
|
|
||||||
callback: () => deleteKeyPressed.set(false)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [
|
trigger: getShortcutTrigger(panActivationKey, () => panActivationKeyPressed.set(true)),
|
||||||
{
|
|
||||||
...panActivationKeyDefinition,
|
|
||||||
enabled: panActivationKeyDefinition.key !== null,
|
|
||||||
callback: () => panActivationKeyPressed.set(true)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [
|
trigger: getShortcutTrigger(panActivationKey, () => panActivationKeyPressed.set(false)),
|
||||||
{
|
|
||||||
...panActivationKeyDefinition,
|
|
||||||
enabled: panActivationKeyDefinition.key !== null,
|
|
||||||
callback: () => panActivationKeyPressed.set(false)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [
|
trigger: getShortcutTrigger(zoomActivationKey, () => zoomActivationKeyPressed.set(true)),
|
||||||
{
|
|
||||||
...zoomActivationKeyDefinition,
|
|
||||||
enabled: zoomActivationKeyDefinition.key !== null,
|
|
||||||
callback: () => zoomActivationKeyPressed.set(true)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [
|
trigger: getShortcutTrigger(zoomActivationKey, () => zoomActivationKeyPressed.set(false)),
|
||||||
{
|
|
||||||
...zoomActivationKeyDefinition,
|
|
||||||
enabled: zoomActivationKeyDefinition.key !== null,
|
|
||||||
callback: () => zoomActivationKeyPressed.set(false)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { KeyDefinition } from '$lib/types';
|
import type { KeyDefinition } from '$lib/types';
|
||||||
|
|
||||||
export type KeyHandlerProps = {
|
export type KeyHandlerProps = {
|
||||||
selectionKey?: KeyDefinition | null;
|
selectionKey?: KeyDefinition | KeyDefinition[] | null;
|
||||||
multiSelectionKey?: KeyDefinition | null;
|
multiSelectionKey?: KeyDefinition | KeyDefinition[] | null;
|
||||||
deleteKey?: KeyDefinition | null;
|
deleteKey?: KeyDefinition | KeyDefinition[] | null;
|
||||||
panActivationKey?: KeyDefinition | null;
|
panActivationKey?: KeyDefinition | KeyDefinition[] | null;
|
||||||
zoomActivationKey?: KeyDefinition | null;
|
zoomActivationKey?: KeyDefinition | KeyDefinition[] | null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<svelte:options immutable />
|
<svelte:options immutable />
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, setContext, onDestroy } from 'svelte';
|
import { setContext, onDestroy } from 'svelte';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { errorMessages, Position } from '@xyflow/system';
|
import { errorMessages, Position } from '@xyflow/system';
|
||||||
@@ -10,40 +10,39 @@
|
|||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||||
import type { NodeWrapperProps } from './types';
|
import type { NodeWrapperProps } from './types';
|
||||||
import type { Node } from '$lib/types';
|
|
||||||
import { getNodeInlineStyleDimensions } from './utils';
|
import { getNodeInlineStyleDimensions } from './utils';
|
||||||
import { createNodeEventDispatcher } from '$lib';
|
import { createNodeEventDispatcher } from '$lib';
|
||||||
|
|
||||||
interface $$Props extends NodeWrapperProps {}
|
interface $$Props extends NodeWrapperProps {}
|
||||||
|
|
||||||
export let node: NodeWrapperProps['node'];
|
export let node: $$Props['node'];
|
||||||
export let id: NodeWrapperProps['id'];
|
export let id: $$Props['id'];
|
||||||
export let data: NodeWrapperProps['data'] = {};
|
export let data: $$Props['data'] = {};
|
||||||
export let selected: NodeWrapperProps['selected'] = false;
|
export let selected: $$Props['selected'] = false;
|
||||||
export let draggable: NodeWrapperProps['draggable'] = undefined;
|
export let draggable: $$Props['draggable'] = undefined;
|
||||||
export let selectable: NodeWrapperProps['selectable'] = undefined;
|
export let selectable: $$Props['selectable'] = undefined;
|
||||||
export let connectable: NodeWrapperProps['connectable'] = true;
|
export let connectable: $$Props['connectable'] = true;
|
||||||
export let hidden: NodeWrapperProps['hidden'] = false;
|
export let hidden: $$Props['hidden'] = false;
|
||||||
export let dragging: boolean = false;
|
export let dragging: boolean = false;
|
||||||
export let resizeObserver: NodeWrapperProps['resizeObserver'] = null;
|
export let resizeObserver: $$Props['resizeObserver'] = null;
|
||||||
export let style: NodeWrapperProps['style'] = undefined;
|
export let style: $$Props['style'] = undefined;
|
||||||
export let type: NodeWrapperProps['type'] = 'default';
|
export let type: $$Props['type'] = 'default';
|
||||||
export let isParent: NodeWrapperProps['isParent'] = false;
|
export let isParent: $$Props['isParent'] = false;
|
||||||
export let positionX: NodeWrapperProps['positionX'];
|
export let positionX: $$Props['positionX'];
|
||||||
export let positionY: NodeWrapperProps['positionY'];
|
export let positionY: $$Props['positionY'];
|
||||||
export let positionOriginX: NodeWrapperProps['positionOriginX'];
|
export let positionOriginX: $$Props['positionOriginX'];
|
||||||
export let positionOriginY: NodeWrapperProps['positionOriginY'];
|
export let positionOriginY: $$Props['positionOriginY'];
|
||||||
export let sourcePosition: NodeWrapperProps['sourcePosition'] = undefined;
|
export let sourcePosition: $$Props['sourcePosition'] = undefined;
|
||||||
export let targetPosition: NodeWrapperProps['targetPosition'] = undefined;
|
export let targetPosition: $$Props['targetPosition'] = undefined;
|
||||||
export let zIndex: NodeWrapperProps['zIndex'];
|
export let zIndex: $$Props['zIndex'];
|
||||||
export let computedWidth: NodeWrapperProps['computedWidth'] = undefined;
|
export let computedWidth: $$Props['computedWidth'] = undefined;
|
||||||
export let computedHeight: NodeWrapperProps['computedHeight'] = undefined;
|
export let computedHeight: $$Props['computedHeight'] = undefined;
|
||||||
export let initialWidth: NodeWrapperProps['initialWidth'] = undefined;
|
export let initialWidth: $$Props['initialWidth'] = undefined;
|
||||||
export let initialHeight: NodeWrapperProps['initialHeight'] = undefined;
|
export let initialHeight: $$Props['initialHeight'] = undefined;
|
||||||
export let width: NodeWrapperProps['width'] = undefined;
|
export let width: $$Props['width'] = undefined;
|
||||||
export let height: NodeWrapperProps['height'] = undefined;
|
export let height: $$Props['height'] = undefined;
|
||||||
export let dragHandle: NodeWrapperProps['dragHandle'] = undefined;
|
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||||
export let initialized: NodeWrapperProps['initialized'] = false;
|
export let initialized: $$Props['initialized'] = false;
|
||||||
let className: string = '';
|
let className: string = '';
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
@@ -55,24 +54,26 @@
|
|||||||
handleNodeSelection,
|
handleNodeSelection,
|
||||||
updateNodeDimensions
|
updateNodeDimensions
|
||||||
} = store;
|
} = store;
|
||||||
const nodeType = type || 'default';
|
|
||||||
|
|
||||||
let nodeRef: HTMLDivElement;
|
let nodeRef: HTMLDivElement;
|
||||||
let prevNodeRef: HTMLDivElement;
|
let prevNodeRef: HTMLDivElement | null = null;
|
||||||
|
|
||||||
const nodeTypeValid = !!$nodeTypes[nodeType];
|
|
||||||
|
|
||||||
if (!nodeTypeValid) {
|
|
||||||
console.warn('003', errorMessages['error003'](type!));
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeComponent = $nodeTypes[nodeType] || DefaultNode;
|
|
||||||
const dispatchNodeEvent = createNodeEventDispatcher();
|
const dispatchNodeEvent = createNodeEventDispatcher();
|
||||||
const connectableStore = writable(connectable);
|
const connectableStore = writable(connectable);
|
||||||
let prevType: string | undefined = undefined;
|
let prevType: string | undefined = undefined;
|
||||||
let prevSourcePosition: Position | undefined = undefined;
|
let prevSourcePosition: Position | undefined = undefined;
|
||||||
let prevTargetPosition: 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({
|
$: inlineStyleDimensions = getNodeInlineStyleDimensions({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@@ -120,20 +121,17 @@
|
|||||||
setContext('svelteflow__node_connectable', connectableStore);
|
setContext('svelteflow__node_connectable', connectableStore);
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (nodeRef) {
|
if (resizeObserver && nodeRef !== prevNodeRef) {
|
||||||
if (!prevNodeRef) {
|
prevNodeRef && resizeObserver.unobserve(prevNodeRef);
|
||||||
resizeObserver?.observe(nodeRef);
|
nodeRef && resizeObserver.observe(nodeRef);
|
||||||
prevNodeRef = nodeRef;
|
prevNodeRef = nodeRef;
|
||||||
} else if (prevNodeRef !== nodeRef || (!computedWidth && !computedHeight)) {
|
|
||||||
resizeObserver?.unobserve(prevNodeRef);
|
|
||||||
resizeObserver?.observe(nodeRef);
|
|
||||||
prevNodeRef = nodeRef;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
resizeObserver?.unobserve(nodeRef);
|
if (prevNodeRef) {
|
||||||
|
resizeObserver?.unobserve(prevNodeRef);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function onSelectNodeHandler(event: MouseEvent | TouchEvent) {
|
function onSelectNodeHandler(event: MouseEvent | TouchEvent) {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
import portal from '$lib/actions/portal';
|
import portal from '$lib/actions/portal';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
type $$Props = {};
|
|
||||||
|
|
||||||
const { domNode } = useStore();
|
const { domNode } = useStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@
|
|||||||
// onSelectionEnd?.(event);
|
// onSelectionEnd?.(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMouseLeave = (event: MouseEvent) => {
|
const onMouseLeave = () => {
|
||||||
if ($selectionRectMode === 'user') {
|
if ($selectionRectMode === 'user') {
|
||||||
selectionRectMode.set(selectedNodes.length > 0 ? 'nodes' : null);
|
selectionRectMode.set(selectedNodes.length > 0 ? 'nodes' : null);
|
||||||
// onSelectionEnd?.(event);
|
// onSelectionEnd?.(event);
|
||||||
|
|||||||
@@ -86,27 +86,27 @@ export type SvelteFlowProps = DOMAttributes<HTMLDivElement> & {
|
|||||||
/** Pressing down this key you can select multiple elements with a selection box.
|
/** Pressing down this key you can select multiple elements with a selection box.
|
||||||
* @default 'Shift'
|
* @default 'Shift'
|
||||||
*/
|
*/
|
||||||
selectionKey?: KeyDefinition | null;
|
selectionKey?: KeyDefinition | KeyDefinition[] | null;
|
||||||
/** If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
/** If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
||||||
*
|
*
|
||||||
* By setting this prop to null you can disable this functionality.
|
* By setting this prop to null you can disable this functionality.
|
||||||
* @default 'Space'
|
* @default 'Space'
|
||||||
*/
|
*/
|
||||||
panActivationKey?: KeyDefinition | null;
|
panActivationKey?: KeyDefinition | KeyDefinition[] | null;
|
||||||
/** Pressing down this key deletes all selected nodes & edges.
|
/** Pressing down this key deletes all selected nodes & edges.
|
||||||
* @default 'Backspace'
|
* @default 'Backspace'
|
||||||
*/
|
*/
|
||||||
deleteKey?: KeyDefinition | null;
|
deleteKey?: KeyDefinition | KeyDefinition[] | null;
|
||||||
/** Pressing down this key you can select multiple elements by clicking.
|
/** Pressing down this key you can select multiple elements by clicking.
|
||||||
* @default 'Meta' for macOS, "Ctrl" for other systems
|
* @default 'Meta' for macOS, "Ctrl" for other systems
|
||||||
*/
|
*/
|
||||||
multiSelectionKey?: KeyDefinition | null;
|
multiSelectionKey?: KeyDefinition | KeyDefinition[] | null;
|
||||||
/** If a key is set, you can zoom the viewport while that key is held down even if panOnScroll is set to false.
|
/** If a key is set, you can zoom the viewport while that key is held down even if panOnScroll is set to false.
|
||||||
*
|
*
|
||||||
* By setting this prop to null you can disable this functionality.
|
* By setting this prop to null you can disable this functionality.
|
||||||
* @default 'Meta' for macOS, "Ctrl" for other systems
|
* @default 'Meta' for macOS, "Ctrl" for other systems
|
||||||
* */
|
* */
|
||||||
zoomActivationKey?: KeyDefinition | null;
|
zoomActivationKey?: KeyDefinition | KeyDefinition[] | null;
|
||||||
/** If set, initial viewport will show all nodes & edges */
|
/** If set, initial viewport will show all nodes & edges */
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
/** Options to be used in combination with fitView
|
/** Options to be used in combination with fitView
|
||||||
|
|||||||
@@ -76,8 +76,7 @@ export function updateStoreByKeys(store: SvelteFlowStore, keys: UpdatableStorePr
|
|||||||
getKeys(keys).forEach((prop) => {
|
getKeys(keys).forEach((prop) => {
|
||||||
const update = keys[prop];
|
const update = keys[prop];
|
||||||
if (update !== undefined) {
|
if (update !== undefined) {
|
||||||
// @todo: how to fix this TS error?
|
// @ts-expect-error @todo: how to fix this TS error?
|
||||||
// @ts-ignore
|
|
||||||
store[prop].set(update);
|
store[prop].set(update);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export function useNodesData<NodeType extends Node = Node>(
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export function useNodesData(nodeIds: any): any {
|
export function useNodesData(nodeIds: any): any {
|
||||||
const { nodes, nodeLookup } = useStore();
|
const { nodes, nodeLookup } = useStore();
|
||||||
let prevNodesData: any = [];
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
let prevNodesData: any[] = [];
|
||||||
|
|
||||||
return derived([nodes, nodeLookup], ([, nodeLookup], set) => {
|
return derived([nodes, nodeLookup], ([, nodeLookup], set) => {
|
||||||
const nextNodesData = [];
|
const nextNodesData = [];
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ export {
|
|||||||
type OnResizeEnd,
|
type OnResizeEnd,
|
||||||
type ControlPosition,
|
type ControlPosition,
|
||||||
type ControlLinePosition,
|
type ControlLinePosition,
|
||||||
type ResizeControlVariant
|
type ResizeControlVariant,
|
||||||
|
type IsValidConnection
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
// system utils
|
// system utils
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import cc from 'classcat';
|
||||||
|
|
||||||
import Panel from '$lib/container/Panel/Panel.svelte';
|
import Panel from '$lib/container/Panel/Panel.svelte';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import ControlButton from './ControlButton.svelte';
|
import ControlButton from './ControlButton.svelte';
|
||||||
@@ -22,6 +24,11 @@
|
|||||||
export let buttonColorHover: $$Props['buttonColorHover'] = undefined;
|
export let buttonColorHover: $$Props['buttonColorHover'] = undefined;
|
||||||
export let buttonBorderColor: $$Props['buttonColorHover'] = undefined;
|
export let buttonBorderColor: $$Props['buttonColorHover'] = undefined;
|
||||||
export let ariaLabel: $$Props['aria-label'] = undefined;
|
export let ariaLabel: $$Props['aria-label'] = undefined;
|
||||||
|
export let style: $$Props['style'] = undefined;
|
||||||
|
export let orientation: $$Props['orientation'] = 'vertical';
|
||||||
|
|
||||||
|
let className: $$Props['class'] = '';
|
||||||
|
export { className as class };
|
||||||
|
|
||||||
const {
|
const {
|
||||||
zoomIn,
|
zoomIn,
|
||||||
@@ -66,13 +73,16 @@
|
|||||||
nodesConnectable.set(isInteractive);
|
nodesConnectable.set(isInteractive);
|
||||||
elementsSelectable.set(isInteractive);
|
elementsSelectable.set(isInteractive);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$: orientationClass = orientation === 'horizontal' ? 'horizontal' : 'vertical';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel
|
<Panel
|
||||||
class="svelte-flow__controls"
|
class={cc(['svelte-flow__controls', orientationClass, className])}
|
||||||
{position}
|
{position}
|
||||||
data-testid="svelte-flow__controls"
|
data-testid="svelte-flow__controls"
|
||||||
aria-label={ariaLabel ?? 'Svelte Flow controls'}
|
aria-label={ariaLabel ?? 'Svelte Flow controls'}
|
||||||
|
{style}
|
||||||
>
|
>
|
||||||
<slot name="before" />
|
<slot name="before" />
|
||||||
{#if showZoom}
|
{#if showZoom}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ export type ControlsProps = {
|
|||||||
buttonColor?: string;
|
buttonColor?: string;
|
||||||
buttonColorHover?: string;
|
buttonColorHover?: string;
|
||||||
'aria-label'?: string;
|
'aria-label'?: string;
|
||||||
|
style?: string;
|
||||||
|
class?: string;
|
||||||
|
orientation?: 'horizontal' | 'vertical';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ControlButtonProps = HTMLButtonAttributes & {
|
export type ControlButtonProps = HTMLButtonAttributes & {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
declare const window: any;
|
declare const window: Window | undefined;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const getAttrFunction = (func: any): GetMiniMapNodeAttribute =>
|
const getAttrFunction = (func: any): GetMiniMapNodeAttribute =>
|
||||||
func instanceof Function ? func : () => func;
|
func instanceof Function ? func : () => func;
|
||||||
</script>
|
</script>
|
||||||
@@ -61,6 +61,7 @@
|
|||||||
const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor);
|
const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor);
|
||||||
const nodeClassFunc = getAttrFunction(nodeClass);
|
const nodeClassFunc = getAttrFunction(nodeClass);
|
||||||
const shapeRendering =
|
const shapeRendering =
|
||||||
|
// @ts-expect-error - TS doesn't know about chrome
|
||||||
typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
|
typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
|
||||||
const labelledBy = `svelte-flow__minimap-desc-${$flowId}`;
|
const labelledBy = `svelte-flow__minimap-desc-${$flowId}`;
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
$: colorStyleProp = variant === ResizeControlVariant.Line ? 'border-color' : 'background-color';
|
$: colorStyleProp = variant === ResizeControlVariant.Line ? 'border-color' : 'background-color';
|
||||||
$: _style = style ?? '';
|
$: _style = style ?? '';
|
||||||
$: controlStyle = !!color ? `${_style} ${colorStyleProp}: ${color};` : _style;
|
$: controlStyle = color ? `${_style} ${colorStyleProp}: ${color};` : _style;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (resizeControlRef) {
|
if (resizeControlRef) {
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
Position,
|
Position,
|
||||||
internalsSymbol
|
internalsSymbol,
|
||||||
|
type HandleElement
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { SvelteFlowStoreState } from './types';
|
import type { SvelteFlowStoreState } from './types';
|
||||||
@@ -68,16 +69,16 @@ export function getDerivedConnectionProps(
|
|||||||
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
||||||
const handleBoundsStrict =
|
const handleBoundsStrict =
|
||||||
fromHandleBounds?.[connection.connectionStartHandle.type || 'source'] || [];
|
fromHandleBounds?.[connection.connectionStartHandle.type || 'source'] || [];
|
||||||
const handleBoundsLoose = handleBoundsStrict
|
const handleBoundsLoose: HandleElement[] | undefined | null = handleBoundsStrict
|
||||||
? handleBoundsStrict
|
? handleBoundsStrict
|
||||||
: fromHandleBounds?.[
|
: fromHandleBounds?.[
|
||||||
connection?.connectionStartHandle?.type === 'source' ? 'target' : 'source'
|
connection?.connectionStartHandle?.type === 'source' ? 'target' : 'source'
|
||||||
]!;
|
];
|
||||||
const handleBounds =
|
const handleBounds =
|
||||||
connectionMode === ConnectionMode.Strict ? handleBoundsStrict : handleBoundsLoose;
|
connectionMode === ConnectionMode.Strict ? handleBoundsStrict : handleBoundsLoose;
|
||||||
const fromHandle = connection.connectionStartHandle?.handleId
|
const fromHandle = connection.connectionStartHandle?.handleId
|
||||||
? handleBounds.find((d) => d.id === connection.connectionStartHandle?.handleId)
|
? handleBounds?.find((d) => d.id === connection.connectionStartHandle?.handleId)
|
||||||
: handleBounds[0];
|
: handleBounds?.[0];
|
||||||
const fromHandleX = fromHandle
|
const fromHandleX = fromHandle
|
||||||
? fromHandle.x + fromHandle.width / 2
|
? fromHandle.x + fromHandle.width / 2
|
||||||
: (fromNode?.computed?.width ?? 0) / 2;
|
: (fromNode?.computed?.width ?? 0) / 2;
|
||||||
|
|||||||
@@ -397,6 +397,13 @@ svg.xy-flow__connectionline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.xy-flow__controls {
|
.xy-flow__controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
&-button {
|
&-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user