@dou-ui/flow 0.0.0-stage → 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/collaboration/index.js +360 -0
  4. package/dist/collaboration/index.js.map +1 -0
  5. package/dist/collaboration/peers.js +76 -0
  6. package/dist/collaboration/peers.js.map +1 -0
  7. package/dist/components/default-node.js +6 -0
  8. package/dist/components/default-node.js.map +1 -0
  9. package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js +87 -0
  10. package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
  11. package/dist/components/default-node2.js +116 -0
  12. package/dist/components/default-node2.js.map +1 -0
  13. package/dist/components/flow-controls.js +6 -0
  14. package/dist/components/flow-controls.js.map +1 -0
  15. package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js +148 -0
  16. package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js.map +1 -0
  17. package/dist/components/flow-menu.js +6 -0
  18. package/dist/components/flow-menu.js.map +1 -0
  19. package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js +52 -0
  20. package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  21. package/dist/components/flow-minimap.js +6 -0
  22. package/dist/components/flow-minimap.js.map +1 -0
  23. package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js +238 -0
  24. package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js.map +1 -0
  25. package/dist/components/flow-port.js +6 -0
  26. package/dist/components/flow-port.js.map +1 -0
  27. package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js +57 -0
  28. package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js.map +1 -0
  29. package/dist/components/flow-quick-add.js +6 -0
  30. package/dist/components/flow-quick-add.js.map +1 -0
  31. package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js +126 -0
  32. package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js.map +1 -0
  33. package/dist/components/flow-toolbar.js +6 -0
  34. package/dist/components/flow-toolbar.js.map +1 -0
  35. package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js +161 -0
  36. package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  37. package/dist/components/group-node.js +6 -0
  38. package/dist/components/group-node.js.map +1 -0
  39. package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js +84 -0
  40. package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/components/group-node2.js +42 -0
  42. package/dist/components/group-node2.js.map +1 -0
  43. package/dist/components/node-view.js +6 -0
  44. package/dist/components/node-view.js.map +1 -0
  45. package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js +179 -0
  46. package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js.map +1 -0
  47. package/dist/context.js +13 -0
  48. package/dist/context.js.map +1 -0
  49. package/dist/core/clipboard.js +37 -0
  50. package/dist/core/clipboard.js.map +1 -0
  51. package/dist/core/connect.js +74 -0
  52. package/dist/core/connect.js.map +1 -0
  53. package/dist/core/edge-types.js +240 -0
  54. package/dist/core/edge-types.js.map +1 -0
  55. package/dist/core/extension.js +63 -0
  56. package/dist/core/extension.js.map +1 -0
  57. package/dist/core/geometry.js +232 -0
  58. package/dist/core/geometry.js.map +1 -0
  59. package/dist/core/groups.js +76 -0
  60. package/dist/core/groups.js.map +1 -0
  61. package/dist/core/history.js +79 -0
  62. package/dist/core/history.js.map +1 -0
  63. package/dist/core/model.js +21 -0
  64. package/dist/core/model.js.map +1 -0
  65. package/dist/core/node-types.js +32 -0
  66. package/dist/core/node-types.js.map +1 -0
  67. package/dist/core/order.js +42 -0
  68. package/dist/core/order.js.map +1 -0
  69. package/dist/core/transaction.js +288 -0
  70. package/dist/core/transaction.js.map +1 -0
  71. package/dist/document.js +220 -0
  72. package/dist/document.js.map +1 -0
  73. package/dist/export/color.js +38 -0
  74. package/dist/export/color.js.map +1 -0
  75. package/dist/export/context.js +223 -0
  76. package/dist/export/context.js.map +1 -0
  77. package/dist/export/draw.js +249 -0
  78. package/dist/export/draw.js.map +1 -0
  79. package/dist/flow-editor.js +6 -0
  80. package/dist/flow-editor.js.map +1 -0
  81. package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js +3721 -0
  82. package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
  83. package/dist/index.js +31 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/kit.js +234 -0
  86. package/dist/kit.js.map +1 -0
  87. package/dist/layers.js +150 -0
  88. package/dist/layers.js.map +1 -0
  89. package/dist/locale.js +426 -0
  90. package/dist/locale.js.map +1 -0
  91. package/dist/style.css +1 -0
  92. package/dist/style.d.ts +1 -0
  93. package/dist/style.js +8 -0
  94. package/dist/types/collaboration/index.d.ts +42 -0
  95. package/dist/types/collaboration/peers.d.ts +27 -0
  96. package/dist/types/components/default-node.d.ts +18 -0
  97. package/dist/types/components/default-node.vue.d.ts +6 -0
  98. package/dist/types/components/flow-controls.vue.d.ts +8 -0
  99. package/dist/types/components/flow-menu.vue.d.ts +29 -0
  100. package/dist/types/components/flow-minimap.vue.d.ts +8 -0
  101. package/dist/types/components/flow-port.vue.d.ts +9 -0
  102. package/dist/types/components/flow-quick-add.vue.d.ts +31 -0
  103. package/dist/types/components/flow-toolbar.vue.d.ts +25 -0
  104. package/dist/types/components/group-node.d.ts +13 -0
  105. package/dist/types/components/group-node.vue.d.ts +6 -0
  106. package/dist/types/components/node-view.vue.d.ts +28 -0
  107. package/dist/types/context.d.ts +362 -0
  108. package/dist/types/core/clipboard.d.ts +9 -0
  109. package/dist/types/core/connect.d.ts +63 -0
  110. package/dist/types/core/edge-types.d.ts +50 -0
  111. package/dist/types/core/extension.d.ts +141 -0
  112. package/dist/types/core/geometry.d.ts +63 -0
  113. package/dist/types/core/groups.d.ts +23 -0
  114. package/dist/types/core/history.d.ts +24 -0
  115. package/dist/types/core/menu.d.ts +40 -0
  116. package/dist/types/core/model.d.ts +87 -0
  117. package/dist/types/core/node-types.d.ts +103 -0
  118. package/dist/types/core/order.d.ts +7 -0
  119. package/dist/types/core/transaction.d.ts +97 -0
  120. package/dist/types/document.d.ts +57 -0
  121. package/dist/types/export/color.d.ts +7 -0
  122. package/dist/types/export/context.d.ts +90 -0
  123. package/dist/types/export/draw.d.ts +72 -0
  124. package/dist/types/flow-editor.vue.d.ts +63 -0
  125. package/dist/types/index.d.ts +202 -0
  126. package/dist/types/kit.d.ts +22 -0
  127. package/dist/types/layers.d.ts +24 -0
  128. package/dist/types/locale.d.ts +172 -0
  129. package/dist/types/whiteboard/board-draft.vue.d.ts +3 -0
  130. package/dist/types/whiteboard/board-node.vue.d.ts +19 -0
  131. package/dist/types/whiteboard/connector.d.ts +12 -0
  132. package/dist/types/whiteboard/draw.d.ts +5 -0
  133. package/dist/types/whiteboard/image.d.ts +27 -0
  134. package/dist/types/whiteboard/index.d.ts +6 -0
  135. package/dist/types/whiteboard/ink.d.ts +30 -0
  136. package/dist/types/whiteboard/kit.d.ts +36 -0
  137. package/dist/types/whiteboard/model.d.ts +79 -0
  138. package/dist/types/whiteboard/pen.d.ts +7 -0
  139. package/dist/types/whiteboard/state.d.ts +37 -0
  140. package/dist/types/whiteboard/transform.d.ts +67 -0
  141. package/dist/types/whiteboard/whiteboard-toolbar.vue.d.ts +3 -0
  142. package/dist/whiteboard/board-draft.js +6 -0
  143. package/dist/whiteboard/board-draft.js.map +1 -0
  144. package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js +58 -0
  145. package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js.map +1 -0
  146. package/dist/whiteboard/board-node.js +6 -0
  147. package/dist/whiteboard/board-node.js.map +1 -0
  148. package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js +332 -0
  149. package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
  150. package/dist/whiteboard/connector.js +180 -0
  151. package/dist/whiteboard/connector.js.map +1 -0
  152. package/dist/whiteboard/draw.js +68 -0
  153. package/dist/whiteboard/draw.js.map +1 -0
  154. package/dist/whiteboard/image.js +104 -0
  155. package/dist/whiteboard/image.js.map +1 -0
  156. package/dist/whiteboard/index.js +5 -0
  157. package/dist/whiteboard/ink.js +168 -0
  158. package/dist/whiteboard/ink.js.map +1 -0
  159. package/dist/whiteboard/kit.js +406 -0
  160. package/dist/whiteboard/kit.js.map +1 -0
  161. package/dist/whiteboard/model.js +52 -0
  162. package/dist/whiteboard/model.js.map +1 -0
  163. package/dist/whiteboard/pen.js +88 -0
  164. package/dist/whiteboard/pen.js.map +1 -0
  165. package/dist/whiteboard/state.js +26 -0
  166. package/dist/whiteboard/state.js.map +1 -0
  167. package/dist/whiteboard/transform.js +256 -0
  168. package/dist/whiteboard/transform.js.map +1 -0
  169. package/dist/whiteboard/whiteboard-toolbar.js +6 -0
  170. package/dist/whiteboard/whiteboard-toolbar.js.map +1 -0
  171. package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js +171 -0
  172. package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  173. package/package.json +74 -4
