use new createContext utility for better type safety
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from 'svelte';
|
import { getEdgeIdContext } from '$lib/store/context';
|
||||||
|
|
||||||
import { hideOnSSR, portal } from '$lib/actions/portal';
|
import { hideOnSSR, portal } from '$lib/actions/portal';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import type { EdgeLabelProps } from './types';
|
|
||||||
import { toPxString } from '$lib/utils';
|
import { toPxString } from '$lib/utils';
|
||||||
|
|
||||||
|
import type { EdgeLabelProps } from './types';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
x = 0,
|
x = 0,
|
||||||
y = 0,
|
y = 0,
|
||||||
@@ -19,13 +19,12 @@
|
|||||||
}: EdgeLabelProps = $props();
|
}: EdgeLabelProps = $props();
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const edgeId = getContext<string>('svelteflow__edge_id');
|
|
||||||
|
|
||||||
if (!edgeId) {
|
const edgeId = getEdgeIdContext('EdgeLabel must be used within a Custom Edge component');
|
||||||
throw new Error('EdgeLabel must be used within an edge');
|
|
||||||
}
|
|
||||||
|
|
||||||
let z = $derived(store.visible.edges.get(edgeId)?.zIndex);
|
let z = $derived.by(() => {
|
||||||
|
return store.visible.edges.get(edgeId)?.zIndex;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -40,7 +39,7 @@
|
|||||||
style:z-index={z}
|
style:z-index={z}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
if (selectEdgeOnClick) store.handleEdgeSelection(edgeId);
|
if (selectEdgeOnClick && edgeId) store.handleEdgeSelection(edgeId);
|
||||||
}}
|
}}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
import { getEdgeIdContext } from '$lib/store/context';
|
||||||
import type { Edge } from '$lib/types';
|
import type { Edge } from '$lib/types';
|
||||||
import { XYHandle, type HandleType, type OnConnectStart } from '@xyflow/system';
|
import { XYHandle, type HandleType, type OnConnectStart } from '@xyflow/system';
|
||||||
import { getContext } from 'svelte';
|
|
||||||
import { EdgeLabel } from '../EdgeLabel';
|
import { EdgeLabel } from '../EdgeLabel';
|
||||||
import type { EdgeReconnectAnchorProps } from './types';
|
import type { EdgeReconnectAnchorProps } from './types';
|
||||||
|
|
||||||
@@ -19,11 +19,9 @@
|
|||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
let edgeId: string | undefined = getContext('svelteflow__edge_id');
|
const edgeId = getEdgeIdContext(
|
||||||
|
'EdgeReconnectAnchor must be used within a Custom Edge component'
|
||||||
if (!edgeId) {
|
);
|
||||||
throw new Error('EdgeReconnectAnchor must be used within an Edge component');
|
|
||||||
}
|
|
||||||
|
|
||||||
const onPointerDown = (event: PointerEvent) => {
|
const onPointerDown = (event: PointerEvent) => {
|
||||||
if (event.button !== 0) {
|
if (event.button !== 0) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
|
<script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
|
||||||
import { setContext } from 'svelte';
|
|
||||||
|
|
||||||
import { elementSelectionKeys, getMarkerId } from '@xyflow/system';
|
import { elementSelectionKeys, getMarkerId } from '@xyflow/system';
|
||||||
|
|
||||||
|
import { setEdgeIdContext } from '$lib/store/context';
|
||||||
|
|
||||||
import { BezierEdgeInternal } from '$lib/components/edges';
|
import { BezierEdgeInternal } from '$lib/components/edges';
|
||||||
|
|
||||||
import type { Node, EdgeLayouted, Edge, EdgeEvents } from '$lib/types';
|
import type { Node, EdgeLayouted, Edge, EdgeEvents } from '$lib/types';
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
} & EdgeEvents<EdgeType> = $props();
|
} & EdgeEvents<EdgeType> = $props();
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
id,
|
||||||
source,
|
source,
|
||||||
target,
|
target,
|
||||||
sourceX,
|
sourceX,
|
||||||
@@ -51,12 +52,11 @@
|
|||||||
ariaLabel
|
ariaLabel
|
||||||
} = $derived(edge);
|
} = $derived(edge);
|
||||||
|
|
||||||
|
setEdgeIdContext(edge.id);
|
||||||
|
|
||||||
// svelte-ignore non_reactive_update
|
// svelte-ignore non_reactive_update
|
||||||
let edgeRef: SVGGElement | null = null;
|
let edgeRef: SVGGElement | null = null;
|
||||||
|
|
||||||
const { id } = edge;
|
|
||||||
setContext('svelteflow__edge_id', id);
|
|
||||||
|
|
||||||
let selectable = $derived(_selectable ?? store.elementsSelectable);
|
let selectable = $derived(_selectable ?? store.elementsSelectable);
|
||||||
let focusable = $derived(_focusable ?? store.edgesFocusable);
|
let focusable = $derived(_focusable ?? store.edgesFocusable);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from 'svelte';
|
|
||||||
import {
|
import {
|
||||||
Position,
|
Position,
|
||||||
XYHandle,
|
XYHandle,
|
||||||
@@ -16,8 +15,8 @@
|
|||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
import type { ConnectableContext } from '../NodeWrapper/types';
|
|
||||||
import type { HandleProps } from './types';
|
import type { HandleProps } from './types';
|
||||||
|
import { getNodeConnectableContext, getNodeIdContext } from '$lib/store/context';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
id: handleId = null,
|
id: handleId = null,
|
||||||
@@ -35,8 +34,10 @@
|
|||||||
...rest
|
...rest
|
||||||
}: HandleProps = $props();
|
}: HandleProps = $props();
|
||||||
|
|
||||||
const nodeId = getContext<string>('svelteflow__node_id');
|
const nodeId = getNodeIdContext('Handle must be used within a Custom Node component');
|
||||||
const isConnectableContext = getContext<ConnectableContext>('svelteflow__node_connectable');
|
const isConnectableContext = getNodeConnectableContext(
|
||||||
|
'Handle must be used within a Custom Node component'
|
||||||
|
);
|
||||||
|
|
||||||
let isTarget = $derived(type === 'target');
|
let isTarget = $derived(type === 'target');
|
||||||
let isConnectable = $derived(
|
let isConnectable = $derived(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
|
<script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
|
||||||
import { setContext, onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import {
|
import {
|
||||||
elementSelectionKeys,
|
elementSelectionKeys,
|
||||||
errorMessages,
|
errorMessages,
|
||||||
@@ -10,13 +10,15 @@
|
|||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import drag from '$lib/actions/drag';
|
import drag from '$lib/actions/drag';
|
||||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
import { setNodeConnectableContext, setNodeIdContext } from '$lib/store/context';
|
||||||
|
|
||||||
import type { ConnectableContext, NodeWrapperProps } from './types';
|
|
||||||
import type { Node, Edge, NodeEvents } from '$lib/types';
|
|
||||||
import { arrowKeyDiffs, toPxString } from '$lib/utils';
|
import { arrowKeyDiffs, toPxString } from '$lib/utils';
|
||||||
import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions';
|
import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions';
|
||||||
|
|
||||||
|
import type { Node, Edge, NodeEvents } from '$lib/types';
|
||||||
|
import type { ConnectableContext, NodeWrapperProps } from './types';
|
||||||
|
|
||||||
|
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
store = $bindable(),
|
store = $bindable(),
|
||||||
node,
|
node,
|
||||||
@@ -95,8 +97,9 @@
|
|||||||
return connectable;
|
return connectable;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
setContext('svelteflow__node_connectable', connectableContext);
|
|
||||||
setContext('svelteflow__node_id', id);
|
setNodeIdContext(id);
|
||||||
|
setNodeConnectableContext(connectableContext);
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { key } from '$lib/store';
|
|
||||||
import type { StoreContext } from '$lib/store/types';
|
|
||||||
import { getContext } from 'svelte';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Warns the user that they should use $derived() when calling a hook.
|
|
||||||
* This is not neccessarry when the hook is called inside a child of <SvelteFlowFlow />,
|
|
||||||
* however exceptions can be made if you don't want to return a closure.
|
|
||||||
* @param functionName - The name of the function that is being called
|
|
||||||
* @param force - If true, the warning will be shown regardless if child of <SvelteFlowFlow />
|
|
||||||
*/
|
|
||||||
export function derivedWarning(functionName: string) {
|
|
||||||
const storeContext = getContext<StoreContext>(key);
|
|
||||||
|
|
||||||
if (!storeContext) {
|
|
||||||
throw new Error(
|
|
||||||
`In order to use ${functionName}() you need to wrap your component in a <SvelteFlowProvider />`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (storeContext.provider && typeof window === 'object' && !$effect.tracking()) {
|
|
||||||
throw new Error(`Use $derived(${functionName}()) to receive updates when values change.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import { getContext } from 'svelte';
|
import { getNodeIdContext } from '$lib/store/context';
|
||||||
|
|
||||||
type UseNodeConnectionsParams = {
|
type UseNodeConnectionsParams = {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -42,7 +42,7 @@ export function useNodeConnections({
|
|||||||
}: UseNodeConnectionsParams = {}) {
|
}: UseNodeConnectionsParams = {}) {
|
||||||
const { edges, connectionLookup } = $derived(useStore());
|
const { edges, connectionLookup } = $derived(useStore());
|
||||||
|
|
||||||
const contextNodeId = getContext<string>('svelteflow__node_id');
|
const contextNodeId = getNodeIdContext();
|
||||||
const nodeId = id ?? contextNodeId;
|
const nodeId = id ?? contextNodeId;
|
||||||
|
|
||||||
let connectionMaps: { previous: ConnectionMap; next: ConnectionMap } = {
|
let connectionMaps: { previous: ConnectionMap; next: ConnectionMap } = {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { getContext } from 'svelte';
|
|||||||
import type { StoreContext, SvelteFlowStore } from '../store/types';
|
import type { StoreContext, SvelteFlowStore } from '../store/types';
|
||||||
|
|
||||||
import { key } from '../store';
|
import { key } from '../store';
|
||||||
import { derivedWarning } from './derivedWarning.svelte';
|
|
||||||
import type { Node, Edge } from '$lib/types';
|
import type { Node, Edge } from '$lib/types';
|
||||||
|
|
||||||
export function useStore<
|
export function useStore<
|
||||||
@@ -17,9 +16,5 @@ export function useStore<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
derivedWarning('useStore');
|
|
||||||
}
|
|
||||||
|
|
||||||
return storeContext.getStore();
|
return storeContext.getStore();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable svelte/prefer-svelte-reactivity */
|
/* eslint-disable svelte/prefer-svelte-reactivity */
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import { getContext } from 'svelte';
|
import { getNodeIdContext } from '$lib/store/context';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When you programmatically add or remove handles to a node or update a node's
|
* When you programmatically add or remove handles to a node or update a node's
|
||||||
@@ -14,7 +14,7 @@ import { getContext } from 'svelte';
|
|||||||
*/
|
*/
|
||||||
export function useUpdateNodeInternals(): (nodeId?: string | string[]) => void {
|
export function useUpdateNodeInternals(): (nodeId?: string | string[]) => void {
|
||||||
const { domNode, updateNodeInternals } = $derived(useStore());
|
const { domNode, updateNodeInternals } = $derived(useStore());
|
||||||
const nodeId = getContext('svelteflow__node_id') as string | undefined;
|
const nodeId = getNodeIdContext();
|
||||||
|
|
||||||
// @todo: do we want to add this to system?
|
// @todo: do we want to add this to system?
|
||||||
const updateInternals = (id?: string | string[]) => {
|
const updateInternals = (id?: string | string[]) => {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
import { getNodeIdContext } from '$lib/store/context';
|
||||||
import {
|
import {
|
||||||
XYResizer,
|
XYResizer,
|
||||||
ResizeControlVariant,
|
ResizeControlVariant,
|
||||||
@@ -9,6 +11,7 @@
|
|||||||
type XYResizerChange,
|
type XYResizerChange,
|
||||||
type XYResizerChildChange
|
type XYResizerChildChange
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { ResizeControlProps } from './types';
|
import type { ResizeControlProps } from './types';
|
||||||
import type { Node } from '$lib/types';
|
import type { Node } from '$lib/types';
|
||||||
|
|
||||||
@@ -34,10 +37,14 @@
|
|||||||
}: ResizeControlProps = $props();
|
}: ResizeControlProps = $props();
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
const contextNodeId = getNodeIdContext();
|
||||||
|
|
||||||
let id = $derived(
|
let id = $derived(typeof nodeId === 'string' ? nodeId : contextNodeId);
|
||||||
typeof nodeId === 'string' ? nodeId : getContext<string>('svelteflow__node_id')
|
|
||||||
);
|
// svelte-ignore state_referenced_locally
|
||||||
|
if (!id) {
|
||||||
|
throw new Error('Either pass a nodeId or use within a Custom Node component');
|
||||||
|
}
|
||||||
|
|
||||||
let resizeControlRef: HTMLDivElement;
|
let resizeControlRef: HTMLDivElement;
|
||||||
let resizer: XYResizerInstance | null = $state(null);
|
let resizer: XYResizerInstance | null = $state(null);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from 'svelte';
|
|
||||||
import { Position, getNodeToolbarTransform } from '@xyflow/system';
|
import { Position, getNodeToolbarTransform } from '@xyflow/system';
|
||||||
|
|
||||||
import { hideOnSSR, portal } from '$lib/actions/portal';
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
import { getNodeIdContext } from '$lib/store/context';
|
||||||
|
import { hideOnSSR, portal } from '$lib/actions/portal';
|
||||||
import { useSvelteFlow } from '$lib/hooks/useSvelteFlow.svelte';
|
import { useSvelteFlow } from '$lib/hooks/useSvelteFlow.svelte';
|
||||||
|
|
||||||
import type { InternalNode } from '$lib/types';
|
import type { InternalNode } from '$lib/types';
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
const { getNodesBounds } = useSvelteFlow();
|
const { getNodesBounds } = useSvelteFlow();
|
||||||
const contextNodeId = getContext<string>('svelteflow__node_id');
|
const contextNodeId = getNodeIdContext();
|
||||||
|
|
||||||
let toolbarNodes: InternalNode[] = $derived.by(() => {
|
let toolbarNodes: InternalNode[] = $derived.by(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
@@ -30,6 +30,9 @@
|
|||||||
const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId ?? contextNodeId];
|
const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId ?? contextNodeId];
|
||||||
|
|
||||||
return nodeIds.reduce<InternalNode[]>((res, nodeId) => {
|
return nodeIds.reduce<InternalNode[]>((res, nodeId) => {
|
||||||
|
if (!nodeId) {
|
||||||
|
throw new Error('Either pass a nodeId or use within a Custom Node component');
|
||||||
|
}
|
||||||
const node = store.nodeLookup.get(nodeId);
|
const node = store.nodeLookup.get(nodeId);
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import type { ConnectableContext } from '$lib/components/NodeWrapper/types';
|
||||||
|
import { setContext, getContext, hasContext } from 'svelte';
|
||||||
|
import type { StoreContext } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a type-safe context getter and setter pair.
|
||||||
|
* Extended from Svelte's official createContext pattern.
|
||||||
|
* - When called with an error message string, it throws if the context is not set
|
||||||
|
* - When called without arguments, it returns the context value or undefined
|
||||||
|
*/
|
||||||
|
function createContext<T>(): [
|
||||||
|
{
|
||||||
|
(errorMessage: string): T;
|
||||||
|
(): T | undefined;
|
||||||
|
},
|
||||||
|
(context: T) => T
|
||||||
|
] {
|
||||||
|
const key = {};
|
||||||
|
|
||||||
|
return [
|
||||||
|
(errorMessage?: string) => {
|
||||||
|
if (errorMessage && !hasContext(key)) {
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getContext(key);
|
||||||
|
},
|
||||||
|
(context) => setContext(key, context)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const [getNodeIdContext, setNodeIdContext] = createContext<string>();
|
||||||
|
export const [getNodeConnectableContext, setNodeConnectableContext] =
|
||||||
|
createContext<ConnectableContext>();
|
||||||
|
|
||||||
|
export const [getEdgeIdContext, setEdgeIdContext] = createContext<string>();
|
||||||
Reference in New Issue
Block a user