@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,19 @@
1
+ import type { NodeViewProps } from '../core/node-types';
2
+ import type { Tone } from 'dou-ui';
3
+ import { type WhiteboardShape } from './model';
4
+ type BoardData = {
5
+ [key: string]: unknown;
6
+ text?: string;
7
+ shape?: WhiteboardShape;
8
+ tone?: Tone;
9
+ rotation?: number;
10
+ points?: number[];
11
+ box?: [number, number];
12
+ size?: number;
13
+ src?: string;
14
+ alt?: string;
15
+ };
16
+ type __VLS_Props = NodeViewProps<BoardData>;
17
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import type { FlowEditorContext } from '../context';
2
+ import type { FlowToolbarItem } from '../core/menu';
3
+ import type { FlowEdge } from '../core/model';
4
+ import type { WhiteboardConnectorData } from './model';
5
+ /**
6
+ * The whiteboard's line: straight, with right-angled turns or curved
7
+ * (`data.route`), solid or dashed (`data.dash`), with arrowheads at the end,
8
+ * both ends or neither (`data.arrow`).
9
+ */
10
+ export declare const connectorEdge: import("..").EdgeTypeSpec<WhiteboardConnectorData>;
11
+ /** Route, dash and arrow choices over the selected connectors. */
12
+ export declare function connectorToolbar(ctx: FlowEditorContext, edges: FlowEdge[]): FlowToolbarItem[];
@@ -0,0 +1,5 @@
1
+ import type { FlowNode } from '../core/model';
2
+ import type { FlowDrawContext } from '../export/context';
3
+ import type { WhiteboardNodeData } from './model';
4
+ /** A board node as SVG, as `board-node.vue` draws it. */
5
+ export declare function drawBoardNode(node: FlowNode<WhiteboardNodeData>, draw: FlowDrawContext): string;
@@ -0,0 +1,27 @@
1
+ import type { FlowEditorContext } from '../context';
2
+ import type { Point } from '../core/model';
3
+ /**
4
+ * Turns a file into the `src` an image node keeps, e.g. by uploading it.
5
+ * Return `{ src, alt }` to give the image its description; it is the file's
6
+ * name otherwise.
7
+ */
8
+ export type WhiteboardUpload = (file: File) => WhiteboardUploaded | Promise<WhiteboardUploaded>;
9
+ /** An image's address, or its address and description. */
10
+ export type WhiteboardUploaded = string | {
11
+ src: string;
12
+ alt?: string;
13
+ };
14
+ export interface WhiteboardImageOptions {
15
+ /** Where the images go, in canvas units; the middle of the view by default. */
16
+ at?: Point;
17
+ /** Overrides the kit's `upload` option for these images. */
18
+ upload?: WhiteboardUpload;
19
+ }
20
+ export declare const imageFiles: (files: Iterable<File> | null | undefined) => File[];
21
+ /**
22
+ * Adds an image node for each file, centred on `at` and stepped down and
23
+ * right when there are several. Files go through the kit's `upload` option.
24
+ */
25
+ export declare function insertImages(ctx: FlowEditorContext, files: File[], options?: WhiteboardImageOptions): Promise<void>;
26
+ /** Opens the file picker and inserts what is picked, as `insertImages` does. */
27
+ export declare function pickImages(ctx: FlowEditorContext, options?: WhiteboardImageOptions): void;
@@ -0,0 +1,6 @@
1
+ export { connectorEdge, imageNode, inkNode, shapeNode, stickyNode, textNode, whiteboardKit, } from './kit';
2
+ export { imageKey, whiteboardKeys } from './model';
3
+ export { insertImages, pickImages } from './image';
4
+ export type { WhiteboardKitOptions } from './kit';
5
+ export type { WhiteboardImageOptions, WhiteboardUpload, WhiteboardUploaded } from './image';
6
+ export type { WhiteboardImageData, WhiteboardInkData, WhiteboardNodeData, WhiteboardShape, WhiteboardShapeData, WhiteboardShapeTool, WhiteboardStickyData, WhiteboardTextData, WhiteboardTool, WhiteboardArrow, WhiteboardConnectorData, WhiteboardDash, WhiteboardRoute, } from './model';
@@ -0,0 +1,30 @@
1
+ import type { FlowNode, Point } from '../core/model';
2
+ import type { WhiteboardInkData } from './model';
3
+ /** A sampled point: canvas position and pen pressure from 0 to 1. */
4
+ export interface InkPoint extends Point {
5
+ pressure: number;
6
+ }
7
+ export declare const INK_SIZE = 4;
8
+ /**
9
+ * Follows the pointer with a little lag, which smooths out the shake of a
10
+ * hand; samples closer than `gap` to the last one are dropped.
11
+ */
12
+ export declare function follow(points: InkPoint[], next: InkPoint, gap: number): undefined;
13
+ /** The node a finished stroke becomes: its box with room for the stroke's width. */
14
+ export declare function strokeNode(id: string, points: InkPoint[], size?: number): FlowNode<WhiteboardInkData>;
15
+ /**
16
+ * A stroke's points in a box of `width` by `height`: the line itself
17
+ * stretches, but the room left around it for the pen's width does not, so
18
+ * a stretched stroke keeps its thickness.
19
+ */
20
+ export declare function fitInk(points: readonly InkPoint[], box: readonly [number, number], width: number, height: number, size?: number): InkPoint[];
21
+ /** The triples of `data.points` as points. */
22
+ export declare function unpack(flat: readonly number[]): InkPoint[];
23
+ /**
24
+ * The outline of a stroke as a filled path: each side offset by half the
25
+ * width at that point, joined by round ends, with curves through the middles
26
+ * of the samples.
27
+ */
28
+ export declare function inkOutline(points: readonly InkPoint[], size?: number): string;
29
+ /** Whether a stroke passes within `radius` of a canvas point. */
30
+ export declare function inkHit(node: FlowNode, point: Point, radius: number): boolean;
@@ -0,0 +1,36 @@
1
+ import { type Ref } from 'vue';
2
+ import { type FlowExtension } from '../core/extension';
3
+ import { type WhiteboardImageData, type WhiteboardInkData, type WhiteboardShapeData, type WhiteboardStickyData, type WhiteboardTextData, type WhiteboardTool } from './model';
4
+ import { type WhiteboardUpload } from './image';
5
+ import { connectorEdge } from './connector';
6
+ /** A rectangle, ellipse or diamond with centred text; `data.shape` says which. */
7
+ export declare const shapeNode: import("..").NodeTypeSpec<WhiteboardShapeData>;
8
+ /** A square note in a soft color. */
9
+ export declare const stickyNode: import("..").NodeTypeSpec<WhiteboardStickyData>;
10
+ /** Text on its own; it grows as it is typed. */
11
+ export declare const textNode: import("..").NodeTypeSpec<WhiteboardTextData>;
12
+ /** A pen stroke; it stretches with its box. */
13
+ export declare const inkNode: import("..").NodeTypeSpec<WhiteboardInkData>;
14
+ /** An image; `data.src` is its address. */
15
+ export declare const imageNode: import("..").NodeTypeSpec<WhiteboardImageData>;
16
+ export interface WhiteboardKitOptions {
17
+ /**
18
+ * The current tool, to share with your own toolbar. Made inside the kit
19
+ * when left out.
20
+ */
21
+ tool?: Ref<WhiteboardTool>;
22
+ /** The built-in toolbar at the top of the editor. Defaults to `true`. */
23
+ toolbar?: boolean;
24
+ /**
25
+ * Turns an image file that is dropped, pasted or picked into the `src` it
26
+ * is kept as, e.g. by uploading it and returning its address. Without it,
27
+ * images are kept in the document as data URLs.
28
+ */
29
+ upload?: WhiteboardUpload;
30
+ }
31
+ /**
32
+ * The whiteboard: shape, sticky note and text nodes, the `connector` edge,
33
+ * the drawing tools with their keys, and a toolbar. Add it after `flowKit()`.
34
+ */
35
+ export declare function whiteboardKit(options?: WhiteboardKitOptions): FlowExtension;
36
+ export { connectorEdge };
@@ -0,0 +1,79 @@
1
+ import type { Tone } from 'dou-ui';
2
+ import type { FlowPortSpec } from '../core/model';
3
+ /** What a press on the board does. */
4
+ export type WhiteboardTool = 'select' | 'hand' | 'rect' | 'ellipse' | 'diamond' | 'sticky' | 'text' | 'pen' | 'eraser';
5
+ /** The tools that put down a node with a box. */
6
+ export type WhiteboardShapeTool = Exclude<WhiteboardTool, 'select' | 'hand' | 'pen' | 'eraser'>;
7
+ export type WhiteboardShape = 'rect' | 'ellipse' | 'diamond';
8
+ /** What every board node can carry. */
9
+ export interface WhiteboardNodeData {
10
+ [key: string]: unknown;
11
+ /** Degrees clockwise around the node's centre. */
12
+ rotation?: number;
13
+ }
14
+ export interface WhiteboardShapeData extends WhiteboardNodeData {
15
+ shape: WhiteboardShape;
16
+ text?: string;
17
+ /** `neutral` by default. */
18
+ tone?: Tone;
19
+ }
20
+ export interface WhiteboardStickyData extends WhiteboardNodeData {
21
+ text?: string;
22
+ /** `warning` (yellow) by default. */
23
+ tone?: Tone;
24
+ }
25
+ export interface WhiteboardTextData extends WhiteboardNodeData {
26
+ text?: string;
27
+ }
28
+ /** A pen stroke. */
29
+ export interface WhiteboardInkData extends WhiteboardNodeData {
30
+ /**
31
+ * The points as `x, y, pressure` triples, flat, in the node's own units:
32
+ * `0, 0` is its top left corner when it is `box` in size.
33
+ */
34
+ points: number[];
35
+ /** The size the points were drawn at; the stroke stretches with the node. */
36
+ box: [number, number];
37
+ /**
38
+ * The stroke's width drawn with a mouse or finger. `4` by default; a pen
39
+ * makes it 0.4 to 1.6 times as wide as it presses lighter or harder.
40
+ */
41
+ size?: number;
42
+ /** The text color when left out. */
43
+ tone?: Tone;
44
+ }
45
+ /** How a connector runs: one straight line, right-angled turns, or a smooth curve. */
46
+ export type WhiteboardRoute = 'straight' | 'elbow' | 'curve';
47
+ export type WhiteboardDash = 'solid' | 'dashed' | 'dotted' | 'dash-dot';
48
+ /** Which ends of a connector have an arrowhead. */
49
+ export type WhiteboardArrow = 'none' | 'end' | 'both';
50
+ /** A `connector` edge's look. */
51
+ export interface WhiteboardConnectorData {
52
+ [key: string]: unknown;
53
+ /** `straight` by default. */
54
+ route?: WhiteboardRoute;
55
+ /** `solid` by default. */
56
+ dash?: WhiteboardDash;
57
+ /** `end` by default. */
58
+ arrow?: WhiteboardArrow;
59
+ /** Where an `elbow` connector's middle run sits, from 0 to 1; set by dragging it. */
60
+ bend?: number;
61
+ }
62
+ export interface WhiteboardImageData extends WhiteboardNodeData {
63
+ src: string;
64
+ /** Read by screen readers. */
65
+ alt?: string;
66
+ }
67
+ /** Keys for the tools. */
68
+ export declare const whiteboardKeys: Record<WhiteboardTool, string>;
69
+ /** The key that inserts an image. */
70
+ export declare const imageKey = "i";
71
+ /**
72
+ * Four anchors, one on each side. Each is an output and an input at the
73
+ * same spot, so a connector can start and end on any of them. Edges in
74
+ * data go from an output (`right`) to an input (`left-in`). The order puts
75
+ * `right` and `left-in` first, so a node added after another joins it left
76
+ * to right.
77
+ */
78
+ export declare const anchorPorts: FlowPortSpec[];
79
+ export declare const anchorSides: readonly ["top", "right", "bottom", "left"];
@@ -0,0 +1,7 @@
1
+ import type { FlowEditorContext } from '../context';
2
+ import type { FlowPressHandler } from '../core/extension';
3
+ import { type Point } from '../core/model';
4
+ /** Draws a stroke with the pen; it becomes an `ink` node once let go. */
5
+ export declare function drawInk(ctx: FlowEditorContext, event: PointerEvent): FlowPressHandler;
6
+ /** Wipes the strokes the eraser passes over; they go once it lets go. */
7
+ export declare function erase(ctx: FlowEditorContext, start: Point): FlowPressHandler;
@@ -0,0 +1,37 @@
1
+ import { type Ref, type ShallowRef } from 'vue';
2
+ import type { FlowEditorContext } from '../context';
3
+ import type { Point, Rect } from '../core/model';
4
+ import type { WhiteboardUpload } from './image';
5
+ import type { InkPoint } from './ink';
6
+ import type { WhiteboardShapeTool, WhiteboardTool } from './model';
7
+ /** What the board's pieces share within one editor. */
8
+ export interface WhiteboardState {
9
+ tool: Ref<WhiteboardTool>;
10
+ /** Keeps the tool after drawing, instead of going back to select. */
11
+ locked: Ref<boolean>;
12
+ /** The hand is dragging the view. */
13
+ panning: Ref<boolean>;
14
+ /** The node whose text is being typed. */
15
+ editing: ShallowRef<string | null>;
16
+ /** The box being drawn, with the tool drawing it. */
17
+ draft: ShallowRef<(Rect & {
18
+ tool: WhiteboardShapeTool;
19
+ }) | null>;
20
+ /** The stroke being drawn with the pen, in canvas coordinates. */
21
+ ink: ShallowRef<InkPoint[] | null>;
22
+ /** Strokes the eraser has touched, removed once it lets go. */
23
+ erasing: ShallowRef<ReadonlySet<string>>;
24
+ /** Where the eraser is, while it is pressed. */
25
+ eraser: ShallowRef<Point | null>;
26
+ /**
27
+ * Where the turn and corner handles of the selected node are drawn: a
28
+ * layer above every node, so a neighbour never covers them.
29
+ */
30
+ handles: ShallowRef<HTMLElement | null>;
31
+ /** Images being uploaded. */
32
+ uploading: Ref<number>;
33
+ /** The kit's `upload` option. */
34
+ upload?: WhiteboardUpload;
35
+ }
36
+ /** The board's state in an editor, made on first use. */
37
+ export declare function boardState(context: FlowEditorContext): WhiteboardState;
@@ -0,0 +1,67 @@
1
+ import { type Ref } from 'vue';
2
+ import type { FlowEditorContext } from '../context';
3
+ import type { FlowNode, Point } from '../core/model';
4
+ /** Degrees in `[0, 360)`, rounded to a whole degree. */
5
+ export declare const normalizeAngle: (degrees: number) => number;
6
+ /** Where a turn lands: a multiple of 15° with Shift; near a right angle, the right angle. */
7
+ export declare function snapAngle(degrees: number, shift: boolean): number;
8
+ /** A resize handle: the side or corner it sits on, `-1`, `0` or `1` along each axis. */
9
+ export interface ResizeHandle {
10
+ x: -1 | 0 | 1;
11
+ y: -1 | 0 | 1;
12
+ }
13
+ /** The eight handles, corners first. */
14
+ export declare const RESIZE_HANDLES: readonly ResizeHandle[];
15
+ /**
16
+ * The box a node gets when `handle` is dragged by `delta` (canvas units),
17
+ * turned by `rotation` or not: it grows along the node's own axes, and the
18
+ * corner or side across from the handle stays where it is. With `keepRatio`
19
+ * a corner keeps the box's proportions.
20
+ */
21
+ export declare function resizeBox(start: {
22
+ x: number;
23
+ y: number;
24
+ width: number;
25
+ height: number;
26
+ }, rotation: number, handle: ResizeHandle, delta: Point, min: {
27
+ width: number;
28
+ height: number;
29
+ }, keepRatio?: boolean): {
30
+ x: number;
31
+ y: number;
32
+ width: number;
33
+ height: number;
34
+ };
35
+ /**
36
+ * The turn and corner handles of a board node. While a handle is dragged
37
+ * the node is only shown changed, through `previewNode`, so its frame,
38
+ * handles, ports and edges all follow; the document changes once, when it
39
+ * is let go. `turning` and `sizing` say what is being dragged to.
40
+ */
41
+ export declare function useTransformHandles(ctx: FlowEditorContext, node: Ref<FlowNode>, rotation: Ref<number>, min: {
42
+ width: number;
43
+ height: number;
44
+ }): {
45
+ turning: Ref<number | null, number | null>;
46
+ sizing: Ref<{
47
+ x: number;
48
+ y: number;
49
+ width: number;
50
+ height: number;
51
+ } | null, {
52
+ x: number;
53
+ y: number;
54
+ width: number;
55
+ height: number;
56
+ } | {
57
+ x: number;
58
+ y: number;
59
+ width: number;
60
+ height: number;
61
+ } | null>;
62
+ startTurn: (event: PointerEvent) => void;
63
+ startResize: (event: PointerEvent, handle: ResizeHandle, options?: {
64
+ keepRatio?: boolean;
65
+ widthOnly?: boolean;
66
+ }) => void;
67
+ };
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,6 @@
1
+ import board_draft_vue_vue_type_script_setup_true_lang_default from "./board-draft.vue_vue_type_script_setup_true_lang.js";
2
+ // src/whiteboard/board-draft.vue
3
+ var board_draft_default = board_draft_vue_vue_type_script_setup_true_lang_default;
4
+ export { board_draft_default as default };
5
+
6
+ //# sourceMappingURL=board-draft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-draft.js","names":["_sfc_main"],"sources":["../../src/whiteboard/board-draft.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useFlowEditor } from '../context'\nimport { inkOutline } from './ink'\nimport { boardState } from './state'\n\n// What a tool is drawing: the box of a shape, note or text, the pen's\n// stroke, or the eraser's reach.\nconst ctx = useFlowEditor()\nconst state = boardState(ctx)\nconst ink = computed(() => (state.ink.value ? inkOutline(state.ink.value) : ''))\nconst eraserRadius = computed(() => 8 / ctx.viewport.value.zoom)\n// the selected node's turn and corner handles are moved in here, above every node\nconst handles = (el: unknown) => (state.handles.value = (el as HTMLElement | null) ?? null)\n</script>\n\n<template>\n <div :ref=\"handles\" class=\"dou-whiteboard-handles\" />\n <div\n v-if=\"state.draft.value\"\n class=\"dou-whiteboard-draft\"\n :class=\"`is-${state.draft.value.tool}`\"\n :style=\"{\n transform: `translate(${state.draft.value.x}px, ${state.draft.value.y}px)`,\n width: `${state.draft.value.width}px`,\n height: `${state.draft.value.height}px`,\n }\"\n aria-hidden=\"true\"\n />\n <svg v-if=\"ink || state.eraser.value\" class=\"dou-whiteboard-strokes\" aria-hidden=\"true\">\n <path v-if=\"ink\" class=\"dou-whiteboard-strokes__ink\" :d=\"ink\" />\n <circle\n v-if=\"state.eraser.value\"\n class=\"dou-whiteboard-strokes__eraser\"\n :cx=\"state.eraser.value.x\"\n :cy=\"state.eraser.value.y\"\n :r=\"eraserRadius\"\n />\n </svg>\n</template>\n"],"mappings":""}
@@ -0,0 +1,58 @@
1
+ import { useFlowEditor } from "../context.js";
2
+ import { inkOutline } from "./ink.js";
3
+ import { boardState } from "./state.js";
4
+ import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, unref } from "vue";
5
+ // src/whiteboard/board-draft.vue?vue&type=script&setup=true&lang.ts
6
+ var _hoisted_1 = {
7
+ key: 1,
8
+ class: "dou-whiteboard-strokes",
9
+ "aria-hidden": "true"
10
+ };
11
+ var _hoisted_2 = ["d"];
12
+ var _hoisted_3 = [
13
+ "cx",
14
+ "cy",
15
+ "r"
16
+ ];
17
+ var board_draft_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
18
+ __name: "board-draft",
19
+ setup(__props) {
20
+ const ctx = useFlowEditor();
21
+ const state = boardState(ctx);
22
+ const ink = computed(() => state.ink.value ? inkOutline(state.ink.value) : "");
23
+ const eraserRadius = computed(() => 8 / ctx.viewport.value.zoom);
24
+ const handles = (el) => state.handles.value = el ?? null;
25
+ return (_ctx, _cache) => {
26
+ return openBlock(), createElementBlock(Fragment, null, [
27
+ createElementVNode("div", {
28
+ ref: handles,
29
+ class: "dou-whiteboard-handles"
30
+ }, null, 512),
31
+ unref(state).draft.value ? (openBlock(), createElementBlock("div", {
32
+ key: 0,
33
+ class: normalizeClass(["dou-whiteboard-draft", `is-${unref(state).draft.value.tool}`]),
34
+ style: normalizeStyle({
35
+ transform: `translate(${unref(state).draft.value.x}px, ${unref(state).draft.value.y}px)`,
36
+ width: `${unref(state).draft.value.width}px`,
37
+ height: `${unref(state).draft.value.height}px`
38
+ }),
39
+ "aria-hidden": "true"
40
+ }, null, 6)) : createCommentVNode("", true),
41
+ ink.value || unref(state).eraser.value ? (openBlock(), createElementBlock("svg", _hoisted_1, [ink.value ? (openBlock(), createElementBlock("path", {
42
+ key: 0,
43
+ class: "dou-whiteboard-strokes__ink",
44
+ d: ink.value
45
+ }, null, 8, _hoisted_2)) : createCommentVNode("", true), unref(state).eraser.value ? (openBlock(), createElementBlock("circle", {
46
+ key: 1,
47
+ class: "dou-whiteboard-strokes__eraser",
48
+ cx: unref(state).eraser.value.x,
49
+ cy: unref(state).eraser.value.y,
50
+ r: eraserRadius.value
51
+ }, null, 8, _hoisted_3)) : createCommentVNode("", true)])) : createCommentVNode("", true)
52
+ ], 64);
53
+ };
54
+ }
55
+ });
56
+ export { board_draft_vue_vue_type_script_setup_true_lang_default as default };
57
+
58
+ //# sourceMappingURL=board-draft.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-draft.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_Fragment","_createElementVNode","_unref","_normalizeClass","_normalizeStyle","_createCommentVNode"],"sources":["../../src/whiteboard/board-draft.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useFlowEditor } from '../context'\nimport { inkOutline } from './ink'\nimport { boardState } from './state'\n\n// What a tool is drawing: the box of a shape, note or text, the pen's\n// stroke, or the eraser's reach.\nconst ctx = useFlowEditor()\nconst state = boardState(ctx)\nconst ink = computed(() => (state.ink.value ? inkOutline(state.ink.value) : ''))\nconst eraserRadius = computed(() => 8 / ctx.viewport.value.zoom)\n// the selected node's turn and corner handles are moved in here, above every node\nconst handles = (el: unknown) => (state.handles.value = (el as HTMLElement | null) ?? null)\n</script>\n\n<template>\n <div :ref=\"handles\" class=\"dou-whiteboard-handles\" />\n <div\n v-if=\"state.draft.value\"\n class=\"dou-whiteboard-draft\"\n :class=\"`is-${state.draft.value.tool}`\"\n :style=\"{\n transform: `translate(${state.draft.value.x}px, ${state.draft.value.y}px)`,\n width: `${state.draft.value.width}px`,\n height: `${state.draft.value.height}px`,\n }\"\n aria-hidden=\"true\"\n />\n <svg v-if=\"ink || state.eraser.value\" class=\"dou-whiteboard-strokes\" aria-hidden=\"true\">\n <path v-if=\"ink\" class=\"dou-whiteboard-strokes__ink\" :d=\"ink\" />\n <circle\n v-if=\"state.eraser.value\"\n class=\"dou-whiteboard-strokes__eraser\"\n :cx=\"state.eraser.value.x\"\n :cy=\"state.eraser.value.y\"\n :r=\"eraserRadius\"\n />\n </svg>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAQA,MAAM,MAAM,cAAc;EAC1B,MAAM,QAAQ,WAAW,GAAG;EAC5B,MAAM,MAAM,eAAgB,MAAM,IAAI,QAAQ,WAAW,MAAM,IAAI,KAAK,IAAI,EAAG;EAC/E,MAAM,eAAe,eAAe,IAAI,IAAI,SAAS,MAAM,IAAI;EAE/D,MAAM,WAAW,OAAiB,MAAM,QAAQ,QAAS,MAA6B;;;IAIpFG,mBAAqD,OAAA;KAA/C,KAAK;KAAS,OAAM;;IAElBC,MAAA,KAAA,CAAK,CAAC,MAAM,SADpBJ,UAAA,GAAAC,mBAUE,OAAA;;KARA,OAAKI,eAAA,CAAC,wBAAsB,MACdD,MAAA,KAAA,CAAK,CAAC,MAAM,MAAM,MAAI,CAAA;KACnC,OAAKE,eAAA;MAAkC,WAAA,aAAAF,MAAA,KAAA,CAAK,CAAC,MAAM,MAAM,EAAC,MAAOA,MAAA,KAAA,CAAK,CAAC,MAAM,MAAM,EAAC;MAAuB,OAAA,GAAAA,MAAA,KAAA,CAAK,CAAC,MAAM,MAAM,MAAK;MAAuB,QAAA,GAAAA,MAAA,KAAA,CAAK,CAAC,MAAM,MAAM,OAAM;;KAKlL,eAAY;;IAEH,IAAA,SAAOA,MAAA,KAAA,CAAK,CAAC,OAAO,SAA/BJ,UAAA,GAAAC,mBASM,OATN,YASM,CARQ,IAAA,SAAZD,UAAA,GAAAC,mBAAgE,QAAA;;KAA/C,OAAM;KAA+B,GAAG,IAAA;I,yDAEjDG,MAAA,KAAA,CAAK,CAAC,OAAO,SADrBJ,UAAA,GAAAC,mBAME,UAAA;;KAJA,OAAM;KACL,IAAIG,MAAA,KAAA,CAAK,CAAC,OAAO,MAAM;KACvB,IAAIA,MAAA,KAAA,CAAK,CAAC,OAAO,MAAM;KACvB,GAAG,aAAA"}
@@ -0,0 +1,6 @@
1
+ import board_node_vue_vue_type_script_setup_true_lang_default from "./board-node.vue_vue_type_script_setup_true_lang.js";
2
+ // src/whiteboard/board-node.vue
3
+ var board_node_default = board_node_vue_vue_type_script_setup_true_lang_default;
4
+ export { board_node_default as default };
5
+
6
+ //# sourceMappingURL=board-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-node.js","names":["_sfc_main"],"sources":["../../src/whiteboard/board-node.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ImageOffIcon } from '@dou-ui/icons'\nimport { computed, nextTick, ref, toRef, useTemplateRef, watch } from 'vue'\nimport DouFlowPort from '../components/flow-port.vue'\nimport { useFlowEditor } from '../context'\nimport type { NodeViewProps } from '../core/node-types'\nimport type { Tone } from 'dou-ui'\nimport { fitInk, inkOutline, INK_SIZE, unpack } from './ink'\nimport { anchorSides, type WhiteboardShape } from './model'\nimport { boardState } from './state'\nimport { RESIZE_HANDLES, useTransformHandles, type ResizeHandle } from './transform'\n\ntype BoardData = {\n [key: string]: unknown\n text?: string\n shape?: WhiteboardShape\n tone?: Tone\n rotation?: number\n points?: number[]\n box?: [number, number]\n size?: number\n src?: string\n alt?: string\n}\n\ntype Kind = 'shape' | 'sticky' | 'text' | 'ink' | 'image'\n\nconst props = defineProps<NodeViewProps<BoardData>>()\nconst ctx = useFlowEditor()\nconst state = boardState(ctx)\n\nconst KINDS: readonly Kind[] = ['sticky', 'text', 'ink', 'image']\n/** What the node is: one of the shapes, a note, text, a pen stroke or an image. */\nconst kind = computed<Kind>(() => KINDS.find((k) => k === props.node.type) ?? 'shape')\nconst shape = computed<WhiteboardShape>(() => props.data.shape ?? 'rect')\nconst tone = computed<Tone>(\n () => props.data.tone ?? (kind.value === 'sticky' ? 'warning' : 'neutral'),\n)\nconst writable = computed(() => ['shape', 'sticky', 'text'].includes(kind.value))\n\nconst MIN: Record<Kind, number> = { shape: 24, sticky: 64, text: 40, ink: 8, image: 24 }\nconst rotation = computed(() => props.data.rotation ?? 0)\nconst { turning, startTurn, startResize } = useTransformHandles(\n ctx,\n toRef(props, 'node'),\n rotation,\n { width: MIN[kind.value], height: MIN[kind.value] },\n)\n/*\n * A handle being dragged shows the node changed through the editor, so\n * `node` and `data` already are what it is dragged to: the node's own frame,\n * its ports and its edges follow along with it.\n */\nconst transform = computed(() => (rotation.value ? { rotate: `${rotation.value}deg` } : undefined))\n/**\n * The resize handles: an image keeps its proportions, from its corners; text\n * only widens, so it has no top or bottom side; the rest has every corner\n * and side.\n */\nconst resizeHandles = computed(() =>\n kind.value === 'image'\n ? RESIZE_HANDLES.filter((h) => h.x && h.y)\n : kind.value === 'text'\n ? RESIZE_HANDLES.filter((h) => h.x)\n : RESIZE_HANDLES,\n)\nconst sideHandles = computed(() => resizeHandles.value.filter((h) => !h.x || !h.y))\nconst cornerHandles = computed(() => resizeHandles.value.filter((h) => h.x && h.y))\n/** The node's box, turned with it, for the handles drawn above every node. */\nconst frameStyle = computed(() => {\n const rect = ctx.getNodeRect(props.node.id) ?? { ...props.node, width: 0, height: 0 }\n return {\n // `translate`, not `transform`: it applies before `rotate`, so the box\n // turns about its own centre and its offset is not turned with it\n translate: `${rect.x}px ${rect.y}px`,\n width: `${rect.width}px`,\n height: `${rect.height}px`,\n rotate: rotation.value ? `${rotation.value}deg` : undefined,\n }\n})\nconst CURSORS = ['ew-resize', 'nwse-resize', 'ns-resize', 'nesw-resize'] as const\n/** The resize cursor along a handle's way out, turned with the node. */\nfunction cursorOf(handle: ResizeHandle) {\n // text only widens, from a corner too\n if (kind.value === 'text') handle = { x: handle.x, y: 0 }\n const degrees = (Math.atan2(handle.y, handle.x) * 180) / Math.PI + rotation.value\n return CURSORS[((Math.round(degrees / 45) % 4) + 4) % 4]\n}\nfunction onResize(event: PointerEvent, handle: ResizeHandle) {\n startResize(event, handle, {\n keepRatio: kind.value === 'image',\n widthOnly: kind.value === 'text',\n })\n}\n/** Where a handle sits on the box: `-1` is the start, `1` the end. */\nconst place = (v: -1 | 0 | 1) => `${(v + 1) * 50}%`\n\n/** Handles show on the one selected node, while the select tool is in hand. */\nconst handles = computed(\n () =>\n props.selected &&\n !props.readonly &&\n !editing.value &&\n state.tool.value === 'select' &&\n ctx.selection.value.nodes.length === 1,\n)\n\n/** The stroke fitted to the box shown, so stretching it keeps its thickness. */\nconst ink = computed(() => {\n const { points, box: inkBox } = props.data\n if (kind.value !== 'ink' || !points || !inkBox) return ''\n const size = props.data.size ?? INK_SIZE\n return inkOutline(fitInk(unpack(points), inkBox, shown.value.w, shown.value.h, size), size)\n})\nconst erasing = computed(() => state.erasing.value.has(props.node.id))\n\n// drawn in the node's own units, half the 1.5px stroke in from the edge\nconst shown = computed(() => ({ w: props.node.width ?? 160, h: props.node.height ?? 100 }))\nconst diamond = computed(() => {\n const { w, h } = shown.value\n const i = 1\n return `${w / 2},${i} ${w - i},${h / 2} ${w / 2},${h - i} ${i},${h / 2}`\n})\n\n// Whole lines that fit, the last one ending in an ellipsis, as an export draws them.\nconst textEl = useTemplateRef<HTMLElement>('textEl')\nconst textMetrics = ref<{ line: number; padding: number } | null>(null)\nwatch(\n textEl,\n (el) => {\n if (!el) return\n const s = getComputedStyle(el)\n const line = Number.parseFloat(s.lineHeight)\n if (!line) return\n const padding = Number.parseFloat(s.paddingTop) + Number.parseFloat(s.paddingBottom)\n textMetrics.value = { line, padding: padding || 0 }\n },\n { flush: 'post' },\n)\nconst textStyle = computed(() => {\n const m = textMetrics.value\n if (!m || kind.value === 'text') return undefined\n const height = shown.value.h\n return { '-webkit-line-clamp': Math.max(1, Math.floor((height - m.padding + 0.5) / m.line)) }\n})\n\n// An image that can't be loaded: a plain box with a sign, as an export draws it.\nconst broken = ref(false)\nwatch(\n () => props.data.src,\n () => (broken.value = false),\n)\n\nconst editing = computed(() => state.editing.value === props.node.id)\nconst draft = ref('')\nconst field = useTemplateRef<HTMLTextAreaElement>('field')\n\nwatch(\n editing,\n (on) => {\n if (!on) return\n draft.value = props.data.text ?? ''\n void nextTick(() => {\n fit()\n field.value?.focus()\n field.value?.select()\n })\n },\n { immediate: true },\n)\n\n// browsers without `field-sizing` grow the field by hand\nfunction fit() {\n const el = field.value\n if (!el || CSS.supports?.('field-sizing', 'content')) return\n el.style.height = 'auto'\n el.style.height = `${el.scrollHeight}px`\n}\nwatch(draft, () => void nextTick(fit))\n\nfunction edit() {\n if (props.readonly || !writable.value) return\n state.editing.value = props.node.id\n}\n\nfunction finish() {\n if (!editing.value) return\n state.editing.value = null\n const text = draft.value.replace(/\\s+$/, '')\n // text with nothing written in it is nothing at all\n if (!text && kind.value === 'text') ctx.transact((tr) => tr.removeNode(props.node.id))\n else if (text !== (props.data.text ?? '')) props.updateData({ text: text || undefined })\n // back to the canvas, so the keys go on working\n ctx.root.value?.focus({ preventScroll: true })\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.isComposing) return\n // Escape, or mod+Enter, puts the text down; Enter is a new line\n if (event.key === 'Escape' || (event.key === 'Enter' && (event.metaKey || event.ctrlKey))) {\n event.preventDefault()\n event.stopPropagation()\n finish()\n }\n}\n</script>\n\n<template>\n <div\n class=\"dou-whiteboard-node\"\n :class=\"[\n `is-${kind}`,\n kind === 'shape' && `is-${shape}`,\n `is-${tone}`,\n {\n 'is-editing': editing,\n 'is-turned': !!rotation,\n 'is-turning': turning !== null,\n 'is-selected': selected,\n 'is-erasing': erasing,\n },\n ]\"\n :style=\"transform\"\n @dblclick.stop=\"edit\"\n >\n <svg\n v-if=\"kind === 'shape'\"\n class=\"dou-whiteboard-node__shape\"\n :viewBox=\"`0 0 ${shown.w} ${shown.h}`\"\n aria-hidden=\"true\"\n >\n <rect\n v-if=\"shape === 'rect'\"\n x=\"0.75\"\n y=\"0.75\"\n :width=\"Math.max(0, shown.w - 1.5)\"\n :height=\"Math.max(0, shown.h - 1.5)\"\n rx=\"8\"\n />\n <ellipse\n v-else-if=\"shape === 'ellipse'\"\n :cx=\"shown.w / 2\"\n :cy=\"shown.h / 2\"\n :rx=\"Math.max(0, shown.w / 2 - 0.75)\"\n :ry=\"Math.max(0, shown.h / 2 - 0.75)\"\n />\n <polygon v-else :points=\"diamond\" />\n </svg>\n <svg\n v-else-if=\"kind === 'ink' && data.box\"\n class=\"dou-whiteboard-node__ink\"\n :viewBox=\"`0 0 ${shown.w} ${shown.h}`\"\n aria-hidden=\"true\"\n >\n <path class=\"dou-whiteboard-node__ink-hit\" :d=\"ink\" />\n <path :d=\"ink\" />\n </svg>\n <div\n v-else-if=\"kind === 'image' && broken\"\n class=\"dou-whiteboard-node__image is-broken\"\n role=\"img\"\n :aria-label=\"data.alt || undefined\"\n >\n <ImageOffIcon />\n </div>\n <img\n v-else-if=\"kind === 'image'\"\n class=\"dou-whiteboard-node__image\"\n :src=\"data.src\"\n :alt=\"data.alt ?? ''\"\n draggable=\"false\"\n @error=\"broken = true\"\n />\n <textarea\n v-if=\"editing\"\n ref=\"field\"\n v-model=\"draft\"\n class=\"dou-whiteboard-node__text dou-whiteboard-node__field\"\n :aria-label=\"ctx.messages.value.board.editText\"\n @keydown=\"onKeydown\"\n @blur=\"finish\"\n @pointerdown.stop\n />\n <div\n v-else-if=\"writable\"\n ref=\"textEl\"\n class=\"dou-whiteboard-node__text\"\n :style=\"textStyle\"\n v-text=\"data.text\"\n />\n <div v-if=\"kind !== 'ink'\" class=\"dou-whiteboard-node__anchors\">\n <template v-for=\"side in anchorSides\" :key=\"side\">\n <DouFlowPort :id=\"`${side}-in`\" class=\"dou-whiteboard-node__anchor\" />\n <DouFlowPort :id=\"side\" class=\"dou-whiteboard-node__anchor\" />\n </template>\n </div>\n <template v-if=\"handles\">\n <!-- the sides stay on the node, under its anchors; the turn and the\n corners go above every node, so a neighbour never covers them -->\n <span\n v-for=\"handle in sideHandles\"\n :key=\"`${handle.x},${handle.y}`\"\n :class=\"['dou-whiteboard-node__side', handle.x ? 'is-x' : 'is-y']\"\n :style=\"{ left: place(handle.x), top: place(handle.y), cursor: cursorOf(handle) }\"\n :data-handle=\"`${handle.x},${handle.y}`\"\n data-dou-nodrag\n aria-hidden=\"true\"\n @pointerdown=\"onResize($event, handle)\"\n />\n <Teleport :to=\"state.handles.value\" :disabled=\"!state.handles.value\">\n <div class=\"dou-whiteboard-node__frame\" :style=\"frameStyle\" aria-hidden=\"true\">\n <!-- data-flow-overlay on its target: the selection's toolbar keeps\n clear of where a finger can take it, when it floats below the\n node or the node is turned upside down -->\n <span class=\"dou-whiteboard-node__turn\" data-dou-nodrag @pointerdown=\"startTurn\">\n <span class=\"dou-whiteboard-node__target\" data-flow-overlay />\n </span>\n <span\n v-for=\"handle in cornerHandles\"\n :key=\"`${handle.x},${handle.y}`\"\n class=\"dou-whiteboard-node__corner\"\n :style=\"{ left: place(handle.x), top: place(handle.y), cursor: cursorOf(handle) }\"\n :data-handle=\"`${handle.x},${handle.y}`\"\n data-dou-nodrag\n @pointerdown=\"onResize($event, handle)\"\n />\n </div>\n </Teleport>\n </template>\n </div>\n</template>\n"],"mappings":""}