@@ -0,0 +1,161 @@
1
+ import { useFlowEditor } from "../context.js";
2
+ import { DouTooltip } from "dou-ui";
3
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, ref, renderList, resolveDynamicComponent, shallowReactive, unref, useTemplateRef, vShow, watch, withCtx, withDirectives } from "vue";
4
+ // src/components/flow-toolbar.vue?vue&type=script&setup=true&lang.ts
5
+ var _hoisted_1 = ["aria-label"];
6
+ var _hoisted_2 = {
7
+ key: 0,
8
+ class: "dou-flow-controls__divider"
9
+ };
10
+ var _hoisted_3 = [
11
+ "disabled",
12
+ "aria-label",
13
+ "aria-pressed",
14
+ "aria-haspopup",
15
+ "aria-expanded",
16
+ "onClick"
17
+ ];
18
+ var GAP = 8;
19
+ var flow_toolbar_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
20
+ __name: "flow-toolbar",
21
+ props: {
22
+ groups: {},
23
+ anchor: {},
24
+ size: {}
25
+ },
26
+ setup(__props) {
27
+ const props = __props;
28
+ const { messages, root, showMenu, menuFrom } = useFlowEditor();
29
+ const opensMenu = (item) => !!item.children?.length;
30
+ function press(item, event) {
31
+ if (!opensMenu(item)) return item.run?.();
32
+ const button = event.currentTarget;
33
+ const box = button.getBoundingClientRect();
34
+ showMenu(item.children, {
35
+ x: box.left,
36
+ y: box.bottom + 4
37
+ }, button, { focusChecked: event.detail === 0 });
38
+ }
39
+ const warned = /* @__PURE__ */ new Set();
40
+ /** Says once when a button does nothing when pressed. */
41
+ function warnOnce(item) {
42
+ if (process.env.NODE_ENV === "production" || warned.has(item.key)) return;
43
+ if (item.run || opensMenu(item)) return;
44
+ warned.add(item.key);
45
+ console.warn(`[@dou-ui/flow] the toolbar item "${item.key}" has no run or children: pressing it does nothing`);
46
+ }
47
+ watch(() => props.groups, (groups) => groups.flat().forEach(warnOnce), { immediate: true });
48
+ const el = useTemplateRef("el");
49
+ const box = ref({
50
+ width: 0,
51
+ height: 0
52
+ });
53
+ watch([el, () => props.groups], () => {
54
+ if (el.value) box.value = {
55
+ width: el.value.offsetWidth,
56
+ height: el.value.offsetHeight
57
+ };
58
+ }, { flush: "post" });
59
+ /** Room between the toolbar and the selection, and the editor's edges. */
60
+ const measured = ref(0);
61
+ watch(() => props.anchor, () => measured.value++, {
62
+ flush: "post",
63
+ immediate: true
64
+ });
65
+ /**
66
+ * Boxes of what the toolbar must not cover, from the editor's top left: what
67
+ * floats over the canvas, e.g. a whiteboard's tools, and handles on the
68
+ * selection, e.g. a whiteboard's turn handle.
69
+ */
70
+ function overlays() {
71
+ measured.value;
72
+ const editor = root.value;
73
+ if (!editor) return [];
74
+ const origin = editor.getBoundingClientRect();
75
+ return [...editor.querySelectorAll("[data-flow-overlay]")].map((item) => {
76
+ const r = item.getBoundingClientRect();
77
+ return {
78
+ left: r.left - origin.left,
79
+ top: r.top - origin.top,
80
+ right: r.right - origin.left,
81
+ bottom: r.bottom - origin.top
82
+ };
83
+ });
84
+ }
85
+ const place = computed(() => {
86
+ const { width, height } = box.value;
87
+ const { anchor, size } = props;
88
+ const left = Math.min(Math.max(anchor.x - width / 2, GAP), size.width - width - GAP);
89
+ const above = anchor.top - GAP - height;
90
+ const under = anchor.bottom + GAP;
91
+ const boxes = [...overlays(), ...anchor.avoid ?? []];
92
+ const hits = (top) => boxes.filter((o) => left < o.right && left + width > o.left && top < o.bottom && top + height > o.top);
93
+ const fits = (top) => top >= GAP && top + height <= size.height - GAP && !hits(top).length;
94
+ const candidates = [above, under];
95
+ const over = hits(above);
96
+ if (over.length) candidates.push(Math.min(...over.map((o) => o.top)) - GAP - height);
97
+ const beneath = hits(under);
98
+ if (beneath.length) candidates.push(Math.max(...beneath.map((o) => o.bottom)) + GAP);
99
+ const top = candidates.find(fits) ?? (above >= GAP ? above : under);
100
+ return {
101
+ left,
102
+ top: Math.min(Math.max(top, GAP), size.height - height - GAP)
103
+ };
104
+ });
105
+ const style = computed(() => ({
106
+ left: `${Math.round(place.value.left)}px`,
107
+ top: `${Math.round(place.value.top)}px`
108
+ }));
109
+ /** Tips open away from the selection, so they never cover it. */
110
+ const tipPlacement = computed(() => place.value.top < props.anchor.top ? "top" : "bottom");
111
+ /** The buttons by key, to tell which one opened the menu. */
112
+ const buttons = shallowReactive(/* @__PURE__ */ new Map());
113
+ function setButton(key, button) {
114
+ if (button instanceof Element) buttons.set(key, button);
115
+ else buttons.delete(key);
116
+ }
117
+ const hidden = computed(() => props.anchor.bottom < 0 || props.anchor.top > props.size.height || props.anchor.x < 0 || props.anchor.x > props.size.width);
118
+ return (_ctx, _cache) => {
119
+ return withDirectives((openBlock(), createElementBlock("div", {
120
+ ref_key: "el",
121
+ ref: el,
122
+ class: "dou-flow-toolbar",
123
+ role: "toolbar",
124
+ "aria-label": unref(messages).toolbar,
125
+ style: normalizeStyle(style.value)
126
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.groups, (group, i) => {
127
+ return openBlock(), createElementBlock(Fragment, { key: i }, [i > 0 ? (openBlock(), createElementBlock("span", _hoisted_2)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(group, (item) => {
128
+ return openBlock(), createBlock(unref(DouTooltip), {
129
+ key: item.key,
130
+ content: item.label,
131
+ shortcut: item.shortcut,
132
+ disabled: !!unref(menuFrom) && unref(menuFrom) === unref(buttons).get(item.key),
133
+ placement: tipPlacement.value
134
+ }, {
135
+ default: withCtx(() => [createElementVNode("button", {
136
+ ref_for: true,
137
+ ref: (button) => setButton(item.key, button),
138
+ type: "button",
139
+ class: normalizeClass(["dou-flow-controls__button", { "is-active": item.pressed }]),
140
+ disabled: item.disabled,
141
+ "aria-label": item.label,
142
+ "aria-pressed": opensMenu(item) ? void 0 : item.pressed ?? void 0,
143
+ "aria-haspopup": opensMenu(item) ? "menu" : void 0,
144
+ "aria-expanded": opensMenu(item) ? !!unref(menuFrom) && unref(menuFrom) === unref(buttons).get(item.key) : void 0,
145
+ onClick: ($event) => press(item, $event)
146
+ }, [(openBlock(), createBlock(resolveDynamicComponent(item.icon)))], 10, _hoisted_3)]),
147
+ _: 2
148
+ }, 1032, [
149
+ "content",
150
+ "shortcut",
151
+ "disabled",
152
+ "placement"
153
+ ]);
154
+ }), 128))], 64);
155
+ }), 128))], 12, _hoisted_1)), [[vShow, !hidden.value]]);
156
+ };
157
+ }
158
+ });
159
+ export { flow_toolbar_vue_vue_type_script_setup_true_lang_default as default };
160
+
161
+ //# sourceMappingURL=flow-toolbar.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-toolbar.vue_vue_type_script_setup_true_lang.js","names":["_withDirectives","_openBlock","_createElementBlock","_unref","_normalizeStyle","_Fragment","_renderList","_createCommentVNode","_createBlock","_withCtx","_createElementVNode","_normalizeClass","_resolveDynamicComponent","_vShow"],"sources":["../../src/components/flow-toolbar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { DouTooltip } from 'dou-ui'\nimport { computed, ref, shallowReactive, useTemplateRef, watch } from 'vue'\nimport { useFlowEditor } from '../context'\nimport type { FlowToolbarItem } from '../core/menu'\n\nconst props = defineProps<{\n /** Each extension's buttons, with a divider between them. */\n groups: FlowToolbarItem[][]\n /** The selection on screen, in px from the editor's top left. */\n anchor: {\n x: number\n top: number\n bottom: number\n /** Boxes it should rather not cover, e.g. nodes beside selected edges, in the same px. */\n avoid?: { left: number; top: number; right: number; bottom: number }[]\n }\n size: { width: number; height: number }\n}>()\n\nconst { messages, root, showMenu, menuFrom } = useFlowEditor()\n\nconst opensMenu = (item: FlowToolbarItem) => !!item.children?.length\n\n// A button with `children` opens its choices under it; a click from the\n// keyboard (no pointer, `detail` 0) puts focus on the one in use.\nfunction press(item: FlowToolbarItem, event: MouseEvent) {\n if (!opensMenu(item)) return item.run?.()\n const button = event.currentTarget as HTMLElement\n const box = button.getBoundingClientRect()\n showMenu(item.children!, { x: box.left, y: box.bottom + 4 }, button, {\n focusChecked: event.detail === 0,\n })\n}\n// By key: extensions give new item objects on every update.\nconst warned = new Set<string>()\n/** Says once when a button does nothing when pressed. */\nfunction warnOnce(item: FlowToolbarItem) {\n // the app's bundler replaces this, as for Vue's own warnings\n if (process.env.NODE_ENV === 'production' || warned.has(item.key)) return\n if (item.run || opensMenu(item)) return\n warned.add(item.key)\n console.warn(\n `[@dou-ui/flow] the toolbar item \"${item.key}\" has no run or children: pressing it does nothing`,\n )\n}\nwatch(\n () => props.groups,\n (groups) => groups.flat().forEach(warnOnce),\n { immediate: true },\n)\n\nconst el = useTemplateRef<HTMLElement>('el')\nconst box = ref({ width: 0, height: 0 })\n\nwatch(\n [el, () => props.groups],\n () => {\n if (el.value) box.value = { width: el.value.offsetWidth, height: el.value.offsetHeight }\n },\n { flush: 'post' },\n)\n\n/** Room between the toolbar and the selection, and the editor's edges. */\nconst GAP = 8\n\n/*\n * What it keeps clear of is read from the page, so it is read again once the\n * page has caught up with the selection: a handle drawn for the selected node\n * appears in the same update as the toolbar's new anchor.\n */\nconst measured = ref(0)\nwatch(\n () => props.anchor,\n () => measured.value++,\n { flush: 'post', immediate: true },\n)\n\n/**\n * Boxes of what the toolbar must not cover, from the editor's top left: what\n * floats over the canvas, e.g. a whiteboard's tools, and handles on the\n * selection, e.g. a whiteboard's turn handle.\n */\nfunction overlays() {\n void measured.value\n const editor = root.value\n if (!editor) return []\n const origin = editor.getBoundingClientRect()\n return [...editor.querySelectorAll<HTMLElement>('[data-flow-overlay]')].map((item) => {\n const r = item.getBoundingClientRect()\n return {\n left: r.left - origin.left,\n top: r.top - origin.top,\n right: r.right - origin.left,\n bottom: r.bottom - origin.top,\n }\n })\n}\n\n// Above the selection, below it when there is no room or it would cover\n// something floating there, kept inside the editor.\nconst place = computed(() => {\n const { width, height } = box.value\n const { anchor, size } = props\n const left = Math.min(Math.max(anchor.x - width / 2, GAP), size.width - width - GAP)\n const above = anchor.top - GAP - height\n const under = anchor.bottom + GAP\n const boxes = [...overlays(), ...(anchor.avoid ?? [])]\n const hits = (top: number) =>\n boxes.filter(\n (o) => left < o.right && left + width > o.left && top < o.bottom && top + height > o.top,\n )\n const fits = (top: number) => top >= GAP && top + height <= size.height - GAP && !hits(top).length\n // Above, else below; when something is in the way, just past it.\n const candidates = [above, under]\n const over = hits(above)\n if (over.length) candidates.push(Math.min(...over.map((o) => o.top)) - GAP - height)\n const beneath = hits(under)\n if (beneath.length) candidates.push(Math.max(...beneath.map((o) => o.bottom)) + GAP)\n const top = candidates.find(fits) ?? (above >= GAP ? above : under)\n return { left, top: Math.min(Math.max(top, GAP), size.height - height - GAP) }\n})\nconst style = computed(() => ({\n left: `${Math.round(place.value.left)}px`,\n top: `${Math.round(place.value.top)}px`,\n}))\n/** Tips open away from the selection, so they never cover it. */\nconst tipPlacement = computed(() => (place.value.top < props.anchor.top ? 'top' : 'bottom'))\n\n/** The buttons by key, to tell which one opened the menu. */\nconst buttons = shallowReactive(new Map<string, Element>())\nfunction setButton(key: string, button: unknown) {\n if (button instanceof Element) buttons.set(key, button)\n else buttons.delete(key)\n}\n\n// The selection scrolled out of view: nothing to point at.\nconst hidden = computed(\n () =>\n props.anchor.bottom < 0 ||\n props.anchor.top > props.size.height ||\n props.anchor.x < 0 ||\n props.anchor.x > props.size.width,\n)\n</script>\n\n<template>\n <div\n v-show=\"!hidden\"\n ref=\"el\"\n class=\"dou-flow-toolbar\"\n role=\"toolbar\"\n :aria-label=\"messages.toolbar\"\n :style=\"style\"\n >\n <template v-for=\"(group, i) in groups\" :key=\"i\">\n <span v-if=\"i > 0\" class=\"dou-flow-controls__divider\" />\n <!-- The tip stands aside while the button's menu is open. -->\n <DouTooltip\n v-for=\"item in group\"\n :key=\"item.key\"\n :content=\"item.label\"\n :shortcut=\"item.shortcut\"\n :disabled=\"!!menuFrom && menuFrom === buttons.get(item.key)\"\n :placement=\"tipPlacement\"\n >\n <button\n :ref=\"(button) => setButton(item.key, button)\"\n type=\"button\"\n class=\"dou-flow-controls__button\"\n :class=\"{ 'is-active': item.pressed }\"\n :disabled=\"item.disabled\"\n :aria-label=\"item.label\"\n :aria-pressed=\"opensMenu(item) ? undefined : (item.pressed ?? undefined)\"\n :aria-haspopup=\"opensMenu(item) ? 'menu' : undefined\"\n :aria-expanded=\"\n opensMenu(item) ? !!menuFrom && menuFrom === buttons.get(item.key) : undefined\n \"\n @click=\"press(item, $event)\"\n >\n <component :is=\"item.icon\" />\n </button>\n </DouTooltip>\n </template>\n </div>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import group_node_vue_vue_type_script_setup_true_lang_default from "./group-node.vue_vue_type_script_setup_true_lang.js";
2
+ // src/components/group-node.vue
3
+ var group_node_default = group_node_vue_vue_type_script_setup_true_lang_default;
4
+ export { group_node_default as default };
5
+
6
+ //# sourceMappingURL=group-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-node.js","names":["_sfc_main"],"sources":["../../src/components/group-node.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ChevronDownIcon } from '@dou-ui/icons'\nimport { nextTick, ref, useTemplateRef } from 'vue'\nimport { useFlowEditor } from '../context'\nimport { format } from '../locale'\nimport type { NodeViewProps } from '../core/node-types'\nimport type { GroupNodeData } from './group-node'\n\nconst props = defineProps<NodeViewProps<GroupNodeData>>()\nconst { messages, announce } = useFlowEditor()\n\nconst editing = ref(false)\nconst draft = ref('')\nconst input = useTemplateRef<HTMLInputElement>('input')\n\nfunction edit() {\n if (props.readonly) return\n draft.value = props.data.label ?? ''\n editing.value = true\n void nextTick(() => {\n input.value?.focus()\n input.value?.select()\n })\n}\n\nfunction finish(save: boolean) {\n if (!editing.value) return\n editing.value = false\n if (save && draft.value.trim() !== (props.data.label ?? ''))\n props.updateData({ label: draft.value.trim() })\n}\n\nfunction toggle() {\n const collapsed = !props.data.collapsed\n props.updateData({ collapsed: collapsed || undefined })\n const t = messages.value\n announce(format(collapsed ? t.collapsed : t.expanded, { group: props.data.label || t.group }))\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.isComposing) return\n if (event.key === 'Enter') finish(true)\n else if (event.key === 'Escape') finish(false)\n else return\n event.preventDefault()\n event.stopPropagation()\n}\n</script>\n\n<template>\n <div class=\"dou-flow-group-node\" :class=\"{ 'is-collapsed': data.collapsed }\">\n <div class=\"dou-flow-group-node__head\">\n <button\n type=\"button\"\n class=\"dou-flow-group-node__toggle\"\n :aria-label=\"data.collapsed ? messages.expand : messages.collapse\"\n :aria-expanded=\"!data.collapsed\"\n :disabled=\"readonly\"\n @click=\"toggle\"\n >\n <ChevronDownIcon />\n </button>\n <input\n v-if=\"editing\"\n ref=\"input\"\n v-model=\"draft\"\n class=\"dou-flow-group-node__input\"\n :aria-label=\"messages.groupName\"\n @keydown=\"onKeydown\"\n @blur=\"finish(true)\"\n />\n <span v-else class=\"dou-flow-group-node__title\" @dblclick.stop=\"edit\">\n {{ data.label || messages.group }}\n </span>\n </div>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,84 @@
1
+ import { useFlowEditor } from "../context.js";
2
+ import { format } from "../locale.js";
3
+ import { createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, openBlock, ref, toDisplayString, unref, useTemplateRef, vModelText, withDirectives, withModifiers } from "vue";
4
+ import { ChevronDownIcon } from "@dou-ui/icons";
5
+ // src/components/group-node.vue?vue&type=script&setup=true&lang.ts
6
+ var _hoisted_1 = { class: "dou-flow-group-node__head" };
7
+ var _hoisted_2 = [
8
+ "aria-label",
9
+ "aria-expanded",
10
+ "disabled"
11
+ ];
12
+ var _hoisted_3 = ["aria-label"];
13
+ var group_node_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
14
+ __name: "group-node",
15
+ props: {
16
+ node: {},
17
+ data: {},
18
+ ports: {},
19
+ selected: { type: Boolean },
20
+ readonly: { type: Boolean },
21
+ updateData: { type: Function }
22
+ },
23
+ setup(__props) {
24
+ const props = __props;
25
+ const { messages, announce } = useFlowEditor();
26
+ const editing = ref(false);
27
+ const draft = ref("");
28
+ const input = useTemplateRef("input");
29
+ function edit() {
30
+ if (props.readonly) return;
31
+ draft.value = props.data.label ?? "";
32
+ editing.value = true;
33
+ nextTick(() => {
34
+ input.value?.focus();
35
+ input.value?.select();
36
+ });
37
+ }
38
+ function finish(save) {
39
+ if (!editing.value) return;
40
+ editing.value = false;
41
+ if (save && draft.value.trim() !== (props.data.label ?? "")) props.updateData({ label: draft.value.trim() });
42
+ }
43
+ function toggle() {
44
+ const collapsed = !props.data.collapsed;
45
+ props.updateData({ collapsed: collapsed || void 0 });
46
+ const t = messages.value;
47
+ announce(format(collapsed ? t.collapsed : t.expanded, { group: props.data.label || t.group }));
48
+ }
49
+ function onKeydown(event) {
50
+ if (event.isComposing) return;
51
+ if (event.key === "Enter") finish(true);
52
+ else if (event.key === "Escape") finish(false);
53
+ else return;
54
+ event.preventDefault();
55
+ event.stopPropagation();
56
+ }
57
+ return (_ctx, _cache) => {
58
+ return openBlock(), createElementBlock("div", { class: normalizeClass(["dou-flow-group-node", { "is-collapsed": __props.data.collapsed }]) }, [createElementVNode("div", _hoisted_1, [createElementVNode("button", {
59
+ type: "button",
60
+ class: "dou-flow-group-node__toggle",
61
+ "aria-label": __props.data.collapsed ? unref(messages).expand : unref(messages).collapse,
62
+ "aria-expanded": !__props.data.collapsed,
63
+ disabled: __props.readonly,
64
+ onClick: toggle
65
+ }, [createVNode(unref(ChevronDownIcon))], 8, _hoisted_2), editing.value ? withDirectives((openBlock(), createElementBlock("input", {
66
+ key: 0,
67
+ ref_key: "input",
68
+ ref: input,
69
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => draft.value = $event),
70
+ class: "dou-flow-group-node__input",
71
+ "aria-label": unref(messages).groupName,
72
+ onKeydown,
73
+ onBlur: _cache[1] || (_cache[1] = ($event) => finish(true))
74
+ }, null, 40, _hoisted_3)), [[vModelText, draft.value]]) : (openBlock(), createElementBlock("span", {
75
+ key: 1,
76
+ class: "dou-flow-group-node__title",
77
+ onDblclick: withModifiers(edit, ["stop"])
78
+ }, toDisplayString(__props.data.label || unref(messages).group), 33))])], 2);
79
+ };
80
+ }
81
+ });
82
+ export { group_node_vue_vue_type_script_setup_true_lang_default as default };
83
+
84
+ //# sourceMappingURL=group-node.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-node.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_createElementVNode","_unref","_createVNode","_withDirectives","_vModelText","_withModifiers","_toDisplayString"],"sources":["../../src/components/group-node.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ChevronDownIcon } from '@dou-ui/icons'\nimport { nextTick, ref, useTemplateRef } from 'vue'\nimport { useFlowEditor } from '../context'\nimport { format } from '../locale'\nimport type { NodeViewProps } from '../core/node-types'\nimport type { GroupNodeData } from './group-node'\n\nconst props = defineProps<NodeViewProps<GroupNodeData>>()\nconst { messages, announce } = useFlowEditor()\n\nconst editing = ref(false)\nconst draft = ref('')\nconst input = useTemplateRef<HTMLInputElement>('input')\n\nfunction edit() {\n if (props.readonly) return\n draft.value = props.data.label ?? ''\n editing.value = true\n void nextTick(() => {\n input.value?.focus()\n input.value?.select()\n })\n}\n\nfunction finish(save: boolean) {\n if (!editing.value) return\n editing.value = false\n if (save && draft.value.trim() !== (props.data.label ?? ''))\n props.updateData({ label: draft.value.trim() })\n}\n\nfunction toggle() {\n const collapsed = !props.data.collapsed\n props.updateData({ collapsed: collapsed || undefined })\n const t = messages.value\n announce(format(collapsed ? t.collapsed : t.expanded, { group: props.data.label || t.group }))\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.isComposing) return\n if (event.key === 'Enter') finish(true)\n else if (event.key === 'Escape') finish(false)\n else return\n event.preventDefault()\n event.stopPropagation()\n}\n</script>\n\n<template>\n <div class=\"dou-flow-group-node\" :class=\"{ 'is-collapsed': data.collapsed }\">\n <div class=\"dou-flow-group-node__head\">\n <button\n type=\"button\"\n class=\"dou-flow-group-node__toggle\"\n :aria-label=\"data.collapsed ? messages.expand : messages.collapse\"\n :aria-expanded=\"!data.collapsed\"\n :disabled=\"readonly\"\n @click=\"toggle\"\n >\n <ChevronDownIcon />\n </button>\n <input\n v-if=\"editing\"\n ref=\"input\"\n v-model=\"draft\"\n class=\"dou-flow-group-node__input\"\n :aria-label=\"messages.groupName\"\n @keydown=\"onKeydown\"\n @blur=\"finish(true)\"\n />\n <span v-else class=\"dou-flow-group-node__title\" @dblclick.stop=\"edit\">\n {{ data.label || messages.group }}\n </span>\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;EAQA,MAAM,QAAQ;EACd,MAAM,EAAE,UAAU,aAAa,cAAc;EAE7C,MAAM,UAAU,IAAI,KAAK;EACzB,MAAM,QAAQ,IAAI,EAAE;EACpB,MAAM,QAAQ,eAAiC,OAAO;EAEtD,SAAS,OAAO;GACd,IAAI,MAAM,UAAU;GACpB,MAAM,QAAQ,MAAM,KAAK,SAAS;GAClC,QAAQ,QAAQ;GAChB,eAAoB;IAClB,MAAM,OAAO,MAAM;IACnB,MAAM,OAAO,OAAO;GACtB,CAAC;EACH;EAEA,SAAS,OAAO,MAAe;GAC7B,IAAI,CAAC,QAAQ,OAAO;GACpB,QAAQ,QAAQ;GAChB,IAAI,QAAQ,MAAM,MAAM,KAAK,OAAO,MAAM,KAAK,SAAS,KACtD,MAAM,WAAW,EAAE,OAAO,MAAM,MAAM,KAAK,EAAE,CAAC;EAClD;EAEA,SAAS,SAAS;GAChB,MAAM,YAAY,CAAC,MAAM,KAAK;GAC9B,MAAM,WAAW,EAAE,WAAW,aAAa,KAAA,EAAU,CAAC;GACtD,MAAM,IAAI,SAAS;GACnB,SAAS,OAAO,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,MAAM,KAAK,SAAS,EAAE,MAAM,CAAC,CAAC;EAC/F;EAEA,SAAS,UAAU,OAAsB;GACvC,IAAI,MAAM,aAAa;GACvB,IAAI,MAAM,QAAQ,SAAS,OAAO,IAAI;QACjC,IAAI,MAAM,QAAQ,UAAU,OAAO,KAAK;QACxC;GACL,MAAM,eAAe;GACrB,MAAM,gBAAgB;EACxB;;GAIE,OAAAA,UAAA,GAAAC,mBAyBM,OAAA,EAzBD,OAAKC,eAAA,CAAC,uBAAqB,EAAA,gBAA2B,QAAA,KAAK,UAAS,CAAA,CAAA,E,IACvEC,mBAuBM,OAvBN,YAuBM,CAtBJA,mBASS,UAAA;IARP,MAAK;IACL,OAAM;IACL,cAAY,QAAA,KAAK,YAAYC,MAAA,QAAA,CAAQ,CAAC,SAASA,MAAA,QAAA,CAAQ,CAAC;IACxD,iBAAa,CAAG,QAAA,KAAK;IACrB,UAAU,QAAA;IACV,SAAO;G,IAERC,YAAmBD,MAAA,eAAA,CAAA,C,mBAGb,QAAA,QADRE,gBAAAN,UAAA,GAAAC,mBAQE,SAAA;;IANI,SAAA;IAAJ,KAAI;IACK,uBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,MAAK,QAAA;IACd,OAAM;IACL,cAAYG,MAAA,QAAA,CAAQ,CAAC;IACZ;IACT,QAAI,OAAA,OAAA,OAAA,MAAA,WAAE,OAAM,IAAA;G,4BAJJ,CAAAG,YAAA,MAAA,KAAK,C,MAMhBP,UAAA,GAAAC,mBAEO,QAAA;;IAFM,OAAM;IAA8B,YAAQO,cAAO,MAAI,CAAA,MAAA,CAAA;GAC/D,GAAAC,gBAAA,QAAA,KAAK,SAASL,MAAA,QAAA,CAAQ,CAAC,KAAK,GAAA,EAAA,E"}
@@ -0,0 +1,42 @@
1
+ import { defineNodeType } from "../core/node-types.js";
2
+ import { drawCard } from "../export/draw.js";
3
+ import group_node_default from "./group-node.js";
4
+ // src/components/group-node.ts
5
+ /**
6
+ * A titled frame that holds other nodes: nodes dropped inside move with it,
7
+ * and it folds down to its title. Not in the palette; add
8
+ * `{ ...groupNode, palette: { label } }` to offer it there.
9
+ */
10
+ var groupNode = defineNodeType({
11
+ type: "group",
12
+ component: group_node_default,
13
+ container: true,
14
+ resizable: {
15
+ minWidth: 160,
16
+ minHeight: 96
17
+ },
18
+ size: {
19
+ width: 320,
20
+ height: 200
21
+ },
22
+ defaults: () => ({ label: "" }),
23
+ draw(node, draw) {
24
+ const title = draw.style("dou-flow-group-node__title");
25
+ return drawCard(draw, ["is-container"].concat(node.data?.collapsed ? ["is-collapsed"] : [])) + draw.text(node.data?.label || draw.messages.group, {
26
+ x: 12,
27
+ y: 0,
28
+ width: Math.max(0, draw.width - 24),
29
+ height: 40
30
+ }, {
31
+ size: Number.parseFloat(title.fontSize) || 13,
32
+ weight: title.fontWeight,
33
+ color: draw.color(title.color),
34
+ valign: "middle",
35
+ nowrap: true,
36
+ lineHeight: 40
37
+ });
38
+ }
39
+ });
40
+ export { groupNode };
41
+
42
+ //# sourceMappingURL=group-node2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-node2.js","names":["GroupNode"],"sources":["../../src/components/group-node.ts"],"sourcesContent":["import { defineNodeType } from '../core/node-types'\nimport { drawCard } from '../export/draw'\nimport GroupNode from './group-node.vue'\n\nexport interface GroupNodeData {\n [key: string]: unknown\n /** The title. */\n label?: string\n /** Folded to its title; the nodes inside are hidden and their edges end on it. */\n collapsed?: boolean\n}\n\n/**\n * A titled frame that holds other nodes: nodes dropped inside move with it,\n * and it folds down to its title. Not in the palette; add\n * `{ ...groupNode, palette: { label } }` to offer it there.\n */\nexport const groupNode = defineNodeType<GroupNodeData>({\n type: 'group',\n component: GroupNode,\n container: true,\n resizable: { minWidth: 160, minHeight: 96 },\n size: { width: 320, height: 200 },\n defaults: () => ({ label: '' }),\n draw(node, draw) {\n const title = draw.style('dou-flow-group-node__title')\n // a picture has nothing to fold: no toggle, the title where it starts\n return (\n drawCard(draw, ['is-container'].concat(node.data?.collapsed ? ['is-collapsed'] : [])) +\n draw.text(\n node.data?.label || draw.messages.group,\n { x: 12, y: 0, width: Math.max(0, draw.width - 24), height: 40 },\n {\n size: Number.parseFloat(title.fontSize) || 13,\n weight: title.fontWeight,\n color: draw.color(title.color),\n valign: 'middle',\n nowrap: true,\n lineHeight: 40,\n },\n )\n )\n },\n})\n"],"mappings":";;;;;;;;;AAiBA,IAAa,YAAY,eAA8B;CACrD,MAAM;CACN,WAAWA;CACX,WAAW;CACX,WAAW;EAAE,UAAU;EAAK,WAAW;CAAG;CAC1C,MAAM;EAAE,OAAO;EAAK,QAAQ;CAAI;CAChC,iBAAiB,EAAE,OAAO,GAAG;CAC7B,KAAK,MAAM,MAAM;EACf,MAAM,QAAQ,KAAK,MAAM,4BAA4B;EAErD,OACE,SAAS,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,KAAK,MAAM,YAAY,CAAC,cAAc,IAAI,CAAC,CAAC,CAAC,IACpF,KAAK,KACH,KAAK,MAAM,SAAS,KAAK,SAAS,OAClC;GAAE,GAAG;GAAI,GAAG;GAAG,OAAO,KAAK,IAAI,GAAG,KAAK,QAAQ,EAAE;GAAG,QAAQ;EAAG,GAC/D;GACE,MAAM,OAAO,WAAW,MAAM,QAAQ,KAAK;GAC3C,QAAQ,MAAM;GACd,OAAO,KAAK,MAAM,MAAM,KAAK;GAC7B,QAAQ;GACR,QAAQ;GACR,YAAY;EACd,CACF;CAEJ;AACF,CAAC"}
@@ -0,0 +1,6 @@
1
+ import node_view_vue_vue_type_script_setup_true_lang_default from "./node-view.vue_vue_type_script_setup_true_lang.js";
2
+ // src/components/node-view.vue
3
+ var node_view_default = node_view_vue_vue_type_script_setup_true_lang_default;
4
+ export { node_view_default as default };
5
+
6
+ //# sourceMappingURL=node-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-view.js","names":["_sfc_main"],"sources":["../../src/components/node-view.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n provide,\n shallowRef,\n useTemplateRef,\n watch,\n} from 'vue'\nimport type { CSSProperties } from 'vue'\nimport { flowNodeKey, type NodeMetrics, type PortAnchor, type PortState } from '../context'\nimport type { FlowNode, FlowPortSpec, PortSide } from '../core/model'\nimport { portSide, portsOf, resizeOf, type NodeTypeSpec } from '../core/node-types'\nimport DefaultNode from './default-node.vue'\nimport FlowPortView from './flow-port.vue'\n\nconst props = defineProps<{\n node: FlowNode\n spec: NodeTypeSpec | undefined\n selected: boolean\n readonly: boolean\n dragging: boolean\n connected: Set<string>\n portStates: Map<string, PortState> | null\n roleDescription: string\n /** The node's accessible name. */\n label: string\n /** Read out after the name, e.g. the port counts. */\n description: string\n /** Where the nodes being dragged will be put. */\n dropTarget?: boolean\n}>()\nconst emit = defineEmits<{\n measure: [id: string, metrics: NodeMetrics]\n update: [id: string, patch: Record<string, unknown>]\n}>()\n\nconst el = useTemplateRef<HTMLElement>('el')\nconst ports = computed(() => portsOf(props.spec, props.node))\n\n// Ports placed by the node's component, and the ones the editor places.\nconst placed = new Map<string, HTMLElement>()\nconst autoPlaced = new Map<string, HTMLElement>()\nconst placedIds = shallowRef(new Set<string>())\nconst mounted = shallowRef(false)\n\nprovide(flowNodeKey, {\n node: computed(() => props.node),\n ports,\n connected: computed(() => props.connected),\n portStates: computed(() => props.portStates),\n registerPort(id, portEl, auto) {\n if (auto) autoPlaced.set(id, portEl)\n else {\n placed.set(id, portEl)\n placedIds.value = new Set(placed.keys())\n }\n scheduleMeasure()\n },\n unregisterPort(id, auto) {\n if (auto) autoPlaced.delete(id)\n else {\n placed.delete(id)\n placedIds.value = new Set(placed.keys())\n }\n scheduleMeasure()\n },\n})\n\nconst autoPorts = computed(() =>\n mounted.value ? ports.value.filter((port) => !placedIds.value.has(port.id)) : [],\n)\n\n/** Spreads the editor-placed ports evenly along each side. */\nfunction autoStyle(port: FlowPortSpec): CSSProperties {\n const side = portSide(port)\n const same = autoPorts.value.filter((p) => portSide(p) === side)\n const at = `${((same.indexOf(port) + 1) / (same.length + 1)) * 100}%`\n return side === 'left' || side === 'right' ? { top: at } : { left: at }\n}\n\nfunction measure() {\n const node = el.value\n if (!node || !node.offsetWidth) return\n const box = node.getBoundingClientRect()\n // The canvas is scaled; measure in its own units.\n const scale = box.width / node.offsetWidth || 1\n const anchors = new Map<string, PortAnchor>()\n for (const port of ports.value) {\n const portEl = placed.get(port.id) ?? autoPlaced.get(port.id)\n if (!portEl) continue\n const rect = portEl.getBoundingClientRect()\n const side: PortSide = portSide(port)\n anchors.set(port.id, {\n x: (rect.left + rect.width / 2 - box.left) / scale,\n y: (rect.top + rect.height / 2 - box.top) / scale,\n side,\n })\n }\n emit('measure', props.node.id, {\n width: node.offsetWidth,\n height: node.offsetHeight,\n ports: anchors,\n })\n}\n\nlet pending = false\nfunction scheduleMeasure() {\n if (pending) return\n pending = true\n void nextTick(() => {\n pending = false\n measure()\n })\n}\n\nlet observer: ResizeObserver | undefined\nonMounted(() => {\n mounted.value = true\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver(() => measure())\n observer.observe(el.value!)\n }\n scheduleMeasure()\n})\nonBeforeUnmount(() => observer?.disconnect())\n\n// Content changes can move ports without resizing the node.\nwatch(\n () => [props.node.data, props.node.type, ports.value],\n () => scheduleMeasure(),\n { flush: 'post' },\n)\n\nconst container = computed(() => !!props.spec?.container)\n// A collapsed container is as tall as its content, its title.\nconst collapsed = computed(() => container.value && !!props.node.data?.collapsed)\nconst resizable = computed(\n () => !!resizeOf(props.spec, props.node) && !props.readonly && !collapsed.value,\n)\n\n/** How far the node is drawn turned, so its selection outline turns with it. */\nconst rotation = computed(() => props.spec?.rotation?.(props.node) ?? 0)\n\nconst style = computed<CSSProperties>(() => ({\n transform: `translate(${props.node.x}px, ${props.node.y}px)`,\n '--dou-flow-node-rotation': rotation.value ? `${rotation.value}deg` : undefined,\n width: props.node.width != null ? `${props.node.width}px` : undefined,\n height: props.node.height != null && !collapsed.value ? `${props.node.height}px` : undefined,\n}))\n\nconst viewProps = computed(() => ({\n node: props.node,\n data: props.node.data ?? {},\n ports: ports.value,\n selected: props.selected,\n readonly: props.readonly,\n updateData: (patch: Record<string, unknown>) => emit('update', props.node.id, patch),\n}))\n</script>\n\n<template>\n <div\n ref=\"el\"\n class=\"dou-flow-editor__node\"\n :class=\"{\n 'is-selected': selected,\n 'is-locked': node.locked,\n 'is-dragging': dragging,\n 'is-sized': node.width != null,\n 'is-container': container,\n 'is-bare': spec?.frame === false,\n 'is-turned': !!rotation,\n 'is-collapsed': collapsed,\n 'is-drop-target': dropTarget,\n }\"\n :style=\"style\"\n :data-node-id=\"node.id\"\n role=\"group\"\n :aria-roledescription=\"roleDescription\"\n :aria-label=\"label\"\n :aria-description=\"description || undefined\"\n tabindex=\"-1\"\n >\n <component :is=\"spec?.component ?? DefaultNode\" v-bind=\"viewProps\" />\n <FlowPortView\n v-for=\"port in autoPorts\"\n :id=\"port.id\"\n :key=\"port.id\"\n auto\n class=\"is-auto\"\n :style=\"autoStyle(port)\"\n />\n <span v-if=\"resizable && selected\" class=\"dou-flow-editor__resize\" aria-hidden=\"true\" />\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,179 @@
1
+ import { flowNodeKey } from "../context.js";
2
+ import { portSide, portsOf, resizeOf } from "../core/node-types.js";
3
+ import flow_port_default from "./flow-port.js";
4
+ import default_node_default from "./default-node.js";
5
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, defineComponent, guardReactiveProps, nextTick, normalizeClass, normalizeProps, normalizeStyle, onBeforeUnmount, onMounted, openBlock, provide, renderList, resolveDynamicComponent, shallowRef, useTemplateRef, watch } from "vue";
6
+ // src/components/node-view.vue?vue&type=script&setup=true&lang.ts
7
+ var _hoisted_1 = [
8
+ "data-node-id",
9
+ "aria-roledescription",
10
+ "aria-label",
11
+ "aria-description"
12
+ ];
13
+ var _hoisted_2 = {
14
+ key: 0,
15
+ class: "dou-flow-editor__resize",
16
+ "aria-hidden": "true"
17
+ };
18
+ var node_view_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
19
+ __name: "node-view",
20
+ props: {
21
+ node: {},
22
+ spec: {},
23
+ selected: { type: Boolean },
24
+ readonly: { type: Boolean },
25
+ dragging: { type: Boolean },
26
+ connected: {},
27
+ portStates: {},
28
+ roleDescription: {},
29
+ label: {},
30
+ description: {},
31
+ dropTarget: { type: Boolean }
32
+ },
33
+ emits: ["measure", "update"],
34
+ setup(__props, { emit: __emit }) {
35
+ const props = __props;
36
+ const emit = __emit;
37
+ const el = useTemplateRef("el");
38
+ const ports = computed(() => portsOf(props.spec, props.node));
39
+ const placed = /* @__PURE__ */ new Map();
40
+ const autoPlaced = /* @__PURE__ */ new Map();
41
+ const placedIds = shallowRef(/* @__PURE__ */ new Set());
42
+ const mounted = shallowRef(false);
43
+ provide(flowNodeKey, {
44
+ node: computed(() => props.node),
45
+ ports,
46
+ connected: computed(() => props.connected),
47
+ portStates: computed(() => props.portStates),
48
+ registerPort(id, portEl, auto) {
49
+ if (auto) autoPlaced.set(id, portEl);
50
+ else {
51
+ placed.set(id, portEl);
52
+ placedIds.value = new Set(placed.keys());
53
+ }
54
+ scheduleMeasure();
55
+ },
56
+ unregisterPort(id, auto) {
57
+ if (auto) autoPlaced.delete(id);
58
+ else {
59
+ placed.delete(id);
60
+ placedIds.value = new Set(placed.keys());
61
+ }
62
+ scheduleMeasure();
63
+ }
64
+ });
65
+ const autoPorts = computed(() => mounted.value ? ports.value.filter((port) => !placedIds.value.has(port.id)) : []);
66
+ /** Spreads the editor-placed ports evenly along each side. */
67
+ function autoStyle(port) {
68
+ const side = portSide(port);
69
+ const same = autoPorts.value.filter((p) => portSide(p) === side);
70
+ const at = `${(same.indexOf(port) + 1) / (same.length + 1) * 100}%`;
71
+ return side === "left" || side === "right" ? { top: at } : { left: at };
72
+ }
73
+ function measure() {
74
+ const node = el.value;
75
+ if (!node || !node.offsetWidth) return;
76
+ const box = node.getBoundingClientRect();
77
+ const scale = box.width / node.offsetWidth || 1;
78
+ const anchors = /* @__PURE__ */ new Map();
79
+ for (const port of ports.value) {
80
+ const portEl = placed.get(port.id) ?? autoPlaced.get(port.id);
81
+ if (!portEl) continue;
82
+ const rect = portEl.getBoundingClientRect();
83
+ const side = portSide(port);
84
+ anchors.set(port.id, {
85
+ x: (rect.left + rect.width / 2 - box.left) / scale,
86
+ y: (rect.top + rect.height / 2 - box.top) / scale,
87
+ side
88
+ });
89
+ }
90
+ emit("measure", props.node.id, {
91
+ width: node.offsetWidth,
92
+ height: node.offsetHeight,
93
+ ports: anchors
94
+ });
95
+ }
96
+ let pending = false;
97
+ function scheduleMeasure() {
98
+ if (pending) return;
99
+ pending = true;
100
+ nextTick(() => {
101
+ pending = false;
102
+ measure();
103
+ });
104
+ }
105
+ let observer;
106
+ onMounted(() => {
107
+ mounted.value = true;
108
+ if (typeof ResizeObserver !== "undefined") {
109
+ observer = new ResizeObserver(() => measure());
110
+ observer.observe(el.value);
111
+ }
112
+ scheduleMeasure();
113
+ });
114
+ onBeforeUnmount(() => observer?.disconnect());
115
+ watch(() => [
116
+ props.node.data,
117
+ props.node.type,
118
+ ports.value
119
+ ], () => scheduleMeasure(), { flush: "post" });
120
+ const container = computed(() => !!props.spec?.container);
121
+ const collapsed = computed(() => container.value && !!props.node.data?.collapsed);
122
+ const resizable = computed(() => !!resizeOf(props.spec, props.node) && !props.readonly && !collapsed.value);
123
+ /** How far the node is drawn turned, so its selection outline turns with it. */
124
+ const rotation = computed(() => props.spec?.rotation?.(props.node) ?? 0);
125
+ const style = computed(() => ({
126
+ transform: `translate(${props.node.x}px, ${props.node.y}px)`,
127
+ "--dou-flow-node-rotation": rotation.value ? `${rotation.value}deg` : void 0,
128
+ width: props.node.width != null ? `${props.node.width}px` : void 0,
129
+ height: props.node.height != null && !collapsed.value ? `${props.node.height}px` : void 0
130
+ }));
131
+ const viewProps = computed(() => ({
132
+ node: props.node,
133
+ data: props.node.data ?? {},
134
+ ports: ports.value,
135
+ selected: props.selected,
136
+ readonly: props.readonly,
137
+ updateData: (patch) => emit("update", props.node.id, patch)
138
+ }));
139
+ return (_ctx, _cache) => {
140
+ return openBlock(), createElementBlock("div", {
141
+ ref_key: "el",
142
+ ref: el,
143
+ class: normalizeClass(["dou-flow-editor__node", {
144
+ "is-selected": __props.selected,
145
+ "is-locked": __props.node.locked,
146
+ "is-dragging": __props.dragging,
147
+ "is-sized": __props.node.width != null,
148
+ "is-container": container.value,
149
+ "is-bare": __props.spec?.frame === false,
150
+ "is-turned": !!rotation.value,
151
+ "is-collapsed": collapsed.value,
152
+ "is-drop-target": __props.dropTarget
153
+ }]),
154
+ style: normalizeStyle(style.value),
155
+ "data-node-id": __props.node.id,
156
+ role: "group",
157
+ "aria-roledescription": __props.roleDescription,
158
+ "aria-label": __props.label,
159
+ "aria-description": __props.description || void 0,
160
+ tabindex: "-1"
161
+ }, [
162
+ (openBlock(), createBlock(resolveDynamicComponent(__props.spec?.component ?? default_node_default), normalizeProps(guardReactiveProps(viewProps.value)), null, 16)),
163
+ (openBlock(true), createElementBlock(Fragment, null, renderList(autoPorts.value, (port) => {
164
+ return openBlock(), createBlock(flow_port_default, {
165
+ id: port.id,
166
+ key: port.id,
167
+ auto: "",
168
+ class: "is-auto",
169
+ style: normalizeStyle(autoStyle(port))
170
+ }, null, 8, ["id", "style"]);
171
+ }), 128)),
172
+ resizable.value && __props.selected ? (openBlock(), createElementBlock("span", _hoisted_2)) : createCommentVNode("", true)
173
+ ], 14, _hoisted_1);
174
+ };
175
+ }
176
+ });
177
+ export { node_view_vue_vue_type_script_setup_true_lang_default as default };
178
+
179
+ //# sourceMappingURL=node-view.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-view.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createBlock","_resolveDynamicComponent","DefaultNode","_normalizeProps","_guardReactiveProps","_Fragment","_renderList","FlowPortView","_createCommentVNode"],"sources":["../../src/components/node-view.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n provide,\n shallowRef,\n useTemplateRef,\n watch,\n} from 'vue'\nimport type { CSSProperties } from 'vue'\nimport { flowNodeKey, type NodeMetrics, type PortAnchor, type PortState } from '../context'\nimport type { FlowNode, FlowPortSpec, PortSide } from '../core/model'\nimport { portSide, portsOf, resizeOf, type NodeTypeSpec } from '../core/node-types'\nimport DefaultNode from './default-node.vue'\nimport FlowPortView from './flow-port.vue'\n\nconst props = defineProps<{\n node: FlowNode\n spec: NodeTypeSpec | undefined\n selected: boolean\n readonly: boolean\n dragging: boolean\n connected: Set<string>\n portStates: Map<string, PortState> | null\n roleDescription: string\n /** The node's accessible name. */\n label: string\n /** Read out after the name, e.g. the port counts. */\n description: string\n /** Where the nodes being dragged will be put. */\n dropTarget?: boolean\n}>()\nconst emit = defineEmits<{\n measure: [id: string, metrics: NodeMetrics]\n update: [id: string, patch: Record<string, unknown>]\n}>()\n\nconst el = useTemplateRef<HTMLElement>('el')\nconst ports = computed(() => portsOf(props.spec, props.node))\n\n// Ports placed by the node's component, and the ones the editor places.\nconst placed = new Map<string, HTMLElement>()\nconst autoPlaced = new Map<string, HTMLElement>()\nconst placedIds = shallowRef(new Set<string>())\nconst mounted = shallowRef(false)\n\nprovide(flowNodeKey, {\n node: computed(() => props.node),\n ports,\n connected: computed(() => props.connected),\n portStates: computed(() => props.portStates),\n registerPort(id, portEl, auto) {\n if (auto) autoPlaced.set(id, portEl)\n else {\n placed.set(id, portEl)\n placedIds.value = new Set(placed.keys())\n }\n scheduleMeasure()\n },\n unregisterPort(id, auto) {\n if (auto) autoPlaced.delete(id)\n else {\n placed.delete(id)\n placedIds.value = new Set(placed.keys())\n }\n scheduleMeasure()\n },\n})\n\nconst autoPorts = computed(() =>\n mounted.value ? ports.value.filter((port) => !placedIds.value.has(port.id)) : [],\n)\n\n/** Spreads the editor-placed ports evenly along each side. */\nfunction autoStyle(port: FlowPortSpec): CSSProperties {\n const side = portSide(port)\n const same = autoPorts.value.filter((p) => portSide(p) === side)\n const at = `${((same.indexOf(port) + 1) / (same.length + 1)) * 100}%`\n return side === 'left' || side === 'right' ? { top: at } : { left: at }\n}\n\nfunction measure() {\n const node = el.value\n if (!node || !node.offsetWidth) return\n const box = node.getBoundingClientRect()\n // The canvas is scaled; measure in its own units.\n const scale = box.width / node.offsetWidth || 1\n const anchors = new Map<string, PortAnchor>()\n for (const port of ports.value) {\n const portEl = placed.get(port.id) ?? autoPlaced.get(port.id)\n if (!portEl) continue\n const rect = portEl.getBoundingClientRect()\n const side: PortSide = portSide(port)\n anchors.set(port.id, {\n x: (rect.left + rect.width / 2 - box.left) / scale,\n y: (rect.top + rect.height / 2 - box.top) / scale,\n side,\n })\n }\n emit('measure', props.node.id, {\n width: node.offsetWidth,\n height: node.offsetHeight,\n ports: anchors,\n })\n}\n\nlet pending = false\nfunction scheduleMeasure() {\n if (pending) return\n pending = true\n void nextTick(() => {\n pending = false\n measure()\n })\n}\n\nlet observer: ResizeObserver | undefined\nonMounted(() => {\n mounted.value = true\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver(() => measure())\n observer.observe(el.value!)\n }\n scheduleMeasure()\n})\nonBeforeUnmount(() => observer?.disconnect())\n\n// Content changes can move ports without resizing the node.\nwatch(\n () => [props.node.data, props.node.type, ports.value],\n () => scheduleMeasure(),\n { flush: 'post' },\n)\n\nconst container = computed(() => !!props.spec?.container)\n// A collapsed container is as tall as its content, its title.\nconst collapsed = computed(() => container.value && !!props.node.data?.collapsed)\nconst resizable = computed(\n () => !!resizeOf(props.spec, props.node) && !props.readonly && !collapsed.value,\n)\n\n/** How far the node is drawn turned, so its selection outline turns with it. */\nconst rotation = computed(() => props.spec?.rotation?.(props.node) ?? 0)\n\nconst style = computed<CSSProperties>(() => ({\n transform: `translate(${props.node.x}px, ${props.node.y}px)`,\n '--dou-flow-node-rotation': rotation.value ? `${rotation.value}deg` : undefined,\n width: props.node.width != null ? `${props.node.width}px` : undefined,\n height: props.node.height != null && !collapsed.value ? `${props.node.height}px` : undefined,\n}))\n\nconst viewProps = computed(() => ({\n node: props.node,\n data: props.node.data ?? {},\n ports: ports.value,\n selected: props.selected,\n readonly: props.readonly,\n updateData: (patch: Record<string, unknown>) => emit('update', props.node.id, patch),\n}))\n</script>\n\n<template>\n <div\n ref=\"el\"\n class=\"dou-flow-editor__node\"\n :class=\"{\n 'is-selected': selected,\n 'is-locked': node.locked,\n 'is-dragging': dragging,\n 'is-sized': node.width != null,\n 'is-container': container,\n 'is-bare': spec?.frame === false,\n 'is-turned': !!rotation,\n 'is-collapsed': collapsed,\n 'is-drop-target': dropTarget,\n }\"\n :style=\"style\"\n :data-node-id=\"node.id\"\n role=\"group\"\n :aria-roledescription=\"roleDescription\"\n :aria-label=\"label\"\n :aria-description=\"description || undefined\"\n tabindex=\"-1\"\n >\n <component :is=\"spec?.component ?? DefaultNode\" v-bind=\"viewProps\" />\n <FlowPortView\n v-for=\"port in autoPorts\"\n :id=\"port.id\"\n :key=\"port.id\"\n auto\n class=\"is-auto\"\n :style=\"autoStyle(port)\"\n />\n <span v-if=\"resizable && selected\" class=\"dou-flow-editor__resize\" aria-hidden=\"true\" />\n </div>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { inject } from "vue";
2
+ // src/context.ts
3
+ var flowEditorKey = Symbol("dou-flow-editor");
4
+ /** The surrounding flow editor; for controls and panels placed in its default slot. */
5
+ function useFlowEditor() {
6
+ const context = inject(flowEditorKey, null);
7
+ if (!context) throw new Error("[@dou-ui/flow] useFlowEditor() must be called inside <DouFlowEditor>");
8
+ return context;
9
+ }
10
+ var flowNodeKey = Symbol("dou-flow-node");
11
+ export { flowEditorKey, flowNodeKey, useFlowEditor };
12
+
13
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import type { ContextMenuItem } from 'dou-ui'\nimport type { FlowExportOptions, FlowImageOptions } from './export/draw'\nimport type { FlowTree } from './core/groups'\nimport { inject, type ComputedRef, type InjectionKey, type Ref, type ShallowRef } from 'vue'\nimport type { FlowMessages, FlowMessagesOverride } from './locale'\nimport type {\n FlowData,\n FlowDocument,\n FlowEndpoint,\n FlowNode,\n FlowPortSpec,\n FlowSelection,\n FlowViewport,\n Point,\n PortSide,\n Rect,\n} from './core/model'\nimport type { FlowConnection, PortTypes, PortTypeSpec } from './core/connect'\nimport type { EdgeTypeSpec } from './core/edge-types'\nimport type { FlowExtension } from './core/extension'\nimport type { FlowMenuTarget } from './core/menu'\nimport type { NodeTypeSpec } from './core/node-types'\nimport type { FlowTransaction, FlowTransactionMeta } from './core/transaction'\n\nexport type FlowPanelPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n\nexport interface FlowControlsProps {\n /** Corner of the editor to sit in. */\n placement?: FlowPanelPlacement\n /** Show undo and redo. */\n history?: boolean\n /** Show the tidy button, which lays the nodes out along their edges. */\n tidy?: boolean\n}\n\nexport interface FlowMinimapProps {\n /** Corner of the editor to sit in. */\n placement?: FlowPanelPlacement\n /** Size of the map in px. */\n width?: number\n height?: number\n}\n\n/** What the find panel looks for: text in the nodes' names. */\nexport interface FlowSearchQuery {\n search: string\n /** Tell capital letters from small ones. */\n caseSensitive?: boolean\n}\n\nexport interface FlowEditorProps {\n /**\n * What the editor is made of: node types, port types, rules, keys and\n * layers. `flowKit()` by default.\n */\n extensions?: (FlowExtension | FlowExtension[])[]\n /** Node types besides the extensions'; they win over an extension's type of the same name. */\n nodeTypes?: NodeTypeSpec<any>[]\n /** Edge types besides the extensions'; they win over an extension's type of the same name. */\n edgeTypes?: EdgeTypeSpec<any>[]\n /** The type of edges without `type`, new ones included. `bezier` by default. */\n edgeType?: string\n /**\n * Below this zoom nodes are drawn as plain boxes with their names, which\n * keeps a large flow quick; see the `simple` slot. `0` never does. `0.4` by default.\n */\n simpleZoom?: number\n /** Grid step for snapping and arrow keys, in px. `0` turns snapping to the grid off. */\n grid?: number\n /** Line nodes up with their neighbours while dragging. */\n guides?: boolean\n minZoom?: number\n maxZoom?: number\n /**\n * Which way the flow runs: `horizontal` left to right, `vertical` top to\n * bottom. Tidying lays nodes out this way.\n */\n direction?: 'horizontal' | 'vertical'\n /** Fit all nodes into view once they are measured. */\n fitView?: boolean\n /**\n * The furthest `fit-view` zooms out, so text stays readable; a larger flow\n * starts at its top left and is panned to. `0` always fits everything.\n */\n fitMinZoom?: number\n /** View, select and pan, but not change anything. */\n readonly?: boolean\n /** The built-in find panel (`Ctrl/⌘+F`). Off, the keys are left to the page. On by default. */\n searchable?: boolean\n /** Scrolling pans the canvas; with Ctrl / ⌘ it zooms. Off, only Ctrl / ⌘ + scroll zooms. */\n panOnScroll?: boolean\n /** Looks and connection rules of port types, by the name used in `dataType`. */\n portTypes?: PortTypes\n /** Refuse edges that would close a cycle. */\n acyclic?: boolean\n /**\n * The last check before an edge is made. Return `false` to refuse it, or a\n * sentence saying why. Called for every port while an edge is dragged.\n */\n canConnect?: (connection: FlowConnection, doc: FlowDocument) => boolean | string\n /** Accessible name of the canvas. */\n ariaLabel?: string\n /** Texts to change; only the ones given are replaced. */\n messages?: FlowMessagesOverride\n}\n\nexport interface FlowEditorEmits {\n /** Every change to the document, with its steps; `update:modelValue` follows. */\n transaction: [tr: FlowTransaction]\n /**\n * An extension's `transaction` hook cancelled a change; `tr.cancelReason`\n * says why, and the editor shows it and reads it out.\n */\n transactionCancel: [tr: FlowTransaction]\n nodeClick: [node: FlowNode, event: MouseEvent]\n nodeDblclick: [node: FlowNode, event: MouseEvent]\n nodeContextmenu: [node: FlowNode, event: MouseEvent]\n edgeClick: [edge: FlowDocument['edges'][number], event: MouseEvent]\n edgeContextmenu: [edge: FlowDocument['edges'][number], event: MouseEvent]\n /** Clicking the empty canvas; `point` is in canvas coordinates. */\n canvasClick: [point: Point, event: MouseEvent]\n /**\n * Right click or long press on the empty canvas. The context menu events\n * come before the editor's own menu; `event.preventDefault()` keeps it closed.\n */\n canvasContextmenu: [point: Point, event: MouseEvent]\n /** An edge was drawn, or moved to another port. */\n connect: [edge: FlowDocument['edges'][number]]\n /**\n * A new edge was dropped on empty canvas; `point` is where, in canvas\n * coordinates. The editor then offers the node types that can take the\n * edge; `event.preventDefault()` handles it yourself instead.\n */\n connectToCanvas: [from: Required<FlowEndpoint>, point: Point, event: MouseEvent]\n}\n\nexport interface FlowEditorExpose {\n /** Applies changes as one transaction, one undo step. */\n transact: (fn: (tr: FlowTransaction) => void, meta?: FlowTransactionMeta) => FlowTransaction\n undo: () => boolean\n redo: () => boolean\n readonly canUndo: boolean\n readonly canRedo: boolean\n /** Shows all nodes, or the given ones. */\n fit: (ids?: string[]) => void\n zoomTo: (zoom: number) => void\n zoomIn: () => void\n zoomOut: () => void\n /** Centers a node in view without changing the zoom. */\n centerNode: (id: string) => void\n /** Opens the find panel (`Ctrl/⌘+F`), with a query put in; `false` when `searchable` is off. */\n openSearch: (query?: Partial<FlowSearchQuery>) => boolean\n /** Closes the find panel; `false` when it was not open. */\n closeSearch: () => boolean\n /** The ids of the nodes whose names contain the search, in reading order. */\n findNodes: (query: FlowSearchQuery) => string[]\n /**\n * Moves the view like the user's pans do: no further than having the nodes\n * just leave it. `v-model:viewport` goes anywhere.\n */\n setViewport: (viewport: FlowViewport, animate?: boolean) => void\n /** Converts a point on screen (client coordinates) to canvas coordinates. */\n toCanvas: (point: Point) => Point\n /** A node's measured box in canvas coordinates. */\n getNodeRect: (id: string) => Rect | null\n /**\n * The flow as an SVG document, as it looks now: every node and edge, what\n * is in view, or the selected ones (it throws when nothing is selected). Colors are written as `rgb(…)`, `rgba(…)` or `#rrggbb`, so\n * vector editors read them; nodes are drawn by their type's `draw`.\n */\n exportSVG: (options?: FlowExportOptions) => string\n /** The flow as a PNG image, with images embedded where their address can be read. */\n exportPNG: (options?: FlowImageOptions) => Promise<Blob>\n /**\n * Lays nodes out in columns along their edges, as one undo step: the given\n * ones, the selection when two or more nodes are selected, or all.\n */\n tidy: (ids?: string[]) => void\n /**\n * Ids of edges whose ports no longer fit: a port is gone, or the types of\n * its two ends don't match any more. They are drawn red and dashed.\n */\n readonly staleEdges: string[]\n /** Removes the stale edges as one undo step; returns how many. */\n removeStaleEdges: () => number\n /**\n * Adds a node of a type with its defaults and selects it, as one undo\n * step; returns its id. See `FlowInsertOptions` for where it goes.\n */\n insertNode: (type: string, options?: FlowInsertOptions) => string | null\n /**\n * Starts dragging a new node of a type from `event`, a `pointerdown` on\n * any element on the page; letting go over the canvas adds it there, and\n * Escape cancels. The click that ends a drag is stopped. Returns `false`\n * when nothing was started.\n */\n startInsertDrag: (type: string, event: PointerEvent) => boolean\n /** Node types with a `palette` entry, in the order they were given. */\n readonly paletteTypes: NodeTypeSpec[]\n /** Selects an edge and opens the text field on its label. */\n editLabel: (id: string) => void\n /**\n * Puts nodes, the selected ones by default, into a new container of\n * `type` (`'group'` by default) drawn around them, as one undo step;\n * returns its id, or `null` when nothing was grouped.\n */\n groupNodes: (ids?: string[], type?: string) => string | null\n /** Removes a container and keeps the nodes inside, as one undo step. */\n ungroupNode: (id: string) => boolean\n /**\n * Takes nodes, the selected ones by default, out of their groups and puts\n * them just below, as one undo step; returns how many moved.\n */\n leaveGroup: (ids?: string[]) => number\n}\n\nexport interface FlowEditorSlots {\n /** Panels over the canvas, e.g. `DouFlowControls`. */\n default?: () => unknown\n /** What follows the pointer while a new node is dragged in. */\n ghost?: (props: { spec: NodeTypeSpec; over: boolean }) => unknown\n /** Replaces the list offered when an edge is dropped on the empty canvas. */\n quickAdd?: (props: {\n types: NodeTypeSpec[]\n choose: (type: string) => void\n close: () => void\n }) => unknown\n /** A node's content while zoomed out past `simple-zoom`; its name by default. */\n simple?: (props: { node: FlowNode }) => unknown\n}\n\nexport interface FlowInsertOptions {\n /**\n * Where the node goes, in canvas coordinates: its centre, or with `from`,\n * the port that takes the edge. Without it the node goes after the one\n * selected node, connected to it when it can be, or else in the middle\n * of the view; either way clear of other nodes.\n */\n at?: Point\n /** Connect the new node to this port, through the first port that can take it. */\n from?: Required<FlowEndpoint>\n /** Move focus to the new node, so keys such as undo act on it. `true` by default. */\n focus?: boolean\n /** Merged over the type's `defaults()`. */\n data?: FlowData\n /** The node's size instead of the type's `size`. */\n size?: { width?: number; height?: number }\n}\n\n/** Where a port's centre is, from its node's top left corner. */\nexport interface PortAnchor extends Point {\n side: PortSide\n}\n\nexport interface NodeMetrics {\n width: number\n height: number\n ports: Map<string, PortAnchor>\n}\n\n/** What `previewNode` can show changed: the box, and the data. */\nexport type FlowNodePreview = Partial<Pick<FlowNode, 'x' | 'y' | 'width' | 'height' | 'data'>>\n\n/** How a port shows while an edge is being drawn. */\nexport type PortState = 'active' | 'valid' | 'candidate' | 'refused'\n\nexport interface FlowEditorContext {\n doc: Ref<FlowDocument>\n selection: Ref<FlowSelection>\n viewport: Ref<FlowViewport>\n /** See `FlowEditorExpose.setViewport`; writing `viewport` goes anywhere. */\n setViewport: FlowEditorExpose['setViewport']\n readonly: ComputedRef<boolean>\n messages: ComputedRef<FlowMessages>\n minZoom: ComputedRef<number>\n maxZoom: ComputedRef<number>\n transact: FlowEditorExpose['transact']\n undo: () => boolean\n redo: () => boolean\n canUndo: Ref<boolean>\n canRedo: Ref<boolean>\n fit: FlowEditorExpose['fit']\n zoomTo: FlowEditorExpose['zoomTo']\n zoomIn: () => void\n zoomOut: () => void\n /** The registered type of a port, if any. */\n portTypeOf: (port: FlowPortSpec) => PortTypeSpec | undefined\n /** A port type's color as CSS. */\n colorOf: (spec: PortTypeSpec | undefined) => string | undefined\n getNodeRect: FlowEditorExpose['getNodeRect']\n exportSVG: FlowEditorExpose['exportSVG']\n exportPNG: FlowEditorExpose['exportPNG']\n tidy: FlowEditorExpose['tidy']\n centerNode: FlowEditorExpose['centerNode']\n /** Says `text` to screen readers. */\n announce: (text: string) => void\n /** The editor's size on screen, in px. */\n size: Ref<{ width: number; height: number }>\n selectedNodes: ComputedRef<Set<string>>\n insertNode: FlowEditorExpose['insertNode']\n /** Node types with a `palette` entry, in the order they were given. */\n paletteTypes: ComputedRef<NodeTypeSpec[]>\n startInsertDrag: FlowEditorExpose['startInsertDrag']\n editLabel: FlowEditorExpose['editLabel']\n groupNodes: FlowEditorExpose['groupNodes']\n ungroupNode: FlowEditorExpose['ungroupNode']\n leaveGroup: FlowEditorExpose['leaveGroup']\n /** Which node is inside which, from the nodes' `parent`. */\n tree: ComputedRef<FlowTree>\n /** Whether the node's type can hold other nodes. */\n isContainer: (node: FlowNode) => boolean\n /** How far the node is drawn turned about its centre, from its type's `rotation`. */\n nodeRotation: (id: string) => number\n /**\n * While nodes are moved or resized, or an edge is bent: the document\n * changes on every frame until the pointer is let go.\n */\n gesturing: ComputedRef<boolean>\n /**\n * Shows a node as if `patch` were applied, without changing the document:\n * its frame, ports, edges and `getNodeRect` all follow. For handles that\n * change the document once, when let go; `null` shows it as it is again,\n * and must be passed when the drag ends or is cancelled. `patch.data`\n * replaces the node's data, it is not merged. Only this editor shows it:\n * peers see the change once it is committed. A preview is dropped when\n * its node leaves the document.\n */\n previewNode: (id: string, patch: FlowNodePreview | null) => void\n /** The edge being drawn, as an SVG path in canvas coordinates. */\n connectingPath: ComputedRef<string | null>\n /** The editor's element. */\n root: Readonly<ShallowRef<HTMLElement | null>>\n /**\n * Opens the context menu for a target at a point on screen; `false` when\n * no extension has items for it. Pass the button that opens it as `from`:\n * the menu then opens below the button instead of at the point, or above\n * it when there is no room, and pressing the button again closes it.\n */\n openMenu: (target: FlowMenuTarget, client: Point, from?: Element) => boolean\n /** The items the context menu has for a target, as `openMenu` shows them. */\n menuItems: (target: FlowMenuTarget) => ContextMenuItem[]\n /**\n * Opens the editor's menu with these items, e.g. the choices behind a\n * toolbar button; `from` works as in `openMenu`.\n */\n showMenu: (\n items: ContextMenuItem[],\n client: Point,\n from?: Element,\n options?: { focusChecked?: boolean },\n ) => boolean\n /** The element that opened the menu, while it is open, e.g. to mark a button expanded. */\n menuFrom: Readonly<ShallowRef<Element | null>>\n toCanvas: (point: Point) => Point\n /** Copies the selected nodes to the clipboard; `false` when none are selected. */\n copy: () => boolean\n cut: () => boolean\n /** Pastes what was copied, at `at` (canvas coordinates) or next to the original. */\n paste: (at?: Point) => Promise<boolean>\n /** Adds a copy of the selected nodes next to them. */\n duplicate: () => void\n /** Deletes the selected nodes and edges, except locked nodes. */\n deleteSelection: () => void\n selectAll: () => void\n}\n\nexport const flowEditorKey: InjectionKey<FlowEditorContext> = Symbol('dou-flow-editor')\n\n/** The surrounding flow editor; for controls and panels placed in its default slot. */\nexport function useFlowEditor() {\n const context = inject(flowEditorKey, null)\n if (!context)\n throw new Error('[@dou-ui/flow] useFlowEditor() must be called inside <DouFlowEditor>')\n return context\n}\n\nexport interface FlowNodeContext {\n node: ComputedRef<FlowNode>\n ports: ComputedRef<FlowPortSpec[]>\n /** Port ids with at least one edge. */\n connected: ComputedRef<Set<string>>\n /** While an edge is being drawn, how each port shows; `null` otherwise. */\n portStates: ComputedRef<Map<string, PortState> | null>\n registerPort: (id: string, el: HTMLElement, auto?: boolean) => void\n unregisterPort: (id: string, auto?: boolean) => void\n}\n\nexport const flowNodeKey: InjectionKey<FlowNodeContext> = Symbol('dou-flow-node')\n"],"mappings":";;AA8WA,IAAa,gBAAiD,OAAO,iBAAiB;;AAGtF,SAAgB,gBAAgB;CAC9B,MAAM,UAAU,OAAO,eAAe,IAAI;CAC1C,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,sEAAsE;CACxF,OAAO;AACT;AAaA,IAAa,cAA6C,OAAO,eAAe"}