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,163 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import ImageTooltip from "./tooltips/Image.vue";
3
+ const DEFAULT_ACCEPT = "image/png,image/jpeg,image/webp,image/gif,image/svg+xml,image/avif,image/bmp";
4
+ function makeCache() {
5
+ return /* @__PURE__ */ new Map();
6
+ }
7
+ function fileToDataUrl(file) {
8
+ return new Promise((resolve, reject) => {
9
+ const reader = new FileReader();
10
+ reader.onload = () => resolve(reader.result);
11
+ reader.onerror = () => reject(reader.error);
12
+ reader.readAsDataURL(file);
13
+ });
14
+ }
15
+ function decodeImage(src) {
16
+ return new Promise((resolve, reject) => {
17
+ const img = new Image();
18
+ img.crossOrigin = "anonymous";
19
+ img.onload = () => resolve(img);
20
+ img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
21
+ img.src = src;
22
+ });
23
+ }
24
+ export function imageTool(options = {}) {
25
+ const cache = makeCache();
26
+ let latestApi = null;
27
+ function ensureLoaded(src) {
28
+ const existing = cache.get(src);
29
+ if (existing) return existing;
30
+ const img = new Image();
31
+ img.crossOrigin = "anonymous";
32
+ const entry = { img, failed: false };
33
+ const triggerRender = () => {
34
+ if (latestApi) {
35
+ latestApi.requestRender();
36
+ } else {
37
+ const poll = () => {
38
+ if (latestApi) latestApi.requestRender();
39
+ else requestAnimationFrame(poll);
40
+ };
41
+ requestAnimationFrame(poll);
42
+ }
43
+ };
44
+ img.onload = triggerRender;
45
+ img.onerror = () => {
46
+ entry.failed = true;
47
+ console.warn(`[imageTool] Failed to load image: ${src}`);
48
+ triggerRender();
49
+ };
50
+ img.src = src;
51
+ cache.set(src, entry);
52
+ return entry;
53
+ }
54
+ function pickFiles(input) {
55
+ return new Promise((resolve) => {
56
+ let settled = false;
57
+ let focusTimer = null;
58
+ const cleanup = () => {
59
+ input.onchange = null;
60
+ input.oncancel = null;
61
+ window.removeEventListener("focus", onFocus);
62
+ if (focusTimer !== null) {
63
+ clearTimeout(focusTimer);
64
+ focusTimer = null;
65
+ }
66
+ };
67
+ const settle = (value) => {
68
+ if (settled) return;
69
+ settled = true;
70
+ cleanup();
71
+ resolve(value);
72
+ };
73
+ const onFocus = () => {
74
+ focusTimer = setTimeout(() => {
75
+ if (!settled) {
76
+ settle(input.files && input.files.length > 0 ? input.files : null);
77
+ }
78
+ }, 300);
79
+ };
80
+ input.onchange = () => settle(input.files);
81
+ input.oncancel = () => settle(null);
82
+ window.addEventListener("focus", onFocus);
83
+ input.click();
84
+ });
85
+ }
86
+ async function pickAndAdd(api) {
87
+ latestApi = api;
88
+ const input = document.createElement("input");
89
+ input.type = "file";
90
+ input.accept = api.options.accept;
91
+ input.multiple = api.options.multiple;
92
+ const file = await pickFiles(input);
93
+ if (!file || file.length === 0) return;
94
+ const { width: cw, height: ch } = api.size();
95
+ for (const f of Array.from(file)) {
96
+ try {
97
+ const src = await fileToDataUrl(f);
98
+ const img = await decodeImage(src);
99
+ cache.set(src, { img, failed: false });
100
+ const max = api.options.maxSize;
101
+ const scale = Math.min(
102
+ 1,
103
+ max / Math.max(img.naturalWidth, img.naturalHeight)
104
+ );
105
+ const w = img.naturalWidth * scale;
106
+ const h = img.naturalHeight * scale;
107
+ api.addNode({
108
+ type: "image",
109
+ x: (cw - w) / 2,
110
+ y: (ch - h) / 2,
111
+ width: w,
112
+ height: h,
113
+ data: {
114
+ src,
115
+ naturalWidth: img.naturalWidth,
116
+ naturalHeight: img.naturalHeight,
117
+ alt: f.name
118
+ }
119
+ });
120
+ } catch {
121
+ }
122
+ }
123
+ }
124
+ return defineCanvasTool({
125
+ id: "image",
126
+ label: "Image",
127
+ icon: "image",
128
+ kind: "action",
129
+ tooltip: ImageTooltip,
130
+ defaultOptions: {
131
+ accept: DEFAULT_ACCEPT,
132
+ maxSize: 320,
133
+ multiple: false,
134
+ ...options
135
+ },
136
+ disabled(api) {
137
+ latestApi = api;
138
+ return false;
139
+ },
140
+ action(api) {
141
+ latestApi = api;
142
+ void pickAndAdd(api);
143
+ },
144
+ render(ctx, node) {
145
+ const data = node.data;
146
+ if (!data?.src) return;
147
+ const w = node.width ?? data.naturalWidth;
148
+ const h = node.height ?? data.naturalHeight;
149
+ const entry = ensureLoaded(data.src);
150
+ if (!entry.failed && entry.img.complete && entry.img.naturalWidth > 0) {
151
+ ctx.drawImage(entry.img, node.x, node.y, w, h);
152
+ return;
153
+ }
154
+ ctx.save();
155
+ ctx.fillStyle = entry.failed ? "rgba(200, 0, 0, 0.08)" : "rgba(0, 0, 0, 0.05)";
156
+ ctx.fillRect(node.x, node.y, w, h);
157
+ ctx.strokeStyle = entry.failed ? "rgba(200, 0, 0, 0.3)" : "rgba(0, 0, 0, 0.2)";
158
+ ctx.lineWidth = 1;
159
+ ctx.strokeRect(node.x, node.y, w, h);
160
+ ctx.restore();
161
+ }
162
+ });
163
+ }
@@ -0,0 +1,5 @@
1
+ export interface MoveToolOptions extends Record<string, unknown> {
2
+ /** Hit-test radius in CSS pixels. */
3
+ radius: number;
4
+ }
5
+ export declare function moveTool(options?: Partial<MoveToolOptions>): import("../types.js").CanvasTool<never, MoveToolOptions>;
@@ -0,0 +1,94 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import { findTopNode, renderHighlightRect } from "./hitTest.js";
3
+ import MoveTooltip from "./tooltips/Move.vue";
4
+ export function moveTool(options = {}) {
5
+ let activeNodeId = null;
6
+ let hoveredNodeId = null;
7
+ let lastX = 0;
8
+ let lastY = 0;
9
+ return defineCanvasTool({
10
+ id: "move",
11
+ label: "Move",
12
+ icon: "move",
13
+ cursor: "grab",
14
+ tooltip: MoveTooltip,
15
+ defaultOptions: {
16
+ radius: 10,
17
+ ...options
18
+ },
19
+ onPointerDown(e, api) {
20
+ const node = findTopNode(e, api, api.options.radius);
21
+ if (!node) return;
22
+ activeNodeId = node.id;
23
+ hoveredNodeId = node.id;
24
+ lastX = e.x;
25
+ lastY = e.y;
26
+ },
27
+ onPointerMove(e, api) {
28
+ if (activeNodeId && e.buttons !== 0) {
29
+ const node2 = api.getNode(activeNodeId);
30
+ if (!node2) return;
31
+ const dx = e.x - lastX;
32
+ const dy = e.y - lastY;
33
+ lastX = e.x;
34
+ lastY = e.y;
35
+ const patch = {
36
+ x: node2.x + dx,
37
+ y: node2.y + dy
38
+ };
39
+ if (Array.isArray(node2.data?.points)) {
40
+ const data = node2.data;
41
+ patch.data = {
42
+ ...node2.data,
43
+ points: data.points.map((p) => ({ x: p.x + dx, y: p.y + dy }))
44
+ };
45
+ }
46
+ api.updateNode(activeNodeId, patch);
47
+ return;
48
+ }
49
+ const node = findTopNode(e, api, api.options.radius);
50
+ const newId = node?.id ?? null;
51
+ if (newId !== hoveredNodeId) {
52
+ hoveredNodeId = newId;
53
+ api.requestRender();
54
+ }
55
+ },
56
+ onPointerUp() {
57
+ activeNodeId = null;
58
+ },
59
+ onDeactivate(api) {
60
+ if (hoveredNodeId) {
61
+ hoveredNodeId = null;
62
+ api.requestRender();
63
+ }
64
+ },
65
+ onKeyDown(e, api) {
66
+ const targetId = activeNodeId ?? hoveredNodeId;
67
+ if (!targetId) return;
68
+ if (e.key !== "[" && e.key !== "]") return;
69
+ const node = api.getNode(targetId);
70
+ if (!node) return;
71
+ const nodes = api.nodes.value;
72
+ const zIndices = nodes.map((n) => n.zIndex ?? 0);
73
+ const currentZ = node.zIndex ?? 0;
74
+ if (e.key === "]") {
75
+ const maxZ = Math.max(...zIndices);
76
+ if (currentZ < maxZ) {
77
+ api.updateNode(targetId, { zIndex: maxZ + 1 });
78
+ }
79
+ } else {
80
+ const minZ = Math.min(...zIndices);
81
+ if (currentZ > minZ) {
82
+ api.updateNode(targetId, { zIndex: minZ - 1 });
83
+ }
84
+ }
85
+ },
86
+ renderOverlay(ctx, api) {
87
+ const id = activeNodeId ?? hoveredNodeId;
88
+ if (!id) return;
89
+ const node = api.getNode(id);
90
+ if (!node) return;
91
+ renderHighlightRect(ctx, node);
92
+ }
93
+ });
94
+ }
@@ -0,0 +1 @@
1
+ export declare function redoTool(): import("../types.js").CanvasTool<unknown, Record<string, unknown>>;
@@ -0,0 +1,17 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import Redo from "./tooltips/Redo.vue";
3
+ export function redoTool() {
4
+ return defineCanvasTool({
5
+ id: "redo",
6
+ label: "Redo",
7
+ icon: "redo",
8
+ kind: "action",
9
+ tooltip: Redo,
10
+ action(api) {
11
+ api.redo();
12
+ },
13
+ disabled(api) {
14
+ return !api.canRedo.value;
15
+ }
16
+ });
17
+ }
@@ -0,0 +1,7 @@
1
+ export interface ResizeToolOptions extends Record<string, unknown> {
2
+ /** Hit-test radius in CSS pixels for picking a node to select. */
3
+ radius: number;
4
+ /** Minimum side length when resizing. */
5
+ minSize: number;
6
+ }
7
+ export declare function resizeTool(options?: Partial<ResizeToolOptions>): import("../types.js").CanvasTool<never, ResizeToolOptions>;
@@ -0,0 +1,132 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import {
3
+ findTopNode,
4
+ getNodeBounds,
5
+ renderHighlightRect,
6
+ toLocalPoint
7
+ } from "./hitTest.js";
8
+ import {
9
+ computeResizePatch,
10
+ cursorForHandle,
11
+ hitHandle,
12
+ renderResizeHandles
13
+ } from "./transformHandles.js";
14
+ import ResizeTooltip from "./tooltips/Resize.vue";
15
+ const RESIZE_HANDLES = [
16
+ "n",
17
+ "s",
18
+ "e",
19
+ "w",
20
+ "nw",
21
+ "ne",
22
+ "sw",
23
+ "se"
24
+ ];
25
+ export function resizeTool(options = {}) {
26
+ let selectedNodeId = null;
27
+ let activeHandle = null;
28
+ let dragCtx = null;
29
+ let hoveredHandle = null;
30
+ function clearSelection(api) {
31
+ if (selectedNodeId) {
32
+ selectedNodeId = null;
33
+ hoveredHandle = null;
34
+ api.setCursor(null);
35
+ api.requestRender();
36
+ }
37
+ }
38
+ return defineCanvasTool({
39
+ id: "resize",
40
+ label: "Resize",
41
+ icon: "resize",
42
+ cursor: "default",
43
+ tooltip: ResizeTooltip,
44
+ defaultOptions: {
45
+ radius: 10,
46
+ minSize: 8,
47
+ ...options
48
+ },
49
+ onPointerDown(e, api) {
50
+ if (selectedNodeId) {
51
+ const node2 = api.getNode(selectedNodeId);
52
+ if (node2) {
53
+ const handle = hitHandle(node2, e, { handles: RESIZE_HANDLES });
54
+ if (handle && handle !== "rotate") {
55
+ activeHandle = handle;
56
+ dragCtx = {
57
+ original: {
58
+ ...node2,
59
+ data: JSON.parse(JSON.stringify(node2.data))
60
+ },
61
+ originalBounds: getNodeBounds(node2),
62
+ startLocal: toLocalPoint(node2, e)
63
+ };
64
+ api.setCursor("none");
65
+ return;
66
+ }
67
+ }
68
+ }
69
+ const node = findTopNode(e, api, api.options.radius);
70
+ selectedNodeId = node ? node.id : null;
71
+ hoveredHandle = null;
72
+ api.requestRender();
73
+ },
74
+ onPointerMove(e, api) {
75
+ if (activeHandle && dragCtx && selectedNodeId && e.buttons !== 0) {
76
+ const node = api.getNode(selectedNodeId);
77
+ if (!node) return;
78
+ const local = toLocalPoint(dragCtx.original, e);
79
+ const patch = computeResizePatch(activeHandle, local, dragCtx, {
80
+ minSize: api.options.minSize
81
+ });
82
+ if (patch) api.updateNode(selectedNodeId, patch);
83
+ api.setCursor("none");
84
+ return;
85
+ }
86
+ if (selectedNodeId) {
87
+ const node = api.getNode(selectedNodeId);
88
+ if (node) {
89
+ const handle = hitHandle(node, e, { handles: RESIZE_HANDLES });
90
+ const next = handle && handle !== "rotate" ? handle : null;
91
+ if (next !== hoveredHandle) {
92
+ hoveredHandle = next;
93
+ api.requestRender();
94
+ }
95
+ api.setCursor(next ? cursorForHandle(next, node.rotation) : null);
96
+ return;
97
+ }
98
+ }
99
+ api.setCursor(null);
100
+ },
101
+ onPointerUp(_e, api) {
102
+ activeHandle = null;
103
+ dragCtx = null;
104
+ api.setCursor(null);
105
+ },
106
+ onDeactivate(api) {
107
+ activeHandle = null;
108
+ dragCtx = null;
109
+ api.setCursor(null);
110
+ clearSelection(api);
111
+ },
112
+ onKeyDown(e, api) {
113
+ if (e.key === "Escape" && selectedNodeId) {
114
+ clearSelection(api);
115
+ e.preventDefault();
116
+ }
117
+ },
118
+ renderOverlay(ctx, api) {
119
+ if (!selectedNodeId) return;
120
+ const node = api.getNode(selectedNodeId);
121
+ if (!node) {
122
+ selectedNodeId = null;
123
+ return;
124
+ }
125
+ renderHighlightRect(ctx, node);
126
+ renderResizeHandles(ctx, node, {
127
+ handles: RESIZE_HANDLES,
128
+ hovered: activeHandle ?? hoveredHandle
129
+ });
130
+ }
131
+ });
132
+ }
@@ -0,0 +1,5 @@
1
+ export interface RotateToolOptions extends Record<string, unknown> {
2
+ /** Hit-test radius in CSS pixels for picking a node to select. */
3
+ radius: number;
4
+ }
5
+ export declare function rotateTool(options?: Partial<RotateToolOptions>): import("../types.js").CanvasTool<never, RotateToolOptions>;
@@ -0,0 +1,109 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import { findTopNode, getNodeBounds, renderHighlightRect } from "./hitTest.js";
3
+ import {
4
+ cursorForHandle,
5
+ hitHandle,
6
+ normalizeAngleDelta,
7
+ renderRotationHandle
8
+ } from "./transformHandles.js";
9
+ import RotateTooltip from "./tooltips/Rotate.vue";
10
+ export function rotateTool(options = {}) {
11
+ let selectedNodeId = null;
12
+ let prevAngle = 0;
13
+ let accumulatedRotation = 0;
14
+ let dragging = false;
15
+ function clearSelection(api) {
16
+ dragging = false;
17
+ prevAngle = 0;
18
+ if (selectedNodeId) {
19
+ selectedNodeId = null;
20
+ api.setCursor(null);
21
+ api.requestRender();
22
+ }
23
+ }
24
+ return defineCanvasTool({
25
+ id: "rotate",
26
+ label: "Rotate",
27
+ icon: "rotate",
28
+ cursor: "default",
29
+ tooltip: RotateTooltip,
30
+ defaultOptions: {
31
+ radius: 10,
32
+ ...options
33
+ },
34
+ onPointerDown(e, api) {
35
+ if (selectedNodeId) {
36
+ const node2 = api.getNode(selectedNodeId);
37
+ if (node2) {
38
+ const handle = hitHandle(node2, e, { handles: ["rotate"] });
39
+ if (handle === "rotate") {
40
+ const b = getNodeBounds(node2);
41
+ dragging = true;
42
+ accumulatedRotation = node2.rotation ?? 0;
43
+ prevAngle = Math.atan2(
44
+ e.y - (b.y + b.height / 2),
45
+ e.x - (b.x + b.width / 2)
46
+ );
47
+ api.setCursor("none");
48
+ return;
49
+ }
50
+ }
51
+ }
52
+ dragging = false;
53
+ prevAngle = 0;
54
+ const node = findTopNode(e, api, api.options.radius);
55
+ selectedNodeId = node ? node.id : null;
56
+ api.requestRender();
57
+ },
58
+ onPointerMove(e, api) {
59
+ if (dragging && selectedNodeId && e.buttons !== 0) {
60
+ const node = api.getNode(selectedNodeId);
61
+ if (!node) return;
62
+ const b = getNodeBounds(node);
63
+ const angle = Math.atan2(
64
+ e.y - (b.y + b.height / 2),
65
+ e.x - (b.x + b.width / 2)
66
+ );
67
+ accumulatedRotation += normalizeAngleDelta(angle - prevAngle);
68
+ prevAngle = angle;
69
+ api.updateNode(selectedNodeId, { rotation: accumulatedRotation });
70
+ api.setCursor("none");
71
+ return;
72
+ }
73
+ if (selectedNodeId) {
74
+ const node = api.getNode(selectedNodeId);
75
+ if (node) {
76
+ const handle = hitHandle(node, e, { handles: ["rotate"] });
77
+ api.setCursor(handle ? cursorForHandle(handle, node.rotation) : null);
78
+ return;
79
+ }
80
+ }
81
+ api.setCursor(null);
82
+ },
83
+ onPointerUp(_e, api) {
84
+ dragging = false;
85
+ api.setCursor(null);
86
+ },
87
+ onDeactivate(api) {
88
+ dragging = false;
89
+ api.setCursor(null);
90
+ clearSelection(api);
91
+ },
92
+ onKeyDown(e, api) {
93
+ if (e.key === "Escape" && selectedNodeId) {
94
+ clearSelection(api);
95
+ e.preventDefault();
96
+ }
97
+ },
98
+ renderOverlay(ctx, api) {
99
+ if (!selectedNodeId) return;
100
+ const node = api.getNode(selectedNodeId);
101
+ if (!node) {
102
+ selectedNodeId = null;
103
+ return;
104
+ }
105
+ renderHighlightRect(ctx, node);
106
+ renderRotationHandle(ctx, node);
107
+ }
108
+ });
109
+ }
@@ -0,0 +1,14 @@
1
+ export interface TextToolOptions extends Record<string, unknown> {
2
+ fontSize: number;
3
+ fontFamily: string;
4
+ color: string;
5
+ weight: "normal" | "bold" | number;
6
+ }
7
+ export interface TextNodeData {
8
+ text: string;
9
+ fontSize: number;
10
+ fontFamily: string;
11
+ color: string;
12
+ weight: TextToolOptions["weight"];
13
+ }
14
+ export declare function textTool(options?: Partial<TextToolOptions>): import("../types.js").CanvasTool<TextNodeData, TextToolOptions>;
@@ -0,0 +1,99 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import TextTooltip from "./tooltips/Text.vue";
3
+ export function textTool(options = {}) {
4
+ return defineCanvasTool({
5
+ id: "text",
6
+ label: "Text",
7
+ icon: "text",
8
+ cursor: "text",
9
+ tooltip: TextTooltip,
10
+ defaultOptions: {
11
+ fontSize: 24,
12
+ fontFamily: "system-ui, sans-serif",
13
+ color: "#111111",
14
+ weight: "normal",
15
+ ...options
16
+ },
17
+ onPointerDown(e, api) {
18
+ const stage = api.stageEl();
19
+ if (!stage) return;
20
+ if (e.originalEvent.target?.closest?.(
21
+ ".canvas-text-editor"
22
+ )) {
23
+ return;
24
+ }
25
+ const input = document.createElement("input");
26
+ input.type = "text";
27
+ input.className = "canvas-text-editor";
28
+ input.style.position = "absolute";
29
+ input.style.left = `${e.x}px`;
30
+ input.style.top = `${e.y}px`;
31
+ input.style.transform = "translateY(-50%)";
32
+ input.style.font = `${api.options.weight} ${api.options.fontSize}px ${api.options.fontFamily}`;
33
+ input.style.color = api.options.color;
34
+ input.style.background = "transparent";
35
+ input.style.border = "1px dashed currentColor";
36
+ input.style.outline = "none";
37
+ input.style.padding = "0 2px";
38
+ input.style.minWidth = "1ch";
39
+ input.style.zIndex = "10";
40
+ const snapshot = { ...api.options };
41
+ stage.appendChild(input);
42
+ requestAnimationFrame(() => input.focus());
43
+ let committed = false;
44
+ const commit = () => {
45
+ if (committed) return;
46
+ committed = true;
47
+ const text = input.value;
48
+ input.remove();
49
+ if (!text.trim()) return;
50
+ api.addNode({
51
+ type: "text",
52
+ x: e.x,
53
+ y: e.y,
54
+ data: {
55
+ text,
56
+ fontSize: snapshot.fontSize,
57
+ fontFamily: snapshot.fontFamily,
58
+ color: snapshot.color,
59
+ weight: snapshot.weight
60
+ }
61
+ });
62
+ api.requestRender();
63
+ };
64
+ const cancel = () => {
65
+ if (committed) return;
66
+ committed = true;
67
+ input.remove();
68
+ };
69
+ input.addEventListener("blur", commit);
70
+ input.addEventListener("keydown", (ev) => {
71
+ if (ev.key === "Enter") {
72
+ ev.preventDefault();
73
+ input.blur();
74
+ } else if (ev.key === "Escape") {
75
+ ev.preventDefault();
76
+ input.removeEventListener("blur", commit);
77
+ cancel();
78
+ }
79
+ });
80
+ },
81
+ hitTest(node, point, radius) {
82
+ const { text, fontSize } = node.data;
83
+ if (!text) return false;
84
+ const estimatedWidth = text.length * fontSize * 0.6;
85
+ const halfHeight = fontSize / 2;
86
+ return point.x + radius >= node.x && point.x - radius <= node.x + estimatedWidth && point.y + radius >= node.y - halfHeight && point.y - radius <= node.y + halfHeight;
87
+ },
88
+ render(ctx, node) {
89
+ const { text, fontSize, fontFamily, color, weight } = node.data;
90
+ if (!text) return;
91
+ ctx.save();
92
+ ctx.font = `${weight} ${fontSize}px ${fontFamily}`;
93
+ ctx.fillStyle = color;
94
+ ctx.textBaseline = "middle";
95
+ ctx.fillText(text, node.x, node.y);
96
+ ctx.restore();
97
+ }
98
+ });
99
+ }
@@ -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,12 @@
1
+ <template>
2
+ <div class="tool-tip">
3
+ <orio-view-text type="title" size="medium">Clear</orio-view-text>
4
+ <orio-view-text type="subtitle" size="small">
5
+ Remove all nodes from the canvas.
6
+ </orio-view-text>
7
+ </div>
8
+ </template>
9
+
10
+ <style scoped>
11
+ .tool-tip{--view-text-color:#fff;display:flex;flex-direction:column;gap:.25rem;max-width:200px;white-space:normal}
12
+ </style>
@@ -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,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;