implemented keyboard interactions
This commit is contained in:
@@ -23,9 +23,9 @@ export const builtinNodeTypes: NodeTypes = {
|
|||||||
export function getNodeInlineStyleDimensions<NodeType extends Node = Node>(
|
export function getNodeInlineStyleDimensions<NodeType extends Node = Node>(
|
||||||
node: InternalNode<NodeType>
|
node: InternalNode<NodeType>
|
||||||
): {
|
): {
|
||||||
width: number | string | undefined;
|
width: number | string | undefined;
|
||||||
height: number | string | undefined;
|
height: number | string | undefined;
|
||||||
} {
|
} {
|
||||||
if (node.internals.handleBounds === undefined) {
|
if (node.internals.handleBounds === undefined) {
|
||||||
return {
|
return {
|
||||||
width: node.width ?? node.initialWidth ?? node.style?.width,
|
width: node.width ?? node.initialWidth ?? node.style?.width,
|
||||||
|
|||||||
@@ -61,9 +61,9 @@ export function NodesSelection<NodeType extends Node>({
|
|||||||
|
|
||||||
const onContextMenu = onSelectionContextMenu
|
const onContextMenu = onSelectionContextMenu
|
||||||
? (event: MouseEvent) => {
|
? (event: MouseEvent) => {
|
||||||
const selectedNodes = store.getState().nodes.filter((n) => n.selected);
|
const selectedNodes = store.getState().nodes.filter((n) => n.selected);
|
||||||
onSelectionContextMenu(event, selectedNodes);
|
onSelectionContextMenu(event, selectedNodes);
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext } from 'svelte';
|
import { setContext } from 'svelte';
|
||||||
|
|
||||||
import { getMarkerId } from '@xyflow/system';
|
import { elementSelectionKeys, getMarkerId } from '@xyflow/system';
|
||||||
|
|
||||||
import { BezierEdgeInternal } from '$lib/components/edges';
|
import { BezierEdgeInternal } from '$lib/components/edges';
|
||||||
|
|
||||||
@@ -38,7 +38,8 @@
|
|||||||
targetHandle,
|
targetHandle,
|
||||||
markerStart,
|
markerStart,
|
||||||
markerEnd,
|
markerEnd,
|
||||||
selectable: edgeSelectable,
|
selectable: _selectable,
|
||||||
|
focusable: _focusable,
|
||||||
deletable = true,
|
deletable = true,
|
||||||
hidden,
|
hidden,
|
||||||
zIndex,
|
zIndex,
|
||||||
@@ -46,12 +47,15 @@
|
|||||||
ariaLabel
|
ariaLabel
|
||||||
} = $derived(edge);
|
} = $derived(edge);
|
||||||
|
|
||||||
$inspect(edge);
|
// svelte-ignore non_reactive_update
|
||||||
|
let edgeRef: SVGGElement | null = null;
|
||||||
|
|
||||||
const { id } = edge;
|
const { id } = edge;
|
||||||
setContext('svelteflow__edge_id', id);
|
setContext('svelteflow__edge_id', id);
|
||||||
|
|
||||||
let selectable = $derived(edgeSelectable ?? store.elementsSelectable);
|
let selectable = $derived(_selectable ?? store.elementsSelectable);
|
||||||
|
let focusable = $derived(_focusable ?? store.edgesFocusable);
|
||||||
|
|
||||||
let EdgeComponent = $derived(store.edgeTypes[type] ?? BezierEdgeInternal);
|
let EdgeComponent = $derived(store.edgeTypes[type] ?? BezierEdgeInternal);
|
||||||
|
|
||||||
let markerStartUrl = $derived(
|
let markerStartUrl = $derived(
|
||||||
@@ -70,7 +74,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseEvent<T = MouseEvent>(
|
function onmouseevent<T = MouseEvent>(
|
||||||
event: T,
|
event: T,
|
||||||
callback: ({ edge, event }: { edge: Edge; event: T }) => void
|
callback: ({ edge, event }: { edge: Edge; event: T }) => void
|
||||||
) {
|
) {
|
||||||
@@ -80,13 +84,29 @@
|
|||||||
callback({ event, edge });
|
callback({ event, edge });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onkeydown = (event: KeyboardEvent) => {
|
||||||
|
// TODO: Possible Svelte Bug? onkeydown is always firing for the last edge
|
||||||
|
if (!store.disableKeyboardA11y && elementSelectionKeys.includes(event.key) && selectable) {
|
||||||
|
const { unselectNodesAndEdges, addSelectedEdges } = store;
|
||||||
|
const unselect = event.key === 'Escape';
|
||||||
|
|
||||||
|
if (unselect) {
|
||||||
|
edgeRef?.blur();
|
||||||
|
unselectNodesAndEdges({ edges: [edge] });
|
||||||
|
} else {
|
||||||
|
console.log(id);
|
||||||
|
addSelectedEdges([id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- TODO: aria-label, describedby -->
|
||||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
||||||
{#if !hidden}
|
{#if !hidden}
|
||||||
<svg style:z-index={zIndex}>
|
<svg style:z-index={zIndex}>
|
||||||
<g
|
<g
|
||||||
|
bind:this={edgeRef}
|
||||||
class={['svelte-flow__edge', className]}
|
class={['svelte-flow__edge', className]}
|
||||||
class:animated
|
class:animated
|
||||||
class:selected
|
class:selected
|
||||||
@@ -95,17 +115,17 @@
|
|||||||
{onclick}
|
{onclick}
|
||||||
oncontextmenu={onedgecontextmenu
|
oncontextmenu={onedgecontextmenu
|
||||||
? (e) => {
|
? (e) => {
|
||||||
onMouseEvent(e, onedgecontextmenu);
|
onmouseevent(e, onedgecontextmenu);
|
||||||
}
|
}
|
||||||
: undefined}
|
: undefined}
|
||||||
onpointerenter={onedgepointerenter
|
onpointerenter={onedgepointerenter
|
||||||
? (e) => {
|
? (e) => {
|
||||||
onMouseEvent(e, onedgepointerenter);
|
onmouseevent(e, onedgepointerenter);
|
||||||
}
|
}
|
||||||
: undefined}
|
: undefined}
|
||||||
onpointerleave={onedgepointerleave
|
onpointerleave={onedgepointerleave
|
||||||
? (e) => {
|
? (e) => {
|
||||||
onMouseEvent(e, onedgepointerleave);
|
onmouseevent(e, onedgepointerleave);
|
||||||
}
|
}
|
||||||
: undefined}
|
: undefined}
|
||||||
aria-label={ariaLabel === null
|
aria-label={ariaLabel === null
|
||||||
@@ -113,7 +133,9 @@
|
|||||||
: ariaLabel
|
: ariaLabel
|
||||||
? ariaLabel
|
? ariaLabel
|
||||||
: `Edge from ${source} to ${target}`}
|
: `Edge from ${source} to ${target}`}
|
||||||
role="img"
|
role={focusable ? 'button' : 'img'}
|
||||||
|
onkeydown={focusable ? onkeydown : undefined}
|
||||||
|
tabIndex={focusable ? 0 : undefined}
|
||||||
>
|
>
|
||||||
<EdgeComponent
|
<EdgeComponent
|
||||||
{id}
|
{id}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getInternalNodesBounds, isNumeric, type Rect } from '@xyflow/system';
|
import { arrowKeyDiffs, getInternalNodesBounds, isNumeric, type Rect } from '@xyflow/system';
|
||||||
|
|
||||||
import { Selection } from '$lib/components/Selection';
|
import { Selection } from '$lib/components/Selection';
|
||||||
import drag from '$lib/actions/drag';
|
import drag from '$lib/actions/drag';
|
||||||
@@ -16,6 +16,16 @@
|
|||||||
onselectioncontextmenu
|
onselectioncontextmenu
|
||||||
}: NodeSelectionProps = $props();
|
}: NodeSelectionProps = $props();
|
||||||
|
|
||||||
|
let ref = $state<HTMLDivElement>();
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!store.disableKeyboardA11y) {
|
||||||
|
ref?.focus({
|
||||||
|
preventScroll: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let bounds: Rect | null = $derived.by(() => {
|
let bounds: Rect | null = $derived.by(() => {
|
||||||
if (store.selectionRectMode === 'nodes') {
|
if (store.selectionRectMode === 'nodes') {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
@@ -34,6 +44,14 @@
|
|||||||
const selectedNodes = store.nodes.filter((n) => n.selected);
|
const selectedNodes = store.nodes.filter((n) => n.selected);
|
||||||
onselectionclick?.({ nodes: selectedNodes, event });
|
onselectionclick?.({ nodes: selectedNodes, event });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onkeydown(event: KeyboardEvent) {
|
||||||
|
console.log('yas');
|
||||||
|
if (Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)) {
|
||||||
|
event.preventDefault();
|
||||||
|
store.moveSelectedNodes(arrowKeyDiffs[event.key], event.shiftKey ? 4 : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if store.selectionRectMode === 'nodes' && bounds && isNumeric(bounds.x) && isNumeric(bounds.y)}
|
{#if store.selectionRectMode === 'nodes' && bounds && isNumeric(bounds.x) && isNumeric(bounds.y)}
|
||||||
@@ -57,9 +75,10 @@
|
|||||||
}}
|
}}
|
||||||
{oncontextmenu}
|
{oncontextmenu}
|
||||||
{onclick}
|
{onclick}
|
||||||
role="button"
|
role={store.disableKeyboardA11y ? undefined : 'button'}
|
||||||
tabindex="-1"
|
tabIndex={store.disableKeyboardA11y ? undefined : -1}
|
||||||
onkeyup={() => {}}
|
onkeydown={store.disableKeyboardA11y ? undefined : onkeydown}
|
||||||
|
bind:this={ref}
|
||||||
>
|
>
|
||||||
<Selection width="100%" height="100%" x={0} y={0} />
|
<Selection width="100%" height="100%" x={0} y={0} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext, onDestroy } from 'svelte';
|
import { setContext, onDestroy } from 'svelte';
|
||||||
import { errorMessages, nodeHasDimensions, Position } from '@xyflow/system';
|
import {
|
||||||
|
arrowKeyDiffs,
|
||||||
|
elementSelectionKeys,
|
||||||
|
errorMessages,
|
||||||
|
isInputDOMNode,
|
||||||
|
nodeHasDimensions,
|
||||||
|
Position
|
||||||
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import drag from '$lib/actions/drag';
|
import drag from '$lib/actions/drag';
|
||||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||||
@@ -31,6 +38,7 @@
|
|||||||
selectable: _selectable,
|
selectable: _selectable,
|
||||||
deletable = true,
|
deletable = true,
|
||||||
connectable: _connectable,
|
connectable: _connectable,
|
||||||
|
focusable: _focusable,
|
||||||
hidden = false,
|
hidden = false,
|
||||||
dragging = false,
|
dragging = false,
|
||||||
style = '',
|
style = '',
|
||||||
@@ -57,6 +65,7 @@
|
|||||||
let selectable = $derived(_selectable ?? store.elementsSelectable);
|
let selectable = $derived(_selectable ?? store.elementsSelectable);
|
||||||
let connectable = $derived(_connectable ?? store.nodesConnectable);
|
let connectable = $derived(_connectable ?? store.nodesConnectable);
|
||||||
let initialized = $derived(nodeHasDimensions(node) && !!node.internals.handleBounds);
|
let initialized = $derived(nodeHasDimensions(node) && !!node.internals.handleBounds);
|
||||||
|
let focusable = $derived(_focusable ?? store.nodesFocusable);
|
||||||
|
|
||||||
function isInParentLookup(id: string) {
|
function isInParentLookup(id: string) {
|
||||||
return store.parentLookup.has(id);
|
return store.parentLookup.has(id);
|
||||||
@@ -155,10 +164,36 @@
|
|||||||
|
|
||||||
onnodeclick?.({ node, event });
|
onnodeclick?.({ node, event });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (isInputDOMNode(event) || store.disableKeyboardA11y) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementSelectionKeys.includes(event.key) && selectable) {
|
||||||
|
const unselect = event.key === 'Escape';
|
||||||
|
|
||||||
|
store.handleNodeSelection(id, unselect, nodeRef);
|
||||||
|
} else if (
|
||||||
|
draggable &&
|
||||||
|
node.selected &&
|
||||||
|
Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)
|
||||||
|
) {
|
||||||
|
// prevent default scrolling behavior on arrow key press when node is moved
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// TODO: aria live message
|
||||||
|
// store.setState({
|
||||||
|
// ariaLiveMessage: `Moved selected node ${event.key
|
||||||
|
// .replace('Arrow', '')
|
||||||
|
// .toLowerCase()}. New position, x: ${~~internals.positionAbsolute.x}, y: ${~~internals.positionAbsolute.y}`,
|
||||||
|
// });
|
||||||
|
|
||||||
|
store.moveSelectedNodes(arrowKeyDiffs[event.key], event.shiftKey ? 4 : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
||||||
{#if !hidden}
|
{#if !hidden}
|
||||||
<div
|
<div
|
||||||
use:drag={{
|
use:drag={{
|
||||||
@@ -199,6 +234,9 @@
|
|||||||
onpointerleave={onnodepointerleave ? (event) => onnodepointerleave({ node, event }) : undefined}
|
onpointerleave={onnodepointerleave ? (event) => onnodepointerleave({ node, event }) : undefined}
|
||||||
onpointermove={onnodepointermove ? (event) => onnodepointermove({ node, event }) : undefined}
|
onpointermove={onnodepointermove ? (event) => onnodepointermove({ node, event }) : undefined}
|
||||||
oncontextmenu={onnodecontextmenu ? (event) => onnodecontextmenu({ node, event }) : undefined}
|
oncontextmenu={onnodecontextmenu ? (event) => onnodecontextmenu({ node, event }) : undefined}
|
||||||
|
onkeydown={focusable ? onKeyDown : undefined}
|
||||||
|
tabIndex={focusable ? 0 : undefined}
|
||||||
|
role={focusable ? 'button' : undefined}
|
||||||
>
|
>
|
||||||
<NodeComponent
|
<NodeComponent
|
||||||
{data}
|
{data}
|
||||||
|
|||||||
@@ -77,6 +77,9 @@
|
|||||||
nodesDraggable,
|
nodesDraggable,
|
||||||
nodesConnectable,
|
nodesConnectable,
|
||||||
elementsSelectable,
|
elementsSelectable,
|
||||||
|
nodesFocusable,
|
||||||
|
edgesFocusable,
|
||||||
|
disableKeyboardA11y,
|
||||||
...divAttributes
|
...divAttributes
|
||||||
} = $derived(rest);
|
} = $derived(rest);
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,18 @@ export type SvelteFlowProps = NodeEvents &
|
|||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
elementsSelectable?: boolean;
|
elementsSelectable?: boolean;
|
||||||
|
/**
|
||||||
|
* When `true`, focus between nodes can be cycled with the `Tab` key and selected with the `Enter`
|
||||||
|
* key. This option can be overridden by individual nodes by setting their `focusable` prop.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
nodesFocusable?: boolean;
|
||||||
|
/**
|
||||||
|
* When `true`, focus between edges can be cycled with the `Tab` key and selected with the `Enter`
|
||||||
|
* key. This option can be overridden by individual edges by setting their `focusable` prop.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
edgesFocusable?: boolean;
|
||||||
/** By default the viewport extends infinitely. You can use this prop to set a boundary.
|
/** By default the viewport extends infinitely. You can use this prop to set a boundary.
|
||||||
*
|
*
|
||||||
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
|
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
|
||||||
@@ -297,7 +309,6 @@ export type SvelteFlowProps = NodeEvents &
|
|||||||
* hidden: false,
|
* hidden: false,
|
||||||
* deletable: true,
|
* deletable: true,
|
||||||
* selected: false,
|
* selected: false,
|
||||||
* focusable: true,
|
|
||||||
* markerStart: EdgeMarker.ArrowClosed,
|
* markerStart: EdgeMarker.ArrowClosed,
|
||||||
* markerEnd: EdgeMarker.ArrowClosed,
|
* markerEnd: EdgeMarker.ArrowClosed,
|
||||||
* zIndex: 12,
|
* zIndex: 12,
|
||||||
@@ -343,6 +354,12 @@ export type SvelteFlowProps = NodeEvents &
|
|||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
elevateEdgesOnSelect?: boolean;
|
elevateEdgesOnSelect?: boolean;
|
||||||
|
/**
|
||||||
|
* You can use this prop to disable keyboard accessibility features such as selecting nodes or
|
||||||
|
* moving selected nodes with the arrow keys.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
disableKeyboardA11y?: boolean;
|
||||||
/**
|
/**
|
||||||
* This callback can be used to validate a new connection
|
* This callback can be used to validate a new connection
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import {
|
|||||||
type CoordinateExtent,
|
type CoordinateExtent,
|
||||||
type UpdateConnection,
|
type UpdateConnection,
|
||||||
type ConnectionState,
|
type ConnectionState,
|
||||||
updateAbsolutePositions
|
updateAbsolutePositions,
|
||||||
|
snapPosition,
|
||||||
|
calculateNodePosition
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types';
|
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types';
|
||||||
@@ -247,7 +249,7 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNodeSelection(id: string) {
|
function handleNodeSelection(id: string, unselect?: boolean, nodeRef?: HTMLDivElement | null) {
|
||||||
const node = store.nodeLookup.get(id);
|
const node = store.nodeLookup.get(id);
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@@ -260,8 +262,10 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
|
|||||||
|
|
||||||
if (!node.selected) {
|
if (!node.selected) {
|
||||||
addSelectedNodes([id]);
|
addSelectedNodes([id]);
|
||||||
} else if (node.selected && store.multiselectionKeyPressed) {
|
} else if (unselect || (node.selected && store.multiselectionKeyPressed)) {
|
||||||
unselectNodesAndEdges({ nodes: [node], edges: [] });
|
unselectNodesAndEdges({ nodes: [node], edges: [] });
|
||||||
|
|
||||||
|
requestAnimationFrame(() => nodeRef?.blur());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,6 +292,55 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveSelectedNodes(direction: XYPosition, factor: number) {
|
||||||
|
const { nodeExtent, snapGrid, nodeOrigin, nodeLookup, nodesDraggable, onerror } = store;
|
||||||
|
|
||||||
|
const nodeUpdates = new Map();
|
||||||
|
/*
|
||||||
|
* by default a node moves 5px on each key press
|
||||||
|
* if snap grid is enabled, we use that for the velocity
|
||||||
|
*/
|
||||||
|
const xVelo = snapGrid?.[0] ?? 5;
|
||||||
|
const yVelo = snapGrid?.[1] ?? 5;
|
||||||
|
|
||||||
|
const xDiff = direction.x * xVelo * factor;
|
||||||
|
const yDiff = direction.y * yVelo * factor;
|
||||||
|
|
||||||
|
for (const node of nodeLookup.values()) {
|
||||||
|
const isSelected =
|
||||||
|
node.selected &&
|
||||||
|
(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined'));
|
||||||
|
|
||||||
|
if (!isSelected) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextPosition = {
|
||||||
|
x: node.internals.positionAbsolute.x + xDiff,
|
||||||
|
y: node.internals.positionAbsolute.y + yDiff
|
||||||
|
};
|
||||||
|
|
||||||
|
if (snapGrid) {
|
||||||
|
nextPosition = snapPosition(nextPosition, snapGrid);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { position, positionAbsolute } = calculateNodePosition({
|
||||||
|
nodeId: node.id,
|
||||||
|
nextPosition,
|
||||||
|
nodeLookup,
|
||||||
|
nodeExtent,
|
||||||
|
nodeOrigin,
|
||||||
|
onError: onerror
|
||||||
|
});
|
||||||
|
|
||||||
|
node.position = position;
|
||||||
|
node.internals.positionAbsolute = positionAbsolute;
|
||||||
|
|
||||||
|
nodeUpdates.set(node.id, node);
|
||||||
|
}
|
||||||
|
updateNodePositions(nodeUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
function panBy(delta: XYPosition) {
|
function panBy(delta: XYPosition) {
|
||||||
return panBySystem({
|
return panBySystem({
|
||||||
delta,
|
delta,
|
||||||
@@ -335,6 +388,7 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
|
|||||||
addSelectedEdges,
|
addSelectedEdges,
|
||||||
handleNodeSelection,
|
handleNodeSelection,
|
||||||
handleEdgeSelection,
|
handleEdgeSelection,
|
||||||
|
moveSelectedNodes,
|
||||||
panBy,
|
panBy,
|
||||||
updateConnection,
|
updateConnection,
|
||||||
cancelConnection,
|
cancelConnection,
|
||||||
|
|||||||
@@ -196,6 +196,10 @@ export const getInitialStore = (signals: StoreSignals) => {
|
|||||||
nodesDraggable: boolean = $derived(signals.props.nodesDraggable ?? true);
|
nodesDraggable: boolean = $derived(signals.props.nodesDraggable ?? true);
|
||||||
nodesConnectable: boolean = $derived(signals.props.nodesConnectable ?? true);
|
nodesConnectable: boolean = $derived(signals.props.nodesConnectable ?? true);
|
||||||
elementsSelectable: boolean = $derived(signals.props.elementsSelectable ?? true);
|
elementsSelectable: boolean = $derived(signals.props.elementsSelectable ?? true);
|
||||||
|
nodesFocusable: boolean = $derived(signals.props.nodesFocusable ?? true);
|
||||||
|
edgesFocusable: boolean = $derived(signals.props.edgesFocusable ?? true);
|
||||||
|
|
||||||
|
disableKeyboardA11y: boolean = $derived(signals.props.disableKeyboardA11y ?? false);
|
||||||
|
|
||||||
minZoom: number = $derived(signals.props.minZoom ?? 0.5);
|
minZoom: number = $derived(signals.props.minZoom ?? 0.5);
|
||||||
maxZoom: number = $derived(signals.props.maxZoom ?? 2);
|
maxZoom: number = $derived(signals.props.maxZoom ?? 2);
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ export type SvelteFlowStoreActions = {
|
|||||||
unselectNodesAndEdges: (params?: { nodes?: Node[]; edges?: Edge[] }) => void;
|
unselectNodesAndEdges: (params?: { nodes?: Node[]; edges?: Edge[] }) => void;
|
||||||
addSelectedNodes: (ids: string[]) => void;
|
addSelectedNodes: (ids: string[]) => void;
|
||||||
addSelectedEdges: (ids: string[]) => void;
|
addSelectedEdges: (ids: string[]) => void;
|
||||||
handleNodeSelection: (id: string) => void;
|
handleNodeSelection: (id: string, unselect?: boolean, nodeRef?: HTMLDivElement | null) => void;
|
||||||
handleEdgeSelection: (id: string) => void;
|
handleEdgeSelection: (id: string) => void;
|
||||||
|
moveSelectedNodes: (direction: XYPosition, factor: number) => void;
|
||||||
panBy: (delta: XYPosition) => Promise<boolean>;
|
panBy: (delta: XYPosition) => Promise<boolean>;
|
||||||
updateConnection: UpdateConnection;
|
updateConnection: UpdateConnection;
|
||||||
cancelConnection: () => void;
|
cancelConnection: () => void;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export type Edge<
|
|||||||
labelStyle?: string;
|
labelStyle?: string;
|
||||||
style?: string;
|
style?: string;
|
||||||
class?: ClassValue;
|
class?: ClassValue;
|
||||||
|
focusable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BaseEdgeProps = Pick<
|
export type BaseEdgeProps = Pick<
|
||||||
@@ -150,30 +151,7 @@ export type EdgeTypes = Record<
|
|||||||
|
|
||||||
export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>;
|
export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>;
|
||||||
|
|
||||||
export type EdgeLayouted = Pick<
|
export type EdgeLayouted = Edge &
|
||||||
Edge,
|
|
||||||
| 'type'
|
|
||||||
| 'id'
|
|
||||||
| 'data'
|
|
||||||
| 'style'
|
|
||||||
| 'source'
|
|
||||||
| 'target'
|
|
||||||
| 'animated'
|
|
||||||
| 'selected'
|
|
||||||
| 'selectable'
|
|
||||||
| 'deletable'
|
|
||||||
| 'label'
|
|
||||||
| 'labelStyle'
|
|
||||||
| 'interactionWidth'
|
|
||||||
| 'markerStart'
|
|
||||||
| 'markerEnd'
|
|
||||||
| 'sourceHandle'
|
|
||||||
| 'targetHandle'
|
|
||||||
| 'ariaLabel'
|
|
||||||
| 'hidden'
|
|
||||||
| 'class'
|
|
||||||
| 'zIndex'
|
|
||||||
> &
|
|
||||||
EdgePosition & {
|
EdgePosition & {
|
||||||
sourceNode?: Node;
|
sourceNode?: Node;
|
||||||
targetNode?: Node;
|
targetNode?: Node;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export type Node<
|
|||||||
> = NodeBase<NodeData, NodeType> & {
|
> = NodeBase<NodeData, NodeType> & {
|
||||||
class?: ClassValue;
|
class?: ClassValue;
|
||||||
style?: string;
|
style?: string;
|
||||||
|
focusable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// @todo: currently generics for nodes are not really supported
|
// @todo: currently generics for nodes are not really supported
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CoordinateExtent, HandleType } from './types';
|
import { CoordinateExtent, HandleType, XYPosition } from './types';
|
||||||
|
|
||||||
export const errorMessages = {
|
export const errorMessages = {
|
||||||
error001: () =>
|
error001: () =>
|
||||||
@@ -36,3 +36,9 @@ export const infiniteExtent: CoordinateExtent = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const elementSelectionKeys = ['Enter', ' ', 'Escape'];
|
export const elementSelectionKeys = ['Enter', ' ', 'Escape'];
|
||||||
|
export const arrowKeyDiffs: Record<string, XYPosition> = {
|
||||||
|
ArrowUp: { x: 0, y: -1 },
|
||||||
|
ArrowDown: { x: 0, y: 1 },
|
||||||
|
ArrowLeft: { x: -1, y: 0 },
|
||||||
|
ArrowRight: { x: 1, y: 0 },
|
||||||
|
};
|
||||||
|
|||||||
@@ -144,6 +144,10 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 1px solid red !important;
|
||||||
|
}
|
||||||
|
|
||||||
&.selected,
|
&.selected,
|
||||||
&:focus,
|
&:focus,
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
|
|||||||
Reference in New Issue
Block a user