orio-ui 1.19.0 → 1.23.2

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 (161) hide show
  1. package/README.md +12 -5
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +5 -2
  4. package/dist/runtime/canvas.d.ts +21 -0
  5. package/dist/runtime/canvas.js +49 -0
  6. package/dist/runtime/components/Button.vue +2 -1
  7. package/dist/runtime/components/Canvas/REQUIREMENTS.md +174 -0
  8. package/dist/runtime/components/Canvas/components/Stage.d.vue.ts +3 -0
  9. package/dist/runtime/components/Canvas/components/Stage.vue +150 -0
  10. package/dist/runtime/components/Canvas/components/Stage.vue.d.ts +3 -0
  11. package/dist/runtime/components/Canvas/components/ToolButton.d.vue.ts +24 -0
  12. package/dist/runtime/components/Canvas/components/ToolButton.vue +62 -0
  13. package/dist/runtime/components/Canvas/components/ToolButton.vue.d.ts +24 -0
  14. package/dist/runtime/components/Canvas/components/Toolbar.d.vue.ts +24 -0
  15. package/dist/runtime/components/Canvas/components/Toolbar.vue +48 -0
  16. package/dist/runtime/components/Canvas/components/Toolbar.vue.d.ts +24 -0
  17. package/dist/runtime/components/Canvas/composables/useCanvasHistory.d.ts +17 -0
  18. package/dist/runtime/components/Canvas/composables/useCanvasHistory.js +76 -0
  19. package/dist/runtime/components/Canvas/composables/useCanvasNodes.d.ts +13 -0
  20. package/dist/runtime/components/Canvas/composables/useCanvasNodes.js +60 -0
  21. package/dist/runtime/components/Canvas/composables/useCanvasSetup.d.ts +5 -0
  22. package/dist/runtime/components/Canvas/composables/useCanvasSetup.js +19 -0
  23. package/dist/runtime/components/Canvas/context.d.ts +38 -0
  24. package/dist/runtime/components/Canvas/context.js +11 -0
  25. package/dist/runtime/components/Canvas/index.d.vue.ts +77 -0
  26. package/dist/runtime/components/Canvas/index.vue +208 -0
  27. package/dist/runtime/components/Canvas/index.vue.d.ts +77 -0
  28. package/dist/runtime/components/Canvas/registry.d.ts +1 -0
  29. package/dist/runtime/components/Canvas/registry.js +2 -0
  30. package/dist/runtime/components/Canvas/tools/ColorPickerWidget.d.vue.ts +7 -0
  31. package/dist/runtime/components/Canvas/tools/ColorPickerWidget.vue +32 -0
  32. package/dist/runtime/components/Canvas/tools/ColorPickerWidget.vue.d.ts +7 -0
  33. package/dist/runtime/components/Canvas/tools/clearTool.d.ts +1 -0
  34. package/dist/runtime/components/Canvas/tools/clearTool.js +16 -0
  35. package/dist/runtime/components/Canvas/tools/colorPickerTool.d.ts +6 -0
  36. package/dist/runtime/components/Canvas/tools/colorPickerTool.js +15 -0
  37. package/dist/runtime/components/Canvas/tools/drawTool.d.ts +16 -0
  38. package/dist/runtime/components/Canvas/tools/drawTool.js +92 -0
  39. package/dist/runtime/components/Canvas/tools/eraseTool.d.ts +5 -0
  40. package/dist/runtime/components/Canvas/tools/eraseTool.js +62 -0
  41. package/dist/runtime/components/Canvas/tools/exportTool.d.ts +18 -0
  42. package/dist/runtime/components/Canvas/tools/exportTool.js +89 -0
  43. package/dist/runtime/components/Canvas/tools/highlightTool.d.ts +11 -0
  44. package/dist/runtime/components/Canvas/tools/highlightTool.js +51 -0
  45. package/dist/runtime/components/Canvas/tools/hitTest.d.ts +20 -0
  46. package/dist/runtime/components/Canvas/tools/hitTest.js +111 -0
  47. package/dist/runtime/components/Canvas/tools/imageTool.d.ts +18 -0
  48. package/dist/runtime/components/Canvas/tools/imageTool.js +163 -0
  49. package/dist/runtime/components/Canvas/tools/moveTool.d.ts +5 -0
  50. package/dist/runtime/components/Canvas/tools/moveTool.js +94 -0
  51. package/dist/runtime/components/Canvas/tools/redoTool.d.ts +1 -0
  52. package/dist/runtime/components/Canvas/tools/redoTool.js +17 -0
  53. package/dist/runtime/components/Canvas/tools/resizeTool.d.ts +7 -0
  54. package/dist/runtime/components/Canvas/tools/resizeTool.js +132 -0
  55. package/dist/runtime/components/Canvas/tools/rotateTool.d.ts +5 -0
  56. package/dist/runtime/components/Canvas/tools/rotateTool.js +109 -0
  57. package/dist/runtime/components/Canvas/tools/textTool.d.ts +14 -0
  58. package/dist/runtime/components/Canvas/tools/textTool.js +99 -0
  59. package/dist/runtime/components/Canvas/tools/tooltips/Clear.d.vue.ts +3 -0
  60. package/dist/runtime/components/Canvas/tools/tooltips/Clear.vue +12 -0
  61. package/dist/runtime/components/Canvas/tools/tooltips/Clear.vue.d.ts +3 -0
  62. package/dist/runtime/components/Canvas/tools/tooltips/Draw.d.vue.ts +3 -0
  63. package/dist/runtime/components/Canvas/tools/tooltips/Draw.vue +12 -0
  64. package/dist/runtime/components/Canvas/tools/tooltips/Draw.vue.d.ts +3 -0
  65. package/dist/runtime/components/Canvas/tools/tooltips/Erase.d.vue.ts +3 -0
  66. package/dist/runtime/components/Canvas/tools/tooltips/Erase.vue +12 -0
  67. package/dist/runtime/components/Canvas/tools/tooltips/Erase.vue.d.ts +3 -0
  68. package/dist/runtime/components/Canvas/tools/tooltips/Export.d.vue.ts +3 -0
  69. package/dist/runtime/components/Canvas/tools/tooltips/Export.vue +13 -0
  70. package/dist/runtime/components/Canvas/tools/tooltips/Export.vue.d.ts +3 -0
  71. package/dist/runtime/components/Canvas/tools/tooltips/Highlight.d.vue.ts +3 -0
  72. package/dist/runtime/components/Canvas/tools/tooltips/Highlight.vue +12 -0
  73. package/dist/runtime/components/Canvas/tools/tooltips/Highlight.vue.d.ts +3 -0
  74. package/dist/runtime/components/Canvas/tools/tooltips/Image.d.vue.ts +3 -0
  75. package/dist/runtime/components/Canvas/tools/tooltips/Image.vue +13 -0
  76. package/dist/runtime/components/Canvas/tools/tooltips/Image.vue.d.ts +3 -0
  77. package/dist/runtime/components/Canvas/tools/tooltips/Move.d.vue.ts +3 -0
  78. package/dist/runtime/components/Canvas/tools/tooltips/Move.vue +13 -0
  79. package/dist/runtime/components/Canvas/tools/tooltips/Move.vue.d.ts +3 -0
  80. package/dist/runtime/components/Canvas/tools/tooltips/Redo.d.vue.ts +3 -0
  81. package/dist/runtime/components/Canvas/tools/tooltips/Redo.vue +13 -0
  82. package/dist/runtime/components/Canvas/tools/tooltips/Redo.vue.d.ts +3 -0
  83. package/dist/runtime/components/Canvas/tools/tooltips/Resize.d.vue.ts +3 -0
  84. package/dist/runtime/components/Canvas/tools/tooltips/Resize.vue +13 -0
  85. package/dist/runtime/components/Canvas/tools/tooltips/Resize.vue.d.ts +3 -0
  86. package/dist/runtime/components/Canvas/tools/tooltips/Rotate.d.vue.ts +3 -0
  87. package/dist/runtime/components/Canvas/tools/tooltips/Rotate.vue +13 -0
  88. package/dist/runtime/components/Canvas/tools/tooltips/Rotate.vue.d.ts +3 -0
  89. package/dist/runtime/components/Canvas/tools/tooltips/Text.d.vue.ts +3 -0
  90. package/dist/runtime/components/Canvas/tools/tooltips/Text.vue +13 -0
  91. package/dist/runtime/components/Canvas/tools/tooltips/Text.vue.d.ts +3 -0
  92. package/dist/runtime/components/Canvas/tools/tooltips/Transform.d.vue.ts +3 -0
  93. package/dist/runtime/components/Canvas/tools/tooltips/Transform.vue +14 -0
  94. package/dist/runtime/components/Canvas/tools/tooltips/Transform.vue.d.ts +3 -0
  95. package/dist/runtime/components/Canvas/tools/tooltips/Undo.d.vue.ts +3 -0
  96. package/dist/runtime/components/Canvas/tools/tooltips/Undo.vue +13 -0
  97. package/dist/runtime/components/Canvas/tools/tooltips/Undo.vue.d.ts +3 -0
  98. package/dist/runtime/components/Canvas/tools/transformHandles.d.ts +74 -0
  99. package/dist/runtime/components/Canvas/tools/transformHandles.js +191 -0
  100. package/dist/runtime/components/Canvas/tools/transformTool.d.ts +7 -0
  101. package/dist/runtime/components/Canvas/tools/transformTool.js +210 -0
  102. package/dist/runtime/components/Canvas/tools/undoTool.d.ts +1 -0
  103. package/dist/runtime/components/Canvas/tools/undoTool.js +17 -0
  104. package/dist/runtime/components/Canvas/types.d.ts +125 -0
  105. package/dist/runtime/components/Canvas/types.js +3 -0
  106. package/dist/runtime/components/CheckBox.vue +6 -3
  107. package/dist/runtime/components/CheckboxGroup.vue +2 -1
  108. package/dist/runtime/components/ControlElement.d.vue.ts +5 -0
  109. package/dist/runtime/components/ControlElement.vue +19 -3
  110. package/dist/runtime/components/ControlElement.vue.d.ts +5 -0
  111. package/dist/runtime/components/DateRangePicker.vue +16 -4
  112. package/dist/runtime/components/Icon.vue +2 -2
  113. package/dist/runtime/components/Input.vue +2 -1
  114. package/dist/runtime/components/ListItem.d.vue.ts +29 -0
  115. package/dist/runtime/components/ListItem.vue +72 -0
  116. package/dist/runtime/components/ListItem.vue.d.ts +29 -0
  117. package/dist/runtime/components/LocaleSwitcher.d.vue.ts +13 -0
  118. package/dist/runtime/components/LocaleSwitcher.vue +43 -0
  119. package/dist/runtime/components/LocaleSwitcher.vue.d.ts +13 -0
  120. package/dist/runtime/components/NavButton.vue +2 -1
  121. package/dist/runtime/components/NumberInput/Horizontal.vue +2 -1
  122. package/dist/runtime/components/NumberInput/Vertical.vue +2 -1
  123. package/dist/runtime/components/NumberInput/index.vue +2 -1
  124. package/dist/runtime/components/RadioButton.vue +2 -1
  125. package/dist/runtime/components/Selector.vue +24 -22
  126. package/dist/runtime/components/SwitchButton.vue +2 -1
  127. package/dist/runtime/components/Tag.d.vue.ts +3 -2
  128. package/dist/runtime/components/Tag.vue +1 -0
  129. package/dist/runtime/components/Tag.vue.d.ts +3 -2
  130. package/dist/runtime/components/TaggableSelector.d.vue.ts +16 -0
  131. package/dist/runtime/components/TaggableSelector.vue +35 -0
  132. package/dist/runtime/components/TaggableSelector.vue.d.ts +16 -0
  133. package/dist/runtime/components/Textarea.vue +2 -1
  134. package/dist/runtime/components/Tooltip.vue +17 -7
  135. package/dist/runtime/components/ZoomableContainer.d.vue.ts +48 -0
  136. package/dist/runtime/components/ZoomableContainer.vue +238 -0
  137. package/dist/runtime/components/ZoomableContainer.vue.d.ts +48 -0
  138. package/dist/runtime/components/gallery/Carousel.vue +1 -1
  139. package/dist/runtime/components/gallery/CarouselPreview.d.vue.ts +31 -0
  140. package/dist/runtime/components/gallery/CarouselPreview.vue +64 -0
  141. package/dist/runtime/components/gallery/CarouselPreview.vue.d.ts +31 -0
  142. package/dist/runtime/components/view/Dates.vue +5 -3
  143. package/dist/runtime/components/view/KeyBinds.d.vue.ts +7 -0
  144. package/dist/runtime/components/view/KeyBinds.vue +36 -0
  145. package/dist/runtime/components/view/KeyBinds.vue.d.ts +7 -0
  146. package/dist/runtime/components/view/Text.vue +4 -4
  147. package/dist/runtime/composables/useInertia.d.ts +10 -0
  148. package/dist/runtime/composables/useInertia.js +49 -0
  149. package/dist/runtime/composables/usePinchZoom.d.ts +13 -0
  150. package/dist/runtime/composables/usePinchZoom.js +66 -0
  151. package/dist/runtime/composables/useValidation.js +11 -1
  152. package/dist/runtime/i18n/en.json +20 -0
  153. package/dist/runtime/i18n/index.d.ts +11 -0
  154. package/dist/runtime/i18n/index.js +19 -0
  155. package/dist/runtime/i18n/uk.json +20 -0
  156. package/dist/runtime/index.d.ts +8 -1
  157. package/dist/runtime/index.js +19 -1
  158. package/dist/runtime/plugins/i18n.d.ts +2 -0
  159. package/dist/runtime/plugins/i18n.js +18 -0
  160. package/dist/runtime/utils/icon-registry.js +13 -1
  161. package/package.json +9 -4
