@dou-ui/flow 0.0.0-stage → 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/collaboration/index.js +360 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/peers.js +76 -0
- package/dist/collaboration/peers.js.map +1 -0
- package/dist/components/default-node.js +6 -0
- package/dist/components/default-node.js.map +1 -0
- package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js +87 -0
- package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/default-node2.js +116 -0
- package/dist/components/default-node2.js.map +1 -0
- package/dist/components/flow-controls.js +6 -0
- package/dist/components/flow-controls.js.map +1 -0
- package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js +148 -0
- package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-menu.js +6 -0
- package/dist/components/flow-menu.js.map +1 -0
- package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js +52 -0
- package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-minimap.js +6 -0
- package/dist/components/flow-minimap.js.map +1 -0
- package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js +238 -0
- package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-port.js +6 -0
- package/dist/components/flow-port.js.map +1 -0
- package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js +57 -0
- package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-quick-add.js +6 -0
- package/dist/components/flow-quick-add.js.map +1 -0
- package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js +126 -0
- package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/flow-toolbar.js +6 -0
- package/dist/components/flow-toolbar.js.map +1 -0
- package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js +161 -0
- package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/group-node.js +6 -0
- package/dist/components/group-node.js.map +1 -0
- package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js +84 -0
- package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/group-node2.js +42 -0
- package/dist/components/group-node2.js.map +1 -0
- package/dist/components/node-view.js +6 -0
- package/dist/components/node-view.js.map +1 -0
- package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js +179 -0
- package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/context.js +13 -0
- package/dist/context.js.map +1 -0
- package/dist/core/clipboard.js +37 -0
- package/dist/core/clipboard.js.map +1 -0
- package/dist/core/connect.js +74 -0
- package/dist/core/connect.js.map +1 -0
- package/dist/core/edge-types.js +240 -0
- package/dist/core/edge-types.js.map +1 -0
- package/dist/core/extension.js +63 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/geometry.js +232 -0
- package/dist/core/geometry.js.map +1 -0
- package/dist/core/groups.js +76 -0
- package/dist/core/groups.js.map +1 -0
- package/dist/core/history.js +79 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/model.js +21 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/node-types.js +32 -0
- package/dist/core/node-types.js.map +1 -0
- package/dist/core/order.js +42 -0
- package/dist/core/order.js.map +1 -0
- package/dist/core/transaction.js +288 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/document.js +220 -0
- package/dist/document.js.map +1 -0
- package/dist/export/color.js +38 -0
- package/dist/export/color.js.map +1 -0
- package/dist/export/context.js +223 -0
- package/dist/export/context.js.map +1 -0
- package/dist/export/draw.js +249 -0
- package/dist/export/draw.js.map +1 -0
- package/dist/flow-editor.js +6 -0
- package/dist/flow-editor.js.map +1 -0
- package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js +3721 -0
- package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/kit.js +234 -0
- package/dist/kit.js.map +1 -0
- package/dist/layers.js +150 -0
- package/dist/layers.js.map +1 -0
- package/dist/locale.js +426 -0
- package/dist/locale.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +8 -0
- package/dist/types/collaboration/index.d.ts +42 -0
- package/dist/types/collaboration/peers.d.ts +27 -0
- package/dist/types/components/default-node.d.ts +18 -0
- package/dist/types/components/default-node.vue.d.ts +6 -0
- package/dist/types/components/flow-controls.vue.d.ts +8 -0
- package/dist/types/components/flow-menu.vue.d.ts +29 -0
- package/dist/types/components/flow-minimap.vue.d.ts +8 -0
- package/dist/types/components/flow-port.vue.d.ts +9 -0
- package/dist/types/components/flow-quick-add.vue.d.ts +31 -0
- package/dist/types/components/flow-toolbar.vue.d.ts +25 -0
- package/dist/types/components/group-node.d.ts +13 -0
- package/dist/types/components/group-node.vue.d.ts +6 -0
- package/dist/types/components/node-view.vue.d.ts +28 -0
- package/dist/types/context.d.ts +362 -0
- package/dist/types/core/clipboard.d.ts +9 -0
- package/dist/types/core/connect.d.ts +63 -0
- package/dist/types/core/edge-types.d.ts +50 -0
- package/dist/types/core/extension.d.ts +141 -0
- package/dist/types/core/geometry.d.ts +63 -0
- package/dist/types/core/groups.d.ts +23 -0
- package/dist/types/core/history.d.ts +24 -0
- package/dist/types/core/menu.d.ts +40 -0
- package/dist/types/core/model.d.ts +87 -0
- package/dist/types/core/node-types.d.ts +103 -0
- package/dist/types/core/order.d.ts +7 -0
- package/dist/types/core/transaction.d.ts +97 -0
- package/dist/types/document.d.ts +57 -0
- package/dist/types/export/color.d.ts +7 -0
- package/dist/types/export/context.d.ts +90 -0
- package/dist/types/export/draw.d.ts +72 -0
- package/dist/types/flow-editor.vue.d.ts +63 -0
- package/dist/types/index.d.ts +202 -0
- package/dist/types/kit.d.ts +22 -0
- package/dist/types/layers.d.ts +24 -0
- package/dist/types/locale.d.ts +172 -0
- package/dist/types/whiteboard/board-draft.vue.d.ts +3 -0
- package/dist/types/whiteboard/board-node.vue.d.ts +19 -0
- package/dist/types/whiteboard/connector.d.ts +12 -0
- package/dist/types/whiteboard/draw.d.ts +5 -0
- package/dist/types/whiteboard/image.d.ts +27 -0
- package/dist/types/whiteboard/index.d.ts +6 -0
- package/dist/types/whiteboard/ink.d.ts +30 -0
- package/dist/types/whiteboard/kit.d.ts +36 -0
- package/dist/types/whiteboard/model.d.ts +79 -0
- package/dist/types/whiteboard/pen.d.ts +7 -0
- package/dist/types/whiteboard/state.d.ts +37 -0
- package/dist/types/whiteboard/transform.d.ts +67 -0
- package/dist/types/whiteboard/whiteboard-toolbar.vue.d.ts +3 -0
- package/dist/whiteboard/board-draft.js +6 -0
- package/dist/whiteboard/board-draft.js.map +1 -0
- package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js +58 -0
- package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/whiteboard/board-node.js +6 -0
- package/dist/whiteboard/board-node.js.map +1 -0
- package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js +332 -0
- package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/whiteboard/connector.js +180 -0
- package/dist/whiteboard/connector.js.map +1 -0
- package/dist/whiteboard/draw.js +68 -0
- package/dist/whiteboard/draw.js.map +1 -0
- package/dist/whiteboard/image.js +104 -0
- package/dist/whiteboard/image.js.map +1 -0
- package/dist/whiteboard/index.js +5 -0
- package/dist/whiteboard/ink.js +168 -0
- package/dist/whiteboard/ink.js.map +1 -0
- package/dist/whiteboard/kit.js +406 -0
- package/dist/whiteboard/kit.js.map +1 -0
- package/dist/whiteboard/model.js +52 -0
- package/dist/whiteboard/model.js.map +1 -0
- package/dist/whiteboard/pen.js +88 -0
- package/dist/whiteboard/pen.js.map +1 -0
- package/dist/whiteboard/state.js +26 -0
- package/dist/whiteboard/state.js.map +1 -0
- package/dist/whiteboard/transform.js +256 -0
- package/dist/whiteboard/transform.js.map +1 -0
- package/dist/whiteboard/whiteboard-toolbar.js +6 -0
- package/dist/whiteboard/whiteboard-toolbar.js.map +1 -0
- package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js +171 -0
- package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/package.json +74 -4
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/core/clipboard.ts
|
|
2
|
+
var MARK = "dou-flow";
|
|
3
|
+
var copied = null;
|
|
4
|
+
/** Remembers `data` and returns the text to put on the system clipboard. */
|
|
5
|
+
function writeClipboard(data) {
|
|
6
|
+
const text = JSON.stringify({
|
|
7
|
+
[MARK]: 1,
|
|
8
|
+
nodes: data.nodes,
|
|
9
|
+
edges: data.edges
|
|
10
|
+
});
|
|
11
|
+
copied = {
|
|
12
|
+
data,
|
|
13
|
+
text
|
|
14
|
+
};
|
|
15
|
+
return text;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The nodes to paste: from the system clipboard's `text` when it holds a
|
|
19
|
+
* copy, else the last copy made on this page. `null` when `text` is
|
|
20
|
+
* something else.
|
|
21
|
+
*/
|
|
22
|
+
function readClipboard(text) {
|
|
23
|
+
if (text == null || text === "") return copied?.data ?? null;
|
|
24
|
+
if (copied && text === copied.text) return copied.data;
|
|
25
|
+
try {
|
|
26
|
+
const value = JSON.parse(text);
|
|
27
|
+
if (value?.[MARK] && Array.isArray(value.nodes) && Array.isArray(value.edges)) return {
|
|
28
|
+
version: 1,
|
|
29
|
+
nodes: value.nodes,
|
|
30
|
+
edges: value.edges
|
|
31
|
+
};
|
|
32
|
+
} catch {}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
export { readClipboard, writeClipboard };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=clipboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard.js","names":[],"sources":["../../src/core/clipboard.ts"],"sourcesContent":["import type { FlowDocument } from './model'\n\n/*\n * Copied nodes go to the system clipboard as JSON, so they paste into another\n * tab or a text box too. The last copy is also kept here as is: node data can\n * hold things JSON can't, and it is what a paste uses when the clipboard\n * still holds that same copy.\n */\n\nconst MARK = 'dou-flow'\n\nlet copied: { data: FlowDocument; text: string } | null = null\n\n/** Remembers `data` and returns the text to put on the system clipboard. */\nexport function writeClipboard(data: FlowDocument) {\n const text = JSON.stringify({ [MARK]: 1, nodes: data.nodes, edges: data.edges })\n copied = { data, text }\n return text\n}\n\n/**\n * The nodes to paste: from the system clipboard's `text` when it holds a\n * copy, else the last copy made on this page. `null` when `text` is\n * something else.\n */\nexport function readClipboard(text?: string | null): FlowDocument | null {\n if (text == null || text === '') return copied?.data ?? null\n if (copied && text === copied.text) return copied.data\n try {\n const value = JSON.parse(text)\n if (value?.[MARK] && Array.isArray(value.nodes) && Array.isArray(value.edges))\n return { version: 1, nodes: value.nodes, edges: value.edges }\n } catch {\n // Not ours.\n }\n return null\n}\n"],"mappings":";AASA,IAAM,OAAO;AAEb,IAAI,SAAsD;;AAG1D,SAAgB,eAAe,MAAoB;CACjD,MAAM,OAAO,KAAK,UAAU;GAAG,OAAO;EAAG,OAAO,KAAK;EAAO,OAAO,KAAK;CAAM,CAAC;CAC/E,SAAS;EAAE;EAAM;CAAK;CACtB,OAAO;AACT;;;;;;AAOA,SAAgB,cAAc,MAA2C;CACvE,IAAI,QAAQ,QAAQ,SAAS,IAAI,OAAO,QAAQ,QAAQ;CACxD,IAAI,UAAU,SAAS,OAAO,MAAM,OAAO,OAAO;CAClD,IAAI;EACF,MAAM,QAAQ,KAAK,MAAM,IAAI;EAC7B,IAAI,QAAQ,SAAS,MAAM,QAAQ,MAAM,KAAK,KAAK,MAAM,QAAQ,MAAM,KAAK,GAC1E,OAAO;GAAE,SAAS;GAAG,OAAO,MAAM;GAAO,OAAO,MAAM;EAAM;CAChE,QAAQ,CAER;CACA,OAAO;AACT"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/core/connect.ts
|
|
2
|
+
var typesOf = (port) => port.dataType == null ? [] : Array.isArray(port.dataType) ? port.dataType : [port.dataType];
|
|
3
|
+
/** The first type of a port, which decides its look. */
|
|
4
|
+
var primaryType = (port) => typesOf(port)[0];
|
|
5
|
+
/** Whether data of the output's types may flow into the input. */
|
|
6
|
+
function typesMatch(output, input, portTypes = {}) {
|
|
7
|
+
const from = typesOf(output);
|
|
8
|
+
const to = typesOf(input);
|
|
9
|
+
if (!from.length || !to.length || from.includes("any") || to.includes("any")) return true;
|
|
10
|
+
return to.some((type) => from.includes(type) || from.some((other) => portTypes[type]?.accepts?.includes(other)));
|
|
11
|
+
}
|
|
12
|
+
/** Whether `to` can be reached from `from` along edges. */
|
|
13
|
+
function reaches(doc, from, to) {
|
|
14
|
+
const next = /* @__PURE__ */ new Map();
|
|
15
|
+
for (const edge of doc.edges) {
|
|
16
|
+
const list = next.get(edge.source.node);
|
|
17
|
+
if (list) list.push(edge.target.node);
|
|
18
|
+
else next.set(edge.source.node, [edge.target.node]);
|
|
19
|
+
}
|
|
20
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21
|
+
const stack = [from];
|
|
22
|
+
while (stack.length) {
|
|
23
|
+
const id = stack.pop();
|
|
24
|
+
if (id === to) return true;
|
|
25
|
+
if (seen.has(id)) continue;
|
|
26
|
+
seen.add(id);
|
|
27
|
+
stack.push(...next.get(id) ?? []);
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
var edgesAt = (doc, end, at) => doc.edges.filter((edge) => edge[end].node === at.node && edge[end].port === at.port);
|
|
32
|
+
/**
|
|
33
|
+
* Checks a connection between two ports and says why it is refused, or
|
|
34
|
+
* `null` when it is fine. `ignore` is an edge being moved, left out. A port
|
|
35
|
+
* that is full still takes the edge: the oldest of its edges makes way.
|
|
36
|
+
*/
|
|
37
|
+
function joinRefusal(doc, connection, ports, options = {}, ignore) {
|
|
38
|
+
const { source, target } = connection;
|
|
39
|
+
if (ports.source.kind !== "output" || ports.target.kind !== "input") return { reason: "direction" };
|
|
40
|
+
if (source.node === target.node) return { reason: "self" };
|
|
41
|
+
if (!typesMatch(ports.source, ports.target, options.portTypes)) return { reason: "type" };
|
|
42
|
+
const others = {
|
|
43
|
+
...doc,
|
|
44
|
+
edges: doc.edges.filter((edge) => edge.id !== ignore)
|
|
45
|
+
};
|
|
46
|
+
if (edgesAt(others, "target", target).some((edge) => edge.source.node === source.node && edge.source.port === source.port)) return { reason: "duplicate" };
|
|
47
|
+
if (options.acyclic && reaches(others, target.node, source.node)) return { reason: "cycle" };
|
|
48
|
+
const verdict = options.canConnect ? options.canConnect(connection, doc) : true;
|
|
49
|
+
if (verdict === true) return null;
|
|
50
|
+
return {
|
|
51
|
+
reason: "custom",
|
|
52
|
+
message: typeof verdict === "string" ? verdict : void 0
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Whether two ports may be joined; see `joinRefusal`. */
|
|
56
|
+
var canJoin = (...args) => !joinRefusal(...args);
|
|
57
|
+
/**
|
|
58
|
+
* The edges that make way when an edge joins two full ports: the oldest at
|
|
59
|
+
* each end, beyond `max` (1 for inputs, unlimited for outputs by default).
|
|
60
|
+
*/
|
|
61
|
+
function displacedEdges(doc, connection, ports, ignore) {
|
|
62
|
+
const out = /* @__PURE__ */ new Set();
|
|
63
|
+
const trim = (end, max) => {
|
|
64
|
+
if (max == null) return;
|
|
65
|
+
const edges = edgesAt(doc, end, connection[end]).filter((edge) => edge.id !== ignore);
|
|
66
|
+
for (const edge of edges.slice(0, Math.max(0, edges.length - max + 1))) out.add(edge.id);
|
|
67
|
+
};
|
|
68
|
+
trim("target", ports.target.max ?? 1);
|
|
69
|
+
trim("source", ports.source.max);
|
|
70
|
+
return [...out];
|
|
71
|
+
}
|
|
72
|
+
export { canJoin, displacedEdges, joinRefusal, primaryType, reaches, typesMatch };
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=connect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.js","names":[],"sources":["../../src/core/connect.ts"],"sourcesContent":["import type { Tone } from 'dou-ui'\nimport type { FlowDocument, FlowEndpoint, FlowPortSpec } from './model'\n\n/*\n * Whether two ports may be joined. The rules come in layers, each one\n * optional, so a flow without types works without any of them:\n *\n * 1. Ports without a `dataType` take anything.\n * 2. Ports with types join when they share one, or either is `'any'`.\n * 3. A registered port type can accept other types too, and has its own look.\n * 4. `canConnect` has the last word.\n */\n\nexport type PortShape = 'circle' | 'square' | 'diamond' | 'triangle'\n\nexport interface PortTypeSpec {\n /** Shown to screen readers and in tooltips. */\n label?: string\n shape?: PortShape\n /** A theme tone, or any CSS color. Also colors the edges leaving the port. */\n color?: Tone | (string & {})\n /** Edge width in px; 1 by default. */\n edgeWidth?: number\n /** Draw edges of this type dashed. */\n dashed?: boolean\n /** Other types a port of this type takes. */\n accepts?: string[]\n}\n\nexport type PortTypes = Record<string, PortTypeSpec>\n\nexport interface FlowConnection {\n source: Required<FlowEndpoint>\n target: Required<FlowEndpoint>\n}\n\nexport interface ConnectOptions {\n portTypes?: PortTypes\n /** Refuse edges that would close a cycle. */\n acyclic?: boolean\n /**\n * The last check. Return `false` to refuse, or a sentence saying why, which\n * is announced to screen readers. Called for every port while dragging.\n */\n canConnect?: (connection: FlowConnection, doc: FlowDocument) => boolean | string\n}\n\n/** Why two ports can't be joined. */\nexport type JoinRefusal =\n | { reason: 'direction' | 'self' | 'type' | 'duplicate' | 'cycle' }\n | { reason: 'custom'; message?: string }\n\nconst typesOf = (port: FlowPortSpec) =>\n port.dataType == null ? [] : Array.isArray(port.dataType) ? port.dataType : [port.dataType]\n\n/** The first type of a port, which decides its look. */\nexport const primaryType = (port: FlowPortSpec) => typesOf(port)[0]\n\n/** Whether data of the output's types may flow into the input. */\nexport function typesMatch(output: FlowPortSpec, input: FlowPortSpec, portTypes: PortTypes = {}) {\n const from = typesOf(output)\n const to = typesOf(input)\n if (!from.length || !to.length || from.includes('any') || to.includes('any')) return true\n return to.some(\n (type) =>\n from.includes(type) || from.some((other) => portTypes[type]?.accepts?.includes(other)),\n )\n}\n\n/** Whether `to` can be reached from `from` along edges. */\nexport function reaches(doc: FlowDocument, from: string, to: string) {\n const next = new Map<string, string[]>()\n for (const edge of doc.edges) {\n const list = next.get(edge.source.node)\n if (list) list.push(edge.target.node)\n else next.set(edge.source.node, [edge.target.node])\n }\n const seen = new Set<string>()\n const stack = [from]\n while (stack.length) {\n const id = stack.pop()!\n if (id === to) return true\n if (seen.has(id)) continue\n seen.add(id)\n stack.push(...(next.get(id) ?? []))\n }\n return false\n}\n\nconst edgesAt = (doc: FlowDocument, end: 'source' | 'target', at: Required<FlowEndpoint>) =>\n doc.edges.filter((edge) => edge[end].node === at.node && edge[end].port === at.port)\n\n/**\n * Checks a connection between two ports and says why it is refused, or\n * `null` when it is fine. `ignore` is an edge being moved, left out. A port\n * that is full still takes the edge: the oldest of its edges makes way.\n */\nexport function joinRefusal(\n doc: FlowDocument,\n connection: FlowConnection,\n ports: { source: FlowPortSpec; target: FlowPortSpec },\n options: ConnectOptions = {},\n ignore?: string,\n): JoinRefusal | null {\n const { source, target } = connection\n if (ports.source.kind !== 'output' || ports.target.kind !== 'input')\n return { reason: 'direction' }\n if (source.node === target.node) return { reason: 'self' }\n if (!typesMatch(ports.source, ports.target, options.portTypes)) return { reason: 'type' }\n const others = { ...doc, edges: doc.edges.filter((edge) => edge.id !== ignore) }\n const into = edgesAt(others, 'target', target)\n if (into.some((edge) => edge.source.node === source.node && edge.source.port === source.port))\n return { reason: 'duplicate' }\n if (options.acyclic && reaches(others, target.node, source.node)) return { reason: 'cycle' }\n const verdict = options.canConnect ? options.canConnect(connection, doc) : true\n if (verdict === true) return null\n return { reason: 'custom', message: typeof verdict === 'string' ? verdict : undefined }\n}\n\n/** Whether two ports may be joined; see `joinRefusal`. */\nexport const canJoin = (...args: Parameters<typeof joinRefusal>) => !joinRefusal(...args)\n\n/**\n * The edges that make way when an edge joins two full ports: the oldest at\n * each end, beyond `max` (1 for inputs, unlimited for outputs by default).\n */\nexport function displacedEdges(\n doc: FlowDocument,\n connection: FlowConnection,\n ports: { source: FlowPortSpec; target: FlowPortSpec },\n ignore?: string,\n) {\n const out = new Set<string>()\n const trim = (end: 'source' | 'target', max: number | undefined) => {\n if (max == null) return\n const edges = edgesAt(doc, end, connection[end]).filter((edge) => edge.id !== ignore)\n for (const edge of edges.slice(0, Math.max(0, edges.length - max + 1))) out.add(edge.id)\n }\n trim('target', ports.target.max ?? 1)\n trim('source', ports.source.max)\n return [...out]\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// src/core/edge-types.ts
|
|
2
|
+
/** Declares an edge type, with `edge.data` typed. */
|
|
3
|
+
function defineEdgeType(spec) {
|
|
4
|
+
return spec;
|
|
5
|
+
}
|
|
6
|
+
var direction = {
|
|
7
|
+
left: {
|
|
8
|
+
x: -1,
|
|
9
|
+
y: 0
|
|
10
|
+
},
|
|
11
|
+
right: {
|
|
12
|
+
x: 1,
|
|
13
|
+
y: 0
|
|
14
|
+
},
|
|
15
|
+
top: {
|
|
16
|
+
x: 0,
|
|
17
|
+
y: -1
|
|
18
|
+
},
|
|
19
|
+
bottom: {
|
|
20
|
+
x: 0,
|
|
21
|
+
y: 1
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var round = (value) => Math.round(value * 100) / 100;
|
|
25
|
+
var fmt = (p) => `${round(p.x)} ${round(p.y)}`;
|
|
26
|
+
var isVertical = (side) => side === "top" || side === "bottom";
|
|
27
|
+
/**
|
|
28
|
+
* A cubic curve leaving `from` and entering `to` straight out of their
|
|
29
|
+
* sides. The handles are equally long, half the distance along the axis
|
|
30
|
+
* they point in (at least `min`), so a fan of edges from one port is
|
|
31
|
+
* mirror-symmetric and a straight run stays a straight line.
|
|
32
|
+
*/
|
|
33
|
+
function bezierControls(from, to, min = 40) {
|
|
34
|
+
const a = direction[from.side];
|
|
35
|
+
const b = direction[to.side];
|
|
36
|
+
const span = (dir) => Math.abs(dir.x ? to.x - from.x : to.y - from.y);
|
|
37
|
+
const lenA = Math.max(min, span(a) / 2);
|
|
38
|
+
const lenB = Math.max(min, span(b) / 2);
|
|
39
|
+
return [{
|
|
40
|
+
x: from.x + a.x * lenA,
|
|
41
|
+
y: from.y + a.y * lenA
|
|
42
|
+
}, {
|
|
43
|
+
x: to.x + b.x * lenB,
|
|
44
|
+
y: to.y + b.y * lenB
|
|
45
|
+
}];
|
|
46
|
+
}
|
|
47
|
+
function bezierPath(from, fromSide, to, toSide, min = 40) {
|
|
48
|
+
const [c1, c2] = bezierControls({
|
|
49
|
+
...from,
|
|
50
|
+
side: fromSide
|
|
51
|
+
}, {
|
|
52
|
+
...to,
|
|
53
|
+
side: toSide
|
|
54
|
+
}, min);
|
|
55
|
+
return `M${fmt(from)}C${fmt(c1)} ${fmt(c2)} ${fmt(to)}`;
|
|
56
|
+
}
|
|
57
|
+
/** The default: a smooth curve out of and into the ports' sides. */
|
|
58
|
+
var bezierEdge = defineEdgeType({
|
|
59
|
+
type: "bezier",
|
|
60
|
+
route(source, target) {
|
|
61
|
+
const [c1, c2] = bezierControls(source, target);
|
|
62
|
+
return {
|
|
63
|
+
path: `M${fmt(source)}C${fmt(c1)} ${fmt(c2)} ${fmt(target)}`,
|
|
64
|
+
label: {
|
|
65
|
+
x: (source.x + 3 * c1.x + 3 * c2.x + target.x) / 8,
|
|
66
|
+
y: (source.y + 3 * c1.y + 3 * c2.y + target.y) / 8
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
var straightEdge = defineEdgeType({
|
|
72
|
+
type: "straight",
|
|
73
|
+
route: (source, target) => ({
|
|
74
|
+
path: `M${fmt(source)}L${fmt(target)}`,
|
|
75
|
+
label: {
|
|
76
|
+
x: (source.x + target.x) / 2,
|
|
77
|
+
y: (source.y + target.y) / 2
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
/** How far a step edge runs out of a port before its first turn. */
|
|
82
|
+
var STUB = 20;
|
|
83
|
+
var RADIUS = 6;
|
|
84
|
+
/** A polyline with its corners rounded. */
|
|
85
|
+
function rounded(points, radius = RADIUS) {
|
|
86
|
+
const pts = [];
|
|
87
|
+
for (const p of points) {
|
|
88
|
+
const last = pts[pts.length - 1];
|
|
89
|
+
if (last && Math.abs(last.x - p.x) < .01 && Math.abs(last.y - p.y) < .01) continue;
|
|
90
|
+
const before = pts[pts.length - 2];
|
|
91
|
+
if (before && last && (Math.abs(before.x - last.x) < .01 && Math.abs(last.x - p.x) < .01 || Math.abs(before.y - last.y) < .01 && Math.abs(last.y - p.y) < .01)) pts.pop();
|
|
92
|
+
pts.push(p);
|
|
93
|
+
}
|
|
94
|
+
let d = `M${fmt(pts[0])}`;
|
|
95
|
+
for (let i = 1; i < pts.length - 1; i++) {
|
|
96
|
+
const [a, b, c] = [
|
|
97
|
+
pts[i - 1],
|
|
98
|
+
pts[i],
|
|
99
|
+
pts[i + 1]
|
|
100
|
+
];
|
|
101
|
+
const lenIn = Math.hypot(b.x - a.x, b.y - a.y);
|
|
102
|
+
const lenOut = Math.hypot(c.x - b.x, c.y - b.y);
|
|
103
|
+
const r = Math.min(radius, lenIn / 2, lenOut / 2);
|
|
104
|
+
const p1 = {
|
|
105
|
+
x: b.x - (b.x - a.x) / lenIn * r,
|
|
106
|
+
y: b.y - (b.y - a.y) / lenIn * r
|
|
107
|
+
};
|
|
108
|
+
const p2 = {
|
|
109
|
+
x: b.x + (c.x - b.x) / lenOut * r,
|
|
110
|
+
y: b.y + (c.y - b.y) / lenOut * r
|
|
111
|
+
};
|
|
112
|
+
d += `L${fmt(p1)}Q${fmt(b)} ${fmt(p2)}`;
|
|
113
|
+
}
|
|
114
|
+
return d + `L${fmt(pts[pts.length - 1])}`;
|
|
115
|
+
}
|
|
116
|
+
var flip = (p) => ({
|
|
117
|
+
x: p.y,
|
|
118
|
+
y: p.x
|
|
119
|
+
});
|
|
120
|
+
var flipSide = {
|
|
121
|
+
left: "top",
|
|
122
|
+
right: "bottom",
|
|
123
|
+
top: "left",
|
|
124
|
+
bottom: "right"
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Right angles with rounded corners. Between ports that face each other the
|
|
128
|
+
* middle segment can be dragged; its place is kept in `edge.data.bend`.
|
|
129
|
+
*/
|
|
130
|
+
var stepEdge = defineEdgeType({
|
|
131
|
+
type: "step",
|
|
132
|
+
route(source, target, edge) {
|
|
133
|
+
const turned = isVertical(source.side) && isVertical(target.side);
|
|
134
|
+
const s = turned ? {
|
|
135
|
+
...flip(source),
|
|
136
|
+
side: flipSide[source.side]
|
|
137
|
+
} : source;
|
|
138
|
+
const t = turned ? {
|
|
139
|
+
...flip(target),
|
|
140
|
+
side: flipSide[target.side]
|
|
141
|
+
} : target;
|
|
142
|
+
const back = (p) => turned ? flip(p) : p;
|
|
143
|
+
const ds = direction[s.side];
|
|
144
|
+
const dt = direction[t.side];
|
|
145
|
+
const s1 = {
|
|
146
|
+
x: s.x + ds.x * STUB,
|
|
147
|
+
y: s.y + ds.y * STUB
|
|
148
|
+
};
|
|
149
|
+
const t1 = {
|
|
150
|
+
x: t.x + dt.x * STUB,
|
|
151
|
+
y: t.y + dt.y * STUB
|
|
152
|
+
};
|
|
153
|
+
let points;
|
|
154
|
+
let label;
|
|
155
|
+
let handle;
|
|
156
|
+
if (ds.x && dt.x && ds.x === -dt.x && (t.x - s.x) * ds.x > 0) {
|
|
157
|
+
const bend = Math.min(.95, Math.max(.05, edge?.data?.bend ?? .5));
|
|
158
|
+
const mid = s.x + (t.x - s.x) * bend;
|
|
159
|
+
points = [
|
|
160
|
+
s,
|
|
161
|
+
{
|
|
162
|
+
x: mid,
|
|
163
|
+
y: s.y
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
x: mid,
|
|
167
|
+
y: t.y
|
|
168
|
+
},
|
|
169
|
+
t
|
|
170
|
+
];
|
|
171
|
+
label = {
|
|
172
|
+
x: mid,
|
|
173
|
+
y: (s.y + t.y) / 2
|
|
174
|
+
};
|
|
175
|
+
if (t.x !== s.x && Math.abs(t.y - s.y) >= 1) {
|
|
176
|
+
const span = t.x - s.x;
|
|
177
|
+
handle = {
|
|
178
|
+
at: back(label),
|
|
179
|
+
axis: turned ? "y" : "x",
|
|
180
|
+
move: (point) => {
|
|
181
|
+
const v = turned ? point.y : point.x;
|
|
182
|
+
return { bend: Math.min(.95, Math.max(.05, (v - s.x) / span)) };
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
} else if (ds.x && dt.x) {
|
|
187
|
+
const my = Math.abs(s.y - t.y) >= 40 ? (s.y + t.y) / 2 : Math.max(s.y, t.y) + 40;
|
|
188
|
+
const x1 = ds.x === dt.x ? ds.x > 0 ? Math.max(s1.x, t1.x) : Math.min(s1.x, t1.x) : s1.x;
|
|
189
|
+
const x2 = ds.x === dt.x ? x1 : t1.x;
|
|
190
|
+
points = [
|
|
191
|
+
s,
|
|
192
|
+
{
|
|
193
|
+
x: x1,
|
|
194
|
+
y: s.y
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
x: x1,
|
|
198
|
+
y: my
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
x: x2,
|
|
202
|
+
y: my
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
x: x2,
|
|
206
|
+
y: t.y
|
|
207
|
+
},
|
|
208
|
+
t
|
|
209
|
+
];
|
|
210
|
+
label = {
|
|
211
|
+
x: (x1 + x2) / 2,
|
|
212
|
+
y: my
|
|
213
|
+
};
|
|
214
|
+
} else {
|
|
215
|
+
const corner = ds.x ? {
|
|
216
|
+
x: t1.x,
|
|
217
|
+
y: s1.y
|
|
218
|
+
} : {
|
|
219
|
+
x: s1.x,
|
|
220
|
+
y: t1.y
|
|
221
|
+
};
|
|
222
|
+
points = [
|
|
223
|
+
s,
|
|
224
|
+
s1,
|
|
225
|
+
corner,
|
|
226
|
+
t1,
|
|
227
|
+
t
|
|
228
|
+
];
|
|
229
|
+
label = corner;
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
path: rounded(points.map(back)),
|
|
233
|
+
label: back(label),
|
|
234
|
+
handle
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
export { bezierEdge, bezierPath, defineEdgeType, stepEdge, straightEdge };
|
|
239
|
+
|
|
240
|
+
//# sourceMappingURL=edge-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-types.js","names":[],"sources":["../../src/core/edge-types.ts"],"sourcesContent":["import type { CSSProperties } from 'vue'\nimport type { FlowData, FlowEdge, Point, PortSide } from './model'\n\n/** Where an edge leaves or enters a node: a point and the side it faces. */\nexport interface EdgeAnchor extends Point {\n side: PortSide\n}\n\nexport interface EdgeRoute {\n /** The SVG path, in canvas coordinates. */\n path: string\n /** Where the label sits. */\n label: Point\n /**\n * A handle shown on the selected edge that moves part of it, e.g. the\n * middle of a step edge. `axis` is the way it moves; `move` turns the\n * pointer's canvas point into a patch for `edge.data`.\n */\n handle?: { at: Point; axis: 'x' | 'y'; move: (point: Point) => FlowData }\n /**\n * Marks drawn with the edge's stroke but never dashed, e.g. arrowheads:\n * an SVG path in canvas coordinates.\n */\n marks?: string\n}\n\nexport interface EdgeTypeSpec<Data extends FlowData = FlowData> {\n type: string\n /** The edge's path between two anchors. `edge` is absent while one is being drawn. */\n route: (source: EdgeAnchor, target: EdgeAnchor, edge?: FlowEdge<Data>) => EdgeRoute\n /** The edge's own look, e.g. `strokeDasharray`, over what its port type gives it. */\n style?: (edge: FlowEdge<Data>) => CSSProperties | undefined\n}\n\n/** Declares an edge type, with `edge.data` typed. */\nexport function defineEdgeType<Data extends FlowData = FlowData>(\n spec: EdgeTypeSpec<Data>,\n): EdgeTypeSpec<Data> {\n return spec\n}\n\nconst direction: Record<PortSide, Point> = {\n left: { x: -1, y: 0 },\n right: { x: 1, y: 0 },\n top: { x: 0, y: -1 },\n bottom: { x: 0, y: 1 },\n}\n\nconst round = (value: number) => Math.round(value * 100) / 100\nconst fmt = (p: Point) => `${round(p.x)} ${round(p.y)}`\nconst isVertical = (side: PortSide) => side === 'top' || side === 'bottom'\n\n/**\n * A cubic curve leaving `from` and entering `to` straight out of their\n * sides. The handles are equally long, half the distance along the axis\n * they point in (at least `min`), so a fan of edges from one port is\n * mirror-symmetric and a straight run stays a straight line.\n */\nfunction bezierControls(from: EdgeAnchor, to: EdgeAnchor, min = 40) {\n const a = direction[from.side]\n const b = direction[to.side]\n const span = (dir: Point) => Math.abs(dir.x ? to.x - from.x : to.y - from.y)\n const lenA = Math.max(min, span(a) / 2)\n const lenB = Math.max(min, span(b) / 2)\n return [\n { x: from.x + a.x * lenA, y: from.y + a.y * lenA },\n { x: to.x + b.x * lenB, y: to.y + b.y * lenB },\n ] as const\n}\n\nexport function bezierPath(from: Point, fromSide: PortSide, to: Point, toSide: PortSide, min = 40) {\n const [c1, c2] = bezierControls({ ...from, side: fromSide }, { ...to, side: toSide }, min)\n return `M${fmt(from)}C${fmt(c1)} ${fmt(c2)} ${fmt(to)}`\n}\n\n/** The default: a smooth curve out of and into the ports' sides. */\nexport const bezierEdge = defineEdgeType({\n type: 'bezier',\n route(source, target) {\n const [c1, c2] = bezierControls(source, target)\n return {\n path: `M${fmt(source)}C${fmt(c1)} ${fmt(c2)} ${fmt(target)}`,\n // The curve at its middle.\n label: {\n x: (source.x + 3 * c1.x + 3 * c2.x + target.x) / 8,\n y: (source.y + 3 * c1.y + 3 * c2.y + target.y) / 8,\n },\n }\n },\n})\n\nexport const straightEdge = defineEdgeType({\n type: 'straight',\n route: (source, target) => ({\n path: `M${fmt(source)}L${fmt(target)}`,\n label: { x: (source.x + target.x) / 2, y: (source.y + target.y) / 2 },\n }),\n})\n\n/** How far a step edge runs out of a port before its first turn. */\nconst STUB = 20\nconst RADIUS = 6\n\n/** A polyline with its corners rounded. */\nfunction rounded(points: Point[], radius = RADIUS) {\n // Drop repeated points and points in the middle of a straight run.\n const pts: Point[] = []\n for (const p of points) {\n const last = pts[pts.length - 1]\n if (last && Math.abs(last.x - p.x) < 0.01 && Math.abs(last.y - p.y) < 0.01) continue\n const before = pts[pts.length - 2]\n if (\n before &&\n last &&\n ((Math.abs(before.x - last.x) < 0.01 && Math.abs(last.x - p.x) < 0.01) ||\n (Math.abs(before.y - last.y) < 0.01 && Math.abs(last.y - p.y) < 0.01))\n )\n pts.pop()\n pts.push(p)\n }\n let d = `M${fmt(pts[0]!)}`\n for (let i = 1; i < pts.length - 1; i++) {\n const [a, b, c] = [pts[i - 1]!, pts[i]!, pts[i + 1]!]\n const lenIn = Math.hypot(b.x - a.x, b.y - a.y)\n const lenOut = Math.hypot(c.x - b.x, c.y - b.y)\n const r = Math.min(radius, lenIn / 2, lenOut / 2)\n const p1 = { x: b.x - ((b.x - a.x) / lenIn) * r, y: b.y - ((b.y - a.y) / lenIn) * r }\n const p2 = { x: b.x + ((c.x - b.x) / lenOut) * r, y: b.y + ((c.y - b.y) / lenOut) * r }\n d += `L${fmt(p1)}Q${fmt(b)} ${fmt(p2)}`\n }\n return d + `L${fmt(pts[pts.length - 1]!)}`\n}\n\nconst flip = (p: Point): Point => ({ x: p.y, y: p.x })\nconst flipSide: Record<PortSide, PortSide> = {\n left: 'top',\n right: 'bottom',\n top: 'left',\n bottom: 'right',\n}\n\ninterface StepData extends FlowData {\n /** Where the middle segment sits between the two ends, from 0 to 1. */\n bend?: number\n}\n\n/**\n * Right angles with rounded corners. Between ports that face each other the\n * middle segment can be dragged; its place is kept in `edge.data.bend`.\n */\nexport const stepEdge = defineEdgeType<StepData>({\n type: 'step',\n route(source, target, edge) {\n // Work as if the ports faced left and right, then turn the result back.\n const turned = isVertical(source.side) && isVertical(target.side)\n const s = turned ? { ...flip(source), side: flipSide[source.side] } : source\n const t = turned ? { ...flip(target), side: flipSide[target.side] } : target\n const back = (p: Point) => (turned ? flip(p) : p)\n const ds = direction[s.side]\n const dt = direction[t.side]\n const s1 = { x: s.x + ds.x * STUB, y: s.y + ds.y * STUB }\n const t1 = { x: t.x + dt.x * STUB, y: t.y + dt.y * STUB }\n let points: Point[]\n let label: Point\n let handle: EdgeRoute['handle']\n\n if (ds.x && dt.x && ds.x === -dt.x && (t.x - s.x) * ds.x > 0) {\n // Facing each other: across, along, across.\n const bend = Math.min(0.95, Math.max(0.05, edge?.data?.bend ?? 0.5))\n const mid = s.x + (t.x - s.x) * bend\n points = [s, { x: mid, y: s.y }, { x: mid, y: t.y }, t]\n label = { x: mid, y: (s.y + t.y) / 2 }\n // No handle when the ends line up: the middle segment has no length.\n if (t.x !== s.x && Math.abs(t.y - s.y) >= 1) {\n const span = t.x - s.x\n const at = back(label)\n handle = {\n at,\n axis: turned ? 'y' : 'x',\n move: (point) => {\n const v = turned ? point.y : point.x\n return { bend: Math.min(0.95, Math.max(0.05, (v - s.x) / span)) }\n },\n }\n }\n } else if (ds.x && dt.x) {\n // Facing away, or the same way: out, around through the middle, in.\n const gap = Math.abs(s.y - t.y)\n const my = gap >= STUB * 2 ? (s.y + t.y) / 2 : Math.max(s.y, t.y) + STUB * 2\n const x1 = ds.x === dt.x ? (ds.x > 0 ? Math.max(s1.x, t1.x) : Math.min(s1.x, t1.x)) : s1.x\n const x2 = ds.x === dt.x ? x1 : t1.x\n points = [s, { x: x1, y: s.y }, { x: x1, y: my }, { x: x2, y: my }, { x: x2, y: t.y }, t]\n label = { x: (x1 + x2) / 2, y: my }\n } else {\n // One across, one along: a single corner when the ends allow it.\n const corner = ds.x ? { x: t1.x, y: s1.y } : { x: s1.x, y: t1.y }\n points = [s, s1, corner, t1, t]\n label = corner\n }\n return { path: rounded(points.map(back)), label: back(label), handle }\n },\n})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/core/extension.ts
|
|
2
|
+
/** Declares an extension; only gives it a type. */
|
|
3
|
+
function defineFlowExtension(extension) {
|
|
4
|
+
return extension;
|
|
5
|
+
}
|
|
6
|
+
function flatten(list) {
|
|
7
|
+
const out = [];
|
|
8
|
+
for (const item of list) if (Array.isArray(item)) out.push(...flatten(item));
|
|
9
|
+
else out.push(item);
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
/** Sorts the extensions by priority and collects what they add. */
|
|
13
|
+
function resolveFlowExtensions(list) {
|
|
14
|
+
const extensions = flatten(list).map((extension, index) => ({
|
|
15
|
+
extension,
|
|
16
|
+
index
|
|
17
|
+
})).sort((a, b) => (b.extension.priority ?? 100) - (a.extension.priority ?? 100) || a.index - b.index).map((entry) => entry.extension);
|
|
18
|
+
const names = /* @__PURE__ */ new Set();
|
|
19
|
+
const nodeTypes = /* @__PURE__ */ new Map();
|
|
20
|
+
const edgeTypes = /* @__PURE__ */ new Map();
|
|
21
|
+
const portTypes = {};
|
|
22
|
+
for (const extension of extensions) {
|
|
23
|
+
if (names.has(extension.name)) throw new Error(`[@dou-ui/flow] the extension "${extension.name}" is added twice`);
|
|
24
|
+
names.add(extension.name);
|
|
25
|
+
for (const spec of extension.nodeTypes ?? []) {
|
|
26
|
+
if (nodeTypes.has(spec.type)) throw new Error(`[@dou-ui/flow] node type "${spec.type}" from "${extension.name}" is already defined`);
|
|
27
|
+
nodeTypes.set(spec.type, spec);
|
|
28
|
+
}
|
|
29
|
+
for (const spec of extension.edgeTypes ?? []) {
|
|
30
|
+
if (edgeTypes.has(spec.type)) throw new Error(`[@dou-ui/flow] edge type "${spec.type}" from "${extension.name}" is already defined`);
|
|
31
|
+
edgeTypes.set(spec.type, spec);
|
|
32
|
+
}
|
|
33
|
+
for (const [name, spec] of Object.entries(extension.portTypes ?? {})) {
|
|
34
|
+
if (name in portTypes) throw new Error(`[@dou-ui/flow] port type "${name}" from "${extension.name}" is already defined`);
|
|
35
|
+
portTypes[name] = spec;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const pick = (key) => extensions.flatMap((e) => e[key] ? [e[key]] : []);
|
|
39
|
+
return {
|
|
40
|
+
extensions,
|
|
41
|
+
nodeTypes,
|
|
42
|
+
edgeTypes,
|
|
43
|
+
edgeType: extensions.reduce((type, e) => e.edgeType ?? type, void 0),
|
|
44
|
+
portTypes,
|
|
45
|
+
rules: pick("canConnect"),
|
|
46
|
+
keymaps: pick("keymap"),
|
|
47
|
+
pasteTransforms: pick("transformPaste"),
|
|
48
|
+
below: extensions.flatMap((e) => e.layers?.below ?? []),
|
|
49
|
+
above: extensions.flatMap((e) => e.layers?.above ?? []),
|
|
50
|
+
overlays: extensions.flatMap((e) => e.overlays ?? []),
|
|
51
|
+
menus: pick("contextMenu"),
|
|
52
|
+
presses: pick("press"),
|
|
53
|
+
toolbars: pick("toolbarItems"),
|
|
54
|
+
edgeToolbars: pick("edgeToolbarItems"),
|
|
55
|
+
drawLayers: pick("drawLayer"),
|
|
56
|
+
setups: pick("setup"),
|
|
57
|
+
transactions: pick("transaction"),
|
|
58
|
+
history: extensions.find((e) => e.history)?.history
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export { defineFlowExtension, resolveFlowExtensions };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=extension.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension.js","names":[],"sources":["../../src/core/extension.ts"],"sourcesContent":["import type { ContextMenuItem } from 'dou-ui'\nimport type { Component } from 'vue'\nimport type { FlowEditorContext } from '../context'\nimport type { FlowDrawContext } from '../export/context'\nimport type { FlowConnection, PortTypes } from './connect'\nimport type { EdgeTypeSpec } from './edge-types'\nimport type { FlowMenuTarget, FlowToolbarItem } from './menu'\nimport type { FlowDocument, FlowEdge, FlowNode, Point } from './model'\nimport type { NodeTypeSpec } from './node-types'\nimport type { FlowTransaction } from './transaction'\n\n/** What an extension's hooks receive; the same as `useFlowEditor()`. */\nexport type FlowExtensionContext = FlowEditorContext\n\n/**\n * A key handler: return `false` to let the key through to the next handler\n * and the editor's own keys; anything else means it was handled.\n */\nexport type FlowKeyHandler = (event: KeyboardEvent) => boolean | void\n\n/** An undo history that replaces the editor's own, e.g. one that only undoes this user's changes. */\nexport interface FlowHistoryHandler {\n /** `false` when there is nothing to undo. */\n undo: () => boolean\n redo: () => boolean\n canUndo: () => boolean\n canRedo: () => boolean\n}\n\n/** A press on the editor that an extension may take over, e.g. a drawing tool. */\nexport interface FlowPress {\n /** Where it was pressed, in canvas coordinates. */\n point: Point\n /** The node under the pointer, if any. */\n node: FlowNode | null\n event: PointerEvent\n}\n\n/** Follows a press an extension took over, until the pointer is let go. */\nexport interface FlowPressHandler {\n move?: (point: Point, event: PointerEvent) => void\n end?: (point: Point, event: PointerEvent) => void\n /** Escape, a second finger, or the browser took the pointer. */\n cancel?: () => void\n}\n\n/**\n * A piece of the editor: node types, port types, connection rules, keys and\n * extra layers. The default node type comes as an extension too (see\n * `flowKit`), so a third-party extension stands on equal footing with it.\n */\nexport interface FlowExtension {\n name: string\n /** Higher runs first: its keys and rules are asked before others. Defaults to 100. */\n priority?: number\n nodeTypes?: NodeTypeSpec<any>[]\n /** Edge types, used by `edge.type`; `flowKit` brings `bezier`, `step` and `straight`. */\n edgeTypes?: EdgeTypeSpec<any>[]\n /**\n * The edge type of edges without a `type`, unless the editor's `edge-type`\n * prop says otherwise. The last extension that sets it wins.\n */\n edgeType?: string\n /** Looks and connection rules of port types; see the `portTypes` prop. */\n portTypes?: PortTypes\n /**\n * A connection rule, asked after the built-in ones. Return `false` to\n * refuse, or a sentence saying why. Called for every port while dragging.\n */\n canConnect?: (connection: FlowConnection, doc: FlowDocument) => boolean | string\n /** Keys, written as in `useHotkey` (e.g. `mod+g`, `shift+t`), while the canvas has focus. */\n keymap?: (context: FlowExtensionContext) => Record<string, FlowKeyHandler>\n /** Rewrites nodes and edges before they are pasted, e.g. to reset run state. */\n transformPaste?: (data: FlowDocument, context: FlowExtensionContext) => FlowDocument\n /**\n * Components drawn in canvas coordinates: `below` under the edges, `above`\n * over the nodes. They move and zoom with the canvas.\n */\n layers?: { below?: Component[]; above?: Component[] }\n /**\n * What `layers.above` shows, for `exportSVG` and `exportPNG`: SVG markup\n * in canvas coordinates, drawn over the nodes. `nodes` are the ones in the\n * picture; `draw.width` and `draw.height` are the picture's size.\n */\n drawLayer?: (context: FlowExtensionContext, draw: FlowDrawContext, nodes: FlowNode[]) => string\n /**\n * Components drawn over the editor in screen space, e.g. a panel or a\n * popup. They can call `useFlowEditor()`.\n */\n overlays?: Component[]\n /**\n * Items for the menu opened by a right click, a long press on the empty\n * canvas, or the context menu key. Extensions' items follow each other\n * in priority order; an empty list leaves them out.\n */\n contextMenu?: (context: FlowExtensionContext, target: FlowMenuTarget) => ContextMenuItem[]\n /** Buttons for the toolbar that floats above the selected nodes. */\n toolbarItems?: (context: FlowExtensionContext, nodes: FlowNode[]) => FlowToolbarItem[]\n /** Buttons for the toolbar that floats above the selected edges, when no node is selected. */\n edgeToolbarItems?: (context: FlowExtensionContext, edges: FlowEdge[]) => FlowToolbarItem[]\n /**\n * Asked first when the canvas or a node is pressed with the main button or\n * one finger, in priority order. Return handlers to take the press over;\n * the editor then doesn't select, drag or pan for it.\n */\n press?: (context: FlowExtensionContext, press: FlowPress) => FlowPressHandler | void\n /** Runs once the editor is mounted. Return a function to run when it unmounts. */\n setup?: (context: FlowExtensionContext) => void | (() => void)\n /**\n * Sees every change before it reaches the document, in priority order,\n * and may add steps to it. Undo and redo come through here too.\n */\n transaction?: (tr: FlowTransaction, context: FlowExtensionContext) => void\n /** Replaces the editor's undo history; the first extension that has one wins. */\n history?: (context: FlowExtensionContext) => FlowHistoryHandler\n}\n\n/** Declares an extension; only gives it a type. */\nexport function defineFlowExtension(extension: FlowExtension): FlowExtension {\n return extension\n}\n\n/** Everything the extensions contribute, gathered once per list. */\nexport interface ResolvedFlowExtensions {\n extensions: FlowExtension[]\n nodeTypes: Map<string, NodeTypeSpec>\n edgeTypes: Map<string, EdgeTypeSpec>\n edgeType: string | undefined\n portTypes: PortTypes\n rules: NonNullable<FlowExtension['canConnect']>[]\n keymaps: NonNullable<FlowExtension['keymap']>[]\n pasteTransforms: NonNullable<FlowExtension['transformPaste']>[]\n below: Component[]\n above: Component[]\n overlays: Component[]\n menus: NonNullable<FlowExtension['contextMenu']>[]\n toolbars: NonNullable<FlowExtension['toolbarItems']>[]\n edgeToolbars: NonNullable<FlowExtension['edgeToolbarItems']>[]\n drawLayers: NonNullable<FlowExtension['drawLayer']>[]\n setups: NonNullable<FlowExtension['setup']>[]\n transactions: NonNullable<FlowExtension['transaction']>[]\n history: FlowExtension['history']\n presses: NonNullable<FlowExtension['press']>[]\n}\n\ntype ExtensionList = readonly (FlowExtension | readonly FlowExtension[])[]\n\nfunction flatten(list: ExtensionList): FlowExtension[] {\n const out: FlowExtension[] = []\n for (const item of list) {\n if (Array.isArray(item)) out.push(...flatten(item))\n else out.push(item as FlowExtension)\n }\n return out\n}\n\n/** Sorts the extensions by priority and collects what they add. */\nexport function resolveFlowExtensions(list: ExtensionList): ResolvedFlowExtensions {\n const extensions = flatten(list)\n .map((extension, index) => ({ extension, index }))\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a fresh array\n .sort(\n (a, b) => (b.extension.priority ?? 100) - (a.extension.priority ?? 100) || a.index - b.index,\n )\n .map((entry) => entry.extension)\n\n const names = new Set<string>()\n const nodeTypes = new Map<string, NodeTypeSpec>()\n const edgeTypes = new Map<string, EdgeTypeSpec>()\n const portTypes: PortTypes = {}\n for (const extension of extensions) {\n if (names.has(extension.name))\n throw new Error(`[@dou-ui/flow] the extension \"${extension.name}\" is added twice`)\n names.add(extension.name)\n for (const spec of extension.nodeTypes ?? []) {\n if (nodeTypes.has(spec.type))\n throw new Error(\n `[@dou-ui/flow] node type \"${spec.type}\" from \"${extension.name}\" is already defined`,\n )\n nodeTypes.set(spec.type, spec)\n }\n for (const spec of extension.edgeTypes ?? []) {\n if (edgeTypes.has(spec.type))\n throw new Error(\n `[@dou-ui/flow] edge type \"${spec.type}\" from \"${extension.name}\" is already defined`,\n )\n edgeTypes.set(spec.type, spec)\n }\n for (const [name, spec] of Object.entries(extension.portTypes ?? {})) {\n if (name in portTypes)\n throw new Error(\n `[@dou-ui/flow] port type \"${name}\" from \"${extension.name}\" is already defined`,\n )\n portTypes[name] = spec\n }\n }\n\n const pick = <K extends keyof FlowExtension>(key: K) =>\n extensions.flatMap((e) => (e[key] ? [e[key]] : [])) as NonNullable<FlowExtension[K]>[]\n\n return {\n extensions,\n nodeTypes,\n edgeTypes,\n edgeType: extensions.reduce<string | undefined>((type, e) => e.edgeType ?? type, undefined),\n portTypes,\n rules: pick('canConnect'),\n keymaps: pick('keymap'),\n pasteTransforms: pick('transformPaste'),\n below: extensions.flatMap((e) => e.layers?.below ?? []),\n above: extensions.flatMap((e) => e.layers?.above ?? []),\n overlays: extensions.flatMap((e) => e.overlays ?? []),\n menus: pick('contextMenu'),\n presses: pick('press'),\n toolbars: pick('toolbarItems'),\n edgeToolbars: pick('edgeToolbarItems'),\n drawLayers: pick('drawLayer'),\n setups: pick('setup'),\n transactions: pick('transaction'),\n history: extensions.find((e) => e.history)?.history,\n }\n}\n"],"mappings":";;AAsHA,SAAgB,oBAAoB,WAAyC;CAC3E,OAAO;AACT;AA2BA,SAAS,QAAQ,MAAsC;CACrD,MAAM,MAAuB,CAAC;CAC9B,KAAK,MAAM,QAAQ,MACjB,IAAI,MAAM,QAAQ,IAAI,GAAG,IAAI,KAAK,GAAG,QAAQ,IAAI,CAAC;MAC7C,IAAI,KAAK,IAAqB;CAErC,OAAO;AACT;;AAGA,SAAgB,sBAAsB,MAA6C;CACjF,MAAM,aAAa,QAAQ,IAAI,CAAC,CAC7B,KAAK,WAAW,WAAW;EAAE;EAAW;CAAM,EAAE,CAAC,CAEjD,MACE,GAAG,OAAO,EAAE,UAAU,YAAY,QAAQ,EAAE,UAAU,YAAY,QAAQ,EAAE,QAAQ,EAAE,KACzF,CAAC,CACA,KAAK,UAAU,MAAM,SAAS;CAEjC,MAAM,wBAAQ,IAAI,IAAY;CAC9B,MAAM,4BAAY,IAAI,IAA0B;CAChD,MAAM,4BAAY,IAAI,IAA0B;CAChD,MAAM,YAAuB,CAAC;CAC9B,KAAK,MAAM,aAAa,YAAY;EAClC,IAAI,MAAM,IAAI,UAAU,IAAI,GAC1B,MAAM,IAAI,MAAM,iCAAiC,UAAU,KAAK,iBAAiB;EACnF,MAAM,IAAI,UAAU,IAAI;EACxB,KAAK,MAAM,QAAQ,UAAU,aAAa,CAAC,GAAG;GAC5C,IAAI,UAAU,IAAI,KAAK,IAAI,GACzB,MAAM,IAAI,MACR,6BAA6B,KAAK,KAAK,UAAU,UAAU,KAAK,qBAClE;GACF,UAAU,IAAI,KAAK,MAAM,IAAI;EAC/B;EACA,KAAK,MAAM,QAAQ,UAAU,aAAa,CAAC,GAAG;GAC5C,IAAI,UAAU,IAAI,KAAK,IAAI,GACzB,MAAM,IAAI,MACR,6BAA6B,KAAK,KAAK,UAAU,UAAU,KAAK,qBAClE;GACF,UAAU,IAAI,KAAK,MAAM,IAAI;EAC/B;EACA,KAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,UAAU,aAAa,CAAC,CAAC,GAAG;GACpE,IAAI,QAAQ,WACV,MAAM,IAAI,MACR,6BAA6B,KAAK,UAAU,UAAU,KAAK,qBAC7D;GACF,UAAU,QAAQ;EACpB;CACF;CAEA,MAAM,QAAuC,QAC3C,WAAW,SAAS,MAAO,EAAE,OAAO,CAAC,EAAE,IAAI,IAAI,CAAC,CAAE;CAEpD,OAAO;EACL;EACA;EACA;EACA,UAAU,WAAW,QAA4B,MAAM,MAAM,EAAE,YAAY,MAAM,KAAA,CAAS;EAC1F;EACA,OAAO,KAAK,YAAY;EACxB,SAAS,KAAK,QAAQ;EACtB,iBAAiB,KAAK,gBAAgB;EACtC,OAAO,WAAW,SAAS,MAAM,EAAE,QAAQ,SAAS,CAAC,CAAC;EACtD,OAAO,WAAW,SAAS,MAAM,EAAE,QAAQ,SAAS,CAAC,CAAC;EACtD,UAAU,WAAW,SAAS,MAAM,EAAE,YAAY,CAAC,CAAC;EACpD,OAAO,KAAK,aAAa;EACzB,SAAS,KAAK,OAAO;EACrB,UAAU,KAAK,cAAc;EAC7B,cAAc,KAAK,kBAAkB;EACrC,YAAY,KAAK,WAAW;EAC5B,QAAQ,KAAK,OAAO;EACpB,cAAc,KAAK,aAAa;EAChC,SAAS,WAAW,MAAM,MAAM,EAAE,OAAO,CAAC,EAAE;CAC9C;AACF"}
|