@conduction/nextcloud-vue 2.72.0 → 2.73.0
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/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js +25 -0
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js +3 -1
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue2.js +36 -1
- package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue2.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue3.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js +37 -0
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js +4 -2
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +105 -4
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/package.json +1 -1
- package/scripts/openregister-reliance.js +206 -0
- package/src/components/CnFlowDetail/CnFlowDetail.vue +27 -0
- package/src/components/CnGraphCanvas/CnFlowNode.vue +36 -1
- package/src/components/CnGraphCanvas/CnGraphCanvas.vue +40 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFlowNode.vue2.js","sources":["../../../../src/components/CnGraphCanvas/CnFlowNode.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n - SPDX-License-Identifier: EUPL-1.2\n -->\n<template>\n\t<!-- THE NODE IS `.cn-flow-node`. There is deliberately no alias.\n\n\t Up to 2.11.1 the canvas was a bespoke SVG implementation in\n\t CnGraphCanvas.vue and a node was `.cn-graph-canvas__node`. 2.15.0\n\t rewrote it on Vue Flow and moved the node into this component.\n\n\t 2.15.0 briefly carried `cn-graph-canvas__node` alongside the new name\n\t so the rewrite would be a drop-in (see #752). That alias is gone: the\n\t migration to Vue Flow is a real migration, not a rename with the old\n\t contract left running underneath. An alias kept every consumer's\n\t selector working while quietly guaranteeing that two names for one\n\t element would drift — which is the situation that produced the\n\t original break.\n\n\t Consumers select `.cn-flow-node` (and `--selected`, `--arming`,\n\t `__handle`, `__body`). Migrated with this change: openregister's\n\t flow-controls e2e and hermiq's FlowBuilder deep styles. -->\n\t<div\n\t\tclass=\"cn-flow-node\"\n\t\t:class=\"{\n\t\t\t'cn-flow-node--selected': selected,\n\t\t\t'cn-flow-node--arming': armedPortIndex !== null,\n\t\t}\"\n\t\ttabindex=\"0\"\n\t\trole=\"button\"\n\t\t:aria-label=\"ariaLabel\"\n\t\t:aria-pressed=\"selected ? 'true' : 'false'\"\n\t\t@keydown=\"onKeydown\">\n\t\t<!-- Resize handles, when the host allows it. `@vue-flow/node-resizer`\n\t\t draws the pointer affordance; the keyboard path is `r` + arrows in\n\t\t onKeydown below, because the resizer itself is pointer-only and the\n\t\t canvas this replaces could be resized from the keyboard. -->\n\t\t<NodeResizer v-if=\"resizable\" :minWidth=\"80\" :minHeight=\"40\" />\n\n\t\t<!-- WHERE A LINE ENTERS AND WHERE IT LEAVES, SAID BY THE PORT ITSELF.\n\n\t\t Every node used to draw one entry on top and one exit underneath,\n\t\t which contradicted the layout the editor actually produces:\n\t\t `useFlowStore.autoSort()` lays a flow out LEFT TO RIGHT, one column\n\t\t per depth. So the primary entry is on the LEFT and the primary exit\n\t\t on the RIGHT — Vue Flow attaches a handle-less edge to the first\n\t\t handle of each type, so DOM order here is what decides the default\n\t\t shape of every line on the canvas. Top and bottom stay as secondary\n\t\t attachment points for a graph the author routes by hand.\n\n\t\t A trigger has no entry and an end step has no exit: a port that can\n\t\t never be connected is not chrome, it is a lie about what the engine\n\t\t will do. -->\n\t\t<Handle\n\t\t\tv-for=\"entry in entryHandles\"\n\t\t\t:id=\"entry.id\"\n\t\t\t:key=\"entry.id\"\n\t\t\ttype=\"target\"\n\t\t\t:position=\"entry.position\"\n\t\t\tclass=\"cn-flow-node__handle cn-flow-node__handle--target\"\n\t\t\t:class=\"[`cn-flow-node__handle--dir-${entry.direction}`, { 'cn-flow-node__handle--orphan': orphanEntry }]\"\n\t\t\t:aria-label=\"entryLabel\"\n\t\t\t:title=\"orphanEntry ? orphanEntryHint : undefined\" />\n\n\t\t<div class=\"cn-flow-node__body\">\n\t\t\t<slot :node=\"{ id, data, selected }\">\n\t\t\t\t<span class=\"cn-flow-node__label\">{{ label }}</span>\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- One out-port per exit. A routing node has several, and EVERY one of\n\t\t them has to be reachable without a mouse — see onKeydown.\n\n\t\t ⚠️ A HANDLE ID IS NOT A PORT ID. Two handles can serve one exit (a\n\t\t single-exit step offers both the right and the bottom edge), and Vue\n\t\t Flow keys handles by id, so they cannot share one. The side is\n\t\t therefore encoded into the handle id and stripped straight back off\n\t\t before a connection leaves this component — see `portIdOf`. The host\n\t\t only ever sees the port it declared. -->\n\t\t<Handle\n\t\t\tv-for=\"exit in exitHandles\"\n\t\t\t:id=\"exit.id\"\n\t\t\t:key=\"exit.id\"\n\t\t\ttype=\"source\"\n\t\t\t:position=\"exit.position\"\n\t\t\tclass=\"cn-flow-node__handle cn-flow-node__handle--source\"\n\t\t\t:class=\"[\n\t\t\t\t`cn-flow-node__handle--dir-${exit.direction}`,\n\t\t\t\t{\n\t\t\t\t\t'cn-flow-node__handle--armed': exit.primary && armedPortIndex === exit.index,\n\t\t\t\t\t'cn-flow-node__handle--orphan': orphanExit,\n\t\t\t\t},\n\t\t\t]\"\n\t\t\t:style=\"exit.style\"\n\t\t\t:aria-label=\"portLabel(exit.port)\"\n\t\t\t:title=\"orphanExit ? orphanExitHint : undefined\"\n\t\t\t:aria-pressed=\"exit.primary && armedPortIndex === exit.index ? 'true' : undefined\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { Handle, Position, useVueFlow } from '@vue-flow/core'\nimport { NodeResizer } from '@vue-flow/node-resizer'\n\n/**\n * What separates a port id from the side of the node its handle sits on.\n *\n * `__` rather than `-` or `.` because a port id is authored data — `out-2` and\n * `openregister.no` are both ordinary ids — and a separator that can occur\n * inside the thing it separates is not a separator. Read with `lastIndexOf`, so\n * even a port id that does contain `__` survives the round trip.\n *\n * ⚠️ AND IT HAS TO BE CSS-SAFE. Vue Flow writes a handle's id straight into a\n * class name (`vue-flow__handle-yes__right`), so a separator of `::` — the first\n * choice here — produced `vue-flow__handle-in::top`, a class that reads as a\n * pseudo-element the moment anyone tries to select it. Nothing targets these\n * classes today; the point is that the next person to try would find a selector\n * that silently matches nothing.\n *\n * Written in exactly one place — `exitHandles` — and read in exactly one place:\n * `portIdOf`.\n */\nconst HANDLE_SIDE_SEPARATOR = '__'\n\n/**\n * The default node component, and the home of the canvas's keyboard contract.\n *\n * WHY THE KEYBOARD CODE LIVES HERE AND NOT IN THE CANVAS\n * ------------------------------------------------------\n * Vue Flow renders nodes through a component the host supplies, and that\n * component owns its own DOM. Focus, ARIA state and key handling therefore have\n * to live in the node, not in the canvas wrapper — the canvas never sees the\n * element that a user tabs to.\n *\n * ⚠️ THIS IS A CARRY-OVER, NOT A NEW FEATURE. The hand-rolled canvas this\n * replaces was keyboard-operable by deliberate design, and its docblock said\n * why: \"A drag-only canvas is not keyboard-operable (WCAG 2.1 AA 2.1.1).\"\n * Vue Flow is pointer-first, so adopting it without re-implementing this would\n * have been a straight accessibility regression dressed up as a dependency\n * upgrade.\n *\n * THE MULTI-EXIT CASE IS THE ONE THAT MATTERS\n * -------------------------------------------\n * Where a node has several exits — a routing node draws one out-port per branch\n * — pressing `c` again steps through them, and the armed port is ringed and\n * marked `aria-pressed`. A mouse picks a branch by pointing at it. Without this\n * the keyboard could only ever reach the first, so every other branch would be\n * mouse-only. That is not a rough edge; it is the feature the canvas exists for\n * being unavailable to half its users.\n */\nexport default {\n\tname: 'CnFlowNode',\n\n\tcomponents: { Handle, NodeResizer },\n\n\t// Vue Flow passes a node's whole internal record down — `zindex`, `events`,\n\t// `dimensions`, `position` and more. Without this they fall through onto the\n\t// root element as DOM attributes (`events=\"[object Object]\"`), which is\n\t// invalid markup, noise in every snapshot, and a needless surface for a\n\t// screen reader to read out. The bindings this component actually uses are\n\t// declared as props below.\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/** Vue Flow's node id. */\n\t\tid: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Vue Flow's per-node data bag. */\n\t\tdata: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether Vue Flow considers this node selected. */\n\t\tselected: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** How far one arrow-key press moves the node, in canvas units. */\n\t\tstep: {\n\t\t\ttype: Number,\n\t\t\tdefault: 8,\n\t\t},\n\n\t\t/** How far a Shift+arrow press moves it. */\n\t\tcoarseStep: {\n\t\t\ttype: Number,\n\t\t\tdefault: 40,\n\t\t},\n\n\t\t/** Whether this node may be resized. */\n\t\tresizable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether Delete/Backspace on this node emits `remove`. Defaults to\n\t\t * true; a read-only canvas passes false so the key does nothing.\n\t\t */\n\t\tdeletable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * @event connect A keyboard-completed connection. Carries Vue Flow's\n\t\t * `{ source, sourceHandle, target, targetHandle }` so the host cannot\n\t\t * tell a keyboard connection from a pointer one. CnGraphCanvas routes\n\t\t * it into the same `onConnect` the <VueFlow> pointer path uses.\n\t\t */\n\t\t'connect',\n\n\t\t/**\n\t\t * @event remove The focused node should be removed, by id. Emitted for\n\t\t * Delete and Backspace. The host decides what removal means — its\n\t\t * edges, its undo stack, its persistence — so nothing is removed here.\n\t\t */\n\t\t'remove',\n\t],\n\n\tsetup() {\n\t\t// `connectionStartNode` is module-level state shared by every node\n\t\t// instance, because a keyboard connection spans TWO nodes: the one that\n\t\t// started it and the one that completes it. Vue Flow's own connection\n\t\t// state is pointer-driven and does not cover this path.\n\t\t// `addEdges` is deliberately NOT pulled in. Writing an edge into Vue\n\t\t// Flow's internal graph is what silently dropped keyboard connections:\n\t\t// the host owns `edges` and this component must tell it, not mutate\n\t\t// around it. See onConnectKey().\n\t\tconst { updateNodePositions, findNode } = useVueFlow()\n\n\t\treturn { updateNodePositions, findNode, Position }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Which out-port is armed for a keyboard connection, or null. */\n\t\t\tarmedPortIndex: null,\n\t\t\t/** While true, arrow keys RESIZE instead of moving. */\n\t\t\tresizing: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {string} The node's visible label.\n\t\t */\n\t\tlabel() {\n\t\t\treturn this.data?.label || this.data?.name || this.id\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether this node accepts inbound connections. A\n\t\t * trigger does not: a run STARTS there, so an entry port on one is an\n\t\t * affordance the engine will never honour.\n\t\t */\n\t\thasTarget() {\n\t\t\treturn this.data?.hasTarget !== false\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether this node offers outbound connections. An\n\t\t * end step does not — the flow stops there by definition.\n\t\t */\n\t\thasSource() {\n\t\t\treturn this.data?.hasSource !== false\n\t\t},\n\n\t\t/**\n\t\t * The entry ports, left first.\n\t\t *\n\t\t * LEFT IS PRIMARY, and that is a deliberate consequence of\n\t\t * `useFlowStore.autoSort()` laying a flow out left to right: Vue Flow\n\t\t * binds an edge that names no handle to the FIRST handle of its type, so\n\t\t * this order is what a line does by default.\n\t\t *\n\t\t * @return {Array<object>} The handles to draw.\n\t\t */\n\t\tentryHandles() {\n\t\t\tif (this.hasTarget === false) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\t{ id: `in${HANDLE_SIDE_SEPARATOR}left`, position: this.Position.Left, direction: 'right' },\n\t\t\t\t{ id: `in${HANDLE_SIDE_SEPARATOR}top`, position: this.Position.Top, direction: 'down' },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The exit ports.\n\t\t *\n\t\t * The first exit sits on the RIGHT, where the next column is. Any\n\t\t * further exit — a routing step's branches — spreads along the bottom\n\t\t * edge, which is the one side with room for several and the only\n\t\t * arrangement that keeps a three-way branch readable.\n\t\t *\n\t\t * A step with a SINGLE exit gets a bottom handle as well, so a line may\n\t\t * leave downwards without the author fighting the router. Both handles\n\t\t * serve the same port; only the right-hand one is `primary`, so the\n\t\t * armed-port ring and `aria-pressed` land on exactly one element per\n\t\t * exit rather than two.\n\t\t *\n\t\t * @return {Array<object>} The handles to draw.\n\t\t */\n\t\texitHandles() {\n\t\t\tif (this.hasSource === false) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\tconst ports = this.ports\n\t\t\tconst branches = ports.length - 1\n\t\t\tconst handles = []\n\n\t\t\tports.forEach((port, index) => {\n\t\t\t\tif (index === 0) {\n\t\t\t\t\thandles.push({\n\t\t\t\t\t\tid: `${port.id}${HANDLE_SIDE_SEPARATOR}right`,\n\t\t\t\t\t\tport,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\tposition: this.Position.Right,\n\t\t\t\t\t\tdirection: 'right',\n\t\t\t\t\t\tprimary: true,\n\t\t\t\t\t\tstyle: {},\n\t\t\t\t\t})\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\thandles.push({\n\t\t\t\t\tid: `${port.id}${HANDLE_SIDE_SEPARATOR}bottom`,\n\t\t\t\t\tport,\n\t\t\t\t\tindex,\n\t\t\t\t\tposition: this.Position.Bottom,\n\t\t\t\t\tdirection: 'down',\n\t\t\t\t\tprimary: true,\n\t\t\t\t\tstyle: { left: `${(index / (branches + 1)) * 100}%` },\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tif (ports.length === 1) {\n\t\t\t\thandles.push({\n\t\t\t\t\tid: `${ports[0].id}${HANDLE_SIDE_SEPARATOR}bottom`,\n\t\t\t\t\tport: ports[0],\n\t\t\t\t\tindex: 0,\n\t\t\t\t\tposition: this.Position.Bottom,\n\t\t\t\t\tdirection: 'down',\n\t\t\t\t\tprimary: false,\n\t\t\t\t\tstyle: {},\n\t\t\t\t})\n\t\t\t}\n\n\t\t\treturn handles\n\t\t},\n\n\t\t/**\n\t\t * Whether this node draws an entry that nothing connects to.\n\t\t *\n\t\t * ⚠️ `=== false`, NOT falsy. A host that does not compute this at all\n\t\t * leaves it undefined, and undefined means \"not measured\" — flagging\n\t\t * every port on such a canvas would put a warning on a graph nobody\n\t\t * claimed anything about.\n\t\t *\n\t\t * @return {boolean} True when the port is drawn and unreachable.\n\t\t */\n\t\torphanEntry() {\n\t\t\treturn this.hasTarget === true && this.data?.hasIncoming === false\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} True when this node draws an exit that no line\n\t\t * leaves from. Same \"not measured\" rule as `orphanEntry`.\n\t\t */\n\t\torphanExit() {\n\t\t\treturn this.hasSource === true && this.data?.hasOutgoing === false\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The accessible name of an entry port.\n\t\t */\n\t\tentryLabel() {\n\t\t\tif (this.orphanEntry === true) {\n\t\t\t\treturn `${this.label}: ${this.orphanEntryHint}`\n\t\t\t}\n\n\t\t\treturn t('nextcloud-vue', '{step}: entry', { step: this.label })\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Why an unconnected entry port is a problem, in the\n\t\t * terms the author cares about — what the ENGINE will do.\n\t\t */\n\t\torphanEntryHint() {\n\t\t\treturn t('nextcloud-vue', 'Nothing connects to this step, so the flow will never reach it.')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Why an unconnected exit port is a problem.\n\t\t */\n\t\torphanExitHint() {\n\t\t\treturn t('nextcloud-vue', 'Nothing leaves this step, so the flow stops here.')\n\t\t},\n\n\t\t/**\n\t\t * The node's exits. A node with none still gets one default port, so an\n\t\t * ordinary step behaves exactly as it did before.\n\t\t *\n\t\t * @return {Array<object>} The out-ports.\n\t\t */\n\t\tports() {\n\t\t\tconst declared = this.data?.ports\n\t\t\tif (Array.isArray(declared) === true && declared.length > 0) {\n\t\t\t\treturn declared.map((port, index) => ({\n\t\t\t\t\tid: String(port.id ?? `out-${index}`),\n\t\t\t\t\tlabel: port.label ?? port.id ?? `Exit ${index + 1}`,\n\t\t\t\t}))\n\t\t\t}\n\n\t\t\treturn [{ id: 'out', label: 'Exit' }]\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The label a screen reader announces for the node.\n\t\t */\n\t\tariaLabel() {\n\t\t\tconst kind = this.data?.type ? ` (${this.data.type})` : ''\n\t\t\treturn `${this.label}${kind}`\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Recover the PORT id a handle id was built from.\n\t\t *\n\t\t * The inverse of the encoding in `exitHandles`, and the reason a\n\t\t * node can offer one exit on two sides without the host ever hearing\n\t\t * about a port it did not declare. Exported through the component so\n\t\t * CnGraphCanvas can apply it to Vue Flow's pointer connections too —\n\t\t * both routes have to end at the same port id or a branch would be\n\t\t * recorded under a name the engine has never seen.\n\t\t *\n\t\t * @param {string|null} handleId The handle id, or null.\n\t\t * @return {string|null} The port id.\n\t\t */\n\t\tportIdOf(handleId) {\n\t\t\tif (typeof handleId !== 'string') {\n\t\t\t\treturn handleId ?? null\n\t\t\t}\n\n\t\t\tconst cut = handleId.lastIndexOf(HANDLE_SIDE_SEPARATOR)\n\n\t\t\treturn cut === -1 ? handleId : handleId.slice(0, cut)\n\t\t},\n\n\t\t/**\n\t\t * @param {object} port The port.\n\t\t * @return {string} Its accessible label. An unconnected exit says so\n\t\t * rather than merely being painted differently — colour alone is not\n\t\t * a state a screen reader can read.\n\t\t */\n\t\tportLabel(port) {\n\t\t\tif (this.orphanExit === true) {\n\t\t\t\treturn `${this.label}: ${this.orphanExitHint}`\n\t\t\t}\n\n\t\t\treturn `${this.label}: ${port.label}`\n\t\t},\n\n\t\t/**\n\t\t * The keyboard contract.\n\t\t *\n\t\t * Arrow keys move; `c` arms an exit and, on a second node, completes the\n\t\t * connection; `Escape` cancels. Repeated `c` on the SAME node steps\n\t\t * through its exits rather than re-arming the first — the behaviour a\n\t\t * mouse gets for free by pointing.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key event.\n\t\t * @return {void}\n\t\t */\n\t\tonKeydown(event) {\n\t\t\tconst distance = event.shiftKey === true ? this.coarseStep : this.step\n\t\t\tconst moves = {\n\t\t\t\tArrowUp: { x: 0, y: -distance },\n\t\t\t\tArrowDown: { x: 0, y: distance },\n\t\t\t\tArrowLeft: { x: -distance, y: 0 },\n\t\t\t\tArrowRight: { x: distance, y: 0 },\n\t\t\t}\n\n\t\t\tif (moves[event.key] !== undefined) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tif (this.resizing === true) {\n\t\t\t\t\tthis.resize(moves[event.key])\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tthis.move(moves[event.key])\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// `r` toggles resize mode, so the SAME arrow keys serve both without\n\t\t\t// a second key family to learn. The pointer affordance is\n\t\t\t// NodeResizer's; this is the keyboard equivalent, which the canvas\n\t\t\t// this replaces also had and which the resizer does not provide.\n\t\t\tif ((event.key === 'r' || event.key === 'R') && this.resizable === true) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.resizing = this.resizing === false\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'Escape') {\n\t\t\t\tthis.resizing = false\n\t\t\t\tthis.cancelConnection()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'c' || event.key === 'C') {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.onConnectKey()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Delete and Backspace both, because which one removes a thing is a\n\t\t\t// platform habit rather than a preference: Backspace is the delete\n\t\t\t// key on a Mac keyboard, and a user who learned one does not think\n\t\t\t// of the other as an alternative.\n\t\t\t//\n\t\t\t// EMITTED, never acted on here. This component knows about Vue Flow\n\t\t\t// nodes; it does not know that removing one also has to drop the\n\t\t\t// edges pointing at it, or that the host keeps an undo stack. A node\n\t\t\t// that deleted itself would leave dangling edges behind — which is\n\t\t\t// the bug `useFlowStore.removeNode` exists to prevent.\n\t\t\tif (event.key === 'Delete' || event.key === 'Backspace') {\n\t\t\t\tif (this.deletable === false) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tevent.preventDefault()\n\t\t\t\t/**\n\t\t\t\t * @event remove The focused node should be removed. Carries the\n\t\t\t\t * node id. The host owns what removal means — edges, undo,\n\t\t\t\t * persistence — so nothing is removed here.\n\t\t\t\t * @type {string}\n\t\t\t\t */\n\t\t\t\tthis.$emit('remove', this.id)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Move this node, through Vue Flow, so the change reaches the host as a\n\t\t * normal node change rather than a direct mutation.\n\t\t *\n\t\t * @param {object} delta The x/y delta.\n\t\t * @return {void}\n\t\t */\n\t\tmove(delta) {\n\t\t\tconst node = this.findNode(this.id)\n\t\t\tif (node === undefined || node === null) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.updateNodePositions(\n\t\t\t\t[{ id: this.id, position: { x: node.position.x + delta.x, y: node.position.y + delta.y } }],\n\t\t\t\ttrue,\n\t\t\t\tfalse,\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Resize this node from the keyboard.\n\t\t *\n\t\t * @param {object} delta The width/height delta.\n\t\t * @return {void}\n\t\t */\n\t\tresize(delta) {\n\t\t\tconst node = this.findNode(this.id)\n\t\t\tif (node === undefined || node === null) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst width = Math.max(80, (node.dimensions?.width || 140) + delta.x)\n\t\t\tconst height = Math.max(40, (node.dimensions?.height || 60) + delta.y)\n\t\t\tnode.style = { ...(node.style || {}), width: `${width}px`, height: `${height}px` }\n\t\t},\n\n\t\t/**\n\t\t * `c` pressed: arm the next exit, or complete a pending connection.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonConnectKey() {\n\t\t\tconst pending = this.$root?.$cnFlowPendingConnection ?? null\n\n\t\t\t// A connection is already in flight and it did not start here, so\n\t\t\t// this press completes it.\n\t\t\tif (pending !== null && pending.nodeId !== this.id) {\n\t\t\t\t// EMIT, DO NOT `addEdges()`.\n\t\t\t\t//\n\t\t\t\t// `addEdges()` writes into Vue Flow's INTERNAL graph, and this\n\t\t\t\t// canvas does not own that graph: CnGraphCanvas binds\n\t\t\t\t// `:edges=\"edges\"` from its prop, which CnFlowDetail fills from\n\t\t\t\t// `store.canvasEdges`. The edges are controlled by the host.\n\t\t\t\t//\n\t\t\t\t// So `addEdges()` drew the edge and told nobody. The host's model\n\t\t\t\t// never learned about it, the save serialised a flow with no\n\t\t\t\t// edge, and the server refused to run it — \"node has no outgoing\n\t\t\t\t// edge and does not end the flow\", naming a node rather than the\n\t\t\t\t// connection that was missing. The canvas and the model\n\t\t\t\t// disagreed, and the canvas is the one the user believes.\n\t\t\t\t//\n\t\t\t\t// Nothing caught it because the READ path was mapped and the\n\t\t\t\t// WRITE path was never written: a one-way adapter is invisible\n\t\t\t\t// until something writes through it.\n\t\t\t\t//\n\t\t\t\t// CnGraphCanvas already forwards pointer connections —\n\t\t\t\t// `@connect=\"onConnect\"` on <VueFlow>, re-emitted to the host —\n\t\t\t\t// and its own docblock states the contract this restores:\n\t\t\t\t// \"@event connect A new connection was made, by pointer OR by\n\t\t\t\t// keyboard.\" Emitting here puts the keyboard on that same path,\n\t\t\t\t// so both routes end at the host's model and the host remains the\n\t\t\t\t// single source of truth for edges.\n\t\t\t\t//\n\t\t\t\t// Vue Flow's connection shape, so the host cannot tell which\n\t\t\t\t// input produced it.\n\t\t\t\tthis.$emit('connect', {\n\t\t\t\t\tsource: pending.nodeId,\n\t\t\t\t\tsourceHandle: pending.portId,\n\t\t\t\t\ttarget: this.id,\n\t\t\t\t\ttargetHandle: null,\n\t\t\t\t})\n\t\t\t\tthis.clearPending()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Otherwise step through this node's own exits. Running off the end\n\t\t\t// cancels, which is what a single-exit node does on the second press\n\t\t\t// — unchanged from the previous canvas.\n\t\t\tconst next = this.armedPortIndex === null ? 0 : this.armedPortIndex + 1\n\t\t\tif (next >= this.ports.length) {\n\t\t\t\tthis.cancelConnection()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.armedPortIndex = next\n\t\t\tthis.setPending({ nodeId: this.id, portId: this.ports[next].id })\n\t\t},\n\n\t\t/**\n\t\t * @param {object} pending The pending connection.\n\t\t * @return {void}\n\t\t */\n\t\tsetPending(pending) {\n\t\t\tif (this.$root !== undefined && this.$root !== null) {\n\t\t\t\tthis.$root.$cnFlowPendingConnection = pending\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @return {void}\n\t\t */\n\t\tclearPending() {\n\t\t\tthis.armedPortIndex = null\n\t\t\tif (this.$root !== undefined && this.$root !== null) {\n\t\t\t\tthis.$root.$cnFlowPendingConnection = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @return {void}\n\t\t */\n\t\tcancelConnection() {\n\t\t\tthis.clearPending()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-node {\n\tposition: relative;\n\tmin-width: 140px;\n\tpadding: 8px 12px;\n\tborder: 2px solid var(--color-border, #d0d0d0);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-main-background, #fff);\n\tcolor: var(--color-main-text, #222);\n}\n\n.cn-flow-node:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 2px;\n}\n\n.cn-flow-node--selected {\n\tborder-color: var(--color-primary-element, #0082c9);\n}\n\n.cn-flow-node__label {\n\tfont-weight: 600;\n}\n\n.cn-flow-node__handle {\n\t/* 14px, not 10: a port now carries a direction arrow, and there is no room\n\t to draw one legibly in 10. Sized against the 22px arrowhead an edge ends\n\t in (see `useFlowStore.canvasEdges`) — the marker still has to clear the\n\t handle it lands on. */\n\twidth: 14px;\n\theight: 14px;\n\tbackground: var(--color-primary-element, #0082c9);\n}\n\n/* AN ARROW IN THE PORT, because a dot cannot say which way it faces.\n\n A canvas with four ports on a node is only readable if each one states\n whether it takes a line in or lets one out, and the port is the only place\n that can say it — a user reading the node has not yet followed the line.\n Every arrow points WITH the flow: an entry on the left and an exit on the\n right both point right, an entry on top and an exit on the bottom both point\n down. So the whole node reads as one direction rather than as four separate\n claims.\n\n Drawn with borders rather than an icon component: a port is 14px, it is\n rendered once per handle per node, and a triangle at that size is crisper\n from a border than from a scaled glyph. */\n.cn-flow-node__handle::after {\n\tcontent: '';\n\tposition: absolute;\n\tinset-block-start: 50%;\n\tinset-inline-start: 50%;\n\tinline-size: 0;\n\tblock-size: 0;\n\tborder: 3px solid transparent;\n\tpointer-events: none;\n}\n\n.cn-flow-node__handle--dir-right::after {\n\tborder-inline-start-color: var(--color-primary-element-text, #fff);\n\ttransform: translate(-30%, -50%);\n}\n\n.cn-flow-node__handle--dir-down::after {\n\tborder-block-start-color: var(--color-primary-element-text, #fff);\n\ttransform: translate(-50%, -30%);\n}\n\n/* The armed port is RINGED, not merely coloured: colour alone is not an\n accessible state indicator, and this is the cue that tells a keyboard user\n which branch the next `c` will connect. */\n.cn-flow-node__handle--armed {\n\toutline: 3px solid var(--color-warning, #c28900);\n\toutline-offset: 2px;\n}\n\n/* A PORT NOTHING IS CONNECTED TO IS A BROKEN FLOW, SHOWN WHERE IT BREAKS.\n\n An entry with no line into it can never be reached; an exit with no line out\n of it stops the run. Both are things the engine will report at run time, and\n the port is where the author can act on it — the Check button's verdict is a\n list of node ids in a card somewhere else on the screen.\n\n ⚠️ NOT COLOUR ALONE. The ring changes the port's SHAPE as well as its fill,\n and every warning port carries a `title` naming the consequence plus an\n `aria-label` that says the same thing — see `orphanEntryHint`. A state a\n sighted user reads from a colour and nobody else can read at all is not a\n state, it is decoration. */\n.cn-flow-node__handle--orphan {\n\tbackground: var(--color-warning, #c28900);\n\tbox-shadow: 0 0 0 2px var(--color-main-background, #fff), 0 0 0 4px var(--color-warning, #c28900);\n}\n\n.cn-flow-node__handle--orphan::after {\n\tborder-inline-start-color: var(--color-main-text, #222);\n\tborder-block-start-color: transparent;\n}\n\n.cn-flow-node__handle--orphan.cn-flow-node__handle--dir-down::after {\n\tborder-inline-start-color: transparent;\n\tborder-block-start-color: var(--color-main-text, #222);\n}\n</style>\n"],"names":["Handle","NodeResizer","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFlowNode.vue2.js","sources":["../../../../src/components/CnGraphCanvas/CnFlowNode.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n - SPDX-License-Identifier: EUPL-1.2\n -->\n<template>\n\t<!-- THE NODE IS `.cn-flow-node`. There is deliberately no alias.\n\n\t Up to 2.11.1 the canvas was a bespoke SVG implementation in\n\t CnGraphCanvas.vue and a node was `.cn-graph-canvas__node`. 2.15.0\n\t rewrote it on Vue Flow and moved the node into this component.\n\n\t 2.15.0 briefly carried `cn-graph-canvas__node` alongside the new name\n\t so the rewrite would be a drop-in (see #752). That alias is gone: the\n\t migration to Vue Flow is a real migration, not a rename with the old\n\t contract left running underneath. An alias kept every consumer's\n\t selector working while quietly guaranteeing that two names for one\n\t element would drift — which is the situation that produced the\n\t original break.\n\n\t Consumers select `.cn-flow-node` (and `--selected`, `--arming`,\n\t `__handle`, `__body`). Migrated with this change: openregister's\n\t flow-controls e2e and hermiq's FlowBuilder deep styles. -->\n\t<div\n\t\tclass=\"cn-flow-node\"\n\t\t:class=\"{\n\t\t\t'cn-flow-node--selected': selected,\n\t\t\t'cn-flow-node--arming': armedPortIndex !== null,\n\t\t}\"\n\t\ttabindex=\"0\"\n\t\trole=\"button\"\n\t\t:aria-label=\"ariaLabel\"\n\t\t:aria-pressed=\"selected ? 'true' : 'false'\"\n\t\t@keydown=\"onKeydown\">\n\t\t<!-- Resize handles, when the host allows it. `@vue-flow/node-resizer`\n\t\t draws the pointer affordance; the keyboard path is `r` + arrows in\n\t\t onKeydown below, because the resizer itself is pointer-only and the\n\t\t canvas this replaces could be resized from the keyboard. -->\n\t\t<NodeResizer v-if=\"resizable\" :minWidth=\"80\" :minHeight=\"40\" />\n\n\t\t<!-- WHERE A LINE ENTERS AND WHERE IT LEAVES, SAID BY THE PORT ITSELF.\n\n\t\t Every node used to draw one entry on top and one exit underneath,\n\t\t which contradicted the layout the editor actually produces:\n\t\t `useFlowStore.autoSort()` lays a flow out LEFT TO RIGHT, one column\n\t\t per depth. So the primary entry is on the LEFT and the primary exit\n\t\t on the RIGHT — Vue Flow attaches a handle-less edge to the first\n\t\t handle of each type, so DOM order here is what decides the default\n\t\t shape of every line on the canvas. Top and bottom stay as secondary\n\t\t attachment points for a graph the author routes by hand.\n\n\t\t A trigger has no entry and an end step has no exit: a port that can\n\t\t never be connected is not chrome, it is a lie about what the engine\n\t\t will do. -->\n\t\t<Handle\n\t\t\tv-for=\"entry in entryHandles\"\n\t\t\t:id=\"entry.id\"\n\t\t\t:key=\"entry.id\"\n\t\t\ttype=\"target\"\n\t\t\t:position=\"entry.position\"\n\t\t\tclass=\"cn-flow-node__handle cn-flow-node__handle--target\"\n\t\t\t:class=\"[`cn-flow-node__handle--dir-${entry.direction}`, { 'cn-flow-node__handle--orphan': orphanEntry }]\"\n\t\t\t:aria-label=\"entryLabel\"\n\t\t\t:title=\"orphanEntry ? orphanEntryHint : undefined\" />\n\n\t\t<div class=\"cn-flow-node__body\">\n\t\t\t<slot :node=\"{ id, data, selected }\">\n\t\t\t\t<span class=\"cn-flow-node__label\">{{ label }}</span>\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- One out-port per exit. A routing node has several, and EVERY one of\n\t\t them has to be reachable without a mouse — see onKeydown.\n\n\t\t ⚠️ A HANDLE ID IS NOT A PORT ID. Two handles can serve one exit (a\n\t\t single-exit step offers both the right and the bottom edge), and Vue\n\t\t Flow keys handles by id, so they cannot share one. The side is\n\t\t therefore encoded into the handle id and stripped straight back off\n\t\t before a connection leaves this component — see `portIdOf`. The host\n\t\t only ever sees the port it declared. -->\n\t\t<Handle\n\t\t\tv-for=\"exit in exitHandles\"\n\t\t\t:id=\"exit.id\"\n\t\t\t:key=\"exit.id\"\n\t\t\ttype=\"source\"\n\t\t\t:position=\"exit.position\"\n\t\t\tclass=\"cn-flow-node__handle cn-flow-node__handle--source\"\n\t\t\t:class=\"[\n\t\t\t\t`cn-flow-node__handle--dir-${exit.direction}`,\n\t\t\t\t{\n\t\t\t\t\t'cn-flow-node__handle--armed': exit.primary && armedPortIndex === exit.index,\n\t\t\t\t\t'cn-flow-node__handle--orphan': orphanExit,\n\t\t\t\t},\n\t\t\t]\"\n\t\t\t:style=\"exit.style\"\n\t\t\t:aria-label=\"portLabel(exit.port)\"\n\t\t\t:title=\"orphanExit ? orphanExitHint : undefined\"\n\t\t\t:aria-pressed=\"exit.primary && armedPortIndex === exit.index ? 'true' : undefined\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { Handle, Position, useVueFlow } from '@vue-flow/core'\nimport { NodeResizer } from '@vue-flow/node-resizer'\n\n/**\n * What separates a port id from the side of the node its handle sits on.\n *\n * `__` rather than `-` or `.` because a port id is authored data — `out-2` and\n * `openregister.no` are both ordinary ids — and a separator that can occur\n * inside the thing it separates is not a separator. Read with `lastIndexOf`, so\n * even a port id that does contain `__` survives the round trip.\n *\n * ⚠️ AND IT HAS TO BE CSS-SAFE. Vue Flow writes a handle's id straight into a\n * class name (`vue-flow__handle-yes__right`), so a separator of `::` — the first\n * choice here — produced `vue-flow__handle-in::top`, a class that reads as a\n * pseudo-element the moment anyone tries to select it. Nothing targets these\n * classes today; the point is that the next person to try would find a selector\n * that silently matches nothing.\n *\n * Written in exactly one place — `exitHandles` — and read in exactly one place:\n * `portIdOf`.\n */\nconst HANDLE_SIDE_SEPARATOR = '__'\n\n/**\n * The default node component, and the home of the canvas's keyboard contract.\n *\n * WHY THE KEYBOARD CODE LIVES HERE AND NOT IN THE CANVAS\n * ------------------------------------------------------\n * Vue Flow renders nodes through a component the host supplies, and that\n * component owns its own DOM. Focus, ARIA state and key handling therefore have\n * to live in the node, not in the canvas wrapper — the canvas never sees the\n * element that a user tabs to.\n *\n * ⚠️ THIS IS A CARRY-OVER, NOT A NEW FEATURE. The hand-rolled canvas this\n * replaces was keyboard-operable by deliberate design, and its docblock said\n * why: \"A drag-only canvas is not keyboard-operable (WCAG 2.1 AA 2.1.1).\"\n * Vue Flow is pointer-first, so adopting it without re-implementing this would\n * have been a straight accessibility regression dressed up as a dependency\n * upgrade.\n *\n * THE MULTI-EXIT CASE IS THE ONE THAT MATTERS\n * -------------------------------------------\n * Where a node has several exits — a routing node draws one out-port per branch\n * — pressing `c` again steps through them, and the armed port is ringed and\n * marked `aria-pressed`. A mouse picks a branch by pointing at it. Without this\n * the keyboard could only ever reach the first, so every other branch would be\n * mouse-only. That is not a rough edge; it is the feature the canvas exists for\n * being unavailable to half its users.\n */\nexport default {\n\tname: 'CnFlowNode',\n\n\tcomponents: { Handle, NodeResizer },\n\n\t// Vue Flow passes a node's whole internal record down — `zindex`, `events`,\n\t// `dimensions`, `position` and more. Without this they fall through onto the\n\t// root element as DOM attributes (`events=\"[object Object]\"`), which is\n\t// invalid markup, noise in every snapshot, and a needless surface for a\n\t// screen reader to read out. The bindings this component actually uses are\n\t// declared as props below.\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/** Vue Flow's node id. */\n\t\tid: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Vue Flow's per-node data bag. */\n\t\tdata: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether Vue Flow considers this node selected. */\n\t\tselected: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** How far one arrow-key press moves the node, in canvas units. */\n\t\tstep: {\n\t\t\ttype: Number,\n\t\t\tdefault: 8,\n\t\t},\n\n\t\t/** How far a Shift+arrow press moves it. */\n\t\tcoarseStep: {\n\t\t\ttype: Number,\n\t\t\tdefault: 40,\n\t\t},\n\n\t\t/** Whether this node may be resized. */\n\t\tresizable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether Delete/Backspace on this node emits `remove`. Defaults to\n\t\t * true; a read-only canvas passes false so the key does nothing.\n\t\t */\n\t\tdeletable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * @event connect A keyboard-completed connection. Carries Vue Flow's\n\t\t * `{ source, sourceHandle, target, targetHandle }` so the host cannot\n\t\t * tell a keyboard connection from a pointer one. CnGraphCanvas routes\n\t\t * it into the same `onConnect` the <VueFlow> pointer path uses.\n\t\t */\n\t\t'connect',\n\n\t\t/**\n\t\t * @event remove The focused node should be removed, by id. Emitted for\n\t\t * Delete and Backspace. The host decides what removal means — its\n\t\t * edges, its undo stack, its persistence — so nothing is removed here.\n\t\t */\n\t\t'remove',\n\n\t\t/**\n\t\t * @event activate Enter or Space on the focused node: open this step.\n\t\t * Carries the node id. The pointer equivalent is a double-click.\n\t\t */\n\t\t'activate',\n\n\t\t/**\n\t\t * @event menu Shift+F10 or the menu key on the focused node: show this\n\t\t * step's actions. Carries `{ id, clientX, clientY }`, a point at the\n\t\t * node, so a host positions the menu as it would for a click.\n\t\t */\n\t\t'menu',\n\t],\n\n\tsetup() {\n\t\t// `connectionStartNode` is module-level state shared by every node\n\t\t// instance, because a keyboard connection spans TWO nodes: the one that\n\t\t// started it and the one that completes it. Vue Flow's own connection\n\t\t// state is pointer-driven and does not cover this path.\n\t\t// `addEdges` is deliberately NOT pulled in. Writing an edge into Vue\n\t\t// Flow's internal graph is what silently dropped keyboard connections:\n\t\t// the host owns `edges` and this component must tell it, not mutate\n\t\t// around it. See onConnectKey().\n\t\tconst { updateNodePositions, findNode } = useVueFlow()\n\n\t\treturn { updateNodePositions, findNode, Position }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Which out-port is armed for a keyboard connection, or null. */\n\t\t\tarmedPortIndex: null,\n\t\t\t/** While true, arrow keys RESIZE instead of moving. */\n\t\t\tresizing: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {string} The node's visible label.\n\t\t */\n\t\tlabel() {\n\t\t\treturn this.data?.label || this.data?.name || this.id\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether this node accepts inbound connections. A\n\t\t * trigger does not: a run STARTS there, so an entry port on one is an\n\t\t * affordance the engine will never honour.\n\t\t */\n\t\thasTarget() {\n\t\t\treturn this.data?.hasTarget !== false\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether this node offers outbound connections. An\n\t\t * end step does not — the flow stops there by definition.\n\t\t */\n\t\thasSource() {\n\t\t\treturn this.data?.hasSource !== false\n\t\t},\n\n\t\t/**\n\t\t * The entry ports, left first.\n\t\t *\n\t\t * LEFT IS PRIMARY, and that is a deliberate consequence of\n\t\t * `useFlowStore.autoSort()` laying a flow out left to right: Vue Flow\n\t\t * binds an edge that names no handle to the FIRST handle of its type, so\n\t\t * this order is what a line does by default.\n\t\t *\n\t\t * @return {Array<object>} The handles to draw.\n\t\t */\n\t\tentryHandles() {\n\t\t\tif (this.hasTarget === false) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\t{ id: `in${HANDLE_SIDE_SEPARATOR}left`, position: this.Position.Left, direction: 'right' },\n\t\t\t\t{ id: `in${HANDLE_SIDE_SEPARATOR}top`, position: this.Position.Top, direction: 'down' },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The exit ports.\n\t\t *\n\t\t * The first exit sits on the RIGHT, where the next column is. Any\n\t\t * further exit — a routing step's branches — spreads along the bottom\n\t\t * edge, which is the one side with room for several and the only\n\t\t * arrangement that keeps a three-way branch readable.\n\t\t *\n\t\t * A step with a SINGLE exit gets a bottom handle as well, so a line may\n\t\t * leave downwards without the author fighting the router. Both handles\n\t\t * serve the same port; only the right-hand one is `primary`, so the\n\t\t * armed-port ring and `aria-pressed` land on exactly one element per\n\t\t * exit rather than two.\n\t\t *\n\t\t * @return {Array<object>} The handles to draw.\n\t\t */\n\t\texitHandles() {\n\t\t\tif (this.hasSource === false) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\tconst ports = this.ports\n\t\t\tconst branches = ports.length - 1\n\t\t\tconst handles = []\n\n\t\t\tports.forEach((port, index) => {\n\t\t\t\tif (index === 0) {\n\t\t\t\t\thandles.push({\n\t\t\t\t\t\tid: `${port.id}${HANDLE_SIDE_SEPARATOR}right`,\n\t\t\t\t\t\tport,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\tposition: this.Position.Right,\n\t\t\t\t\t\tdirection: 'right',\n\t\t\t\t\t\tprimary: true,\n\t\t\t\t\t\tstyle: {},\n\t\t\t\t\t})\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\thandles.push({\n\t\t\t\t\tid: `${port.id}${HANDLE_SIDE_SEPARATOR}bottom`,\n\t\t\t\t\tport,\n\t\t\t\t\tindex,\n\t\t\t\t\tposition: this.Position.Bottom,\n\t\t\t\t\tdirection: 'down',\n\t\t\t\t\tprimary: true,\n\t\t\t\t\tstyle: { left: `${(index / (branches + 1)) * 100}%` },\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tif (ports.length === 1) {\n\t\t\t\thandles.push({\n\t\t\t\t\tid: `${ports[0].id}${HANDLE_SIDE_SEPARATOR}bottom`,\n\t\t\t\t\tport: ports[0],\n\t\t\t\t\tindex: 0,\n\t\t\t\t\tposition: this.Position.Bottom,\n\t\t\t\t\tdirection: 'down',\n\t\t\t\t\tprimary: false,\n\t\t\t\t\tstyle: {},\n\t\t\t\t})\n\t\t\t}\n\n\t\t\treturn handles\n\t\t},\n\n\t\t/**\n\t\t * Whether this node draws an entry that nothing connects to.\n\t\t *\n\t\t * ⚠️ `=== false`, NOT falsy. A host that does not compute this at all\n\t\t * leaves it undefined, and undefined means \"not measured\" — flagging\n\t\t * every port on such a canvas would put a warning on a graph nobody\n\t\t * claimed anything about.\n\t\t *\n\t\t * @return {boolean} True when the port is drawn and unreachable.\n\t\t */\n\t\torphanEntry() {\n\t\t\treturn this.hasTarget === true && this.data?.hasIncoming === false\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} True when this node draws an exit that no line\n\t\t * leaves from. Same \"not measured\" rule as `orphanEntry`.\n\t\t */\n\t\torphanExit() {\n\t\t\treturn this.hasSource === true && this.data?.hasOutgoing === false\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The accessible name of an entry port.\n\t\t */\n\t\tentryLabel() {\n\t\t\tif (this.orphanEntry === true) {\n\t\t\t\treturn `${this.label}: ${this.orphanEntryHint}`\n\t\t\t}\n\n\t\t\treturn t('nextcloud-vue', '{step}: entry', { step: this.label })\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Why an unconnected entry port is a problem, in the\n\t\t * terms the author cares about — what the ENGINE will do.\n\t\t */\n\t\torphanEntryHint() {\n\t\t\treturn t('nextcloud-vue', 'Nothing connects to this step, so the flow will never reach it.')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Why an unconnected exit port is a problem.\n\t\t */\n\t\torphanExitHint() {\n\t\t\treturn t('nextcloud-vue', 'Nothing leaves this step, so the flow stops here.')\n\t\t},\n\n\t\t/**\n\t\t * The node's exits. A node with none still gets one default port, so an\n\t\t * ordinary step behaves exactly as it did before.\n\t\t *\n\t\t * @return {Array<object>} The out-ports.\n\t\t */\n\t\tports() {\n\t\t\tconst declared = this.data?.ports\n\t\t\tif (Array.isArray(declared) === true && declared.length > 0) {\n\t\t\t\treturn declared.map((port, index) => ({\n\t\t\t\t\tid: String(port.id ?? `out-${index}`),\n\t\t\t\t\tlabel: port.label ?? port.id ?? `Exit ${index + 1}`,\n\t\t\t\t}))\n\t\t\t}\n\n\t\t\treturn [{ id: 'out', label: 'Exit' }]\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The label a screen reader announces for the node.\n\t\t */\n\t\tariaLabel() {\n\t\t\tconst kind = this.data?.type ? ` (${this.data.type})` : ''\n\t\t\treturn `${this.label}${kind}`\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Recover the PORT id a handle id was built from.\n\t\t *\n\t\t * The inverse of the encoding in `exitHandles`, and the reason a\n\t\t * node can offer one exit on two sides without the host ever hearing\n\t\t * about a port it did not declare. Exported through the component so\n\t\t * CnGraphCanvas can apply it to Vue Flow's pointer connections too —\n\t\t * both routes have to end at the same port id or a branch would be\n\t\t * recorded under a name the engine has never seen.\n\t\t *\n\t\t * @param {string|null} handleId The handle id, or null.\n\t\t * @return {string|null} The port id.\n\t\t */\n\t\tportIdOf(handleId) {\n\t\t\tif (typeof handleId !== 'string') {\n\t\t\t\treturn handleId ?? null\n\t\t\t}\n\n\t\t\tconst cut = handleId.lastIndexOf(HANDLE_SIDE_SEPARATOR)\n\n\t\t\treturn cut === -1 ? handleId : handleId.slice(0, cut)\n\t\t},\n\n\t\t/**\n\t\t * @param {object} port The port.\n\t\t * @return {string} Its accessible label. An unconnected exit says so\n\t\t * rather than merely being painted differently — colour alone is not\n\t\t * a state a screen reader can read.\n\t\t */\n\t\tportLabel(port) {\n\t\t\tif (this.orphanExit === true) {\n\t\t\t\treturn `${this.label}: ${this.orphanExitHint}`\n\t\t\t}\n\n\t\t\treturn `${this.label}: ${port.label}`\n\t\t},\n\n\t\t/**\n\t\t * The keyboard contract.\n\t\t *\n\t\t * Arrow keys move; `c` arms an exit and, on a second node, completes the\n\t\t * connection; `Escape` cancels. Enter or Space opens the step, Shift+F10\n\t\t * or the menu key opens its actions. Repeated `c` on the SAME node steps\n\t\t * through its exits rather than re-arming the first — the behaviour a\n\t\t * mouse gets for free by pointing.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key event.\n\t\t * @return {void}\n\t\t */\n\t\tonKeydown(event) {\n\t\t\tconst distance = event.shiftKey === true ? this.coarseStep : this.step\n\t\t\tconst moves = {\n\t\t\t\tArrowUp: { x: 0, y: -distance },\n\t\t\t\tArrowDown: { x: 0, y: distance },\n\t\t\t\tArrowLeft: { x: -distance, y: 0 },\n\t\t\t\tArrowRight: { x: distance, y: 0 },\n\t\t\t}\n\n\t\t\tif (moves[event.key] !== undefined) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tif (this.resizing === true) {\n\t\t\t\t\tthis.resize(moves[event.key])\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tthis.move(moves[event.key])\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// `r` toggles resize mode, so the SAME arrow keys serve both without\n\t\t\t// a second key family to learn. The pointer affordance is\n\t\t\t// NodeResizer's; this is the keyboard equivalent, which the canvas\n\t\t\t// this replaces also had and which the resizer does not provide.\n\t\t\tif ((event.key === 'r' || event.key === 'R') && this.resizable === true) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.resizing = this.resizing === false\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'Escape') {\n\t\t\t\tthis.resizing = false\n\t\t\t\tthis.cancelConnection()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'c' || event.key === 'C') {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.onConnectKey()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Opening the step, and its menu, from the keyboard. Only for a key\n\t\t\t// pressed ON the node: a port inside it is a control of its own.\n\t\t\tif (event.target === event.currentTarget) {\n\t\t\t\tif (event.key === 'Enter' || event.key === ' ') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\tthis.$emit('activate', this.id)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif ((event.key === 'F10' && event.shiftKey === true) || event.key === 'ContextMenu') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\tconst rect = event.currentTarget.getBoundingClientRect()\n\t\t\t\t\tthis.$emit('menu', {\n\t\t\t\t\t\tid: this.id,\n\t\t\t\t\t\tclientX: rect.left + rect.width / 2,\n\t\t\t\t\t\tclientY: rect.bottom,\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Delete and Backspace both, because which one removes a thing is a\n\t\t\t// platform habit rather than a preference: Backspace is the delete\n\t\t\t// key on a Mac keyboard, and a user who learned one does not think\n\t\t\t// of the other as an alternative.\n\t\t\t//\n\t\t\t// EMITTED, never acted on here. This component knows about Vue Flow\n\t\t\t// nodes; it does not know that removing one also has to drop the\n\t\t\t// edges pointing at it, or that the host keeps an undo stack. A node\n\t\t\t// that deleted itself would leave dangling edges behind — which is\n\t\t\t// the bug `useFlowStore.removeNode` exists to prevent.\n\t\t\tif (event.key === 'Delete' || event.key === 'Backspace') {\n\t\t\t\tif (this.deletable === false) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tevent.preventDefault()\n\t\t\t\t/**\n\t\t\t\t * @event remove The focused node should be removed. Carries the\n\t\t\t\t * node id. The host owns what removal means — edges, undo,\n\t\t\t\t * persistence — so nothing is removed here.\n\t\t\t\t * @type {string}\n\t\t\t\t */\n\t\t\t\tthis.$emit('remove', this.id)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Move this node, through Vue Flow, so the change reaches the host as a\n\t\t * normal node change rather than a direct mutation.\n\t\t *\n\t\t * @param {object} delta The x/y delta.\n\t\t * @return {void}\n\t\t */\n\t\tmove(delta) {\n\t\t\tconst node = this.findNode(this.id)\n\t\t\tif (node === undefined || node === null) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.updateNodePositions(\n\t\t\t\t[{ id: this.id, position: { x: node.position.x + delta.x, y: node.position.y + delta.y } }],\n\t\t\t\ttrue,\n\t\t\t\tfalse,\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Resize this node from the keyboard.\n\t\t *\n\t\t * @param {object} delta The width/height delta.\n\t\t * @return {void}\n\t\t */\n\t\tresize(delta) {\n\t\t\tconst node = this.findNode(this.id)\n\t\t\tif (node === undefined || node === null) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst width = Math.max(80, (node.dimensions?.width || 140) + delta.x)\n\t\t\tconst height = Math.max(40, (node.dimensions?.height || 60) + delta.y)\n\t\t\tnode.style = { ...(node.style || {}), width: `${width}px`, height: `${height}px` }\n\t\t},\n\n\t\t/**\n\t\t * `c` pressed: arm the next exit, or complete a pending connection.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonConnectKey() {\n\t\t\tconst pending = this.$root?.$cnFlowPendingConnection ?? null\n\n\t\t\t// A connection is already in flight and it did not start here, so\n\t\t\t// this press completes it.\n\t\t\tif (pending !== null && pending.nodeId !== this.id) {\n\t\t\t\t// EMIT, DO NOT `addEdges()`.\n\t\t\t\t//\n\t\t\t\t// `addEdges()` writes into Vue Flow's INTERNAL graph, and this\n\t\t\t\t// canvas does not own that graph: CnGraphCanvas binds\n\t\t\t\t// `:edges=\"edges\"` from its prop, which CnFlowDetail fills from\n\t\t\t\t// `store.canvasEdges`. The edges are controlled by the host.\n\t\t\t\t//\n\t\t\t\t// So `addEdges()` drew the edge and told nobody. The host's model\n\t\t\t\t// never learned about it, the save serialised a flow with no\n\t\t\t\t// edge, and the server refused to run it — \"node has no outgoing\n\t\t\t\t// edge and does not end the flow\", naming a node rather than the\n\t\t\t\t// connection that was missing. The canvas and the model\n\t\t\t\t// disagreed, and the canvas is the one the user believes.\n\t\t\t\t//\n\t\t\t\t// Nothing caught it because the READ path was mapped and the\n\t\t\t\t// WRITE path was never written: a one-way adapter is invisible\n\t\t\t\t// until something writes through it.\n\t\t\t\t//\n\t\t\t\t// CnGraphCanvas already forwards pointer connections —\n\t\t\t\t// `@connect=\"onConnect\"` on <VueFlow>, re-emitted to the host —\n\t\t\t\t// and its own docblock states the contract this restores:\n\t\t\t\t// \"@event connect A new connection was made, by pointer OR by\n\t\t\t\t// keyboard.\" Emitting here puts the keyboard on that same path,\n\t\t\t\t// so both routes end at the host's model and the host remains the\n\t\t\t\t// single source of truth for edges.\n\t\t\t\t//\n\t\t\t\t// Vue Flow's connection shape, so the host cannot tell which\n\t\t\t\t// input produced it.\n\t\t\t\tthis.$emit('connect', {\n\t\t\t\t\tsource: pending.nodeId,\n\t\t\t\t\tsourceHandle: pending.portId,\n\t\t\t\t\ttarget: this.id,\n\t\t\t\t\ttargetHandle: null,\n\t\t\t\t})\n\t\t\t\tthis.clearPending()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Otherwise step through this node's own exits. Running off the end\n\t\t\t// cancels, which is what a single-exit node does on the second press\n\t\t\t// — unchanged from the previous canvas.\n\t\t\tconst next = this.armedPortIndex === null ? 0 : this.armedPortIndex + 1\n\t\t\tif (next >= this.ports.length) {\n\t\t\t\tthis.cancelConnection()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.armedPortIndex = next\n\t\t\tthis.setPending({ nodeId: this.id, portId: this.ports[next].id })\n\t\t},\n\n\t\t/**\n\t\t * @param {object} pending The pending connection.\n\t\t * @return {void}\n\t\t */\n\t\tsetPending(pending) {\n\t\t\tif (this.$root !== undefined && this.$root !== null) {\n\t\t\t\tthis.$root.$cnFlowPendingConnection = pending\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @return {void}\n\t\t */\n\t\tclearPending() {\n\t\t\tthis.armedPortIndex = null\n\t\t\tif (this.$root !== undefined && this.$root !== null) {\n\t\t\t\tthis.$root.$cnFlowPendingConnection = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @return {void}\n\t\t */\n\t\tcancelConnection() {\n\t\t\tthis.clearPending()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-node {\n\tposition: relative;\n\tmin-width: 140px;\n\tpadding: 8px 12px;\n\tborder: 2px solid var(--color-border, #d0d0d0);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-main-background, #fff);\n\tcolor: var(--color-main-text, #222);\n}\n\n.cn-flow-node:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 2px;\n}\n\n.cn-flow-node--selected {\n\tborder-color: var(--color-primary-element, #0082c9);\n}\n\n.cn-flow-node__label {\n\tfont-weight: 600;\n}\n\n.cn-flow-node__handle {\n\t/* 14px, not 10: a port now carries a direction arrow, and there is no room\n\t to draw one legibly in 10. Sized against the 22px arrowhead an edge ends\n\t in (see `useFlowStore.canvasEdges`) — the marker still has to clear the\n\t handle it lands on. */\n\twidth: 14px;\n\theight: 14px;\n\tbackground: var(--color-primary-element, #0082c9);\n}\n\n/* AN ARROW IN THE PORT, because a dot cannot say which way it faces.\n\n A canvas with four ports on a node is only readable if each one states\n whether it takes a line in or lets one out, and the port is the only place\n that can say it — a user reading the node has not yet followed the line.\n Every arrow points WITH the flow: an entry on the left and an exit on the\n right both point right, an entry on top and an exit on the bottom both point\n down. So the whole node reads as one direction rather than as four separate\n claims.\n\n Drawn with borders rather than an icon component: a port is 14px, it is\n rendered once per handle per node, and a triangle at that size is crisper\n from a border than from a scaled glyph. */\n.cn-flow-node__handle::after {\n\tcontent: '';\n\tposition: absolute;\n\tinset-block-start: 50%;\n\tinset-inline-start: 50%;\n\tinline-size: 0;\n\tblock-size: 0;\n\tborder: 3px solid transparent;\n\tpointer-events: none;\n}\n\n.cn-flow-node__handle--dir-right::after {\n\tborder-inline-start-color: var(--color-primary-element-text, #fff);\n\ttransform: translate(-30%, -50%);\n}\n\n.cn-flow-node__handle--dir-down::after {\n\tborder-block-start-color: var(--color-primary-element-text, #fff);\n\ttransform: translate(-50%, -30%);\n}\n\n/* The armed port is RINGED, not merely coloured: colour alone is not an\n accessible state indicator, and this is the cue that tells a keyboard user\n which branch the next `c` will connect. */\n.cn-flow-node__handle--armed {\n\toutline: 3px solid var(--color-warning, #c28900);\n\toutline-offset: 2px;\n}\n\n/* A PORT NOTHING IS CONNECTED TO IS A BROKEN FLOW, SHOWN WHERE IT BREAKS.\n\n An entry with no line into it can never be reached; an exit with no line out\n of it stops the run. Both are things the engine will report at run time, and\n the port is where the author can act on it — the Check button's verdict is a\n list of node ids in a card somewhere else on the screen.\n\n ⚠️ NOT COLOUR ALONE. The ring changes the port's SHAPE as well as its fill,\n and every warning port carries a `title` naming the consequence plus an\n `aria-label` that says the same thing — see `orphanEntryHint`. A state a\n sighted user reads from a colour and nobody else can read at all is not a\n state, it is decoration. */\n.cn-flow-node__handle--orphan {\n\tbackground: var(--color-warning, #c28900);\n\tbox-shadow: 0 0 0 2px var(--color-main-background, #fff), 0 0 0 4px var(--color-warning, #c28900);\n}\n\n.cn-flow-node__handle--orphan::after {\n\tborder-inline-start-color: var(--color-main-text, #222);\n\tborder-block-start-color: transparent;\n}\n\n.cn-flow-node__handle--orphan.cn-flow-node__handle--dir-down::after {\n\tborder-inline-start-color: transparent;\n\tborder-block-start-color: var(--color-main-text, #222);\n}\n</style>\n"],"names":["Handle","NodeResizer","t"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFlowNode.vue3.js","sources":["../../../../src/components/CnGraphCanvas/CnFlowNode.vue?vue&type=template&id=4b186e5f&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n - SPDX-License-Identifier: EUPL-1.2\n -->\n<template>\n\t<!-- THE NODE IS `.cn-flow-node`. There is deliberately no alias.\n\n\t Up to 2.11.1 the canvas was a bespoke SVG implementation in\n\t CnGraphCanvas.vue and a node was `.cn-graph-canvas__node`. 2.15.0\n\t rewrote it on Vue Flow and moved the node into this component.\n\n\t 2.15.0 briefly carried `cn-graph-canvas__node` alongside the new name\n\t so the rewrite would be a drop-in (see #752). That alias is gone: the\n\t migration to Vue Flow is a real migration, not a rename with the old\n\t contract left running underneath. An alias kept every consumer's\n\t selector working while quietly guaranteeing that two names for one\n\t element would drift — which is the situation that produced the\n\t original break.\n\n\t Consumers select `.cn-flow-node` (and `--selected`, `--arming`,\n\t `__handle`, `__body`). Migrated with this change: openregister's\n\t flow-controls e2e and hermiq's FlowBuilder deep styles. -->\n\t<div\n\t\tclass=\"cn-flow-node\"\n\t\t:class=\"{\n\t\t\t'cn-flow-node--selected': selected,\n\t\t\t'cn-flow-node--arming': armedPortIndex !== null,\n\t\t}\"\n\t\ttabindex=\"0\"\n\t\trole=\"button\"\n\t\t:aria-label=\"ariaLabel\"\n\t\t:aria-pressed=\"selected ? 'true' : 'false'\"\n\t\t@keydown=\"onKeydown\">\n\t\t<!-- Resize handles, when the host allows it. `@vue-flow/node-resizer`\n\t\t draws the pointer affordance; the keyboard path is `r` + arrows in\n\t\t onKeydown below, because the resizer itself is pointer-only and the\n\t\t canvas this replaces could be resized from the keyboard. -->\n\t\t<NodeResizer v-if=\"resizable\" :minWidth=\"80\" :minHeight=\"40\" />\n\n\t\t<!-- WHERE A LINE ENTERS AND WHERE IT LEAVES, SAID BY THE PORT ITSELF.\n\n\t\t Every node used to draw one entry on top and one exit underneath,\n\t\t which contradicted the layout the editor actually produces:\n\t\t `useFlowStore.autoSort()` lays a flow out LEFT TO RIGHT, one column\n\t\t per depth. So the primary entry is on the LEFT and the primary exit\n\t\t on the RIGHT — Vue Flow attaches a handle-less edge to the first\n\t\t handle of each type, so DOM order here is what decides the default\n\t\t shape of every line on the canvas. Top and bottom stay as secondary\n\t\t attachment points for a graph the author routes by hand.\n\n\t\t A trigger has no entry and an end step has no exit: a port that can\n\t\t never be connected is not chrome, it is a lie about what the engine\n\t\t will do. -->\n\t\t<Handle\n\t\t\tv-for=\"entry in entryHandles\"\n\t\t\t:id=\"entry.id\"\n\t\t\t:key=\"entry.id\"\n\t\t\ttype=\"target\"\n\t\t\t:position=\"entry.position\"\n\t\t\tclass=\"cn-flow-node__handle cn-flow-node__handle--target\"\n\t\t\t:class=\"[`cn-flow-node__handle--dir-${entry.direction}`, { 'cn-flow-node__handle--orphan': orphanEntry }]\"\n\t\t\t:aria-label=\"entryLabel\"\n\t\t\t:title=\"orphanEntry ? orphanEntryHint : undefined\" />\n\n\t\t<div class=\"cn-flow-node__body\">\n\t\t\t<slot :node=\"{ id, data, selected }\">\n\t\t\t\t<span class=\"cn-flow-node__label\">{{ label }}</span>\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- One out-port per exit. A routing node has several, and EVERY one of\n\t\t them has to be reachable without a mouse — see onKeydown.\n\n\t\t ⚠️ A HANDLE ID IS NOT A PORT ID. Two handles can serve one exit (a\n\t\t single-exit step offers both the right and the bottom edge), and Vue\n\t\t Flow keys handles by id, so they cannot share one. The side is\n\t\t therefore encoded into the handle id and stripped straight back off\n\t\t before a connection leaves this component — see `portIdOf`. The host\n\t\t only ever sees the port it declared. -->\n\t\t<Handle\n\t\t\tv-for=\"exit in exitHandles\"\n\t\t\t:id=\"exit.id\"\n\t\t\t:key=\"exit.id\"\n\t\t\ttype=\"source\"\n\t\t\t:position=\"exit.position\"\n\t\t\tclass=\"cn-flow-node__handle cn-flow-node__handle--source\"\n\t\t\t:class=\"[\n\t\t\t\t`cn-flow-node__handle--dir-${exit.direction}`,\n\t\t\t\t{\n\t\t\t\t\t'cn-flow-node__handle--armed': exit.primary && armedPortIndex === exit.index,\n\t\t\t\t\t'cn-flow-node__handle--orphan': orphanExit,\n\t\t\t\t},\n\t\t\t]\"\n\t\t\t:style=\"exit.style\"\n\t\t\t:aria-label=\"portLabel(exit.port)\"\n\t\t\t:title=\"orphanExit ? orphanExitHint : undefined\"\n\t\t\t:aria-pressed=\"exit.primary && armedPortIndex === exit.index ? 'true' : undefined\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { Handle, Position, useVueFlow } from '@vue-flow/core'\nimport { NodeResizer } from '@vue-flow/node-resizer'\n\n/**\n * What separates a port id from the side of the node its handle sits on.\n *\n * `__` rather than `-` or `.` because a port id is authored data — `out-2` and\n * `openregister.no` are both ordinary ids — and a separator that can occur\n * inside the thing it separates is not a separator. Read with `lastIndexOf`, so\n * even a port id that does contain `__` survives the round trip.\n *\n * ⚠️ AND IT HAS TO BE CSS-SAFE. Vue Flow writes a handle's id straight into a\n * class name (`vue-flow__handle-yes__right`), so a separator of `::` — the first\n * choice here — produced `vue-flow__handle-in::top`, a class that reads as a\n * pseudo-element the moment anyone tries to select it. Nothing targets these\n * classes today; the point is that the next person to try would find a selector\n * that silently matches nothing.\n *\n * Written in exactly one place — `exitHandles` — and read in exactly one place:\n * `portIdOf`.\n */\nconst HANDLE_SIDE_SEPARATOR = '__'\n\n/**\n * The default node component, and the home of the canvas's keyboard contract.\n *\n * WHY THE KEYBOARD CODE LIVES HERE AND NOT IN THE CANVAS\n * ------------------------------------------------------\n * Vue Flow renders nodes through a component the host supplies, and that\n * component owns its own DOM. Focus, ARIA state and key handling therefore have\n * to live in the node, not in the canvas wrapper — the canvas never sees the\n * element that a user tabs to.\n *\n * ⚠️ THIS IS A CARRY-OVER, NOT A NEW FEATURE. The hand-rolled canvas this\n * replaces was keyboard-operable by deliberate design, and its docblock said\n * why: \"A drag-only canvas is not keyboard-operable (WCAG 2.1 AA 2.1.1).\"\n * Vue Flow is pointer-first, so adopting it without re-implementing this would\n * have been a straight accessibility regression dressed up as a dependency\n * upgrade.\n *\n * THE MULTI-EXIT CASE IS THE ONE THAT MATTERS\n * -------------------------------------------\n * Where a node has several exits — a routing node draws one out-port per branch\n * — pressing `c` again steps through them, and the armed port is ringed and\n * marked `aria-pressed`. A mouse picks a branch by pointing at it. Without this\n * the keyboard could only ever reach the first, so every other branch would be\n * mouse-only. That is not a rough edge; it is the feature the canvas exists for\n * being unavailable to half its users.\n */\nexport default {\n\tname: 'CnFlowNode',\n\n\tcomponents: { Handle, NodeResizer },\n\n\t// Vue Flow passes a node's whole internal record down — `zindex`, `events`,\n\t// `dimensions`, `position` and more. Without this they fall through onto the\n\t// root element as DOM attributes (`events=\"[object Object]\"`), which is\n\t// invalid markup, noise in every snapshot, and a needless surface for a\n\t// screen reader to read out. The bindings this component actually uses are\n\t// declared as props below.\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/** Vue Flow's node id. */\n\t\tid: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Vue Flow's per-node data bag. */\n\t\tdata: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether Vue Flow considers this node selected. */\n\t\tselected: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** How far one arrow-key press moves the node, in canvas units. */\n\t\tstep: {\n\t\t\ttype: Number,\n\t\t\tdefault: 8,\n\t\t},\n\n\t\t/** How far a Shift+arrow press moves it. */\n\t\tcoarseStep: {\n\t\t\ttype: Number,\n\t\t\tdefault: 40,\n\t\t},\n\n\t\t/** Whether this node may be resized. */\n\t\tresizable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether Delete/Backspace on this node emits `remove`. Defaults to\n\t\t * true; a read-only canvas passes false so the key does nothing.\n\t\t */\n\t\tdeletable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * @event connect A keyboard-completed connection. Carries Vue Flow's\n\t\t * `{ source, sourceHandle, target, targetHandle }` so the host cannot\n\t\t * tell a keyboard connection from a pointer one. CnGraphCanvas routes\n\t\t * it into the same `onConnect` the <VueFlow> pointer path uses.\n\t\t */\n\t\t'connect',\n\n\t\t/**\n\t\t * @event remove The focused node should be removed, by id. Emitted for\n\t\t * Delete and Backspace. The host decides what removal means — its\n\t\t * edges, its undo stack, its persistence — so nothing is removed here.\n\t\t */\n\t\t'remove',\n\t],\n\n\tsetup() {\n\t\t// `connectionStartNode` is module-level state shared by every node\n\t\t// instance, because a keyboard connection spans TWO nodes: the one that\n\t\t// started it and the one that completes it. Vue Flow's own connection\n\t\t// state is pointer-driven and does not cover this path.\n\t\t// `addEdges` is deliberately NOT pulled in. Writing an edge into Vue\n\t\t// Flow's internal graph is what silently dropped keyboard connections:\n\t\t// the host owns `edges` and this component must tell it, not mutate\n\t\t// around it. See onConnectKey().\n\t\tconst { updateNodePositions, findNode } = useVueFlow()\n\n\t\treturn { updateNodePositions, findNode, Position }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Which out-port is armed for a keyboard connection, or null. */\n\t\t\tarmedPortIndex: null,\n\t\t\t/** While true, arrow keys RESIZE instead of moving. */\n\t\t\tresizing: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {string} The node's visible label.\n\t\t */\n\t\tlabel() {\n\t\t\treturn this.data?.label || this.data?.name || this.id\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether this node accepts inbound connections. A\n\t\t * trigger does not: a run STARTS there, so an entry port on one is an\n\t\t * affordance the engine will never honour.\n\t\t */\n\t\thasTarget() {\n\t\t\treturn this.data?.hasTarget !== false\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether this node offers outbound connections. An\n\t\t * end step does not — the flow stops there by definition.\n\t\t */\n\t\thasSource() {\n\t\t\treturn this.data?.hasSource !== false\n\t\t},\n\n\t\t/**\n\t\t * The entry ports, left first.\n\t\t *\n\t\t * LEFT IS PRIMARY, and that is a deliberate consequence of\n\t\t * `useFlowStore.autoSort()` laying a flow out left to right: Vue Flow\n\t\t * binds an edge that names no handle to the FIRST handle of its type, so\n\t\t * this order is what a line does by default.\n\t\t *\n\t\t * @return {Array<object>} The handles to draw.\n\t\t */\n\t\tentryHandles() {\n\t\t\tif (this.hasTarget === false) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\t{ id: `in${HANDLE_SIDE_SEPARATOR}left`, position: this.Position.Left, direction: 'right' },\n\t\t\t\t{ id: `in${HANDLE_SIDE_SEPARATOR}top`, position: this.Position.Top, direction: 'down' },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The exit ports.\n\t\t *\n\t\t * The first exit sits on the RIGHT, where the next column is. Any\n\t\t * further exit — a routing step's branches — spreads along the bottom\n\t\t * edge, which is the one side with room for several and the only\n\t\t * arrangement that keeps a three-way branch readable.\n\t\t *\n\t\t * A step with a SINGLE exit gets a bottom handle as well, so a line may\n\t\t * leave downwards without the author fighting the router. Both handles\n\t\t * serve the same port; only the right-hand one is `primary`, so the\n\t\t * armed-port ring and `aria-pressed` land on exactly one element per\n\t\t * exit rather than two.\n\t\t *\n\t\t * @return {Array<object>} The handles to draw.\n\t\t */\n\t\texitHandles() {\n\t\t\tif (this.hasSource === false) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\tconst ports = this.ports\n\t\t\tconst branches = ports.length - 1\n\t\t\tconst handles = []\n\n\t\t\tports.forEach((port, index) => {\n\t\t\t\tif (index === 0) {\n\t\t\t\t\thandles.push({\n\t\t\t\t\t\tid: `${port.id}${HANDLE_SIDE_SEPARATOR}right`,\n\t\t\t\t\t\tport,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\tposition: this.Position.Right,\n\t\t\t\t\t\tdirection: 'right',\n\t\t\t\t\t\tprimary: true,\n\t\t\t\t\t\tstyle: {},\n\t\t\t\t\t})\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\thandles.push({\n\t\t\t\t\tid: `${port.id}${HANDLE_SIDE_SEPARATOR}bottom`,\n\t\t\t\t\tport,\n\t\t\t\t\tindex,\n\t\t\t\t\tposition: this.Position.Bottom,\n\t\t\t\t\tdirection: 'down',\n\t\t\t\t\tprimary: true,\n\t\t\t\t\tstyle: { left: `${(index / (branches + 1)) * 100}%` },\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tif (ports.length === 1) {\n\t\t\t\thandles.push({\n\t\t\t\t\tid: `${ports[0].id}${HANDLE_SIDE_SEPARATOR}bottom`,\n\t\t\t\t\tport: ports[0],\n\t\t\t\t\tindex: 0,\n\t\t\t\t\tposition: this.Position.Bottom,\n\t\t\t\t\tdirection: 'down',\n\t\t\t\t\tprimary: false,\n\t\t\t\t\tstyle: {},\n\t\t\t\t})\n\t\t\t}\n\n\t\t\treturn handles\n\t\t},\n\n\t\t/**\n\t\t * Whether this node draws an entry that nothing connects to.\n\t\t *\n\t\t * ⚠️ `=== false`, NOT falsy. A host that does not compute this at all\n\t\t * leaves it undefined, and undefined means \"not measured\" — flagging\n\t\t * every port on such a canvas would put a warning on a graph nobody\n\t\t * claimed anything about.\n\t\t *\n\t\t * @return {boolean} True when the port is drawn and unreachable.\n\t\t */\n\t\torphanEntry() {\n\t\t\treturn this.hasTarget === true && this.data?.hasIncoming === false\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} True when this node draws an exit that no line\n\t\t * leaves from. Same \"not measured\" rule as `orphanEntry`.\n\t\t */\n\t\torphanExit() {\n\t\t\treturn this.hasSource === true && this.data?.hasOutgoing === false\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The accessible name of an entry port.\n\t\t */\n\t\tentryLabel() {\n\t\t\tif (this.orphanEntry === true) {\n\t\t\t\treturn `${this.label}: ${this.orphanEntryHint}`\n\t\t\t}\n\n\t\t\treturn t('nextcloud-vue', '{step}: entry', { step: this.label })\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Why an unconnected entry port is a problem, in the\n\t\t * terms the author cares about — what the ENGINE will do.\n\t\t */\n\t\torphanEntryHint() {\n\t\t\treturn t('nextcloud-vue', 'Nothing connects to this step, so the flow will never reach it.')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Why an unconnected exit port is a problem.\n\t\t */\n\t\torphanExitHint() {\n\t\t\treturn t('nextcloud-vue', 'Nothing leaves this step, so the flow stops here.')\n\t\t},\n\n\t\t/**\n\t\t * The node's exits. A node with none still gets one default port, so an\n\t\t * ordinary step behaves exactly as it did before.\n\t\t *\n\t\t * @return {Array<object>} The out-ports.\n\t\t */\n\t\tports() {\n\t\t\tconst declared = this.data?.ports\n\t\t\tif (Array.isArray(declared) === true && declared.length > 0) {\n\t\t\t\treturn declared.map((port, index) => ({\n\t\t\t\t\tid: String(port.id ?? `out-${index}`),\n\t\t\t\t\tlabel: port.label ?? port.id ?? `Exit ${index + 1}`,\n\t\t\t\t}))\n\t\t\t}\n\n\t\t\treturn [{ id: 'out', label: 'Exit' }]\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The label a screen reader announces for the node.\n\t\t */\n\t\tariaLabel() {\n\t\t\tconst kind = this.data?.type ? ` (${this.data.type})` : ''\n\t\t\treturn `${this.label}${kind}`\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Recover the PORT id a handle id was built from.\n\t\t *\n\t\t * The inverse of the encoding in `exitHandles`, and the reason a\n\t\t * node can offer one exit on two sides without the host ever hearing\n\t\t * about a port it did not declare. Exported through the component so\n\t\t * CnGraphCanvas can apply it to Vue Flow's pointer connections too —\n\t\t * both routes have to end at the same port id or a branch would be\n\t\t * recorded under a name the engine has never seen.\n\t\t *\n\t\t * @param {string|null} handleId The handle id, or null.\n\t\t * @return {string|null} The port id.\n\t\t */\n\t\tportIdOf(handleId) {\n\t\t\tif (typeof handleId !== 'string') {\n\t\t\t\treturn handleId ?? null\n\t\t\t}\n\n\t\t\tconst cut = handleId.lastIndexOf(HANDLE_SIDE_SEPARATOR)\n\n\t\t\treturn cut === -1 ? handleId : handleId.slice(0, cut)\n\t\t},\n\n\t\t/**\n\t\t * @param {object} port The port.\n\t\t * @return {string} Its accessible label. An unconnected exit says so\n\t\t * rather than merely being painted differently — colour alone is not\n\t\t * a state a screen reader can read.\n\t\t */\n\t\tportLabel(port) {\n\t\t\tif (this.orphanExit === true) {\n\t\t\t\treturn `${this.label}: ${this.orphanExitHint}`\n\t\t\t}\n\n\t\t\treturn `${this.label}: ${port.label}`\n\t\t},\n\n\t\t/**\n\t\t * The keyboard contract.\n\t\t *\n\t\t * Arrow keys move; `c` arms an exit and, on a second node, completes the\n\t\t * connection; `Escape` cancels. Repeated `c` on the SAME node steps\n\t\t * through its exits rather than re-arming the first — the behaviour a\n\t\t * mouse gets for free by pointing.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key event.\n\t\t * @return {void}\n\t\t */\n\t\tonKeydown(event) {\n\t\t\tconst distance = event.shiftKey === true ? this.coarseStep : this.step\n\t\t\tconst moves = {\n\t\t\t\tArrowUp: { x: 0, y: -distance },\n\t\t\t\tArrowDown: { x: 0, y: distance },\n\t\t\t\tArrowLeft: { x: -distance, y: 0 },\n\t\t\t\tArrowRight: { x: distance, y: 0 },\n\t\t\t}\n\n\t\t\tif (moves[event.key] !== undefined) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tif (this.resizing === true) {\n\t\t\t\t\tthis.resize(moves[event.key])\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tthis.move(moves[event.key])\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// `r` toggles resize mode, so the SAME arrow keys serve both without\n\t\t\t// a second key family to learn. The pointer affordance is\n\t\t\t// NodeResizer's; this is the keyboard equivalent, which the canvas\n\t\t\t// this replaces also had and which the resizer does not provide.\n\t\t\tif ((event.key === 'r' || event.key === 'R') && this.resizable === true) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.resizing = this.resizing === false\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'Escape') {\n\t\t\t\tthis.resizing = false\n\t\t\t\tthis.cancelConnection()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'c' || event.key === 'C') {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.onConnectKey()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Delete and Backspace both, because which one removes a thing is a\n\t\t\t// platform habit rather than a preference: Backspace is the delete\n\t\t\t// key on a Mac keyboard, and a user who learned one does not think\n\t\t\t// of the other as an alternative.\n\t\t\t//\n\t\t\t// EMITTED, never acted on here. This component knows about Vue Flow\n\t\t\t// nodes; it does not know that removing one also has to drop the\n\t\t\t// edges pointing at it, or that the host keeps an undo stack. A node\n\t\t\t// that deleted itself would leave dangling edges behind — which is\n\t\t\t// the bug `useFlowStore.removeNode` exists to prevent.\n\t\t\tif (event.key === 'Delete' || event.key === 'Backspace') {\n\t\t\t\tif (this.deletable === false) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tevent.preventDefault()\n\t\t\t\t/**\n\t\t\t\t * @event remove The focused node should be removed. Carries the\n\t\t\t\t * node id. The host owns what removal means — edges, undo,\n\t\t\t\t * persistence — so nothing is removed here.\n\t\t\t\t * @type {string}\n\t\t\t\t */\n\t\t\t\tthis.$emit('remove', this.id)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Move this node, through Vue Flow, so the change reaches the host as a\n\t\t * normal node change rather than a direct mutation.\n\t\t *\n\t\t * @param {object} delta The x/y delta.\n\t\t * @return {void}\n\t\t */\n\t\tmove(delta) {\n\t\t\tconst node = this.findNode(this.id)\n\t\t\tif (node === undefined || node === null) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.updateNodePositions(\n\t\t\t\t[{ id: this.id, position: { x: node.position.x + delta.x, y: node.position.y + delta.y } }],\n\t\t\t\ttrue,\n\t\t\t\tfalse,\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Resize this node from the keyboard.\n\t\t *\n\t\t * @param {object} delta The width/height delta.\n\t\t * @return {void}\n\t\t */\n\t\tresize(delta) {\n\t\t\tconst node = this.findNode(this.id)\n\t\t\tif (node === undefined || node === null) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst width = Math.max(80, (node.dimensions?.width || 140) + delta.x)\n\t\t\tconst height = Math.max(40, (node.dimensions?.height || 60) + delta.y)\n\t\t\tnode.style = { ...(node.style || {}), width: `${width}px`, height: `${height}px` }\n\t\t},\n\n\t\t/**\n\t\t * `c` pressed: arm the next exit, or complete a pending connection.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonConnectKey() {\n\t\t\tconst pending = this.$root?.$cnFlowPendingConnection ?? null\n\n\t\t\t// A connection is already in flight and it did not start here, so\n\t\t\t// this press completes it.\n\t\t\tif (pending !== null && pending.nodeId !== this.id) {\n\t\t\t\t// EMIT, DO NOT `addEdges()`.\n\t\t\t\t//\n\t\t\t\t// `addEdges()` writes into Vue Flow's INTERNAL graph, and this\n\t\t\t\t// canvas does not own that graph: CnGraphCanvas binds\n\t\t\t\t// `:edges=\"edges\"` from its prop, which CnFlowDetail fills from\n\t\t\t\t// `store.canvasEdges`. The edges are controlled by the host.\n\t\t\t\t//\n\t\t\t\t// So `addEdges()` drew the edge and told nobody. The host's model\n\t\t\t\t// never learned about it, the save serialised a flow with no\n\t\t\t\t// edge, and the server refused to run it — \"node has no outgoing\n\t\t\t\t// edge and does not end the flow\", naming a node rather than the\n\t\t\t\t// connection that was missing. The canvas and the model\n\t\t\t\t// disagreed, and the canvas is the one the user believes.\n\t\t\t\t//\n\t\t\t\t// Nothing caught it because the READ path was mapped and the\n\t\t\t\t// WRITE path was never written: a one-way adapter is invisible\n\t\t\t\t// until something writes through it.\n\t\t\t\t//\n\t\t\t\t// CnGraphCanvas already forwards pointer connections —\n\t\t\t\t// `@connect=\"onConnect\"` on <VueFlow>, re-emitted to the host —\n\t\t\t\t// and its own docblock states the contract this restores:\n\t\t\t\t// \"@event connect A new connection was made, by pointer OR by\n\t\t\t\t// keyboard.\" Emitting here puts the keyboard on that same path,\n\t\t\t\t// so both routes end at the host's model and the host remains the\n\t\t\t\t// single source of truth for edges.\n\t\t\t\t//\n\t\t\t\t// Vue Flow's connection shape, so the host cannot tell which\n\t\t\t\t// input produced it.\n\t\t\t\tthis.$emit('connect', {\n\t\t\t\t\tsource: pending.nodeId,\n\t\t\t\t\tsourceHandle: pending.portId,\n\t\t\t\t\ttarget: this.id,\n\t\t\t\t\ttargetHandle: null,\n\t\t\t\t})\n\t\t\t\tthis.clearPending()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Otherwise step through this node's own exits. Running off the end\n\t\t\t// cancels, which is what a single-exit node does on the second press\n\t\t\t// — unchanged from the previous canvas.\n\t\t\tconst next = this.armedPortIndex === null ? 0 : this.armedPortIndex + 1\n\t\t\tif (next >= this.ports.length) {\n\t\t\t\tthis.cancelConnection()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.armedPortIndex = next\n\t\t\tthis.setPending({ nodeId: this.id, portId: this.ports[next].id })\n\t\t},\n\n\t\t/**\n\t\t * @param {object} pending The pending connection.\n\t\t * @return {void}\n\t\t */\n\t\tsetPending(pending) {\n\t\t\tif (this.$root !== undefined && this.$root !== null) {\n\t\t\t\tthis.$root.$cnFlowPendingConnection = pending\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @return {void}\n\t\t */\n\t\tclearPending() {\n\t\t\tthis.armedPortIndex = null\n\t\t\tif (this.$root !== undefined && this.$root !== null) {\n\t\t\t\tthis.$root.$cnFlowPendingConnection = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @return {void}\n\t\t */\n\t\tcancelConnection() {\n\t\t\tthis.clearPending()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-node {\n\tposition: relative;\n\tmin-width: 140px;\n\tpadding: 8px 12px;\n\tborder: 2px solid var(--color-border, #d0d0d0);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-main-background, #fff);\n\tcolor: var(--color-main-text, #222);\n}\n\n.cn-flow-node:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 2px;\n}\n\n.cn-flow-node--selected {\n\tborder-color: var(--color-primary-element, #0082c9);\n}\n\n.cn-flow-node__label {\n\tfont-weight: 600;\n}\n\n.cn-flow-node__handle {\n\t/* 14px, not 10: a port now carries a direction arrow, and there is no room\n\t to draw one legibly in 10. Sized against the 22px arrowhead an edge ends\n\t in (see `useFlowStore.canvasEdges`) — the marker still has to clear the\n\t handle it lands on. */\n\twidth: 14px;\n\theight: 14px;\n\tbackground: var(--color-primary-element, #0082c9);\n}\n\n/* AN ARROW IN THE PORT, because a dot cannot say which way it faces.\n\n A canvas with four ports on a node is only readable if each one states\n whether it takes a line in or lets one out, and the port is the only place\n that can say it — a user reading the node has not yet followed the line.\n Every arrow points WITH the flow: an entry on the left and an exit on the\n right both point right, an entry on top and an exit on the bottom both point\n down. So the whole node reads as one direction rather than as four separate\n claims.\n\n Drawn with borders rather than an icon component: a port is 14px, it is\n rendered once per handle per node, and a triangle at that size is crisper\n from a border than from a scaled glyph. */\n.cn-flow-node__handle::after {\n\tcontent: '';\n\tposition: absolute;\n\tinset-block-start: 50%;\n\tinset-inline-start: 50%;\n\tinline-size: 0;\n\tblock-size: 0;\n\tborder: 3px solid transparent;\n\tpointer-events: none;\n}\n\n.cn-flow-node__handle--dir-right::after {\n\tborder-inline-start-color: var(--color-primary-element-text, #fff);\n\ttransform: translate(-30%, -50%);\n}\n\n.cn-flow-node__handle--dir-down::after {\n\tborder-block-start-color: var(--color-primary-element-text, #fff);\n\ttransform: translate(-50%, -30%);\n}\n\n/* The armed port is RINGED, not merely coloured: colour alone is not an\n accessible state indicator, and this is the cue that tells a keyboard user\n which branch the next `c` will connect. */\n.cn-flow-node__handle--armed {\n\toutline: 3px solid var(--color-warning, #c28900);\n\toutline-offset: 2px;\n}\n\n/* A PORT NOTHING IS CONNECTED TO IS A BROKEN FLOW, SHOWN WHERE IT BREAKS.\n\n An entry with no line into it can never be reached; an exit with no line out\n of it stops the run. Both are things the engine will report at run time, and\n the port is where the author can act on it — the Check button's verdict is a\n list of node ids in a card somewhere else on the screen.\n\n ⚠️ NOT COLOUR ALONE. The ring changes the port's SHAPE as well as its fill,\n and every warning port carries a `title` naming the consequence plus an\n `aria-label` that says the same thing — see `orphanEntryHint`. A state a\n sighted user reads from a colour and nobody else can read at all is not a\n state, it is decoration. */\n.cn-flow-node__handle--orphan {\n\tbackground: var(--color-warning, #c28900);\n\tbox-shadow: 0 0 0 2px var(--color-main-background, #fff), 0 0 0 4px var(--color-warning, #c28900);\n}\n\n.cn-flow-node__handle--orphan::after {\n\tborder-inline-start-color: var(--color-main-text, #222);\n\tborder-block-start-color: transparent;\n}\n\n.cn-flow-node__handle--orphan.cn-flow-node__handle--dir-down::after {\n\tborder-inline-start-color: transparent;\n\tborder-block-start-color: var(--color-main-text, #222);\n}\n</style>\n"],"names":["_createElementBlock","_Fragment","_renderList","_createElementVNode","_toDisplayString","_normalizeStyle"],"mappings":";;;AAoEO,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,oBAAA,GAAA;AAEI,MAAA,UAAA,GAAA,EAAA,KAAI,EAAC,qBAAqB,GAAA;;;;;;uBA5ClCA;AACD,IAAA,KAAC,kBAAA,cAAA,EAAA;6BACU,MAAA,CAAA,QAAA;2BAAA,KAAA,CAAA,cAAA,KAAA,IAAA;;AAIX,IAAA,QAAQ,EAAC,GAAG;AACZ,IAAA,IAAG,EAAE,QAAQ;gBACF,EAAE,QAAU,CAAA,SAAA;kBACV,EAAE,MAAQ,CAAA,QAAA,GAAA,MAAA,GAAA,OAAA;aACf,0CAAE,QAAS,CAAA,SAAA,IAAA,QAAA,CAAA,SAAA,CAAA,GAAA,IAAA,CAAA,CAAA,CAAA;;KAKA,MAAQ,CAAA,SAAA;iCAAoC,CAAA,sBAAA,EAAA;;AAAhC,UAAA,QAAQ,EAAC,EAAG;AAAG,UAAA,SAAS,EAAE,EAAE;;;wCAyBN,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CARpC,QAAS,CAAA,YAAA,EAAA,CAAlB,KAAK,KAAA;sCAQwC,CAAA,iBAAA,EAAA;QAPnD,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,GAAG,EAAE,KAAK,CAAC,EAAE;AACd,QAAA,IAAI,EAAC,QAAQ;QACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;kHAEK,KAAA,CAAA,SAAA,sCAAA,QAAA,CAAA,WAAA,EAAA,CAAA,CAAA,CAAA;AAC7B,QAAA,YAAU,EAAE,QAAU,CAAA,UAAA;AACtB,QAAA,KAAI,EAAG,QAAA,CAAA,WAAW,GAAG,QAAA,CAAA,eAAgB,GAAE,SAAS;;;AAElD,IAAAC,kBAAA,QAAA;gBAGQ,CAAA,IAAA,CAAA,MAAA,EAAA,SAAA,EAAA;;eAAA;QADNA,kBAAoD,CAAA,MAAA,EAApD,UAAoD,EAAAC,eAAA,CAAf,QAAK,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;;;oEAc5B,QAAS,CAAA,WAAA,EAAA,CAAjB,IAAI,KAAA;;QACV,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,GAAG,EAAE,IAAE,CAAA,EAAA;AACP,QAAA,IAAG,EAAC,QAAQ;QACZ,QAAA,EAAA,IAAA,CAAA,QAAA;AACC,QAAA,KAAG,kBAAC,mDAAmD,EAAA;AACd,IAAA,CAAA,0BAAA,EAAA,IAAC,CAAA,SAAA,CAAA,CAAA;;AAAA,KAAA,6BAAA,EAAA,IAAA,CAAA,OAAA,IAAA,oBAAA,KAAA,IAAA,CAAA,KAAA;qCAAA,QAAA,CAAA,UAAA;;;QAO3C,KAAE,EAAAC,cAAA,CAAA,IAAA,CAAA,KAAA,CAAA;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnFlowNode.vue3.js","sources":["../../../../src/components/CnGraphCanvas/CnFlowNode.vue?vue&type=template&id=4b186e5f&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n - SPDX-License-Identifier: EUPL-1.2\n -->\n<template>\n\t<!-- THE NODE IS `.cn-flow-node`. There is deliberately no alias.\n\n\t Up to 2.11.1 the canvas was a bespoke SVG implementation in\n\t CnGraphCanvas.vue and a node was `.cn-graph-canvas__node`. 2.15.0\n\t rewrote it on Vue Flow and moved the node into this component.\n\n\t 2.15.0 briefly carried `cn-graph-canvas__node` alongside the new name\n\t so the rewrite would be a drop-in (see #752). That alias is gone: the\n\t migration to Vue Flow is a real migration, not a rename with the old\n\t contract left running underneath. An alias kept every consumer's\n\t selector working while quietly guaranteeing that two names for one\n\t element would drift — which is the situation that produced the\n\t original break.\n\n\t Consumers select `.cn-flow-node` (and `--selected`, `--arming`,\n\t `__handle`, `__body`). Migrated with this change: openregister's\n\t flow-controls e2e and hermiq's FlowBuilder deep styles. -->\n\t<div\n\t\tclass=\"cn-flow-node\"\n\t\t:class=\"{\n\t\t\t'cn-flow-node--selected': selected,\n\t\t\t'cn-flow-node--arming': armedPortIndex !== null,\n\t\t}\"\n\t\ttabindex=\"0\"\n\t\trole=\"button\"\n\t\t:aria-label=\"ariaLabel\"\n\t\t:aria-pressed=\"selected ? 'true' : 'false'\"\n\t\t@keydown=\"onKeydown\">\n\t\t<!-- Resize handles, when the host allows it. `@vue-flow/node-resizer`\n\t\t draws the pointer affordance; the keyboard path is `r` + arrows in\n\t\t onKeydown below, because the resizer itself is pointer-only and the\n\t\t canvas this replaces could be resized from the keyboard. -->\n\t\t<NodeResizer v-if=\"resizable\" :minWidth=\"80\" :minHeight=\"40\" />\n\n\t\t<!-- WHERE A LINE ENTERS AND WHERE IT LEAVES, SAID BY THE PORT ITSELF.\n\n\t\t Every node used to draw one entry on top and one exit underneath,\n\t\t which contradicted the layout the editor actually produces:\n\t\t `useFlowStore.autoSort()` lays a flow out LEFT TO RIGHT, one column\n\t\t per depth. So the primary entry is on the LEFT and the primary exit\n\t\t on the RIGHT — Vue Flow attaches a handle-less edge to the first\n\t\t handle of each type, so DOM order here is what decides the default\n\t\t shape of every line on the canvas. Top and bottom stay as secondary\n\t\t attachment points for a graph the author routes by hand.\n\n\t\t A trigger has no entry and an end step has no exit: a port that can\n\t\t never be connected is not chrome, it is a lie about what the engine\n\t\t will do. -->\n\t\t<Handle\n\t\t\tv-for=\"entry in entryHandles\"\n\t\t\t:id=\"entry.id\"\n\t\t\t:key=\"entry.id\"\n\t\t\ttype=\"target\"\n\t\t\t:position=\"entry.position\"\n\t\t\tclass=\"cn-flow-node__handle cn-flow-node__handle--target\"\n\t\t\t:class=\"[`cn-flow-node__handle--dir-${entry.direction}`, { 'cn-flow-node__handle--orphan': orphanEntry }]\"\n\t\t\t:aria-label=\"entryLabel\"\n\t\t\t:title=\"orphanEntry ? orphanEntryHint : undefined\" />\n\n\t\t<div class=\"cn-flow-node__body\">\n\t\t\t<slot :node=\"{ id, data, selected }\">\n\t\t\t\t<span class=\"cn-flow-node__label\">{{ label }}</span>\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- One out-port per exit. A routing node has several, and EVERY one of\n\t\t them has to be reachable without a mouse — see onKeydown.\n\n\t\t ⚠️ A HANDLE ID IS NOT A PORT ID. Two handles can serve one exit (a\n\t\t single-exit step offers both the right and the bottom edge), and Vue\n\t\t Flow keys handles by id, so they cannot share one. The side is\n\t\t therefore encoded into the handle id and stripped straight back off\n\t\t before a connection leaves this component — see `portIdOf`. The host\n\t\t only ever sees the port it declared. -->\n\t\t<Handle\n\t\t\tv-for=\"exit in exitHandles\"\n\t\t\t:id=\"exit.id\"\n\t\t\t:key=\"exit.id\"\n\t\t\ttype=\"source\"\n\t\t\t:position=\"exit.position\"\n\t\t\tclass=\"cn-flow-node__handle cn-flow-node__handle--source\"\n\t\t\t:class=\"[\n\t\t\t\t`cn-flow-node__handle--dir-${exit.direction}`,\n\t\t\t\t{\n\t\t\t\t\t'cn-flow-node__handle--armed': exit.primary && armedPortIndex === exit.index,\n\t\t\t\t\t'cn-flow-node__handle--orphan': orphanExit,\n\t\t\t\t},\n\t\t\t]\"\n\t\t\t:style=\"exit.style\"\n\t\t\t:aria-label=\"portLabel(exit.port)\"\n\t\t\t:title=\"orphanExit ? orphanExitHint : undefined\"\n\t\t\t:aria-pressed=\"exit.primary && armedPortIndex === exit.index ? 'true' : undefined\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { Handle, Position, useVueFlow } from '@vue-flow/core'\nimport { NodeResizer } from '@vue-flow/node-resizer'\n\n/**\n * What separates a port id from the side of the node its handle sits on.\n *\n * `__` rather than `-` or `.` because a port id is authored data — `out-2` and\n * `openregister.no` are both ordinary ids — and a separator that can occur\n * inside the thing it separates is not a separator. Read with `lastIndexOf`, so\n * even a port id that does contain `__` survives the round trip.\n *\n * ⚠️ AND IT HAS TO BE CSS-SAFE. Vue Flow writes a handle's id straight into a\n * class name (`vue-flow__handle-yes__right`), so a separator of `::` — the first\n * choice here — produced `vue-flow__handle-in::top`, a class that reads as a\n * pseudo-element the moment anyone tries to select it. Nothing targets these\n * classes today; the point is that the next person to try would find a selector\n * that silently matches nothing.\n *\n * Written in exactly one place — `exitHandles` — and read in exactly one place:\n * `portIdOf`.\n */\nconst HANDLE_SIDE_SEPARATOR = '__'\n\n/**\n * The default node component, and the home of the canvas's keyboard contract.\n *\n * WHY THE KEYBOARD CODE LIVES HERE AND NOT IN THE CANVAS\n * ------------------------------------------------------\n * Vue Flow renders nodes through a component the host supplies, and that\n * component owns its own DOM. Focus, ARIA state and key handling therefore have\n * to live in the node, not in the canvas wrapper — the canvas never sees the\n * element that a user tabs to.\n *\n * ⚠️ THIS IS A CARRY-OVER, NOT A NEW FEATURE. The hand-rolled canvas this\n * replaces was keyboard-operable by deliberate design, and its docblock said\n * why: \"A drag-only canvas is not keyboard-operable (WCAG 2.1 AA 2.1.1).\"\n * Vue Flow is pointer-first, so adopting it without re-implementing this would\n * have been a straight accessibility regression dressed up as a dependency\n * upgrade.\n *\n * THE MULTI-EXIT CASE IS THE ONE THAT MATTERS\n * -------------------------------------------\n * Where a node has several exits — a routing node draws one out-port per branch\n * — pressing `c` again steps through them, and the armed port is ringed and\n * marked `aria-pressed`. A mouse picks a branch by pointing at it. Without this\n * the keyboard could only ever reach the first, so every other branch would be\n * mouse-only. That is not a rough edge; it is the feature the canvas exists for\n * being unavailable to half its users.\n */\nexport default {\n\tname: 'CnFlowNode',\n\n\tcomponents: { Handle, NodeResizer },\n\n\t// Vue Flow passes a node's whole internal record down — `zindex`, `events`,\n\t// `dimensions`, `position` and more. Without this they fall through onto the\n\t// root element as DOM attributes (`events=\"[object Object]\"`), which is\n\t// invalid markup, noise in every snapshot, and a needless surface for a\n\t// screen reader to read out. The bindings this component actually uses are\n\t// declared as props below.\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/** Vue Flow's node id. */\n\t\tid: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Vue Flow's per-node data bag. */\n\t\tdata: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether Vue Flow considers this node selected. */\n\t\tselected: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** How far one arrow-key press moves the node, in canvas units. */\n\t\tstep: {\n\t\t\ttype: Number,\n\t\t\tdefault: 8,\n\t\t},\n\n\t\t/** How far a Shift+arrow press moves it. */\n\t\tcoarseStep: {\n\t\t\ttype: Number,\n\t\t\tdefault: 40,\n\t\t},\n\n\t\t/** Whether this node may be resized. */\n\t\tresizable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether Delete/Backspace on this node emits `remove`. Defaults to\n\t\t * true; a read-only canvas passes false so the key does nothing.\n\t\t */\n\t\tdeletable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * @event connect A keyboard-completed connection. Carries Vue Flow's\n\t\t * `{ source, sourceHandle, target, targetHandle }` so the host cannot\n\t\t * tell a keyboard connection from a pointer one. CnGraphCanvas routes\n\t\t * it into the same `onConnect` the <VueFlow> pointer path uses.\n\t\t */\n\t\t'connect',\n\n\t\t/**\n\t\t * @event remove The focused node should be removed, by id. Emitted for\n\t\t * Delete and Backspace. The host decides what removal means — its\n\t\t * edges, its undo stack, its persistence — so nothing is removed here.\n\t\t */\n\t\t'remove',\n\n\t\t/**\n\t\t * @event activate Enter or Space on the focused node: open this step.\n\t\t * Carries the node id. The pointer equivalent is a double-click.\n\t\t */\n\t\t'activate',\n\n\t\t/**\n\t\t * @event menu Shift+F10 or the menu key on the focused node: show this\n\t\t * step's actions. Carries `{ id, clientX, clientY }`, a point at the\n\t\t * node, so a host positions the menu as it would for a click.\n\t\t */\n\t\t'menu',\n\t],\n\n\tsetup() {\n\t\t// `connectionStartNode` is module-level state shared by every node\n\t\t// instance, because a keyboard connection spans TWO nodes: the one that\n\t\t// started it and the one that completes it. Vue Flow's own connection\n\t\t// state is pointer-driven and does not cover this path.\n\t\t// `addEdges` is deliberately NOT pulled in. Writing an edge into Vue\n\t\t// Flow's internal graph is what silently dropped keyboard connections:\n\t\t// the host owns `edges` and this component must tell it, not mutate\n\t\t// around it. See onConnectKey().\n\t\tconst { updateNodePositions, findNode } = useVueFlow()\n\n\t\treturn { updateNodePositions, findNode, Position }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Which out-port is armed for a keyboard connection, or null. */\n\t\t\tarmedPortIndex: null,\n\t\t\t/** While true, arrow keys RESIZE instead of moving. */\n\t\t\tresizing: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {string} The node's visible label.\n\t\t */\n\t\tlabel() {\n\t\t\treturn this.data?.label || this.data?.name || this.id\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether this node accepts inbound connections. A\n\t\t * trigger does not: a run STARTS there, so an entry port on one is an\n\t\t * affordance the engine will never honour.\n\t\t */\n\t\thasTarget() {\n\t\t\treturn this.data?.hasTarget !== false\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether this node offers outbound connections. An\n\t\t * end step does not — the flow stops there by definition.\n\t\t */\n\t\thasSource() {\n\t\t\treturn this.data?.hasSource !== false\n\t\t},\n\n\t\t/**\n\t\t * The entry ports, left first.\n\t\t *\n\t\t * LEFT IS PRIMARY, and that is a deliberate consequence of\n\t\t * `useFlowStore.autoSort()` laying a flow out left to right: Vue Flow\n\t\t * binds an edge that names no handle to the FIRST handle of its type, so\n\t\t * this order is what a line does by default.\n\t\t *\n\t\t * @return {Array<object>} The handles to draw.\n\t\t */\n\t\tentryHandles() {\n\t\t\tif (this.hasTarget === false) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\t{ id: `in${HANDLE_SIDE_SEPARATOR}left`, position: this.Position.Left, direction: 'right' },\n\t\t\t\t{ id: `in${HANDLE_SIDE_SEPARATOR}top`, position: this.Position.Top, direction: 'down' },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The exit ports.\n\t\t *\n\t\t * The first exit sits on the RIGHT, where the next column is. Any\n\t\t * further exit — a routing step's branches — spreads along the bottom\n\t\t * edge, which is the one side with room for several and the only\n\t\t * arrangement that keeps a three-way branch readable.\n\t\t *\n\t\t * A step with a SINGLE exit gets a bottom handle as well, so a line may\n\t\t * leave downwards without the author fighting the router. Both handles\n\t\t * serve the same port; only the right-hand one is `primary`, so the\n\t\t * armed-port ring and `aria-pressed` land on exactly one element per\n\t\t * exit rather than two.\n\t\t *\n\t\t * @return {Array<object>} The handles to draw.\n\t\t */\n\t\texitHandles() {\n\t\t\tif (this.hasSource === false) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\tconst ports = this.ports\n\t\t\tconst branches = ports.length - 1\n\t\t\tconst handles = []\n\n\t\t\tports.forEach((port, index) => {\n\t\t\t\tif (index === 0) {\n\t\t\t\t\thandles.push({\n\t\t\t\t\t\tid: `${port.id}${HANDLE_SIDE_SEPARATOR}right`,\n\t\t\t\t\t\tport,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\tposition: this.Position.Right,\n\t\t\t\t\t\tdirection: 'right',\n\t\t\t\t\t\tprimary: true,\n\t\t\t\t\t\tstyle: {},\n\t\t\t\t\t})\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\thandles.push({\n\t\t\t\t\tid: `${port.id}${HANDLE_SIDE_SEPARATOR}bottom`,\n\t\t\t\t\tport,\n\t\t\t\t\tindex,\n\t\t\t\t\tposition: this.Position.Bottom,\n\t\t\t\t\tdirection: 'down',\n\t\t\t\t\tprimary: true,\n\t\t\t\t\tstyle: { left: `${(index / (branches + 1)) * 100}%` },\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tif (ports.length === 1) {\n\t\t\t\thandles.push({\n\t\t\t\t\tid: `${ports[0].id}${HANDLE_SIDE_SEPARATOR}bottom`,\n\t\t\t\t\tport: ports[0],\n\t\t\t\t\tindex: 0,\n\t\t\t\t\tposition: this.Position.Bottom,\n\t\t\t\t\tdirection: 'down',\n\t\t\t\t\tprimary: false,\n\t\t\t\t\tstyle: {},\n\t\t\t\t})\n\t\t\t}\n\n\t\t\treturn handles\n\t\t},\n\n\t\t/**\n\t\t * Whether this node draws an entry that nothing connects to.\n\t\t *\n\t\t * ⚠️ `=== false`, NOT falsy. A host that does not compute this at all\n\t\t * leaves it undefined, and undefined means \"not measured\" — flagging\n\t\t * every port on such a canvas would put a warning on a graph nobody\n\t\t * claimed anything about.\n\t\t *\n\t\t * @return {boolean} True when the port is drawn and unreachable.\n\t\t */\n\t\torphanEntry() {\n\t\t\treturn this.hasTarget === true && this.data?.hasIncoming === false\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} True when this node draws an exit that no line\n\t\t * leaves from. Same \"not measured\" rule as `orphanEntry`.\n\t\t */\n\t\torphanExit() {\n\t\t\treturn this.hasSource === true && this.data?.hasOutgoing === false\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The accessible name of an entry port.\n\t\t */\n\t\tentryLabel() {\n\t\t\tif (this.orphanEntry === true) {\n\t\t\t\treturn `${this.label}: ${this.orphanEntryHint}`\n\t\t\t}\n\n\t\t\treturn t('nextcloud-vue', '{step}: entry', { step: this.label })\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Why an unconnected entry port is a problem, in the\n\t\t * terms the author cares about — what the ENGINE will do.\n\t\t */\n\t\torphanEntryHint() {\n\t\t\treturn t('nextcloud-vue', 'Nothing connects to this step, so the flow will never reach it.')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Why an unconnected exit port is a problem.\n\t\t */\n\t\torphanExitHint() {\n\t\t\treturn t('nextcloud-vue', 'Nothing leaves this step, so the flow stops here.')\n\t\t},\n\n\t\t/**\n\t\t * The node's exits. A node with none still gets one default port, so an\n\t\t * ordinary step behaves exactly as it did before.\n\t\t *\n\t\t * @return {Array<object>} The out-ports.\n\t\t */\n\t\tports() {\n\t\t\tconst declared = this.data?.ports\n\t\t\tif (Array.isArray(declared) === true && declared.length > 0) {\n\t\t\t\treturn declared.map((port, index) => ({\n\t\t\t\t\tid: String(port.id ?? `out-${index}`),\n\t\t\t\t\tlabel: port.label ?? port.id ?? `Exit ${index + 1}`,\n\t\t\t\t}))\n\t\t\t}\n\n\t\t\treturn [{ id: 'out', label: 'Exit' }]\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The label a screen reader announces for the node.\n\t\t */\n\t\tariaLabel() {\n\t\t\tconst kind = this.data?.type ? ` (${this.data.type})` : ''\n\t\t\treturn `${this.label}${kind}`\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Recover the PORT id a handle id was built from.\n\t\t *\n\t\t * The inverse of the encoding in `exitHandles`, and the reason a\n\t\t * node can offer one exit on two sides without the host ever hearing\n\t\t * about a port it did not declare. Exported through the component so\n\t\t * CnGraphCanvas can apply it to Vue Flow's pointer connections too —\n\t\t * both routes have to end at the same port id or a branch would be\n\t\t * recorded under a name the engine has never seen.\n\t\t *\n\t\t * @param {string|null} handleId The handle id, or null.\n\t\t * @return {string|null} The port id.\n\t\t */\n\t\tportIdOf(handleId) {\n\t\t\tif (typeof handleId !== 'string') {\n\t\t\t\treturn handleId ?? null\n\t\t\t}\n\n\t\t\tconst cut = handleId.lastIndexOf(HANDLE_SIDE_SEPARATOR)\n\n\t\t\treturn cut === -1 ? handleId : handleId.slice(0, cut)\n\t\t},\n\n\t\t/**\n\t\t * @param {object} port The port.\n\t\t * @return {string} Its accessible label. An unconnected exit says so\n\t\t * rather than merely being painted differently — colour alone is not\n\t\t * a state a screen reader can read.\n\t\t */\n\t\tportLabel(port) {\n\t\t\tif (this.orphanExit === true) {\n\t\t\t\treturn `${this.label}: ${this.orphanExitHint}`\n\t\t\t}\n\n\t\t\treturn `${this.label}: ${port.label}`\n\t\t},\n\n\t\t/**\n\t\t * The keyboard contract.\n\t\t *\n\t\t * Arrow keys move; `c` arms an exit and, on a second node, completes the\n\t\t * connection; `Escape` cancels. Enter or Space opens the step, Shift+F10\n\t\t * or the menu key opens its actions. Repeated `c` on the SAME node steps\n\t\t * through its exits rather than re-arming the first — the behaviour a\n\t\t * mouse gets for free by pointing.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key event.\n\t\t * @return {void}\n\t\t */\n\t\tonKeydown(event) {\n\t\t\tconst distance = event.shiftKey === true ? this.coarseStep : this.step\n\t\t\tconst moves = {\n\t\t\t\tArrowUp: { x: 0, y: -distance },\n\t\t\t\tArrowDown: { x: 0, y: distance },\n\t\t\t\tArrowLeft: { x: -distance, y: 0 },\n\t\t\t\tArrowRight: { x: distance, y: 0 },\n\t\t\t}\n\n\t\t\tif (moves[event.key] !== undefined) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tif (this.resizing === true) {\n\t\t\t\t\tthis.resize(moves[event.key])\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tthis.move(moves[event.key])\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// `r` toggles resize mode, so the SAME arrow keys serve both without\n\t\t\t// a second key family to learn. The pointer affordance is\n\t\t\t// NodeResizer's; this is the keyboard equivalent, which the canvas\n\t\t\t// this replaces also had and which the resizer does not provide.\n\t\t\tif ((event.key === 'r' || event.key === 'R') && this.resizable === true) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.resizing = this.resizing === false\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'Escape') {\n\t\t\t\tthis.resizing = false\n\t\t\t\tthis.cancelConnection()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (event.key === 'c' || event.key === 'C') {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.onConnectKey()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Opening the step, and its menu, from the keyboard. Only for a key\n\t\t\t// pressed ON the node: a port inside it is a control of its own.\n\t\t\tif (event.target === event.currentTarget) {\n\t\t\t\tif (event.key === 'Enter' || event.key === ' ') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\tthis.$emit('activate', this.id)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif ((event.key === 'F10' && event.shiftKey === true) || event.key === 'ContextMenu') {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\tconst rect = event.currentTarget.getBoundingClientRect()\n\t\t\t\t\tthis.$emit('menu', {\n\t\t\t\t\t\tid: this.id,\n\t\t\t\t\t\tclientX: rect.left + rect.width / 2,\n\t\t\t\t\t\tclientY: rect.bottom,\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Delete and Backspace both, because which one removes a thing is a\n\t\t\t// platform habit rather than a preference: Backspace is the delete\n\t\t\t// key on a Mac keyboard, and a user who learned one does not think\n\t\t\t// of the other as an alternative.\n\t\t\t//\n\t\t\t// EMITTED, never acted on here. This component knows about Vue Flow\n\t\t\t// nodes; it does not know that removing one also has to drop the\n\t\t\t// edges pointing at it, or that the host keeps an undo stack. A node\n\t\t\t// that deleted itself would leave dangling edges behind — which is\n\t\t\t// the bug `useFlowStore.removeNode` exists to prevent.\n\t\t\tif (event.key === 'Delete' || event.key === 'Backspace') {\n\t\t\t\tif (this.deletable === false) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tevent.preventDefault()\n\t\t\t\t/**\n\t\t\t\t * @event remove The focused node should be removed. Carries the\n\t\t\t\t * node id. The host owns what removal means — edges, undo,\n\t\t\t\t * persistence — so nothing is removed here.\n\t\t\t\t * @type {string}\n\t\t\t\t */\n\t\t\t\tthis.$emit('remove', this.id)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Move this node, through Vue Flow, so the change reaches the host as a\n\t\t * normal node change rather than a direct mutation.\n\t\t *\n\t\t * @param {object} delta The x/y delta.\n\t\t * @return {void}\n\t\t */\n\t\tmove(delta) {\n\t\t\tconst node = this.findNode(this.id)\n\t\t\tif (node === undefined || node === null) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.updateNodePositions(\n\t\t\t\t[{ id: this.id, position: { x: node.position.x + delta.x, y: node.position.y + delta.y } }],\n\t\t\t\ttrue,\n\t\t\t\tfalse,\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Resize this node from the keyboard.\n\t\t *\n\t\t * @param {object} delta The width/height delta.\n\t\t * @return {void}\n\t\t */\n\t\tresize(delta) {\n\t\t\tconst node = this.findNode(this.id)\n\t\t\tif (node === undefined || node === null) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst width = Math.max(80, (node.dimensions?.width || 140) + delta.x)\n\t\t\tconst height = Math.max(40, (node.dimensions?.height || 60) + delta.y)\n\t\t\tnode.style = { ...(node.style || {}), width: `${width}px`, height: `${height}px` }\n\t\t},\n\n\t\t/**\n\t\t * `c` pressed: arm the next exit, or complete a pending connection.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonConnectKey() {\n\t\t\tconst pending = this.$root?.$cnFlowPendingConnection ?? null\n\n\t\t\t// A connection is already in flight and it did not start here, so\n\t\t\t// this press completes it.\n\t\t\tif (pending !== null && pending.nodeId !== this.id) {\n\t\t\t\t// EMIT, DO NOT `addEdges()`.\n\t\t\t\t//\n\t\t\t\t// `addEdges()` writes into Vue Flow's INTERNAL graph, and this\n\t\t\t\t// canvas does not own that graph: CnGraphCanvas binds\n\t\t\t\t// `:edges=\"edges\"` from its prop, which CnFlowDetail fills from\n\t\t\t\t// `store.canvasEdges`. The edges are controlled by the host.\n\t\t\t\t//\n\t\t\t\t// So `addEdges()` drew the edge and told nobody. The host's model\n\t\t\t\t// never learned about it, the save serialised a flow with no\n\t\t\t\t// edge, and the server refused to run it — \"node has no outgoing\n\t\t\t\t// edge and does not end the flow\", naming a node rather than the\n\t\t\t\t// connection that was missing. The canvas and the model\n\t\t\t\t// disagreed, and the canvas is the one the user believes.\n\t\t\t\t//\n\t\t\t\t// Nothing caught it because the READ path was mapped and the\n\t\t\t\t// WRITE path was never written: a one-way adapter is invisible\n\t\t\t\t// until something writes through it.\n\t\t\t\t//\n\t\t\t\t// CnGraphCanvas already forwards pointer connections —\n\t\t\t\t// `@connect=\"onConnect\"` on <VueFlow>, re-emitted to the host —\n\t\t\t\t// and its own docblock states the contract this restores:\n\t\t\t\t// \"@event connect A new connection was made, by pointer OR by\n\t\t\t\t// keyboard.\" Emitting here puts the keyboard on that same path,\n\t\t\t\t// so both routes end at the host's model and the host remains the\n\t\t\t\t// single source of truth for edges.\n\t\t\t\t//\n\t\t\t\t// Vue Flow's connection shape, so the host cannot tell which\n\t\t\t\t// input produced it.\n\t\t\t\tthis.$emit('connect', {\n\t\t\t\t\tsource: pending.nodeId,\n\t\t\t\t\tsourceHandle: pending.portId,\n\t\t\t\t\ttarget: this.id,\n\t\t\t\t\ttargetHandle: null,\n\t\t\t\t})\n\t\t\t\tthis.clearPending()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Otherwise step through this node's own exits. Running off the end\n\t\t\t// cancels, which is what a single-exit node does on the second press\n\t\t\t// — unchanged from the previous canvas.\n\t\t\tconst next = this.armedPortIndex === null ? 0 : this.armedPortIndex + 1\n\t\t\tif (next >= this.ports.length) {\n\t\t\t\tthis.cancelConnection()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.armedPortIndex = next\n\t\t\tthis.setPending({ nodeId: this.id, portId: this.ports[next].id })\n\t\t},\n\n\t\t/**\n\t\t * @param {object} pending The pending connection.\n\t\t * @return {void}\n\t\t */\n\t\tsetPending(pending) {\n\t\t\tif (this.$root !== undefined && this.$root !== null) {\n\t\t\t\tthis.$root.$cnFlowPendingConnection = pending\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @return {void}\n\t\t */\n\t\tclearPending() {\n\t\t\tthis.armedPortIndex = null\n\t\t\tif (this.$root !== undefined && this.$root !== null) {\n\t\t\t\tthis.$root.$cnFlowPendingConnection = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @return {void}\n\t\t */\n\t\tcancelConnection() {\n\t\t\tthis.clearPending()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-node {\n\tposition: relative;\n\tmin-width: 140px;\n\tpadding: 8px 12px;\n\tborder: 2px solid var(--color-border, #d0d0d0);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-main-background, #fff);\n\tcolor: var(--color-main-text, #222);\n}\n\n.cn-flow-node:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 2px;\n}\n\n.cn-flow-node--selected {\n\tborder-color: var(--color-primary-element, #0082c9);\n}\n\n.cn-flow-node__label {\n\tfont-weight: 600;\n}\n\n.cn-flow-node__handle {\n\t/* 14px, not 10: a port now carries a direction arrow, and there is no room\n\t to draw one legibly in 10. Sized against the 22px arrowhead an edge ends\n\t in (see `useFlowStore.canvasEdges`) — the marker still has to clear the\n\t handle it lands on. */\n\twidth: 14px;\n\theight: 14px;\n\tbackground: var(--color-primary-element, #0082c9);\n}\n\n/* AN ARROW IN THE PORT, because a dot cannot say which way it faces.\n\n A canvas with four ports on a node is only readable if each one states\n whether it takes a line in or lets one out, and the port is the only place\n that can say it — a user reading the node has not yet followed the line.\n Every arrow points WITH the flow: an entry on the left and an exit on the\n right both point right, an entry on top and an exit on the bottom both point\n down. So the whole node reads as one direction rather than as four separate\n claims.\n\n Drawn with borders rather than an icon component: a port is 14px, it is\n rendered once per handle per node, and a triangle at that size is crisper\n from a border than from a scaled glyph. */\n.cn-flow-node__handle::after {\n\tcontent: '';\n\tposition: absolute;\n\tinset-block-start: 50%;\n\tinset-inline-start: 50%;\n\tinline-size: 0;\n\tblock-size: 0;\n\tborder: 3px solid transparent;\n\tpointer-events: none;\n}\n\n.cn-flow-node__handle--dir-right::after {\n\tborder-inline-start-color: var(--color-primary-element-text, #fff);\n\ttransform: translate(-30%, -50%);\n}\n\n.cn-flow-node__handle--dir-down::after {\n\tborder-block-start-color: var(--color-primary-element-text, #fff);\n\ttransform: translate(-50%, -30%);\n}\n\n/* The armed port is RINGED, not merely coloured: colour alone is not an\n accessible state indicator, and this is the cue that tells a keyboard user\n which branch the next `c` will connect. */\n.cn-flow-node__handle--armed {\n\toutline: 3px solid var(--color-warning, #c28900);\n\toutline-offset: 2px;\n}\n\n/* A PORT NOTHING IS CONNECTED TO IS A BROKEN FLOW, SHOWN WHERE IT BREAKS.\n\n An entry with no line into it can never be reached; an exit with no line out\n of it stops the run. Both are things the engine will report at run time, and\n the port is where the author can act on it — the Check button's verdict is a\n list of node ids in a card somewhere else on the screen.\n\n ⚠️ NOT COLOUR ALONE. The ring changes the port's SHAPE as well as its fill,\n and every warning port carries a `title` naming the consequence plus an\n `aria-label` that says the same thing — see `orphanEntryHint`. A state a\n sighted user reads from a colour and nobody else can read at all is not a\n state, it is decoration. */\n.cn-flow-node__handle--orphan {\n\tbackground: var(--color-warning, #c28900);\n\tbox-shadow: 0 0 0 2px var(--color-main-background, #fff), 0 0 0 4px var(--color-warning, #c28900);\n}\n\n.cn-flow-node__handle--orphan::after {\n\tborder-inline-start-color: var(--color-main-text, #222);\n\tborder-block-start-color: transparent;\n}\n\n.cn-flow-node__handle--orphan.cn-flow-node__handle--dir-down::after {\n\tborder-inline-start-color: transparent;\n\tborder-block-start-color: var(--color-main-text, #222);\n}\n</style>\n"],"names":["_createElementBlock","_Fragment","_renderList","_createElementVNode","_toDisplayString","_normalizeStyle"],"mappings":";;;AAoEO,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,oBAAA,GAAA;AAEI,MAAA,UAAA,GAAA,EAAA,KAAI,EAAC,qBAAqB,GAAA;;;;;;uBA5ClCA;AACD,IAAA,KAAC,kBAAA,cAAA,EAAA;6BACU,MAAA,CAAA,QAAA;2BAAA,KAAA,CAAA,cAAA,KAAA,IAAA;;AAIX,IAAA,QAAQ,EAAC,GAAG;AACZ,IAAA,IAAG,EAAE,QAAQ;gBACF,EAAE,QAAU,CAAA,SAAA;kBACV,EAAE,MAAQ,CAAA,QAAA,GAAA,MAAA,GAAA,OAAA;aACf,0CAAE,QAAS,CAAA,SAAA,IAAA,QAAA,CAAA,SAAA,CAAA,GAAA,IAAA,CAAA,CAAA,CAAA;;KAKA,MAAQ,CAAA,SAAA;iCAAoC,CAAA,sBAAA,EAAA;;AAAhC,UAAA,QAAQ,EAAC,EAAG;AAAG,UAAA,SAAS,EAAE,EAAE;;;wCAyBN,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CARpC,QAAS,CAAA,YAAA,EAAA,CAAlB,KAAK,KAAA;sCAQwC,CAAA,iBAAA,EAAA;QAPnD,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,GAAG,EAAE,KAAK,CAAC,EAAE;AACd,QAAA,IAAI,EAAC,QAAQ;QACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;kHAEK,KAAA,CAAA,SAAA,sCAAA,QAAA,CAAA,WAAA,EAAA,CAAA,CAAA,CAAA;AAC7B,QAAA,YAAU,EAAE,QAAU,CAAA,UAAA;AACtB,QAAA,KAAI,EAAG,QAAA,CAAA,WAAW,GAAG,QAAA,CAAA,eAAgB,GAAE,SAAS;;;AAElD,IAAAC,kBAAA,QAAA;gBAGQ,CAAA,IAAA,CAAA,MAAA,EAAA,SAAA,EAAA;;eAAA;QADNA,kBAAoD,CAAA,MAAA,EAApD,UAAoD,EAAAC,eAAA,CAAf,QAAK,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;;;oEAc5B,QAAS,CAAA,WAAA,EAAA,CAAjB,IAAI,KAAA;;QACV,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,GAAG,EAAE,IAAE,CAAA,EAAA;AACP,QAAA,IAAG,EAAC,QAAQ;QACZ,QAAA,EAAA,IAAA,CAAA,QAAA;AACC,QAAA,KAAG,kBAAC,mDAAmD,EAAA;AACd,IAAA,CAAA,0BAAA,EAAA,IAAC,CAAA,SAAA,CAAA,CAAA;;AAAA,KAAA,6BAAA,EAAA,IAAA,CAAA,OAAA,IAAA,oBAAA,KAAA,IAAA,CAAA,KAAA;qCAAA,QAAA,CAAA,UAAA;;;QAO3C,KAAE,EAAAC,cAAA,CAAA,IAAA,CAAA,KAAA,CAAA;;;;;;;;;;;"}
|
|
@@ -136,6 +136,8 @@ var script = {
|
|
|
136
136
|
'canvas-drop',
|
|
137
137
|
|
|
138
138
|
'node-remove',
|
|
139
|
+
'node-activate',
|
|
140
|
+
'node-menu',
|
|
139
141
|
|
|
140
142
|
/**
|
|
141
143
|
* The canvas zoom changed, by any route — a control here, the host's own
|
|
@@ -285,6 +287,41 @@ var script = {
|
|
|
285
287
|
return port === 'in' ? null : port
|
|
286
288
|
},
|
|
287
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Forward Enter or Space on a node: the keyboard double-click.
|
|
292
|
+
*
|
|
293
|
+
* @param {string} id The node id.
|
|
294
|
+
* @return {void}
|
|
295
|
+
*/
|
|
296
|
+
onNodeActivate(id) {
|
|
297
|
+
if (this.readOnly === true) {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* @event node-activate Enter or Space on a node. Payload is the id.
|
|
303
|
+
*/
|
|
304
|
+
this.$emit('node-activate', id);
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Forward Shift+F10 or the menu key on a node: the keyboard click menu.
|
|
309
|
+
*
|
|
310
|
+
* @param {object} request `{ id, clientX, clientY }`, a point at the node.
|
|
311
|
+
* @return {void}
|
|
312
|
+
*/
|
|
313
|
+
onNodeMenu(request) {
|
|
314
|
+
if (this.readOnly === true) {
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* @event node-menu Shift+F10 or the menu key on a node. Payload is
|
|
320
|
+
* `{ id, clientX, clientY }`.
|
|
321
|
+
*/
|
|
322
|
+
this.$emit('node-menu', request);
|
|
323
|
+
},
|
|
324
|
+
|
|
288
325
|
/**
|
|
289
326
|
* Forward a node click.
|
|
290
327
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnGraphCanvas.vue2.js","sources":["../../../../src/components/CnGraphCanvas/CnGraphCanvas.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n - SPDX-License-Identifier: EUPL-1.2\n -->\n<template>\n\t<div class=\"cn-graph-canvas\" @drop=\"onDrop\" @dragover.prevent>\n\t\t<VueFlow\n\t\t\t:nodes=\"nodes\"\n\t\t\t:edges=\"edges\"\n\t\t\t:minZoom=\"minZoom\"\n\t\t\t:maxZoom=\"maxZoom\"\n\t\t\t:fitViewOnInit=\"fitView\"\n\t\t\t:snapToGrid=\"snapToGrid\"\n\t\t\t:snapGrid=\"snapGrid\"\n\t\t\t:nodesDraggable=\"interactive\"\n\t\t\t:nodesConnectable=\"interactive\"\n\t\t\t:elementsSelectable=\"interactive\"\n\t\t\t:connectionMode=\"connectionMode\"\n\t\t\t@nodesChange=\"onNodesChange\"\n\t\t\t@edgesChange=\"onEdgesChange\"\n\t\t\t@connect=\"onConnect\"\n\t\t\t@nodeClick=\"onNodeClick\"\n\t\t\t@edgeClick=\"onEdgeClick\"\n\t\t\t@paneClick=\"onPaneClick\">\n\t\t\t<!-- Per-type node components, Vue Flow's convention. A host that\n\t\t\t registers `type: 'trigger'` gets `#node-trigger`; anything\n\t\t\t unregistered falls back to CnFlowNode, which carries the\n\t\t\t keyboard contract. -->\n\t\t\t<template #node-default=\"nodeProps\">\n\t\t\t\t<!-- `@connect` routes a KEYBOARD-completed connection into the\n\t\t\t\t same handler as a pointer one, so `onConnect` is the single\n\t\t\t\t place a new edge leaves this component regardless of input.\n\t\t\t\t Without it the keyboard path wrote into Vue Flow's internal\n\t\t\t\t graph and the host never heard about it — the edge drew and\n\t\t\t\t the save dropped it. -->\n\t\t\t\t<CnFlowNode\n\t\t\t\t\tv-bind=\"nodeProps\"\n\t\t\t\t\t:deletable=\"!readOnly\"\n\t\t\t\t\t@connect=\"onConnect\"\n\t\t\t\t\t@remove=\"onNodeRemove\">\n\t\t\t\t\t<template #default=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot node The body of a step, rendered inside the\n\t\t\t\t\t\t focusable node wrapper. Receives `{ node }` with the\n\t\t\t\t\t\t node's `id`, `data` and `selected`. The wrapper — and\n\t\t\t\t\t\t with it the keyboard contract and ARIA state — stays\n\t\t\t\t\t\t the canvas's, so a host cannot accidentally replace a\n\t\t\t\t\t\t focusable node with an inert div. -->\n\t\t\t\t\t\t<slot name=\"node\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowNode>\n\t\t\t</template>\n\n\t\t\t<!-- Per-type edge components, the same convention as nodes above.\n\t\t\t Registered on `default` because that is the type an edge gets\n\t\t\t when nothing else is asked for — which is every edge, now that\n\t\t\t the ROUTER moved off `type` and into `data.lineType`.\n\n\t\t\t That move is the edge half of the lesson the node slot records:\n\t\t\t `type` selects a COMPONENT, and a router is not a component. So\n\t\t\t long as `type` carried `smoothstep`, Vue Flow answered with its\n\t\t\t own built-in edge and no consumer could add anything to a line\n\t\t\t at all. -->\n\t\t\t<template #edge-default=\"edgeProps\">\n\t\t\t\t<CnFlowEdge\n\t\t\t\t\tv-bind=\"edgeProps\"\n\t\t\t\t\t:lineType=\"edgeProps.data && edgeProps.data.lineType\"\n\t\t\t\t\t:animated=\"!(edgeProps.data && edgeProps.data.animated === false)\"\n\t\t\t\t\t:label-aria-label=\"edgeProps.data && edgeProps.data.labelAriaLabel\"\n\t\t\t\t\t@labelMove=\"onEdgeLabelMove\"\n\t\t\t\t\t@labelClick=\"onEdgeLabelClick\"\n\t\t\t\t\t@labelContext=\"onEdgeLabelContext\">\n\t\t\t\t\t<template #label=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-label The chrome of a connection's\n\t\t\t\t\t\t label. Receives `{ edge }` with the edge's `id`,\n\t\t\t\t\t\t `data` and `selected`. Rendered inside a focusable\n\t\t\t\t\t\t control the canvas owns, so render inert content:\n\t\t\t\t\t\t the arrow keys, the focus ring and the ARIA name\n\t\t\t\t\t\t are already handled. An edge whose host renders\n\t\t\t\t\t\t nothing here draws no label control at all. -->\n\t\t\t\t\t\t<slot name=\"edge-label\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #adornment=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-adornment A host's own controls beside a\n\t\t\t\t\t\t connection's label. Receives `{ edge }`. Unlike\n\t\t\t\t\t\t `edge-label` this is NOT inside the label's button,\n\t\t\t\t\t\t so what goes here may be interactive — a replay's\n\t\t\t\t\t\t payload control is the case it exists for. -->\n\t\t\t\t\t\t<slot name=\"edge-adornment\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowEdge>\n\t\t\t</template>\n\n\t\t\t<Background v-if=\"showBackground\" :gap=\"snapGrid[0]\" />\n\t\t\t<MiniMap v-if=\"showMiniMap\" />\n\t\t</VueFlow>\n\n\t\t<!-- OUR OWN CONTROLS, not `@vue-flow/controls`.\n\t\t The library's control bar renders bare <button> elements with an\n\t\t icon and no accessible name, which axe reports as `button-name` at\n\t\t SERIOUS impact — caught by the e2e in this change, not by the unit\n\t\t lane or the gates. Rather than ship a control bar a screen-reader\n\t\t user cannot identify, the buttons are ours and carry real labels. -->\n\t\t<div v-if=\"showControls\" class=\"cn-graph-canvas__controls\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom in')\"\n\t\t\t\t@click=\"zoomIn()\">\n\t\t\t\t+\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom out')\"\n\t\t\t\t@click=\"zoomOut()\">\n\t\t\t\t−\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Fit the whole flow in view')\"\n\t\t\t\t@click=\"fitViewNow()\">\n\t\t\t\t⤢\n\t\t\t</button>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { Background } from '@vue-flow/background'\nimport { useVueFlow, VueFlow } from '@vue-flow/core'\nimport { MiniMap } from '@vue-flow/minimap'\nimport CnFlowEdge from './CnFlowEdge.vue'\nimport CnFlowNode from './CnFlowNode.vue'\n\n/**\n * A graph canvas built on Vue Flow — the library n8n runs.\n *\n * WHY THIS IS NOT A WRAPPER AROUND THE OLD API\n * --------------------------------------------\n * This component used to be ~1,300 hand-written lines re-implementing pan,\n * zoom, drag, connect, resize and hit-testing on a bare SVG, because the fleet\n * was on Vue 2.7 and every maintained canvas library is Vue-3-only (ADR-065\n * §5). ADR-081 removed that constraint and named this library as the reason for\n * the migration.\n *\n * The props therefore MOVED rather than being translated. The graph page was\n * not in production, so there was no consumer to protect, and a shim can only\n * ever expose what the old API could already express — `fitView`, `<MiniMap>`,\n * `connectionMode` and the node resizer would all have stayed unreachable\n * behind props designed for a hand-drawn SVG.\n *\n * `nodeWidth` / `nodeHeight` are gone entirely. They existed only so hand-drawn\n * edges could guess where a node's centre was, and the old docblock admitted\n * the failure mode: \"set them to match what your node slot actually renders, or\n * edges will attach off-centre\". Vue Flow measures the rendered node, so the\n * whole class of bug went with the props.\n *\n * ⚠️ THE CANVAS STILL NEVER MUTATES THE GRAPH. Vue Flow is perfectly willing to\n * own node positions internally; here it does not. Changes are emitted and the\n * host decides what to persist — the same rule the previous implementation\n * held, and the reason a canvas can be dropped into a read-only surface without\n * auditing it first.\n *\n * ⚠️ KEYBOARD OPERATION LIVES IN {@see CnFlowNode}. Vue Flow is pointer-first;\n * the focus/ARIA/key contract is re-implemented in the node component, not\n * inherited from the library. See that file for why the multi-exit case is the\n * one that matters.\n */\nexport default {\n\tname: 'CnGraphCanvas',\n\n\tcomponents: { VueFlow, Background, MiniMap, CnFlowNode, CnFlowEdge },\n\n\tprops: {\n\t\t/** Nodes in Vue Flow's shape: `{ id, type, position: {x,y}, data }`. */\n\t\tnodes: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Edges in Vue Flow's shape: `{ id, source, target }`. */\n\t\tedges: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * When true, every interaction is refused — drag, connect AND select.\n\t\t *\n\t\t * Expressed as three Vue Flow flags below. Missing one produces a canvas\n\t\t * that LOOKS locked and is not, so the three are set together and tested\n\t\t * together.\n\t\t */\n\t\treadOnly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Frame the whole graph on first render. */\n\t\tfitView: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Snap dragged nodes to the grid. */\n\t\tsnapToGrid: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Grid spacing, as Vue Flow's `[x, y]`. */\n\t\tsnapGrid: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [16, 16],\n\t\t},\n\n\t\t/** Minimum zoom. */\n\t\tminZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0.2,\n\t\t},\n\n\t\t/** Maximum zoom. */\n\t\tmaxZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/** Vue Flow's connection mode — `loose` or `strict`. */\n\t\tconnectionMode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'loose',\n\t\t},\n\n\t\t/** Draw the dotted background. */\n\t\tshowBackground: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the zoom/fit controls. */\n\t\tshowControls: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the minimap. */\n\t\tshowMiniMap: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t'nodes-change',\n\t\t'edges-change',\n\t\t'connect',\n\t\t'node-select',\n\t\t'edge-select',\n\t\t'edge-label-move',\n\t\t'edge-label-click',\n\t\t'edge-label-context',\n\t\t'canvas-click',\n\t\t'canvas-drop',\n\n\t\t'node-remove',\n\n\t\t/**\n\t\t * The canvas zoom changed, by any route — a control here, the host's own\n\t\t * toolbar, the wheel, or a fit-view.\n\t\t *\n\t\t * @type {number}\n\t\t */\n\t\t'zoom-change',\n\t],\n\n\tsetup() {\n\t\t// ⚠️ ALIASED. `fitView` is already a BOOLEAN PROP on this component\n\t\t// (frame the graph on first render), and Vue Flow's composable exports a\n\t\t// FUNCTION of the same name. Returning both puts two `fitView` bindings\n\t\t// on the instance — eslint's `vue/no-dupe-keys` catches it, but the\n\t\t// symptom would have been a fit button that tries to call `true`.\n\t\t// `zoomTo` and `viewport` so a HOST toolbar can drive the zoom and show\n\t\t// the real figure — including one the wheel or a fit-view changed.\n\t\tconst { project, zoomIn, zoomOut, zoomTo, fitView: fitViewNow, viewport } = useVueFlow()\n\n\t\treturn { project, zoomIn, zoomOut, zoomTo, fitViewNow, viewport }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {boolean} Whether the canvas accepts interaction at all.\n\t\t */\n\t\tinteractive() {\n\t\t\treturn this.readOnly === false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// Reported rather than owned: Vue Flow holds the transform, so a host\n\t\t// that mirrors it in its own toolbar must hear every change, not only\n\t\t// the ones its own buttons caused.\n\t\t'viewport.zoom': {\n\t\t\timmediate: true,\n\t\t\thandler(value) {\n\t\t\t\tthis.$emit('zoom-change', value)\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Forward Vue Flow's node changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonNodesChange(changes) {\n\t\t\t/**\n\t\t\t * @event nodes-change Vue Flow's node change stream — position,\n\t\t\t * selection, dimensions, removal. The canvas does NOT apply these;\n\t\t\t * the host decides what to persist. Drag frames arrive with\n\t\t\t * `dragging: true`, so a host that writes on every change writes\n\t\t\t * once per animation frame.\n\t\t\t */\n\t\t\tthis.$emit('nodes-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward Vue Flow's edge changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgesChange(changes) {\n\t\t\t/**\n\t\t\t * @event edges-change Vue Flow's edge change stream, same contract\n\t\t\t * as `nodes-change`: reported, never applied here.\n\t\t\t */\n\t\t\tthis.$emit('edges-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward a new connection.\n\t\t *\n\t\t * @param {object} connection The connection.\n\t\t * @return {void}\n\t\t */\n\t\t/**\n\t\t * Pass a node's removal request up to the host.\n\t\t *\n\t\t * @param {string} id The node that asked to be removed.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeRemove(id) {\n\t\t\t/**\n\t\t\t * @event node-remove The focused node should be removed, by id.\n\t\t\t * Raised by Delete/Backspace on a node. The canvas removes\n\t\t\t * nothing itself — the host owns `nodes` and `edges`, and a node\n\t\t\t * dropped here would leave the edges pointing at it behind.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('node-remove', id)\n\t\t},\n\n\t\tonConnect(connection) {\n\t\t\t/**\n\t\t\t * @event connect A new connection was made, by pointer OR by\n\t\t\t * keyboard. Carries Vue Flow's\n\t\t\t * `{ source, target, sourceHandle, targetHandle }`, with the\n\t\t\t * handles reduced to the PORT the host declared — see\n\t\t\t * `portIdOf` below.\n\t\t\t */\n\t\t\tthis.$emit('connect', {\n\t\t\t\t...connection,\n\t\t\t\tsourceHandle: this.portIdOf(connection?.sourceHandle),\n\t\t\t\ttargetHandle: this.portIdOf(connection?.targetHandle),\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Reduce a HANDLE id to the PORT id the host declared.\n\t\t *\n\t\t * A node draws more handles than it has ports — one exit is offered on\n\t\t * both the right and the bottom edge, one entry on both the left and the\n\t\t * top — because a line has to be able to leave and arrive on the side\n\t\t * the graph reads best from. Vue Flow keys handles by id, so those\n\t\t * cannot share one, and the side is encoded into the id instead\n\t\t * (`yes__right`). See CnFlowNode's `exitHandles`.\n\t\t *\n\t\t * ⚠️ WHICH MEANS THE ENCODED ID MUST NEVER LEAVE THE CANVAS. A branch\n\t\t * recorded as `yes__bottom` names a port the engine has never heard of,\n\t\t * and a flow whose branch ids do not match its catalogue is one the\n\t\t * engine cannot route — it would take the default exit and the canvas\n\t\t * would keep showing the branch the author drew. So both routes into\n\t\t * this method (pointer via <VueFlow>, keyboard via CnFlowNode) are\n\t\t * stripped here, in the one place every connection passes through.\n\t\t *\n\t\t * An entry handle carries no port of its own — a node has exactly one\n\t\t * inbound port — so it reduces to null rather than to the literal `in`.\n\t\t *\n\t\t * @param {string|null|undefined} handleId The handle id Vue Flow reports.\n\t\t * @return {string|null} The port id, or null.\n\t\t */\n\t\tportIdOf(handleId) {\n\t\t\tif (typeof handleId !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst cut = handleId.lastIndexOf('__')\n\t\t\tconst port = cut === -1 ? handleId : handleId.slice(0, cut)\n\n\t\t\treturn port === 'in' ? null : port\n\t\t},\n\n\t\t/**\n\t\t * Forward a node click.\n\t\t *\n\t\t * @param {object} event Vue Flow's node event.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeClick(event) {\n\t\t\t/**\n\t\t\t * @event node-select A node was clicked. Payload is Vue Flow's\n\t\t\t * `{ node }`, so a host tracking bare ids reads `event.node.id`.\n\t\t\t */\n\t\t\tthis.$emit('node-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward an edge click.\n\t\t *\n\t\t * @param {object} event Vue Flow's edge event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeClick(event) {\n\t\t\t/**\n\t\t\t * @event edge-select An edge was clicked.\n\t\t\t */\n\t\t\tthis.$emit('edge-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward a label slid along its line.\n\t\t *\n\t\t * @param {{id: string, labelT: number}} payload The edge and its new\n\t\t * fraction, already clamped.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelMove(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-move A connection's label was moved, by pointer\n\t\t\t * or by keyboard. Carries `{ id, labelT }`. As everywhere else in\n\t\t\t * this component the change is REPORTED, not applied — the host\n\t\t\t * stores `labelT` and feeds it back down.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-move', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward an activated label.\n\t\t *\n\t\t * @param {string} id The edge id.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelClick(id) {\n\t\t\t/**\n\t\t\t * @event edge-label-click A connection's label was activated.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-click', id)\n\t\t},\n\n\t\t/**\n\t\t * Forward a right-click on a label.\n\t\t *\n\t\t * @param {{id: string, event: MouseEvent}} payload The edge and event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelContext(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-context A connection's label was right-clicked.\n\t\t\t * Hosts open the same menu they open for the line itself.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-context', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward a click on the empty pane.\n\t\t *\n\t\t * @param {object} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonPaneClick(event) {\n\t\t\t/**\n\t\t\t * @event canvas-click The empty pane was clicked — hosts use this to\n\t\t\t * clear a selection.\n\t\t\t */\n\t\t\tthis.$emit('canvas-click', event)\n\t\t},\n\n\t\t/**\n\t\t * A palette drop, with the point already converted to canvas space.\n\t\t *\n\t\t * Kept as a Conduction-level event because HTML5 drop plus coordinate\n\t\t * projection is glue every consumer would otherwise write. The\n\t\t * projection is Vue Flow's `project()` rather than the old hand-rolled\n\t\t * `(clientX - rect.left - panOffset.x) / zoom`.\n\t\t *\n\t\t * The canvas does not create the node — the host does, mirroring the\n\t\t * rule that it never mutates positions either.\n\t\t *\n\t\t * @param {DragEvent} event The drop event.\n\t\t * @return {void}\n\t\t */\n\t\tonDrop(event) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst bounds = this.$el.getBoundingClientRect()\n\t\t\tconst position = this.project({\n\t\t\t\tx: event.clientX - bounds.left,\n\t\t\t\ty: event.clientY - bounds.top,\n\t\t\t})\n\n\t\t\t/**\n\t\t\t * @event canvas-drop An HTML5 drop landed on the canvas, with\n\t\t\t * `position` already projected into canvas space and the native\n\t\t\t * `event` alongside so the host can read `dataTransfer`. The canvas\n\t\t\t * never creates the node itself.\n\t\t\t */\n\t\t\tthis.$emit('canvas-drop', { position, event })\n\t\t},\n\t},\n}\n</script>\n\n<style>\n/* Vue Flow's own stylesheet, themed through Nextcloud variables. Imported\n un-scoped because it styles elements Vue Flow renders outside this\n component's scope. */\n@import '@vue-flow/core/dist/style.css';\n@import '@vue-flow/core/dist/theme-default.css';\n\n/* ⚠️ EVERY VUE FLOW PLUGIN SHIPS ITS OWN STYLESHEET, AND ONLY CORE'S WAS HERE.\n\n `@vue-flow/node-resizer` was the one that mattered: its stylesheet is what\n gives a resize control `position: absolute` and a 5px handle. Without it the\n handles CnFlowNode renders have no size and no position, so a node that is\n resizable looks exactly like a node that is not — the component was mounted\n and the affordance was invisible.\n\n The minimap's own sheet is small (a background colour and the grab cursors);\n its geometry comes from `.vue-flow__panel` in core, which was already here.\n Imported for the cursors, and before our own rules so the theme below still\n wins. */\n@import '@vue-flow/node-resizer/dist/style.css';\n@import '@vue-flow/minimap/dist/style.css';\n\n.cn-graph-canvas {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 320px;\n}\n\n/* ⚠️ VUE FLOW'S DEFAULT NODE DRAWS A BOX WE NEVER ASKED FOR.\n\n Every node this canvas renders goes through the `#node-default` slot, so\n Vue Flow wraps our CnFlowNode in `.vue-flow__node-default` — and\n `theme-default.css` gives that wrapper a 1px border, a white background and\n 10px of padding. The result was a box around our box: the wrapper's border,\n then `.cn-flow-node`'s own. Nothing failed and nothing warned; a node just\n quietly had one more outline than it was designed to have.\n\n Stripped, not restyled: `.cn-flow-node` is the node's chrome, and it is the\n ONLY border a node draws. `width` stays 150px because Vue Flow measures the\n wrapper to route edges and place handles — dropping it would move every\n node's footprint, which is a layout change, not a fix for a stray border. */\n.cn-graph-canvas .vue-flow__node-default {\n\tpadding: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\tbackground: none;\n\n\t/* The handle colour reaches `.cn-flow-node__handle` through this variable.\n\t Vue Flow's own `.vue-flow__node-default .vue-flow__handle` rule outranks\n\t our single-class one, so a themed handle lost to a hard-coded #1a192b on\n\t every node — setting the variable themes it at the source instead of\n\t fighting the specificity. */\n\t--vf-handle: var(--color-primary-element, #0082c9);\n}\n\n/* The selected/focus/hover states re-assert the border and shadow the rule\n above just removed, so they have to be stripped too — otherwise clicking a\n node brought the extra box back. Selection is shown by `.cn-flow-node--selected`,\n and focus by `.cn-flow-node:focus-visible`, both on the node itself. */\n.cn-graph-canvas .vue-flow__node-default.selected,\n.cn-graph-canvas .vue-flow__node-default.selected:hover,\n.cn-graph-canvas .vue-flow__node-default.selectable:hover,\n.cn-graph-canvas .vue-flow__node-default:focus,\n.cn-graph-canvas .vue-flow__node-default:focus-visible {\n\tborder: 0;\n\tbox-shadow: none;\n}\n\n/* THEME, NOT OVERRIDE. Every colour comes from a Nextcloud variable with a\n fallback, so the canvas follows the user's theme — including dark — instead\n of being the one component that ignores it. */\n.cn-graph-canvas .vue-flow__background {\n\tbackground: var(--color-main-background, #fff);\n}\n\n.cn-graph-canvas .vue-flow__edge-path {\n\tstroke: var(--color-border-dark, #888);\n}\n\n.cn-graph-canvas .vue-flow__edge.selected .vue-flow__edge-path {\n\tstroke: var(--color-primary-element, #0082c9);\n}\n\n/* An edge says which way the data flows, and it can only say it with an\n arrowhead — the direction is the one thing a line cannot express on its own.\n\n ⚠️ `!important` IS LOAD-BEARING HERE, not laziness. Vue Flow writes the\n arrowhead's colour as an INLINE style on the polyline it generates, and an\n inline style beats any class selector. The alternative is passing a colour\n through the marker definition in JS, which would hard-code a hex and take\n the arrowhead out of the theme — it would stay dark grey in dark mode while\n the line it terminates went light. Themed here, it follows the user.\n\n `markerUnits` defaults to `strokeWidth`, so the size set alongside this in\n `useFlowStore.canvasEdges` is what keeps the arrow clear of the target\n node's port handle. */\n.cn-graph-canvas .vue-flow__arrowhead polyline,\n.cn-graph-canvas .vue-flow__arrowhead path {\n\tfill: var(--color-text-maxcontrast, #6b6b6b) !important;\n\tstroke: var(--color-text-maxcontrast, #6b6b6b) !important;\n}\n\n.cn-graph-canvas__controls {\n\tposition: absolute;\n\tbottom: 12px;\n\tleft: 12px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tz-index: 5;\n}\n\n.cn-graph-canvas__control {\n\twidth: 28px;\n\theight: 28px;\n\tborder: 1px solid var(--color-border, #d0d0d0);\n\tbackground: var(--color-main-background, #fff);\n\tcolor: var(--color-main-text, #222);\n\tcursor: pointer;\n}\n\n.cn-graph-canvas__control:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 1px;\n}\n\n.cn-graph-canvas .vue-flow__minimap {\n\tbackground: var(--color-background-dark, #ededed);\n}\n</style>\n"],"names":["VueFlow","Background","MiniMap","CnFlowNode","CnFlowEdge"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnGraphCanvas.vue2.js","sources":["../../../../src/components/CnGraphCanvas/CnGraphCanvas.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n - SPDX-License-Identifier: EUPL-1.2\n -->\n<template>\n\t<div class=\"cn-graph-canvas\" @drop=\"onDrop\" @dragover.prevent>\n\t\t<VueFlow\n\t\t\t:nodes=\"nodes\"\n\t\t\t:edges=\"edges\"\n\t\t\t:minZoom=\"minZoom\"\n\t\t\t:maxZoom=\"maxZoom\"\n\t\t\t:fitViewOnInit=\"fitView\"\n\t\t\t:snapToGrid=\"snapToGrid\"\n\t\t\t:snapGrid=\"snapGrid\"\n\t\t\t:nodesDraggable=\"interactive\"\n\t\t\t:nodesConnectable=\"interactive\"\n\t\t\t:elementsSelectable=\"interactive\"\n\t\t\t:connectionMode=\"connectionMode\"\n\t\t\t@nodesChange=\"onNodesChange\"\n\t\t\t@edgesChange=\"onEdgesChange\"\n\t\t\t@connect=\"onConnect\"\n\t\t\t@nodeClick=\"onNodeClick\"\n\t\t\t@edgeClick=\"onEdgeClick\"\n\t\t\t@paneClick=\"onPaneClick\">\n\t\t\t<!-- Per-type node components, Vue Flow's convention. A host that\n\t\t\t registers `type: 'trigger'` gets `#node-trigger`; anything\n\t\t\t unregistered falls back to CnFlowNode, which carries the\n\t\t\t keyboard contract. -->\n\t\t\t<template #node-default=\"nodeProps\">\n\t\t\t\t<!-- `@connect` routes a KEYBOARD-completed connection into the\n\t\t\t\t same handler as a pointer one, so `onConnect` is the single\n\t\t\t\t place a new edge leaves this component regardless of input.\n\t\t\t\t Without it the keyboard path wrote into Vue Flow's internal\n\t\t\t\t graph and the host never heard about it — the edge drew and\n\t\t\t\t the save dropped it. -->\n\t\t\t\t<CnFlowNode\n\t\t\t\t\tv-bind=\"nodeProps\"\n\t\t\t\t\t:deletable=\"!readOnly\"\n\t\t\t\t\t@connect=\"onConnect\"\n\t\t\t\t\t@remove=\"onNodeRemove\"\n\t\t\t\t\t@activate=\"onNodeActivate\"\n\t\t\t\t\t@menu=\"onNodeMenu\">\n\t\t\t\t\t<template #default=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot node The body of a step, rendered inside the\n\t\t\t\t\t\t focusable node wrapper. Receives `{ node }` with the\n\t\t\t\t\t\t node's `id`, `data` and `selected`. The wrapper — and\n\t\t\t\t\t\t with it the keyboard contract and ARIA state — stays\n\t\t\t\t\t\t the canvas's, so a host cannot accidentally replace a\n\t\t\t\t\t\t focusable node with an inert div. -->\n\t\t\t\t\t\t<slot name=\"node\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowNode>\n\t\t\t</template>\n\n\t\t\t<!-- Per-type edge components, the same convention as nodes above.\n\t\t\t Registered on `default` because that is the type an edge gets\n\t\t\t when nothing else is asked for — which is every edge, now that\n\t\t\t the ROUTER moved off `type` and into `data.lineType`.\n\n\t\t\t That move is the edge half of the lesson the node slot records:\n\t\t\t `type` selects a COMPONENT, and a router is not a component. So\n\t\t\t long as `type` carried `smoothstep`, Vue Flow answered with its\n\t\t\t own built-in edge and no consumer could add anything to a line\n\t\t\t at all. -->\n\t\t\t<template #edge-default=\"edgeProps\">\n\t\t\t\t<CnFlowEdge\n\t\t\t\t\tv-bind=\"edgeProps\"\n\t\t\t\t\t:lineType=\"edgeProps.data && edgeProps.data.lineType\"\n\t\t\t\t\t:animated=\"!(edgeProps.data && edgeProps.data.animated === false)\"\n\t\t\t\t\t:label-aria-label=\"edgeProps.data && edgeProps.data.labelAriaLabel\"\n\t\t\t\t\t@labelMove=\"onEdgeLabelMove\"\n\t\t\t\t\t@labelClick=\"onEdgeLabelClick\"\n\t\t\t\t\t@labelContext=\"onEdgeLabelContext\">\n\t\t\t\t\t<template #label=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-label The chrome of a connection's\n\t\t\t\t\t\t label. Receives `{ edge }` with the edge's `id`,\n\t\t\t\t\t\t `data` and `selected`. Rendered inside a focusable\n\t\t\t\t\t\t control the canvas owns, so render inert content:\n\t\t\t\t\t\t the arrow keys, the focus ring and the ARIA name\n\t\t\t\t\t\t are already handled. An edge whose host renders\n\t\t\t\t\t\t nothing here draws no label control at all. -->\n\t\t\t\t\t\t<slot name=\"edge-label\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #adornment=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-adornment A host's own controls beside a\n\t\t\t\t\t\t connection's label. Receives `{ edge }`. Unlike\n\t\t\t\t\t\t `edge-label` this is NOT inside the label's button,\n\t\t\t\t\t\t so what goes here may be interactive — a replay's\n\t\t\t\t\t\t payload control is the case it exists for. -->\n\t\t\t\t\t\t<slot name=\"edge-adornment\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowEdge>\n\t\t\t</template>\n\n\t\t\t<Background v-if=\"showBackground\" :gap=\"snapGrid[0]\" />\n\t\t\t<MiniMap v-if=\"showMiniMap\" />\n\t\t</VueFlow>\n\n\t\t<!-- OUR OWN CONTROLS, not `@vue-flow/controls`.\n\t\t The library's control bar renders bare <button> elements with an\n\t\t icon and no accessible name, which axe reports as `button-name` at\n\t\t SERIOUS impact — caught by the e2e in this change, not by the unit\n\t\t lane or the gates. Rather than ship a control bar a screen-reader\n\t\t user cannot identify, the buttons are ours and carry real labels. -->\n\t\t<div v-if=\"showControls\" class=\"cn-graph-canvas__controls\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom in')\"\n\t\t\t\t@click=\"zoomIn()\">\n\t\t\t\t+\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom out')\"\n\t\t\t\t@click=\"zoomOut()\">\n\t\t\t\t−\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Fit the whole flow in view')\"\n\t\t\t\t@click=\"fitViewNow()\">\n\t\t\t\t⤢\n\t\t\t</button>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { Background } from '@vue-flow/background'\nimport { useVueFlow, VueFlow } from '@vue-flow/core'\nimport { MiniMap } from '@vue-flow/minimap'\nimport CnFlowEdge from './CnFlowEdge.vue'\nimport CnFlowNode from './CnFlowNode.vue'\n\n/**\n * A graph canvas built on Vue Flow — the library n8n runs.\n *\n * WHY THIS IS NOT A WRAPPER AROUND THE OLD API\n * --------------------------------------------\n * This component used to be ~1,300 hand-written lines re-implementing pan,\n * zoom, drag, connect, resize and hit-testing on a bare SVG, because the fleet\n * was on Vue 2.7 and every maintained canvas library is Vue-3-only (ADR-065\n * §5). ADR-081 removed that constraint and named this library as the reason for\n * the migration.\n *\n * The props therefore MOVED rather than being translated. The graph page was\n * not in production, so there was no consumer to protect, and a shim can only\n * ever expose what the old API could already express — `fitView`, `<MiniMap>`,\n * `connectionMode` and the node resizer would all have stayed unreachable\n * behind props designed for a hand-drawn SVG.\n *\n * `nodeWidth` / `nodeHeight` are gone entirely. They existed only so hand-drawn\n * edges could guess where a node's centre was, and the old docblock admitted\n * the failure mode: \"set them to match what your node slot actually renders, or\n * edges will attach off-centre\". Vue Flow measures the rendered node, so the\n * whole class of bug went with the props.\n *\n * ⚠️ THE CANVAS STILL NEVER MUTATES THE GRAPH. Vue Flow is perfectly willing to\n * own node positions internally; here it does not. Changes are emitted and the\n * host decides what to persist — the same rule the previous implementation\n * held, and the reason a canvas can be dropped into a read-only surface without\n * auditing it first.\n *\n * ⚠️ KEYBOARD OPERATION LIVES IN {@see CnFlowNode}. Vue Flow is pointer-first;\n * the focus/ARIA/key contract is re-implemented in the node component, not\n * inherited from the library. See that file for why the multi-exit case is the\n * one that matters.\n */\nexport default {\n\tname: 'CnGraphCanvas',\n\n\tcomponents: { VueFlow, Background, MiniMap, CnFlowNode, CnFlowEdge },\n\n\tprops: {\n\t\t/** Nodes in Vue Flow's shape: `{ id, type, position: {x,y}, data }`. */\n\t\tnodes: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Edges in Vue Flow's shape: `{ id, source, target }`. */\n\t\tedges: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * When true, every interaction is refused — drag, connect AND select.\n\t\t *\n\t\t * Expressed as three Vue Flow flags below. Missing one produces a canvas\n\t\t * that LOOKS locked and is not, so the three are set together and tested\n\t\t * together.\n\t\t */\n\t\treadOnly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Frame the whole graph on first render. */\n\t\tfitView: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Snap dragged nodes to the grid. */\n\t\tsnapToGrid: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Grid spacing, as Vue Flow's `[x, y]`. */\n\t\tsnapGrid: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [16, 16],\n\t\t},\n\n\t\t/** Minimum zoom. */\n\t\tminZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0.2,\n\t\t},\n\n\t\t/** Maximum zoom. */\n\t\tmaxZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/** Vue Flow's connection mode — `loose` or `strict`. */\n\t\tconnectionMode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'loose',\n\t\t},\n\n\t\t/** Draw the dotted background. */\n\t\tshowBackground: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the zoom/fit controls. */\n\t\tshowControls: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the minimap. */\n\t\tshowMiniMap: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t'nodes-change',\n\t\t'edges-change',\n\t\t'connect',\n\t\t'node-select',\n\t\t'edge-select',\n\t\t'edge-label-move',\n\t\t'edge-label-click',\n\t\t'edge-label-context',\n\t\t'canvas-click',\n\t\t'canvas-drop',\n\n\t\t'node-remove',\n\t\t'node-activate',\n\t\t'node-menu',\n\n\t\t/**\n\t\t * The canvas zoom changed, by any route — a control here, the host's own\n\t\t * toolbar, the wheel, or a fit-view.\n\t\t *\n\t\t * @type {number}\n\t\t */\n\t\t'zoom-change',\n\t],\n\n\tsetup() {\n\t\t// ⚠️ ALIASED. `fitView` is already a BOOLEAN PROP on this component\n\t\t// (frame the graph on first render), and Vue Flow's composable exports a\n\t\t// FUNCTION of the same name. Returning both puts two `fitView` bindings\n\t\t// on the instance — eslint's `vue/no-dupe-keys` catches it, but the\n\t\t// symptom would have been a fit button that tries to call `true`.\n\t\t// `zoomTo` and `viewport` so a HOST toolbar can drive the zoom and show\n\t\t// the real figure — including one the wheel or a fit-view changed.\n\t\tconst { project, zoomIn, zoomOut, zoomTo, fitView: fitViewNow, viewport } = useVueFlow()\n\n\t\treturn { project, zoomIn, zoomOut, zoomTo, fitViewNow, viewport }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {boolean} Whether the canvas accepts interaction at all.\n\t\t */\n\t\tinteractive() {\n\t\t\treturn this.readOnly === false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// Reported rather than owned: Vue Flow holds the transform, so a host\n\t\t// that mirrors it in its own toolbar must hear every change, not only\n\t\t// the ones its own buttons caused.\n\t\t'viewport.zoom': {\n\t\t\timmediate: true,\n\t\t\thandler(value) {\n\t\t\t\tthis.$emit('zoom-change', value)\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Forward Vue Flow's node changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonNodesChange(changes) {\n\t\t\t/**\n\t\t\t * @event nodes-change Vue Flow's node change stream — position,\n\t\t\t * selection, dimensions, removal. The canvas does NOT apply these;\n\t\t\t * the host decides what to persist. Drag frames arrive with\n\t\t\t * `dragging: true`, so a host that writes on every change writes\n\t\t\t * once per animation frame.\n\t\t\t */\n\t\t\tthis.$emit('nodes-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward Vue Flow's edge changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgesChange(changes) {\n\t\t\t/**\n\t\t\t * @event edges-change Vue Flow's edge change stream, same contract\n\t\t\t * as `nodes-change`: reported, never applied here.\n\t\t\t */\n\t\t\tthis.$emit('edges-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward a new connection.\n\t\t *\n\t\t * @param {object} connection The connection.\n\t\t * @return {void}\n\t\t */\n\t\t/**\n\t\t * Pass a node's removal request up to the host.\n\t\t *\n\t\t * @param {string} id The node that asked to be removed.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeRemove(id) {\n\t\t\t/**\n\t\t\t * @event node-remove The focused node should be removed, by id.\n\t\t\t * Raised by Delete/Backspace on a node. The canvas removes\n\t\t\t * nothing itself — the host owns `nodes` and `edges`, and a node\n\t\t\t * dropped here would leave the edges pointing at it behind.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('node-remove', id)\n\t\t},\n\n\t\tonConnect(connection) {\n\t\t\t/**\n\t\t\t * @event connect A new connection was made, by pointer OR by\n\t\t\t * keyboard. Carries Vue Flow's\n\t\t\t * `{ source, target, sourceHandle, targetHandle }`, with the\n\t\t\t * handles reduced to the PORT the host declared — see\n\t\t\t * `portIdOf` below.\n\t\t\t */\n\t\t\tthis.$emit('connect', {\n\t\t\t\t...connection,\n\t\t\t\tsourceHandle: this.portIdOf(connection?.sourceHandle),\n\t\t\t\ttargetHandle: this.portIdOf(connection?.targetHandle),\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Reduce a HANDLE id to the PORT id the host declared.\n\t\t *\n\t\t * A node draws more handles than it has ports — one exit is offered on\n\t\t * both the right and the bottom edge, one entry on both the left and the\n\t\t * top — because a line has to be able to leave and arrive on the side\n\t\t * the graph reads best from. Vue Flow keys handles by id, so those\n\t\t * cannot share one, and the side is encoded into the id instead\n\t\t * (`yes__right`). See CnFlowNode's `exitHandles`.\n\t\t *\n\t\t * ⚠️ WHICH MEANS THE ENCODED ID MUST NEVER LEAVE THE CANVAS. A branch\n\t\t * recorded as `yes__bottom` names a port the engine has never heard of,\n\t\t * and a flow whose branch ids do not match its catalogue is one the\n\t\t * engine cannot route — it would take the default exit and the canvas\n\t\t * would keep showing the branch the author drew. So both routes into\n\t\t * this method (pointer via <VueFlow>, keyboard via CnFlowNode) are\n\t\t * stripped here, in the one place every connection passes through.\n\t\t *\n\t\t * An entry handle carries no port of its own — a node has exactly one\n\t\t * inbound port — so it reduces to null rather than to the literal `in`.\n\t\t *\n\t\t * @param {string|null|undefined} handleId The handle id Vue Flow reports.\n\t\t * @return {string|null} The port id, or null.\n\t\t */\n\t\tportIdOf(handleId) {\n\t\t\tif (typeof handleId !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst cut = handleId.lastIndexOf('__')\n\t\t\tconst port = cut === -1 ? handleId : handleId.slice(0, cut)\n\n\t\t\treturn port === 'in' ? null : port\n\t\t},\n\n\t\t/**\n\t\t * Forward Enter or Space on a node: the keyboard double-click.\n\t\t *\n\t\t * @param {string} id The node id.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeActivate(id) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event node-activate Enter or Space on a node. Payload is the id.\n\t\t\t */\n\t\t\tthis.$emit('node-activate', id)\n\t\t},\n\n\t\t/**\n\t\t * Forward Shift+F10 or the menu key on a node: the keyboard click menu.\n\t\t *\n\t\t * @param {object} request `{ id, clientX, clientY }`, a point at the node.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeMenu(request) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event node-menu Shift+F10 or the menu key on a node. Payload is\n\t\t\t * `{ id, clientX, clientY }`.\n\t\t\t */\n\t\t\tthis.$emit('node-menu', request)\n\t\t},\n\n\t\t/**\n\t\t * Forward a node click.\n\t\t *\n\t\t * @param {object} event Vue Flow's node event.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeClick(event) {\n\t\t\t/**\n\t\t\t * @event node-select A node was clicked. Payload is Vue Flow's\n\t\t\t * `{ node }`, so a host tracking bare ids reads `event.node.id`.\n\t\t\t */\n\t\t\tthis.$emit('node-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward an edge click.\n\t\t *\n\t\t * @param {object} event Vue Flow's edge event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeClick(event) {\n\t\t\t/**\n\t\t\t * @event edge-select An edge was clicked.\n\t\t\t */\n\t\t\tthis.$emit('edge-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward a label slid along its line.\n\t\t *\n\t\t * @param {{id: string, labelT: number}} payload The edge and its new\n\t\t * fraction, already clamped.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelMove(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-move A connection's label was moved, by pointer\n\t\t\t * or by keyboard. Carries `{ id, labelT }`. As everywhere else in\n\t\t\t * this component the change is REPORTED, not applied — the host\n\t\t\t * stores `labelT` and feeds it back down.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-move', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward an activated label.\n\t\t *\n\t\t * @param {string} id The edge id.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelClick(id) {\n\t\t\t/**\n\t\t\t * @event edge-label-click A connection's label was activated.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-click', id)\n\t\t},\n\n\t\t/**\n\t\t * Forward a right-click on a label.\n\t\t *\n\t\t * @param {{id: string, event: MouseEvent}} payload The edge and event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelContext(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-context A connection's label was right-clicked.\n\t\t\t * Hosts open the same menu they open for the line itself.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-context', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward a click on the empty pane.\n\t\t *\n\t\t * @param {object} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonPaneClick(event) {\n\t\t\t/**\n\t\t\t * @event canvas-click The empty pane was clicked — hosts use this to\n\t\t\t * clear a selection.\n\t\t\t */\n\t\t\tthis.$emit('canvas-click', event)\n\t\t},\n\n\t\t/**\n\t\t * A palette drop, with the point already converted to canvas space.\n\t\t *\n\t\t * Kept as a Conduction-level event because HTML5 drop plus coordinate\n\t\t * projection is glue every consumer would otherwise write. The\n\t\t * projection is Vue Flow's `project()` rather than the old hand-rolled\n\t\t * `(clientX - rect.left - panOffset.x) / zoom`.\n\t\t *\n\t\t * The canvas does not create the node — the host does, mirroring the\n\t\t * rule that it never mutates positions either.\n\t\t *\n\t\t * @param {DragEvent} event The drop event.\n\t\t * @return {void}\n\t\t */\n\t\tonDrop(event) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst bounds = this.$el.getBoundingClientRect()\n\t\t\tconst position = this.project({\n\t\t\t\tx: event.clientX - bounds.left,\n\t\t\t\ty: event.clientY - bounds.top,\n\t\t\t})\n\n\t\t\t/**\n\t\t\t * @event canvas-drop An HTML5 drop landed on the canvas, with\n\t\t\t * `position` already projected into canvas space and the native\n\t\t\t * `event` alongside so the host can read `dataTransfer`. The canvas\n\t\t\t * never creates the node itself.\n\t\t\t */\n\t\t\tthis.$emit('canvas-drop', { position, event })\n\t\t},\n\t},\n}\n</script>\n\n<style>\n/* Vue Flow's own stylesheet, themed through Nextcloud variables. Imported\n un-scoped because it styles elements Vue Flow renders outside this\n component's scope. */\n@import '@vue-flow/core/dist/style.css';\n@import '@vue-flow/core/dist/theme-default.css';\n\n/* ⚠️ EVERY VUE FLOW PLUGIN SHIPS ITS OWN STYLESHEET, AND ONLY CORE'S WAS HERE.\n\n `@vue-flow/node-resizer` was the one that mattered: its stylesheet is what\n gives a resize control `position: absolute` and a 5px handle. Without it the\n handles CnFlowNode renders have no size and no position, so a node that is\n resizable looks exactly like a node that is not — the component was mounted\n and the affordance was invisible.\n\n The minimap's own sheet is small (a background colour and the grab cursors);\n its geometry comes from `.vue-flow__panel` in core, which was already here.\n Imported for the cursors, and before our own rules so the theme below still\n wins. */\n@import '@vue-flow/node-resizer/dist/style.css';\n@import '@vue-flow/minimap/dist/style.css';\n\n.cn-graph-canvas {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 320px;\n}\n\n/* ⚠️ VUE FLOW'S DEFAULT NODE DRAWS A BOX WE NEVER ASKED FOR.\n\n Every node this canvas renders goes through the `#node-default` slot, so\n Vue Flow wraps our CnFlowNode in `.vue-flow__node-default` — and\n `theme-default.css` gives that wrapper a 1px border, a white background and\n 10px of padding. The result was a box around our box: the wrapper's border,\n then `.cn-flow-node`'s own. Nothing failed and nothing warned; a node just\n quietly had one more outline than it was designed to have.\n\n Stripped, not restyled: `.cn-flow-node` is the node's chrome, and it is the\n ONLY border a node draws. `width` stays 150px because Vue Flow measures the\n wrapper to route edges and place handles — dropping it would move every\n node's footprint, which is a layout change, not a fix for a stray border. */\n.cn-graph-canvas .vue-flow__node-default {\n\tpadding: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\tbackground: none;\n\n\t/* The handle colour reaches `.cn-flow-node__handle` through this variable.\n\t Vue Flow's own `.vue-flow__node-default .vue-flow__handle` rule outranks\n\t our single-class one, so a themed handle lost to a hard-coded #1a192b on\n\t every node — setting the variable themes it at the source instead of\n\t fighting the specificity. */\n\t--vf-handle: var(--color-primary-element, #0082c9);\n}\n\n/* The selected/focus/hover states re-assert the border and shadow the rule\n above just removed, so they have to be stripped too — otherwise clicking a\n node brought the extra box back. Selection is shown by `.cn-flow-node--selected`,\n and focus by `.cn-flow-node:focus-visible`, both on the node itself. */\n.cn-graph-canvas .vue-flow__node-default.selected,\n.cn-graph-canvas .vue-flow__node-default.selected:hover,\n.cn-graph-canvas .vue-flow__node-default.selectable:hover,\n.cn-graph-canvas .vue-flow__node-default:focus,\n.cn-graph-canvas .vue-flow__node-default:focus-visible {\n\tborder: 0;\n\tbox-shadow: none;\n}\n\n/* THEME, NOT OVERRIDE. Every colour comes from a Nextcloud variable with a\n fallback, so the canvas follows the user's theme — including dark — instead\n of being the one component that ignores it. */\n.cn-graph-canvas .vue-flow__background {\n\tbackground: var(--color-main-background, #fff);\n}\n\n.cn-graph-canvas .vue-flow__edge-path {\n\tstroke: var(--color-border-dark, #888);\n}\n\n.cn-graph-canvas .vue-flow__edge.selected .vue-flow__edge-path {\n\tstroke: var(--color-primary-element, #0082c9);\n}\n\n/* An edge says which way the data flows, and it can only say it with an\n arrowhead — the direction is the one thing a line cannot express on its own.\n\n ⚠️ `!important` IS LOAD-BEARING HERE, not laziness. Vue Flow writes the\n arrowhead's colour as an INLINE style on the polyline it generates, and an\n inline style beats any class selector. The alternative is passing a colour\n through the marker definition in JS, which would hard-code a hex and take\n the arrowhead out of the theme — it would stay dark grey in dark mode while\n the line it terminates went light. Themed here, it follows the user.\n\n `markerUnits` defaults to `strokeWidth`, so the size set alongside this in\n `useFlowStore.canvasEdges` is what keeps the arrow clear of the target\n node's port handle. */\n.cn-graph-canvas .vue-flow__arrowhead polyline,\n.cn-graph-canvas .vue-flow__arrowhead path {\n\tfill: var(--color-text-maxcontrast, #6b6b6b) !important;\n\tstroke: var(--color-text-maxcontrast, #6b6b6b) !important;\n}\n\n.cn-graph-canvas__controls {\n\tposition: absolute;\n\tbottom: 12px;\n\tleft: 12px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tz-index: 5;\n}\n\n.cn-graph-canvas__control {\n\twidth: 28px;\n\theight: 28px;\n\tborder: 1px solid var(--color-border, #d0d0d0);\n\tbackground: var(--color-main-background, #fff);\n\tcolor: var(--color-main-text, #222);\n\tcursor: pointer;\n}\n\n.cn-graph-canvas__control:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 1px;\n}\n\n.cn-graph-canvas .vue-flow__minimap {\n\tbackground: var(--color-background-dark, #ededed);\n}\n</style>\n"],"names":["VueFlow","Background","MiniMap","CnFlowNode","CnFlowEdge"],"mappings":"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"}
|
|
@@ -43,13 +43,15 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
43
43
|
createVNode(_component_CnFlowNode, mergeProps(nodeProps, {
|
|
44
44
|
deletable: !$props.readOnly,
|
|
45
45
|
onConnect: $options.onConnect,
|
|
46
|
-
onRemove: $options.onNodeRemove
|
|
46
|
+
onRemove: $options.onNodeRemove,
|
|
47
|
+
onActivate: $options.onNodeActivate,
|
|
48
|
+
onMenu: $options.onNodeMenu
|
|
47
49
|
}), {
|
|
48
50
|
default: withCtx((slotProps) => [
|
|
49
51
|
renderSlot(_ctx.$slots, "node", normalizeProps(guardReactiveProps(slotProps)))
|
|
50
52
|
]),
|
|
51
53
|
_: 3 /* FORWARDED */
|
|
52
|
-
}, 16 /* FULL_PROPS */, ["deletable", "onConnect", "onRemove"])
|
|
54
|
+
}, 16 /* FULL_PROPS */, ["deletable", "onConnect", "onRemove", "onActivate", "onMenu"])
|
|
53
55
|
]),
|
|
54
56
|
"edge-default": withCtx((edgeProps) => [
|
|
55
57
|
createVNode(_component_CnFlowEdge, mergeProps(edgeProps, {
|