@dou-ui/flow 0.0.0-stage → 0.1.0-alpha.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.
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,238 @@
1
+ import { useFlowEditor } from "../context.js";
2
+ import { boundsOf } from "../core/geometry.js";
3
+ import { Fragment, computed, createElementBlock, createElementVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, unref, watch } from "vue";
4
+ // src/components/flow-minimap.vue?vue&type=script&setup=true&lang.ts
5
+ var _hoisted_1 = ["aria-label"];
6
+ var _hoisted_2 = [
7
+ "width",
8
+ "height",
9
+ "viewBox"
10
+ ];
11
+ var _hoisted_3 = [
12
+ "x",
13
+ "y",
14
+ "width",
15
+ "height",
16
+ "rx"
17
+ ];
18
+ var _hoisted_4 = ["d"];
19
+ var _hoisted_5 = [
20
+ "x",
21
+ "y",
22
+ "width",
23
+ "height"
24
+ ];
25
+ var LIFT_GAP = 8;
26
+ /**
27
+ * On a narrow editor the map would cover a panel in the same corner row,
28
+ * e.g. the controls: then it sits above (or below) them instead.
29
+ */
30
+ var flow_minimap_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
31
+ name: "DouFlowMinimap",
32
+ __name: "flow-minimap",
33
+ props: {
34
+ placement: { default: "bottom-right" },
35
+ width: { default: 200 },
36
+ height: { default: 136 }
37
+ },
38
+ setup(__props) {
39
+ const props = __props;
40
+ const flow = useFlowEditor();
41
+ const { messages } = flow;
42
+ /** The part of the canvas on screen, in canvas coordinates. */
43
+ const visible = computed(() => {
44
+ const { x, y, zoom } = flow.viewport.value;
45
+ const { width, height } = flow.size.value;
46
+ return {
47
+ x: -x / zoom,
48
+ y: -y / zoom,
49
+ width: width / zoom,
50
+ height: height / zoom
51
+ };
52
+ });
53
+ const rects = computed(() => flow.doc.value.nodes.flatMap((node) => {
54
+ const rect = flow.getNodeRect(node.id);
55
+ return rect ? [{
56
+ id: node.id,
57
+ rect,
58
+ selected: flow.selectedNodes.value.has(node.id)
59
+ }] : [];
60
+ }));
61
+ /** What the map shows: every node and the view, with some room around. */
62
+ const fitted = computed(() => {
63
+ const all = boundsOf([...rects.value.map((r) => r.rect), visible.value]);
64
+ const pad = Math.max(all.width, all.height) * .05;
65
+ return {
66
+ x: all.x - pad,
67
+ y: all.y - pad,
68
+ width: all.width + pad * 2,
69
+ height: all.height + pad * 2
70
+ };
71
+ });
72
+ /**
73
+ * Held still while the view is dragged: a map that grew to take in the view
74
+ * as it went would slide away under the pointer, sending the view further
75
+ * each time.
76
+ */
77
+ const held = ref(null);
78
+ const world = computed(() => held.value ?? fitted.value);
79
+ /** Canvas units per map pixel; the map keeps the canvas's proportions. */
80
+ const scale = computed(() => Math.max(world.value.width / props.width, world.value.height / props.height, 1e-6));
81
+ const viewBox = computed(() => {
82
+ const w = props.width * scale.value;
83
+ const h = props.height * scale.value;
84
+ const { x, y, width, height } = world.value;
85
+ return `${x - (w - width) / 2} ${y - (h - height) / 2} ${w} ${h}`;
86
+ });
87
+ /** Everything outside the view, shaded: the map's box with the view cut out. */
88
+ const shade = computed(() => {
89
+ const [x, y, w, h] = viewBox.value.split(" ").map(Number);
90
+ const v = visible.value;
91
+ return `M${x} ${y}h${w}v${h}h${-w}Z M${v.x} ${v.y}v${v.height}h${v.width}v${-v.height}Z`;
92
+ });
93
+ const svg = ref();
94
+ const dragging = ref(false);
95
+ /** How far along a side `at` is, from 0 to 1, staying on it when past its ends. */
96
+ const along = (at, start, length) => length ? Math.min(1, Math.max(0, (at - start) / length)) : .5;
97
+ /** The point of the map under the pointer; past its edges, the nearest point on them. */
98
+ function toWorld(event) {
99
+ const box = svg.value.getBoundingClientRect();
100
+ const [x, y, w, h] = viewBox.value.split(" ").map(Number);
101
+ return {
102
+ x: x + along(event.clientX, box.left, box.width) * w,
103
+ y: y + along(event.clientY, box.top, box.height) * h
104
+ };
105
+ }
106
+ /** Centres the view on a point of the map, as far as the nodes allow. */
107
+ function moveTo(event) {
108
+ const p = toWorld(event);
109
+ const { zoom } = flow.viewport.value;
110
+ const { width, height } = flow.size.value;
111
+ flow.setViewport({
112
+ x: width / 2 - p.x * zoom,
113
+ y: height / 2 - p.y * zoom,
114
+ zoom
115
+ });
116
+ }
117
+ function onPointerDown(event) {
118
+ if (event.button !== 0) return;
119
+ event.stopPropagation();
120
+ dragging.value = true;
121
+ held.value = fitted.value;
122
+ try {
123
+ svg.value.setPointerCapture(event.pointerId);
124
+ } catch {}
125
+ moveTo(event);
126
+ }
127
+ function onPointerMove(event) {
128
+ if (dragging.value) moveTo(event);
129
+ }
130
+ function onPointerUp() {
131
+ dragging.value = false;
132
+ held.value = null;
133
+ }
134
+ const el = ref();
135
+ /** How far the map moves away from its edge to clear the other panels there, in px. */
136
+ const lift = ref(0);
137
+ function place() {
138
+ const self = el.value;
139
+ const root = flow.root.value;
140
+ if (!self || !root) return;
141
+ const upper = props.placement.startsWith("top");
142
+ const box = self.getBoundingClientRect();
143
+ const top = box.top + (upper ? -lift.value : lift.value);
144
+ const bottom = top + box.height;
145
+ let next = 0;
146
+ for (const panel of root.querySelectorAll(".dou-flow-editor__panels > *")) {
147
+ if (panel === self || panel.classList.contains("dou-flow-minimap")) continue;
148
+ const rect = panel.getBoundingClientRect();
149
+ if (!rect.width || !rect.height) continue;
150
+ if (rect.right <= box.left || rect.left >= box.right) continue;
151
+ if (rect.bottom <= top || rect.top >= bottom) continue;
152
+ next = Math.max(next, upper ? rect.bottom - top + LIFT_GAP : bottom - rect.top + LIFT_GAP);
153
+ }
154
+ lift.value = next;
155
+ }
156
+ let resizes;
157
+ let changes;
158
+ function watchPanels() {
159
+ const panels = el.value?.parentElement;
160
+ if (!panels) return;
161
+ resizes?.disconnect();
162
+ if (typeof ResizeObserver !== "undefined") {
163
+ resizes = new ResizeObserver(() => place());
164
+ for (const panel of panels.children) if (panel !== el.value) resizes.observe(panel);
165
+ }
166
+ place();
167
+ }
168
+ onMounted(() => {
169
+ nextTick(watchPanels);
170
+ const panels = el.value?.parentElement;
171
+ if (panels && typeof MutationObserver !== "undefined") {
172
+ changes = new MutationObserver(watchPanels);
173
+ changes.observe(panels, { childList: true });
174
+ }
175
+ });
176
+ onBeforeUnmount(() => {
177
+ resizes?.disconnect();
178
+ changes?.disconnect();
179
+ });
180
+ watch(() => [
181
+ flow.size.value.width,
182
+ flow.size.value.height,
183
+ props.width,
184
+ props.height,
185
+ props.placement
186
+ ], () => nextTick(place));
187
+ return (_ctx, _cache) => {
188
+ return openBlock(), createElementBlock("div", {
189
+ ref_key: "el",
190
+ ref: el,
191
+ class: normalizeClass(["dou-flow-minimap", [`is-${__props.placement}`, { "is-dragging": dragging.value }]]),
192
+ "data-flow-overlay": "",
193
+ style: normalizeStyle(lift.value ? { translate: `0 ${__props.placement.startsWith("top") ? lift.value : -lift.value}px` } : void 0),
194
+ role: "img",
195
+ "aria-label": unref(messages).minimap
196
+ }, [(openBlock(), createElementBlock("svg", {
197
+ ref_key: "svg",
198
+ ref: svg,
199
+ width: __props.width,
200
+ height: __props.height,
201
+ viewBox: viewBox.value,
202
+ preserveAspectRatio: "xMidYMid meet",
203
+ "aria-hidden": "true",
204
+ onPointerdown: onPointerDown,
205
+ onPointermove: onPointerMove,
206
+ onPointerup: onPointerUp,
207
+ onPointercancel: onPointerUp
208
+ }, [
209
+ (openBlock(true), createElementBlock(Fragment, null, renderList(rects.value, (item) => {
210
+ return openBlock(), createElementBlock("rect", {
211
+ key: item.id,
212
+ class: normalizeClass(["dou-flow-minimap__node", { "is-selected": item.selected }]),
213
+ x: item.rect.x,
214
+ y: item.rect.y,
215
+ width: item.rect.width,
216
+ height: item.rect.height,
217
+ rx: 2 * scale.value
218
+ }, null, 10, _hoisted_3);
219
+ }), 128)),
220
+ createElementVNode("path", {
221
+ class: "dou-flow-minimap__shade",
222
+ d: shade.value,
223
+ "fill-rule": "evenodd"
224
+ }, null, 8, _hoisted_4),
225
+ createElementVNode("rect", {
226
+ class: "dou-flow-minimap__view",
227
+ x: visible.value.x,
228
+ y: visible.value.y,
229
+ width: visible.value.width,
230
+ height: visible.value.height
231
+ }, null, 8, _hoisted_5)
232
+ ], 40, _hoisted_2))], 14, _hoisted_1);
233
+ };
234
+ }
235
+ });
236
+ export { flow_minimap_vue_vue_type_script_setup_true_lang_default as default };
237
+
238
+ //# sourceMappingURL=flow-minimap.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-minimap.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_unref","_Fragment","_renderList","_createElementVNode"],"sources":["../../src/components/flow-minimap.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useFlowEditor, type FlowMinimapProps } from '../context'\nimport { boundsOf } from '../core/geometry'\nimport type { Point, Rect } from '../core/model'\n\ndefineOptions({ name: 'DouFlowMinimap' })\n\nconst props = withDefaults(defineProps<FlowMinimapProps>(), {\n placement: 'bottom-right',\n width: 200,\n height: 136,\n})\n\nconst flow = useFlowEditor()\nconst { messages } = flow\n\n/** The part of the canvas on screen, in canvas coordinates. */\nconst visible = computed<Rect>(() => {\n const { x, y, zoom } = flow.viewport.value\n const { width, height } = flow.size.value\n return { x: -x / zoom, y: -y / zoom, width: width / zoom, height: height / zoom }\n})\n\nconst rects = computed(() =>\n flow.doc.value.nodes.flatMap((node) => {\n const rect = flow.getNodeRect(node.id)\n return rect ? [{ id: node.id, rect, selected: flow.selectedNodes.value.has(node.id) }] : []\n }),\n)\n\n/** What the map shows: every node and the view, with some room around. */\nconst fitted = computed<Rect>(() => {\n const all = boundsOf([...rects.value.map((r) => r.rect), visible.value])!\n const pad = Math.max(all.width, all.height) * 0.05\n return {\n x: all.x - pad,\n y: all.y - pad,\n width: all.width + pad * 2,\n height: all.height + pad * 2,\n }\n})\n\n/**\n * Held still while the view is dragged: a map that grew to take in the view\n * as it went would slide away under the pointer, sending the view further\n * each time.\n */\nconst held = ref<Rect | null>(null)\nconst world = computed(() => held.value ?? fitted.value)\n\n/** Canvas units per map pixel; the map keeps the canvas's proportions. */\nconst scale = computed(() =>\n Math.max(world.value.width / props.width, world.value.height / props.height, 1e-6),\n)\nconst viewBox = computed(() => {\n const w = props.width * scale.value\n const h = props.height * scale.value\n const { x, y, width, height } = world.value\n return `${x - (w - width) / 2} ${y - (h - height) / 2} ${w} ${h}`\n})\n\n/** Everything outside the view, shaded: the map's box with the view cut out. */\nconst shade = computed(() => {\n const [x, y, w, h] = viewBox.value.split(' ').map(Number) as [number, number, number, number]\n const v = visible.value\n return `M${x} ${y}h${w}v${h}h${-w}Z M${v.x} ${v.y}v${v.height}h${v.width}v${-v.height}Z`\n})\n\nconst svg = ref<SVGSVGElement>()\nconst dragging = ref(false)\n\n/** How far along a side `at` is, from 0 to 1, staying on it when past its ends. */\nconst along = (at: number, start: number, length: number) =>\n length ? Math.min(1, Math.max(0, (at - start) / length)) : 0.5\n\n/** The point of the map under the pointer; past its edges, the nearest point on them. */\nfunction toWorld(event: PointerEvent): Point {\n const box = svg.value!.getBoundingClientRect()\n const [x, y, w, h] = viewBox.value.split(' ').map(Number) as [number, number, number, number]\n return {\n x: x + along(event.clientX, box.left, box.width) * w,\n y: y + along(event.clientY, box.top, box.height) * h,\n }\n}\n\n/** Centres the view on a point of the map, as far as the nodes allow. */\nfunction moveTo(event: PointerEvent) {\n const p = toWorld(event)\n const { zoom } = flow.viewport.value\n const { width, height } = flow.size.value\n flow.setViewport({ x: width / 2 - p.x * zoom, y: height / 2 - p.y * zoom, zoom })\n}\n\nfunction onPointerDown(event: PointerEvent) {\n if (event.button !== 0) return\n event.stopPropagation()\n dragging.value = true\n // The map as it is now: the press lands where it was seen.\n held.value = fitted.value\n try {\n svg.value!.setPointerCapture(event.pointerId)\n } catch {\n // Synthetic events have no pointer to capture.\n }\n moveTo(event)\n}\n\nfunction onPointerMove(event: PointerEvent) {\n if (dragging.value) moveTo(event)\n}\n\nfunction onPointerUp() {\n dragging.value = false\n held.value = null\n}\n\nconst el = ref<HTMLElement>()\n/** How far the map moves away from its edge to clear the other panels there, in px. */\nconst lift = ref(0)\nconst LIFT_GAP = 8\n\n/**\n * On a narrow editor the map would cover a panel in the same corner row,\n * e.g. the controls: then it sits above (or below) them instead.\n */\nfunction place() {\n const self = el.value\n const root = flow.root.value\n if (!self || !root) return\n const upper = props.placement.startsWith('top')\n const box = self.getBoundingClientRect()\n // where the map is without the lift\n const top = box.top + (upper ? -lift.value : lift.value)\n const bottom = top + box.height\n let next = 0\n for (const panel of root.querySelectorAll('.dou-flow-editor__panels > *')) {\n if (panel === self || panel.classList.contains('dou-flow-minimap')) continue\n const rect = panel.getBoundingClientRect()\n if (!rect.width || !rect.height) continue\n if (rect.right <= box.left || rect.left >= box.right) continue\n if (rect.bottom <= top || rect.top >= bottom) continue\n next = Math.max(next, upper ? rect.bottom - top + LIFT_GAP : bottom - rect.top + LIFT_GAP)\n }\n lift.value = next\n}\n\n// Panels come, go and change size after the map is shown (controls put in\n// later, undo hidden when made read-only…): place it again each time.\nlet resizes: ResizeObserver | undefined\nlet changes: MutationObserver | undefined\nfunction watchPanels() {\n const panels = el.value?.parentElement\n if (!panels) return\n resizes?.disconnect()\n if (typeof ResizeObserver !== 'undefined') {\n resizes = new ResizeObserver(() => place())\n for (const panel of panels.children) if (panel !== el.value) resizes.observe(panel)\n }\n place()\n}\n\nonMounted(() => {\n nextTick(watchPanels)\n const panels = el.value?.parentElement\n if (panels && typeof MutationObserver !== 'undefined') {\n changes = new MutationObserver(watchPanels)\n changes.observe(panels, { childList: true })\n }\n})\nonBeforeUnmount(() => {\n resizes?.disconnect()\n changes?.disconnect()\n})\nwatch(\n () => [flow.size.value.width, flow.size.value.height, props.width, props.height, props.placement],\n () => nextTick(place),\n)\n</script>\n\n<template>\n <div\n ref=\"el\"\n class=\"dou-flow-minimap\"\n data-flow-overlay\n :style=\"lift ? { translate: `0 ${placement.startsWith('top') ? lift : -lift}px` } : undefined\"\n :class=\"[`is-${placement}`, { 'is-dragging': dragging }]\"\n role=\"img\"\n :aria-label=\"messages.minimap\"\n >\n <svg\n ref=\"svg\"\n :width=\"width\"\n :height=\"height\"\n :viewBox=\"viewBox\"\n preserveAspectRatio=\"xMidYMid meet\"\n aria-hidden=\"true\"\n @pointerdown=\"onPointerDown\"\n @pointermove=\"onPointerMove\"\n @pointerup=\"onPointerUp\"\n @pointercancel=\"onPointerUp\"\n >\n <rect\n v-for=\"item in rects\"\n :key=\"item.id\"\n class=\"dou-flow-minimap__node\"\n :class=\"{ 'is-selected': item.selected }\"\n :x=\"item.rect.x\"\n :y=\"item.rect.y\"\n :width=\"item.rect.width\"\n :height=\"item.rect.height\"\n :rx=\"2 * scale\"\n />\n <path class=\"dou-flow-minimap__shade\" :d=\"shade\" fill-rule=\"evenodd\" />\n <rect\n class=\"dou-flow-minimap__view\"\n :x=\"visible.x\"\n :y=\"visible.y\"\n :width=\"visible.width\"\n :height=\"visible.height\"\n />\n </svg>\n </div>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import flow_port_vue_vue_type_script_setup_true_lang_default from "./flow-port.vue_vue_type_script_setup_true_lang.js";
2
+ // src/components/flow-port.vue
3
+ var flow_port_default = flow_port_vue_vue_type_script_setup_true_lang_default;
4
+ export { flow_port_default as default };
5
+
6
+ //# sourceMappingURL=flow-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-port.js","names":["_sfc_main"],"sources":["../../src/components/flow-port.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, inject, onBeforeUnmount, onMounted, useTemplateRef } from 'vue'\nimport { flowEditorKey, flowNodeKey } from '../context'\nimport { portSide } from '../core/node-types'\n\ndefineOptions({ name: 'DouFlowPort' })\n\nconst props = defineProps<{\n /** The id of one of the node's ports. */\n id: string\n /** @internal placed by the editor, not by the node's component */\n auto?: boolean\n}>()\n\nconst node = inject(flowNodeKey, null)\n\nconst el = useTemplateRef<HTMLElement>('el')\nconst port = computed(() => node?.ports.value.find((p) => p.id === props.id))\nconst side = computed(() => (port.value ? portSide(port.value) : 'left'))\nconst connected = computed(() => !!node?.connected.value.has(props.id))\nconst editor = inject(flowEditorKey, null)\nconst type = computed(() => (port.value ? editor?.portTypeOf(port.value) : undefined))\nconst shape = computed(() => type.value?.shape ?? 'circle')\nconst color = computed(() => editor?.colorOf(type.value))\nconst states = computed(() => node?.portStates.value)\nconst state = computed(() => states.value?.get(props.id))\n\nonMounted(() => {\n if (el.value) node?.registerPort(props.id, el.value, props.auto)\n})\nonBeforeUnmount(() => node?.unregisterPort(props.id, props.auto))\n</script>\n\n<template>\n <span\n ref=\"el\"\n class=\"dou-flow-port\"\n :class=\"[\n `is-${side}`,\n `is-${port?.kind ?? 'input'}`,\n `is-${shape}`,\n state && `is-${state}`,\n { 'is-connected': connected, 'is-unavailable': states && !state },\n ]\"\n :style=\"color ? { '--dou-flow-port-color': color } : undefined\"\n :data-port=\"id\"\n aria-hidden=\"true\"\n >\n <svg v-if=\"shape === 'triangle'\" viewBox=\"0 0 10 10\">\n <path d=\"M2 1.5 8 5 2 8.5Z\" />\n </svg>\n </span>\n</template>\n"],"mappings":""}
@@ -0,0 +1,57 @@
1
+ import { flowEditorKey, flowNodeKey } from "../context.js";
2
+ import { portSide } from "../core/node-types.js";
3
+ import { computed, createCommentVNode, createElementBlock, createElementVNode, defineComponent, inject, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, useTemplateRef } from "vue";
4
+ // src/components/flow-port.vue?vue&type=script&setup=true&lang.ts
5
+ var _hoisted_1 = ["data-port"];
6
+ var _hoisted_2 = {
7
+ key: 0,
8
+ viewBox: "0 0 10 10"
9
+ };
10
+ var flow_port_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
11
+ name: "DouFlowPort",
12
+ __name: "flow-port",
13
+ props: {
14
+ id: {},
15
+ auto: { type: Boolean }
16
+ },
17
+ setup(__props) {
18
+ const props = __props;
19
+ const node = inject(flowNodeKey, null);
20
+ const el = useTemplateRef("el");
21
+ const port = computed(() => node?.ports.value.find((p) => p.id === props.id));
22
+ const side = computed(() => port.value ? portSide(port.value) : "left");
23
+ const connected = computed(() => !!node?.connected.value.has(props.id));
24
+ const editor = inject(flowEditorKey, null);
25
+ const type = computed(() => port.value ? editor?.portTypeOf(port.value) : void 0);
26
+ const shape = computed(() => type.value?.shape ?? "circle");
27
+ const color = computed(() => editor?.colorOf(type.value));
28
+ const states = computed(() => node?.portStates.value);
29
+ const state = computed(() => states.value?.get(props.id));
30
+ onMounted(() => {
31
+ if (el.value) node?.registerPort(props.id, el.value, props.auto);
32
+ });
33
+ onBeforeUnmount(() => node?.unregisterPort(props.id, props.auto));
34
+ return (_ctx, _cache) => {
35
+ return openBlock(), createElementBlock("span", {
36
+ ref_key: "el",
37
+ ref: el,
38
+ class: normalizeClass(["dou-flow-port", [
39
+ `is-${side.value}`,
40
+ `is-${port.value?.kind ?? "input"}`,
41
+ `is-${shape.value}`,
42
+ state.value && `is-${state.value}`,
43
+ {
44
+ "is-connected": connected.value,
45
+ "is-unavailable": states.value && !state.value
46
+ }
47
+ ]]),
48
+ style: normalizeStyle(color.value ? { "--dou-flow-port-color": color.value } : void 0),
49
+ "data-port": __props.id,
50
+ "aria-hidden": "true"
51
+ }, [shape.value === "triangle" ? (openBlock(), createElementBlock("svg", _hoisted_2, [..._cache[0] || (_cache[0] = [createElementVNode("path", { d: "M2 1.5 8 5 2 8.5Z" }, null, -1)])])) : createCommentVNode("", true)], 14, _hoisted_1);
52
+ };
53
+ }
54
+ });
55
+ export { flow_port_vue_vue_type_script_setup_true_lang_default as default };
56
+
57
+ //# sourceMappingURL=flow-port.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-port.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createElementVNode","_createCommentVNode"],"sources":["../../src/components/flow-port.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, inject, onBeforeUnmount, onMounted, useTemplateRef } from 'vue'\nimport { flowEditorKey, flowNodeKey } from '../context'\nimport { portSide } from '../core/node-types'\n\ndefineOptions({ name: 'DouFlowPort' })\n\nconst props = defineProps<{\n /** The id of one of the node's ports. */\n id: string\n /** @internal placed by the editor, not by the node's component */\n auto?: boolean\n}>()\n\nconst node = inject(flowNodeKey, null)\n\nconst el = useTemplateRef<HTMLElement>('el')\nconst port = computed(() => node?.ports.value.find((p) => p.id === props.id))\nconst side = computed(() => (port.value ? portSide(port.value) : 'left'))\nconst connected = computed(() => !!node?.connected.value.has(props.id))\nconst editor = inject(flowEditorKey, null)\nconst type = computed(() => (port.value ? editor?.portTypeOf(port.value) : undefined))\nconst shape = computed(() => type.value?.shape ?? 'circle')\nconst color = computed(() => editor?.colorOf(type.value))\nconst states = computed(() => node?.portStates.value)\nconst state = computed(() => states.value?.get(props.id))\n\nonMounted(() => {\n if (el.value) node?.registerPort(props.id, el.value, props.auto)\n})\nonBeforeUnmount(() => node?.unregisterPort(props.id, props.auto))\n</script>\n\n<template>\n <span\n ref=\"el\"\n class=\"dou-flow-port\"\n :class=\"[\n `is-${side}`,\n `is-${port?.kind ?? 'input'}`,\n `is-${shape}`,\n state && `is-${state}`,\n { 'is-connected': connected, 'is-unavailable': states && !state },\n ]\"\n :style=\"color ? { '--dou-flow-port-color': color } : undefined\"\n :data-port=\"id\"\n aria-hidden=\"true\"\n >\n <svg v-if=\"shape === 'triangle'\" viewBox=\"0 0 10 10\">\n <path d=\"M2 1.5 8 5 2 8.5Z\" />\n </svg>\n </span>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;EAOA,MAAM,QAAQ;EAOd,MAAM,OAAO,OAAO,aAAa,IAAI;EAErC,MAAM,KAAK,eAA4B,IAAI;EAC3C,MAAM,OAAO,eAAe,MAAM,MAAM,MAAM,MAAM,MAAM,EAAE,OAAO,MAAM,EAAE,CAAC;EAC5E,MAAM,OAAO,eAAgB,KAAK,QAAQ,SAAS,KAAK,KAAK,IAAI,MAAO;EACxE,MAAM,YAAY,eAAe,CAAC,CAAC,MAAM,UAAU,MAAM,IAAI,MAAM,EAAE,CAAC;EACtE,MAAM,SAAS,OAAO,eAAe,IAAI;EACzC,MAAM,OAAO,eAAgB,KAAK,QAAQ,QAAQ,WAAW,KAAK,KAAK,IAAI,KAAA,CAAU;EACrF,MAAM,QAAQ,eAAe,KAAK,OAAO,SAAS,QAAQ;EAC1D,MAAM,QAAQ,eAAe,QAAQ,QAAQ,KAAK,KAAK,CAAC;EACxD,MAAM,SAAS,eAAe,MAAM,WAAW,KAAK;EACpD,MAAM,QAAQ,eAAe,OAAO,OAAO,IAAI,MAAM,EAAE,CAAC;EAExD,gBAAgB;GACd,IAAI,GAAG,OAAO,MAAM,aAAa,MAAM,IAAI,GAAG,OAAO,MAAM,IAAI;EACjE,CAAC;EACD,sBAAsB,MAAM,eAAe,MAAM,IAAI,MAAM,IAAI,CAAC;;GAI9D,OAAAA,UAAA,GAAAC,mBAiBO,QAAA;IAhBD,SAAA;IAAJ,KAAI;IACJ,OAAKC,eAAA,CAAC,iBAAe;KACC,MAAA,KAAA;KAAoB,MAAA,KAAA,OAAM,QAAI;KAA2B,MAAA,MAAA;KAAe,MAAA,SAAK,MAAU,MAAA;KAAiC;MAAA,gBAAA,UAAA;MAAS,kBAAoB,OAAA,SAAM,CAAK,MAAA;KAAK;;IAO1L,OAAKC,eAAE,MAAA,QAAK,EAAA,yBAA8B,MAAA,MAAK,IAAK,KAAA,CAAS;IAC7D,aAAW,QAAA;IACZ,eAAY;G,IAED,MAAA,UAAK,cAAhBH,UAAA,GAAAC,mBAEM,OAFN,YAEM,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CADJG,mBAA8B,QAAA,EAAxB,GAAE,oBAAmB,GAAA,MAAA,EAAA,C"}
@@ -0,0 +1,6 @@
1
+ import flow_quick_add_vue_vue_type_script_setup_true_lang_default from "./flow-quick-add.vue_vue_type_script_setup_true_lang.js";
2
+ // src/components/flow-quick-add.vue
3
+ var flow_quick_add_default = flow_quick_add_vue_vue_type_script_setup_true_lang_default;
4
+ export { flow_quick_add_default as default };
5
+
6
+ //# sourceMappingURL=flow-quick-add.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-quick-add.js","names":["_sfc_main"],"sources":["../../src/components/flow-quick-add.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { DouCommand, useFloating, useLayer, useZIndex, type CommandItem } from 'dou-ui'\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\nimport { useFlowEditor } from '../context'\nimport type { Point } from '../core/model'\nimport type { NodeTypeSpec } from '../core/node-types'\n\nconst props = defineProps<{\n /** Where the edge was dropped, on screen, and the types offered. */\n state: { client: Point; types: NodeTypeSpec[]; touch: boolean } | null\n}>()\nconst emit = defineEmits<{ choose: [type: string | null] }>()\nconst slots = defineSlots<{\n /** Replaces the search list. */\n default?: (props: {\n types: NodeTypeSpec[]\n choose: (type: string) => void\n close: () => void\n }) => unknown\n}>()\n\nconst { messages } = useFlowEditor()\nconst panel = useTemplateRef<HTMLElement>('panel')\nconst command = useTemplateRef<InstanceType<typeof DouCommand>>('command')\nconst open = computed(() => !!props.state)\nconst query = shallowRef('')\n\nconst items = computed<CommandItem[]>(() =>\n (props.state?.types ?? []).map((spec) => ({\n value: spec.type,\n label: spec.palette!.label,\n group: spec.palette!.group,\n description: spec.palette!.description,\n keywords: spec.palette!.keywords,\n icon: spec.palette!.icon,\n })),\n)\n\n// Hangs off the point where the edge was dropped.\nconst anchor = {\n getBoundingClientRect() {\n const at = props.state?.client\n return at ? new DOMRect(at.x, at.y, 0, 0) : new DOMRect()\n },\n}\nconst { style, placement, update } = useFloating(() => (open.value ? anchor : null), panel, {\n open,\n placement: 'bottom-start',\n offset: 6,\n})\nconst { next } = useZIndex()\nconst zIndex = shallowRef(0)\nuseLayer({\n open,\n elements: () => [panel.value],\n onOutside: () => emit('choose', null),\n onEscape: () => emit('choose', null),\n})\n\n/** A label cut into the matched letters and the rest. */\nfunction parts(text: string, indices: number[]) {\n const hit = new Set(indices)\n const out: { text: string; match: boolean }[] = []\n for (let i = 0; i < text.length; i++) {\n const match = hit.has(i)\n const last = out[out.length - 1]\n if (last && last.match === match) last.text += text[i]\n else out.push({ text: text[i]!, match })\n }\n return out\n}\n\nwatch(open, (value) => {\n if (!value) return\n query.value = ''\n zIndex.value = next()\n void nextTick(() => {\n update()\n // A phone's keyboard would cover the list: search only when typing is likely.\n if (!props.state?.touch) command.value?.focus()\n })\n})\n</script>\n\n<template>\n <Teleport to=\"body\">\n <Transition name=\"dou-floating\">\n <div\n v-if=\"state\"\n ref=\"panel\"\n class=\"dou-dropdown dou-floating dou-flow-quick-add\"\n :class=\"{ 'is-custom': slots.default }\"\n :style=\"[style, { zIndex }]\"\n :data-placement=\"placement\"\n >\n <slot\n v-if=\"slots.default\"\n :types=\"state.types\"\n :choose=\"(type: string) => emit('choose', type)\"\n :close=\"() => emit('choose', null)\"\n />\n <DouCommand\n v-else\n ref=\"command\"\n v-model:query=\"query\"\n :items=\"items\"\n :placeholder=\"messages.search\"\n :aria-label=\"messages.search\"\n :empty-text=\"messages.noTypes\"\n :max-height=\"280\"\n @select=\"emit('choose', String($event.value))\"\n >\n <template #item=\"{ item, indices }\">\n <span class=\"dou-flow-quick-add__icon\">\n <component :is=\"item.icon\" v-if=\"item.icon\" />\n </span>\n <span class=\"dou-command__label\">\n <template v-for=\"(part, at) in parts(item.label, indices)\" :key=\"at\">\n <mark v-if=\"part.match\" class=\"dou-command__match\">{{ part.text }}</mark>\n <template v-else>{{ part.text }}</template>\n </template>\n </span>\n <span v-if=\"item.description\" class=\"dou-command__description\">\n {{ item.description }}\n </span>\n </template>\n </DouCommand>\n </div>\n </Transition>\n </Teleport>\n</template>\n"],"mappings":""}
@@ -0,0 +1,126 @@
1
+ import { useFlowEditor } from "../context.js";
2
+ import { DouCommand, useFloating, useLayer, useZIndex } from "dou-ui";
3
+ import { Fragment, Teleport, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, openBlock, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, unref, useSlots, useTemplateRef, watch, withCtx } from "vue";
4
+ // src/components/flow-quick-add.vue?vue&type=script&setup=true&lang.ts
5
+ var _hoisted_1 = ["data-placement"];
6
+ var _hoisted_2 = { class: "dou-flow-quick-add__icon" };
7
+ var _hoisted_3 = { class: "dou-command__label" };
8
+ var _hoisted_4 = {
9
+ key: 0,
10
+ class: "dou-command__match"
11
+ };
12
+ var _hoisted_5 = {
13
+ key: 0,
14
+ class: "dou-command__description"
15
+ };
16
+ var flow_quick_add_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
17
+ __name: "flow-quick-add",
18
+ props: { state: {} },
19
+ emits: ["choose"],
20
+ setup(__props, { emit: __emit }) {
21
+ const props = __props;
22
+ const emit = __emit;
23
+ const slots = useSlots();
24
+ const { messages } = useFlowEditor();
25
+ const panel = useTemplateRef("panel");
26
+ const command = useTemplateRef("command");
27
+ const open = computed(() => !!props.state);
28
+ const query = shallowRef("");
29
+ const items = computed(() => (props.state?.types ?? []).map((spec) => ({
30
+ value: spec.type,
31
+ label: spec.palette.label,
32
+ group: spec.palette.group,
33
+ description: spec.palette.description,
34
+ keywords: spec.palette.keywords,
35
+ icon: spec.palette.icon
36
+ })));
37
+ const anchor = { getBoundingClientRect() {
38
+ const at = props.state?.client;
39
+ return at ? new DOMRect(at.x, at.y, 0, 0) : new DOMRect();
40
+ } };
41
+ const { style, placement, update } = useFloating(() => open.value ? anchor : null, panel, {
42
+ open,
43
+ placement: "bottom-start",
44
+ offset: 6
45
+ });
46
+ const { next } = useZIndex();
47
+ const zIndex = shallowRef(0);
48
+ useLayer({
49
+ open,
50
+ elements: () => [panel.value],
51
+ onOutside: () => emit("choose", null),
52
+ onEscape: () => emit("choose", null)
53
+ });
54
+ /** A label cut into the matched letters and the rest. */
55
+ function parts(text, indices) {
56
+ const hit = new Set(indices);
57
+ const out = [];
58
+ for (let i = 0; i < text.length; i++) {
59
+ const match = hit.has(i);
60
+ const last = out[out.length - 1];
61
+ if (last && last.match === match) last.text += text[i];
62
+ else out.push({
63
+ text: text[i],
64
+ match
65
+ });
66
+ }
67
+ return out;
68
+ }
69
+ watch(open, (value) => {
70
+ if (!value) return;
71
+ query.value = "";
72
+ zIndex.value = next();
73
+ nextTick(() => {
74
+ update();
75
+ if (!props.state?.touch) command.value?.focus();
76
+ });
77
+ });
78
+ return (_ctx, _cache) => {
79
+ return openBlock(), createBlock(Teleport, { to: "body" }, [createVNode(Transition, { name: "dou-floating" }, {
80
+ default: withCtx(() => [__props.state ? (openBlock(), createElementBlock("div", {
81
+ key: 0,
82
+ ref_key: "panel",
83
+ ref: panel,
84
+ class: normalizeClass(["dou-dropdown dou-floating dou-flow-quick-add", { "is-custom": slots.default }]),
85
+ style: normalizeStyle([unref(style), { zIndex: zIndex.value }]),
86
+ "data-placement": unref(placement)
87
+ }, [slots.default ? renderSlot(_ctx.$slots, "default", {
88
+ types: __props.state.types,
89
+ choose: (type) => emit("choose", type),
90
+ close: () => emit("choose", null)
91
+ }, void 0, void 0, 0) : (openBlock(), createBlock(unref(DouCommand), {
92
+ key: 1,
93
+ ref_key: "command",
94
+ ref: command,
95
+ query: query.value,
96
+ "onUpdate:query": _cache[0] || (_cache[0] = ($event) => query.value = $event),
97
+ items: items.value,
98
+ placeholder: unref(messages).search,
99
+ "aria-label": unref(messages).search,
100
+ "empty-text": unref(messages).noTypes,
101
+ "max-height": 280,
102
+ onSelect: _cache[1] || (_cache[1] = ($event) => emit("choose", String($event.value)))
103
+ }, {
104
+ item: withCtx(({ item, indices }) => [
105
+ createElementVNode("span", _hoisted_2, [item.icon ? (openBlock(), createBlock(resolveDynamicComponent(item.icon), { key: 0 })) : createCommentVNode("", true)]),
106
+ createElementVNode("span", _hoisted_3, [(openBlock(true), createElementBlock(Fragment, null, renderList(parts(item.label, indices), (part, at) => {
107
+ return openBlock(), createElementBlock(Fragment, { key: at }, [part.match ? (openBlock(), createElementBlock("mark", _hoisted_4, toDisplayString(part.text), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(part.text), 1)], 64))], 64);
108
+ }), 128))]),
109
+ item.description ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(item.description), 1)) : createCommentVNode("", true)
110
+ ]),
111
+ _: 1
112
+ }, 8, [
113
+ "query",
114
+ "items",
115
+ "placeholder",
116
+ "aria-label",
117
+ "empty-text"
118
+ ]))], 14, _hoisted_1)) : createCommentVNode("", true)]),
119
+ _: 3
120
+ })]);
121
+ };
122
+ }
123
+ });
124
+ export { flow_quick_add_vue_vue_type_script_setup_true_lang_default as default };
125
+
126
+ //# sourceMappingURL=flow-quick-add.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-quick-add.vue_vue_type_script_setup_true_lang.js","names":["_useSlots","_openBlock","_createBlock","_Teleport","_createVNode","_Transition","_withCtx","_createElementBlock","_normalizeClass","_normalizeStyle","_unref","_renderSlot","_createElementVNode","_resolveDynamicComponent","_createCommentVNode","_Fragment","_renderList","_toDisplayString","_createTextVNode"],"sources":["../../src/components/flow-quick-add.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { DouCommand, useFloating, useLayer, useZIndex, type CommandItem } from 'dou-ui'\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\nimport { useFlowEditor } from '../context'\nimport type { Point } from '../core/model'\nimport type { NodeTypeSpec } from '../core/node-types'\n\nconst props = defineProps<{\n /** Where the edge was dropped, on screen, and the types offered. */\n state: { client: Point; types: NodeTypeSpec[]; touch: boolean } | null\n}>()\nconst emit = defineEmits<{ choose: [type: string | null] }>()\nconst slots = defineSlots<{\n /** Replaces the search list. */\n default?: (props: {\n types: NodeTypeSpec[]\n choose: (type: string) => void\n close: () => void\n }) => unknown\n}>()\n\nconst { messages } = useFlowEditor()\nconst panel = useTemplateRef<HTMLElement>('panel')\nconst command = useTemplateRef<InstanceType<typeof DouCommand>>('command')\nconst open = computed(() => !!props.state)\nconst query = shallowRef('')\n\nconst items = computed<CommandItem[]>(() =>\n (props.state?.types ?? []).map((spec) => ({\n value: spec.type,\n label: spec.palette!.label,\n group: spec.palette!.group,\n description: spec.palette!.description,\n keywords: spec.palette!.keywords,\n icon: spec.palette!.icon,\n })),\n)\n\n// Hangs off the point where the edge was dropped.\nconst anchor = {\n getBoundingClientRect() {\n const at = props.state?.client\n return at ? new DOMRect(at.x, at.y, 0, 0) : new DOMRect()\n },\n}\nconst { style, placement, update } = useFloating(() => (open.value ? anchor : null), panel, {\n open,\n placement: 'bottom-start',\n offset: 6,\n})\nconst { next } = useZIndex()\nconst zIndex = shallowRef(0)\nuseLayer({\n open,\n elements: () => [panel.value],\n onOutside: () => emit('choose', null),\n onEscape: () => emit('choose', null),\n})\n\n/** A label cut into the matched letters and the rest. */\nfunction parts(text: string, indices: number[]) {\n const hit = new Set(indices)\n const out: { text: string; match: boolean }[] = []\n for (let i = 0; i < text.length; i++) {\n const match = hit.has(i)\n const last = out[out.length - 1]\n if (last && last.match === match) last.text += text[i]\n else out.push({ text: text[i]!, match })\n }\n return out\n}\n\nwatch(open, (value) => {\n if (!value) return\n query.value = ''\n zIndex.value = next()\n void nextTick(() => {\n update()\n // A phone's keyboard would cover the list: search only when typing is likely.\n if (!props.state?.touch) command.value?.focus()\n })\n})\n</script>\n\n<template>\n <Teleport to=\"body\">\n <Transition name=\"dou-floating\">\n <div\n v-if=\"state\"\n ref=\"panel\"\n class=\"dou-dropdown dou-floating dou-flow-quick-add\"\n :class=\"{ 'is-custom': slots.default }\"\n :style=\"[style, { zIndex }]\"\n :data-placement=\"placement\"\n >\n <slot\n v-if=\"slots.default\"\n :types=\"state.types\"\n :choose=\"(type: string) => emit('choose', type)\"\n :close=\"() => emit('choose', null)\"\n />\n <DouCommand\n v-else\n ref=\"command\"\n v-model:query=\"query\"\n :items=\"items\"\n :placeholder=\"messages.search\"\n :aria-label=\"messages.search\"\n :empty-text=\"messages.noTypes\"\n :max-height=\"280\"\n @select=\"emit('choose', String($event.value))\"\n >\n <template #item=\"{ item, indices }\">\n <span class=\"dou-flow-quick-add__icon\">\n <component :is=\"item.icon\" v-if=\"item.icon\" />\n </span>\n <span class=\"dou-command__label\">\n <template v-for=\"(part, at) in parts(item.label, indices)\" :key=\"at\">\n <mark v-if=\"part.match\" class=\"dou-command__match\">{{ part.text }}</mark>\n <template v-else>{{ part.text }}</template>\n </template>\n </span>\n <span v-if=\"item.description\" class=\"dou-command__description\">\n {{ item.description }}\n </span>\n </template>\n </DouCommand>\n </div>\n </Transition>\n </Teleport>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import flow_toolbar_vue_vue_type_script_setup_true_lang_default from "./flow-toolbar.vue_vue_type_script_setup_true_lang.js";
2
+ // src/components/flow-toolbar.vue
3
+ var flow_toolbar_default = flow_toolbar_vue_vue_type_script_setup_true_lang_default;
4
+ export { flow_toolbar_default as default };
5
+
6
+ //# sourceMappingURL=flow-toolbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-toolbar.js","names":["_sfc_main"],"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":""}