@@ -0,0 +1,7 @@
1
+ import type { CanvasTool } from "../types.js";
2
+ interface Props {
3
+ tool: CanvasTool;
4
+ }
5
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,32 @@
1
+ <script setup>
2
+ import { useCanvasContext } from "../context";
3
+ const props = defineProps({
4
+ tool: { type: Object, required: true }
5
+ });
6
+ const ctx = useCanvasContext();
7
+ const options = ctx.getToolOptions(props.tool.id);
8
+ function onChange(e) {
9
+ const color = e.target.value;
10
+ options.color = color;
11
+ for (const targetId of options.targets ?? []) {
12
+ const targetOpts = ctx.getToolOptions(targetId);
13
+ if (targetOpts) targetOpts.color = color;
14
+ }
15
+ }
16
+ </script>
17
+
18
+ <template>
19
+ <orio-control-element>
20
+ <input
21
+ type="color"
22
+ :value="options.color"
23
+ :title="tool.label ?? 'Color'"
24
+ class="canvas-color-picker"
25
+ @input="onChange"
26
+ />
27
+ </orio-control-element>
28
+ </template>
29
+
30
+ <style scoped>
31
+ .canvas-color-picker{background:none;border:1px solid var(--color-border,#ddd);border-radius:4px;cursor:pointer;height:1.75rem;padding:0;width:2rem}
32
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { CanvasTool } from "../types.js";
2
+ interface Props {
3
+ tool: CanvasTool;
4
+ }
5
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1 @@
1
+ export declare function clearTool(): import("../types.js").CanvasTool<unknown, Record<string, unknown>>;
@@ -0,0 +1,16 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import ClearTooltip from "./tooltips/Clear.vue";
3
+ export function clearTool() {
4
+ return defineCanvasTool({
5
+ id: "clear",
6
+ label: "Clear",
7
+ kind: "action",
8
+ tooltip: ClearTooltip,
9
+ action(api) {
10
+ api.clear();
11
+ },
12
+ disabled(api) {
13
+ return api.nodes.value.every((n) => n.frozen);
14
+ }
15
+ });
16
+ }
@@ -0,0 +1,6 @@
1
+ export interface ColorPickerToolOptions extends Record<string, unknown> {
2
+ color: string;
3
+ /** Tool ids whose `color` option should be synced. */
4
+ targets: string[];
5
+ }
6
+ export declare function colorPickerTool(options?: Partial<ColorPickerToolOptions>): import("../types.js").CanvasTool<never, ColorPickerToolOptions>;
@@ -0,0 +1,15 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import ColorPickerWidget from "./ColorPickerWidget.vue";
3
+ export function colorPickerTool(options = {}) {
4
+ return defineCanvasTool({
5
+ id: "color-picker",
6
+ label: "Color",
7
+ kind: "widget",
8
+ toolbar: ColorPickerWidget,
9
+ defaultOptions: {
10
+ color: "#111111",
11
+ targets: [],
12
+ ...options
13
+ }
14
+ });
15
+ }
@@ -0,0 +1,16 @@
1
+ import type { CanvasPoint } from "../types.js";
2
+ export interface DrawToolOptions extends Record<string, unknown> {
3
+ color: string;
4
+ size: number;
5
+ opacity: number;
6
+ /** Reserved for future brushes (eraser, marker, airbrush...). */
7
+ brush: "pen" | "marker";
8
+ }
9
+ export interface DrawNodeData {
10
+ points: CanvasPoint[];
11
+ color: string;
12
+ size: number;
13
+ opacity: number;
14
+ brush: DrawToolOptions["brush"];
15
+ }
16
+ export declare function drawTool(options?: Partial<DrawToolOptions>): import("../types.js").CanvasTool<DrawNodeData, DrawToolOptions>;
@@ -0,0 +1,92 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import DrawTooltip from "./tooltips/Draw.vue";
3
+ export function drawTool(options = {}) {
4
+ let activeStrokeId = null;
5
+ return defineCanvasTool({
6
+ id: "draw",
7
+ label: "Draw",
8
+ icon: "pencil",
9
+ cursor: "crosshair",
10
+ tooltip: DrawTooltip,
11
+ defaultOptions: {
12
+ color: "#111111",
13
+ size: 4,
14
+ opacity: 1,
15
+ brush: "pen",
16
+ ...options
17
+ },
18
+ onPointerDown(e, api) {
19
+ const node = api.addNode({
20
+ type: "draw",
21
+ x: 0,
22
+ y: 0,
23
+ data: {
24
+ points: [{ x: e.x, y: e.y }],
25
+ color: api.options.color,
26
+ size: api.options.size,
27
+ opacity: api.options.opacity,
28
+ brush: api.options.brush
29
+ }
30
+ });
31
+ activeStrokeId = node.id;
32
+ },
33
+ onPointerMove(e, api) {
34
+ if (!activeStrokeId || e.buttons === 0) return;
35
+ const node = api.getNode(activeStrokeId);
36
+ if (!node) return;
37
+ node.data.points.push({ x: e.x, y: e.y });
38
+ api.requestRender();
39
+ },
40
+ onPointerUp(_e, api) {
41
+ if (!activeStrokeId) return;
42
+ const node = api.getNode(activeStrokeId);
43
+ if (node) {
44
+ api.updateNode(activeStrokeId, {
45
+ data: { ...node.data }
46
+ });
47
+ }
48
+ activeStrokeId = null;
49
+ },
50
+ hitTest(node, point, radius) {
51
+ const { points, size } = node.data;
52
+ const r = radius + size / 2;
53
+ return points.some(
54
+ (p) => (p.x - point.x) ** 2 + (p.y - point.y) ** 2 <= r * r
55
+ );
56
+ },
57
+ render(ctx, node) {
58
+ const { points, color, size, opacity, brush } = node.data;
59
+ if (points.length === 0) return;
60
+ ctx.save();
61
+ ctx.globalAlpha = opacity;
62
+ ctx.strokeStyle = color;
63
+ ctx.lineWidth = size;
64
+ ctx.lineCap = "round";
65
+ ctx.lineJoin = "round";
66
+ if (brush === "marker") {
67
+ ctx.globalCompositeOperation = "multiply";
68
+ }
69
+ ctx.beginPath();
70
+ if (points.length === 1) {
71
+ const [p] = points;
72
+ ctx.arc(p.x, p.y, Math.max(size / 2, 0.5), 0, Math.PI * 2);
73
+ ctx.fillStyle = color;
74
+ ctx.fill();
75
+ ctx.restore();
76
+ return;
77
+ }
78
+ ctx.moveTo(points[0].x, points[0].y);
79
+ for (let i = 1; i < points.length - 1; i++) {
80
+ const p = points[i];
81
+ const next = points[i + 1];
82
+ const mx = (p.x + next.x) / 2;
83
+ const my = (p.y + next.y) / 2;
84
+ ctx.quadraticCurveTo(p.x, p.y, mx, my);
85
+ }
86
+ const last = points[points.length - 1];
87
+ ctx.lineTo(last.x, last.y);
88
+ ctx.stroke();
89
+ ctx.restore();
90
+ }
91
+ });
92
+ }
@@ -0,0 +1,5 @@
1
+ export interface EraseToolOptions extends Record<string, unknown> {
2
+ /** Eraser radius in CSS pixels. */
3
+ radius: number;
4
+ }
5
+ export declare function eraseTool(options?: Partial<EraseToolOptions>): import("../types.js").CanvasTool<never, EraseToolOptions>;
@@ -0,0 +1,62 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import { findTopNode, renderHighlightRect } from "./hitTest.js";
3
+ import EraseTooltip from "./tooltips/Erase.vue";
4
+ export function eraseTool(options = {}) {
5
+ let erasing = false;
6
+ let hoveredNodeId = null;
7
+ function eraseAt(point, api) {
8
+ const node = findTopNode(point, api, api.options.radius);
9
+ if (node) {
10
+ hoveredNodeId = null;
11
+ api.removeNode(node.id);
12
+ }
13
+ }
14
+ return defineCanvasTool({
15
+ id: "erase",
16
+ label: "Eraser",
17
+ icon: "eraser",
18
+ cursor: "crosshair",
19
+ tooltip: EraseTooltip,
20
+ defaultOptions: {
21
+ radius: 10,
22
+ ...options
23
+ },
24
+ onPointerDown(e, api) {
25
+ erasing = true;
26
+ eraseAt(e, api);
27
+ },
28
+ onPointerMove(e, api) {
29
+ if (erasing && e.buttons !== 0) {
30
+ eraseAt(e, api);
31
+ return;
32
+ }
33
+ const node = findTopNode(e, api, api.options.radius);
34
+ const newId = node?.id ?? null;
35
+ if (newId !== hoveredNodeId) {
36
+ hoveredNodeId = newId;
37
+ api.requestRender();
38
+ }
39
+ },
40
+ onPointerUp() {
41
+ erasing = false;
42
+ },
43
+ onDeactivate(api) {
44
+ if (hoveredNodeId) {
45
+ hoveredNodeId = null;
46
+ api.requestRender();
47
+ }
48
+ },
49
+ renderOverlay(ctx, api) {
50
+ if (!hoveredNodeId) return;
51
+ const node = api.getNode(hoveredNodeId);
52
+ if (!node) {
53
+ hoveredNodeId = null;
54
+ return;
55
+ }
56
+ renderHighlightRect(ctx, node, {
57
+ strokeColor: "rgba(220, 53, 69, 0.8)",
58
+ fillColor: "rgba(220, 53, 69, 0.08)"
59
+ });
60
+ }
61
+ });
62
+ }
@@ -0,0 +1,18 @@
1
+ import type { CanvasTool } from "../types.js";
2
+ /**
3
+ * Image formats Canvas2D's `toBlob`/`toDataURL` natively supports across
4
+ * modern browsers. Anything else (e.g. AVIF, TIFF) is encoder-dependent and
5
+ * not guaranteed to round-trip.
6
+ */
7
+ export type ExportFormat = "png" | "jpeg" | "webp";
8
+ export interface ExportToolOptions extends Record<string, unknown> {
9
+ /** Output format. Defaults to PNG. */
10
+ format: ExportFormat;
11
+ /** Quality for lossy formats (jpeg, webp), 0–1. Ignored for PNG. */
12
+ quality: number;
13
+ /** File name without extension. */
14
+ filename: string;
15
+ /** Pixel scale relative to the visible canvas (1 = same size). */
16
+ scale: number;
17
+ }
18
+ export declare function exportTool(options?: Partial<ExportToolOptions>): CanvasTool<never, ExportToolOptions>;
@@ -0,0 +1,89 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import { getNodeBounds } from "./hitTest.js";
3
+ import ExportTooltip from "./tooltips/Export.vue";
4
+ const MIME_TYPES = {
5
+ png: "image/png",
6
+ jpeg: "image/jpeg",
7
+ webp: "image/webp"
8
+ };
9
+ function renderNodesTo(target, nodes, tools) {
10
+ const toolMap = new Map(tools.map((t) => [t.id, t]));
11
+ const ordered = [...nodes].sort(
12
+ (a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0)
13
+ );
14
+ for (const node of ordered) {
15
+ const renderFn = toolMap.get(node.type)?.render;
16
+ if (!renderFn) continue;
17
+ if (node.rotation) {
18
+ const b = getNodeBounds(node);
19
+ const cx = b.x + b.width / 2;
20
+ const cy = b.y + b.height / 2;
21
+ target.save();
22
+ target.translate(cx, cy);
23
+ target.rotate(node.rotation);
24
+ target.translate(-cx, -cy);
25
+ renderFn(target, node);
26
+ target.restore();
27
+ } else {
28
+ renderFn(target, node);
29
+ }
30
+ }
31
+ }
32
+ function downloadBlob(blob, filename) {
33
+ const url = URL.createObjectURL(blob);
34
+ const a = document.createElement("a");
35
+ a.href = url;
36
+ a.download = filename;
37
+ document.body.appendChild(a);
38
+ a.click();
39
+ a.remove();
40
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
41
+ }
42
+ export function exportTool(options = {}) {
43
+ return defineCanvasTool({
44
+ id: "export",
45
+ label: "Export",
46
+ icon: "export",
47
+ kind: "action",
48
+ tooltip: ExportTooltip,
49
+ defaultOptions: {
50
+ format: "png",
51
+ quality: 0.92,
52
+ filename: "canvas",
53
+ scale: 1,
54
+ ...options
55
+ },
56
+ disabled(api) {
57
+ return api.nodes.value.length === 0;
58
+ },
59
+ action(api) {
60
+ const { quality, scale } = api.options;
61
+ const { width, height } = api.size();
62
+ const knownFormat = MIME_TYPES[api.options.format] ? api.options.format : "png";
63
+ const mime = MIME_TYPES[knownFormat];
64
+ const ext = knownFormat === "jpeg" ? "jpg" : knownFormat;
65
+ const sanitized = api.options.filename.replace(/[^A-Za-z0-9._-]/g, "_") || "canvas";
66
+ const out = document.createElement("canvas");
67
+ const w = Math.max(1, Math.round(width * scale));
68
+ const h = Math.max(1, Math.round(height * scale));
69
+ out.width = w;
70
+ out.height = h;
71
+ const c = out.getContext("2d");
72
+ if (!c) return;
73
+ c.scale(scale, scale);
74
+ if (knownFormat === "jpeg") {
75
+ c.fillStyle = "#ffffff";
76
+ c.fillRect(0, 0, width, height);
77
+ }
78
+ renderNodesTo(c, api.nodes.value, api.getTools());
79
+ out.toBlob(
80
+ (blob) => {
81
+ if (!blob) return;
82
+ downloadBlob(blob, `${sanitized}.${ext}`);
83
+ },
84
+ mime,
85
+ knownFormat === "png" ? void 0 : quality
86
+ );
87
+ }
88
+ });
89
+ }
@@ -0,0 +1,11 @@
1
+ export interface HighlightToolOptions extends Record<string, unknown> {
2
+ /** Hit-test radius in CSS pixels. */
3
+ radius: number;
4
+ /** Highlight stroke color. */
5
+ strokeColor: string;
6
+ /** Highlight fill color (transparent). */
7
+ fillColor: string;
8
+ /** Highlight stroke width. */
9
+ lineWidth: number;
10
+ }
11
+ export declare function highlightTool(options?: Partial<HighlightToolOptions>): import("../types.js").CanvasTool<never, HighlightToolOptions>;
@@ -0,0 +1,51 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import { findTopNode, renderHighlightRect } from "./hitTest.js";
3
+ import HighlightTooltip from "./tooltips/Highlight.vue";
4
+ export function highlightTool(options = {}) {
5
+ let highlightedNodeId = null;
6
+ return defineCanvasTool({
7
+ id: "highlight",
8
+ label: "Highlight",
9
+ icon: "highlight",
10
+ cursor: "default",
11
+ tooltip: HighlightTooltip,
12
+ defaultOptions: {
13
+ radius: 10,
14
+ strokeColor: "rgba(31, 122, 236, 0.8)",
15
+ fillColor: "rgba(31, 122, 236, 0.08)",
16
+ lineWidth: 2,
17
+ ...options
18
+ },
19
+ onPointerMove(e, api) {
20
+ const node = findTopNode(e, api, api.options.radius);
21
+ const newId = node?.id ?? null;
22
+ if (newId !== highlightedNodeId) {
23
+ highlightedNodeId = newId;
24
+ api.requestRender();
25
+ }
26
+ },
27
+ onPointerDown(e, api) {
28
+ const node = findTopNode(e, api, api.options.radius);
29
+ highlightedNodeId = node?.id ?? null;
30
+ },
31
+ onDeactivate(api) {
32
+ if (highlightedNodeId) {
33
+ highlightedNodeId = null;
34
+ api.requestRender();
35
+ }
36
+ },
37
+ renderOverlay(ctx, api) {
38
+ if (!highlightedNodeId) return;
39
+ const node = api.getNode(highlightedNodeId);
40
+ if (!node) {
41
+ highlightedNodeId = null;
42
+ return;
43
+ }
44
+ renderHighlightRect(ctx, node, {
45
+ strokeColor: api.options.strokeColor,
46
+ fillColor: api.options.fillColor,
47
+ lineWidth: api.options.lineWidth
48
+ });
49
+ }
50
+ });
51
+ }
@@ -0,0 +1,20 @@
1
+ import type { CanvasNode, CanvasPoint, CanvasTool, CanvasToolApi } from "../types.js";
2
+ export declare function defaultHitTest(node: CanvasNode, point: CanvasPoint, radius: number): boolean;
3
+ /** Transform a canvas-space point into the node's pre-rotation local space. */
4
+ export declare function toLocalPoint(node: CanvasNode, point: CanvasPoint): CanvasPoint;
5
+ export declare function hitTestNode(node: CanvasNode, point: CanvasPoint, radius: number, toolMap: Map<string, CanvasTool>): boolean;
6
+ export declare function findTopNode(point: CanvasPoint, api: CanvasToolApi, radius: number): CanvasNode | undefined;
7
+ /** Compute an axis-aligned bounding box for a node. */
8
+ export declare function getNodeBounds(node: CanvasNode): {
9
+ x: number;
10
+ y: number;
11
+ width: number;
12
+ height: number;
13
+ };
14
+ export interface HighlightStyle {
15
+ strokeColor: string;
16
+ fillColor: string;
17
+ lineWidth: number;
18
+ }
19
+ /** Draw a dashed bounding-rect highlight around a node. */
20
+ export declare function renderHighlightRect(ctx: CanvasRenderingContext2D, node: CanvasNode, style?: Partial<HighlightStyle>): void;
@@ -0,0 +1,111 @@
1
+ export function defaultHitTest(node, point, radius) {
2
+ if (node.width != null && node.height != null) {
3
+ return point.x + radius >= node.x && point.x - radius <= node.x + node.width && point.y + radius >= node.y && point.y - radius <= node.y + node.height;
4
+ }
5
+ const dx = point.x - node.x;
6
+ const dy = point.y - node.y;
7
+ return dx * dx + dy * dy <= radius * radius;
8
+ }
9
+ export function toLocalPoint(node, point) {
10
+ if (!node.rotation) return point;
11
+ const b = getNodeBounds(node);
12
+ const cx = b.x + b.width / 2;
13
+ const cy = b.y + b.height / 2;
14
+ const cos = Math.cos(-node.rotation);
15
+ const sin = Math.sin(-node.rotation);
16
+ const dx = point.x - cx;
17
+ const dy = point.y - cy;
18
+ return { x: cx + dx * cos - dy * sin, y: cy + dx * sin + dy * cos };
19
+ }
20
+ export function hitTestNode(node, point, radius, toolMap) {
21
+ const local = toLocalPoint(node, point);
22
+ const ownerTool = toolMap.get(node.type);
23
+ return ownerTool?.hitTest ? ownerTool.hitTest(node, local, radius) : defaultHitTest(node, local, radius);
24
+ }
25
+ export function findTopNode(point, api, radius) {
26
+ const tools = api.getTools();
27
+ const toolMap = new Map(tools.map((t) => [t.id, t]));
28
+ let best;
29
+ for (const node of api.nodes.value) {
30
+ if (node.frozen) continue;
31
+ if (!hitTestNode(node, point, radius, toolMap)) continue;
32
+ if (!best || (node.zIndex ?? 0) >= (best.zIndex ?? 0)) {
33
+ best = node;
34
+ }
35
+ }
36
+ return best;
37
+ }
38
+ export function getNodeBounds(node) {
39
+ if (node.width != null && node.height != null) {
40
+ return { x: node.x, y: node.y, width: node.width, height: node.height };
41
+ }
42
+ const data = node.data;
43
+ if (Array.isArray(data?.points) && data.points.length > 0) {
44
+ const points = data.points;
45
+ let minX = Infinity;
46
+ let minY = Infinity;
47
+ let maxX = -Infinity;
48
+ let maxY = -Infinity;
49
+ for (const p of points) {
50
+ if (p.x < minX) minX = p.x;
51
+ if (p.y < minY) minY = p.y;
52
+ if (p.x > maxX) maxX = p.x;
53
+ if (p.y > maxY) maxY = p.y;
54
+ }
55
+ const pad = (data.size ?? 4) / 2;
56
+ return {
57
+ x: minX - pad,
58
+ y: minY - pad,
59
+ width: maxX - minX + pad * 2,
60
+ height: maxY - minY + pad * 2
61
+ };
62
+ }
63
+ if (typeof data?.text === "string" && typeof data?.fontSize === "number") {
64
+ const fontSize = data.fontSize;
65
+ const text = data.text;
66
+ const estimatedWidth = text.length * fontSize * 0.6;
67
+ return {
68
+ x: node.x,
69
+ y: node.y - fontSize / 2,
70
+ width: estimatedWidth,
71
+ height: fontSize
72
+ };
73
+ }
74
+ return { x: node.x - 10, y: node.y - 10, width: 20, height: 20 };
75
+ }
76
+ const DEFAULT_HIGHLIGHT = {
77
+ strokeColor: "rgba(31, 122, 236, 0.8)",
78
+ fillColor: "rgba(31, 122, 236, 0.08)",
79
+ lineWidth: 2
80
+ };
81
+ export function renderHighlightRect(ctx, node, style = {}) {
82
+ const { strokeColor, fillColor, lineWidth } = {
83
+ ...DEFAULT_HIGHLIGHT,
84
+ ...style
85
+ };
86
+ const bounds = getNodeBounds(node);
87
+ const pad = 6;
88
+ ctx.save();
89
+ if (node.rotation) {
90
+ const cx = bounds.x + bounds.width / 2;
91
+ const cy = bounds.y + bounds.height / 2;
92
+ ctx.translate(cx, cy);
93
+ ctx.rotate(node.rotation);
94
+ ctx.translate(-cx, -cy);
95
+ }
96
+ ctx.strokeStyle = strokeColor;
97
+ ctx.fillStyle = fillColor;
98
+ ctx.lineWidth = lineWidth;
99
+ ctx.setLineDash([6, 3]);
100
+ ctx.beginPath();
101
+ ctx.roundRect(
102
+ bounds.x - pad,
103
+ bounds.y - pad,
104
+ bounds.width + pad * 2,
105
+ bounds.height + pad * 2,
106
+ 4
107
+ );
108
+ ctx.fill();
109
+ ctx.stroke();
110
+ ctx.restore();
111
+ }
@@ -0,0 +1,18 @@
1
+ export interface ImageNodeData {
2
+ /** Object URL or data URL the image was loaded from. */
3
+ src: string;
4
+ /** Original natural dimensions — preserved for aspect-ratio resizing. */
5
+ naturalWidth: number;
6
+ naturalHeight: number;
7
+ /** Optional alt text. */
8
+ alt?: string;
9
+ }
10
+ export interface ImageToolOptions extends Record<string, unknown> {
11
+ /** `accept` attribute on the file picker. Defaults to all canvas-renderable images. */
12
+ accept: string;
13
+ /** Largest side (CSS pixels) when an uploaded image is placed. */
14
+ maxSize: number;
15
+ /** Allow multiple files in one upload. */
16
+ multiple: boolean;
17
+ }
18
+ export declare function imageTool(options?: Partial<ImageToolOptions>): import("../types.js").CanvasTool<ImageNodeData, ImageToolOptions>;