orio-ui 1.20.0 → 1.23.3

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 (139) 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 +52 -0
  6. package/dist/runtime/components/Canvas/REQUIREMENTS.md +174 -0
  7. package/dist/runtime/components/Canvas/components/Stage.d.vue.ts +3 -0
  8. package/dist/runtime/components/Canvas/components/Stage.vue +150 -0
  9. package/dist/runtime/components/Canvas/components/Stage.vue.d.ts +3 -0
  10. package/dist/runtime/components/Canvas/components/ToolButton.d.vue.ts +24 -0
  11. package/dist/runtime/components/Canvas/components/ToolButton.vue +62 -0
  12. package/dist/runtime/components/Canvas/components/ToolButton.vue.d.ts +24 -0
  13. package/dist/runtime/components/Canvas/components/Toolbar.d.vue.ts +24 -0
  14. package/dist/runtime/components/Canvas/components/Toolbar.vue +48 -0
  15. package/dist/runtime/components/Canvas/components/Toolbar.vue.d.ts +24 -0
  16. package/dist/runtime/components/Canvas/composables/useCanvasHistory.d.ts +17 -0
  17. package/dist/runtime/components/Canvas/composables/useCanvasHistory.js +76 -0
  18. package/dist/runtime/components/Canvas/composables/useCanvasNodes.d.ts +13 -0
  19. package/dist/runtime/components/Canvas/composables/useCanvasNodes.js +60 -0
  20. package/dist/runtime/components/Canvas/composables/useCanvasSetup.d.ts +5 -0
  21. package/dist/runtime/components/Canvas/composables/useCanvasSetup.js +19 -0
  22. package/dist/runtime/components/Canvas/context.d.ts +47 -0
  23. package/dist/runtime/components/Canvas/context.js +11 -0
  24. package/dist/runtime/components/Canvas/index.d.vue.ts +80 -0
  25. package/dist/runtime/components/Canvas/index.vue +225 -0
  26. package/dist/runtime/components/Canvas/index.vue.d.ts +80 -0
  27. package/dist/runtime/components/Canvas/registry.d.ts +1 -0
  28. package/dist/runtime/components/Canvas/registry.js +2 -0
  29. package/dist/runtime/components/Canvas/tools/ColorPickerWidget.d.vue.ts +7 -0
  30. package/dist/runtime/components/Canvas/tools/ColorPickerWidget.vue +32 -0
  31. package/dist/runtime/components/Canvas/tools/ColorPickerWidget.vue.d.ts +7 -0
  32. package/dist/runtime/components/Canvas/tools/clearTool.d.ts +1 -0
  33. package/dist/runtime/components/Canvas/tools/clearTool.js +16 -0
  34. package/dist/runtime/components/Canvas/tools/colorPickerTool.d.ts +6 -0
  35. package/dist/runtime/components/Canvas/tools/colorPickerTool.js +15 -0
  36. package/dist/runtime/components/Canvas/tools/drawTool.d.ts +16 -0
  37. package/dist/runtime/components/Canvas/tools/drawTool.js +92 -0
  38. package/dist/runtime/components/Canvas/tools/eraseTool.d.ts +5 -0
  39. package/dist/runtime/components/Canvas/tools/eraseTool.js +62 -0
  40. package/dist/runtime/components/Canvas/tools/exportTool.d.ts +65 -0
  41. package/dist/runtime/components/Canvas/tools/exportTool.js +136 -0
  42. package/dist/runtime/components/Canvas/tools/highlightTool.d.ts +11 -0
  43. package/dist/runtime/components/Canvas/tools/highlightTool.js +51 -0
  44. package/dist/runtime/components/Canvas/tools/hitTest.d.ts +20 -0
  45. package/dist/runtime/components/Canvas/tools/hitTest.js +111 -0
  46. package/dist/runtime/components/Canvas/tools/imageTool.d.ts +18 -0
  47. package/dist/runtime/components/Canvas/tools/imageTool.js +163 -0
  48. package/dist/runtime/components/Canvas/tools/moveTool.d.ts +5 -0
  49. package/dist/runtime/components/Canvas/tools/moveTool.js +94 -0
  50. package/dist/runtime/components/Canvas/tools/redoTool.d.ts +1 -0
  51. package/dist/runtime/components/Canvas/tools/redoTool.js +17 -0
  52. package/dist/runtime/components/Canvas/tools/resizeTool.d.ts +7 -0
  53. package/dist/runtime/components/Canvas/tools/resizeTool.js +132 -0
  54. package/dist/runtime/components/Canvas/tools/rotateTool.d.ts +5 -0
  55. package/dist/runtime/components/Canvas/tools/rotateTool.js +109 -0
  56. package/dist/runtime/components/Canvas/tools/textTool.d.ts +14 -0
  57. package/dist/runtime/components/Canvas/tools/textTool.js +99 -0
  58. package/dist/runtime/components/Canvas/tools/tooltips/Clear.d.vue.ts +3 -0
  59. package/dist/runtime/components/Canvas/tools/tooltips/Clear.vue +12 -0
  60. package/dist/runtime/components/Canvas/tools/tooltips/Clear.vue.d.ts +3 -0
  61. package/dist/runtime/components/Canvas/tools/tooltips/Draw.d.vue.ts +3 -0
  62. package/dist/runtime/components/Canvas/tools/tooltips/Draw.vue +12 -0
  63. package/dist/runtime/components/Canvas/tools/tooltips/Draw.vue.d.ts +3 -0
  64. package/dist/runtime/components/Canvas/tools/tooltips/Erase.d.vue.ts +3 -0
  65. package/dist/runtime/components/Canvas/tools/tooltips/Erase.vue +12 -0
  66. package/dist/runtime/components/Canvas/tools/tooltips/Erase.vue.d.ts +3 -0
  67. package/dist/runtime/components/Canvas/tools/tooltips/Export.d.vue.ts +3 -0
  68. package/dist/runtime/components/Canvas/tools/tooltips/Export.vue +13 -0
  69. package/dist/runtime/components/Canvas/tools/tooltips/Export.vue.d.ts +3 -0
  70. package/dist/runtime/components/Canvas/tools/tooltips/Highlight.d.vue.ts +3 -0
  71. package/dist/runtime/components/Canvas/tools/tooltips/Highlight.vue +12 -0
  72. package/dist/runtime/components/Canvas/tools/tooltips/Highlight.vue.d.ts +3 -0
  73. package/dist/runtime/components/Canvas/tools/tooltips/Image.d.vue.ts +3 -0
  74. package/dist/runtime/components/Canvas/tools/tooltips/Image.vue +13 -0
  75. package/dist/runtime/components/Canvas/tools/tooltips/Image.vue.d.ts +3 -0
  76. package/dist/runtime/components/Canvas/tools/tooltips/Move.d.vue.ts +3 -0
  77. package/dist/runtime/components/Canvas/tools/tooltips/Move.vue +13 -0
  78. package/dist/runtime/components/Canvas/tools/tooltips/Move.vue.d.ts +3 -0
  79. package/dist/runtime/components/Canvas/tools/tooltips/Redo.d.vue.ts +3 -0
  80. package/dist/runtime/components/Canvas/tools/tooltips/Redo.vue +13 -0
  81. package/dist/runtime/components/Canvas/tools/tooltips/Redo.vue.d.ts +3 -0
  82. package/dist/runtime/components/Canvas/tools/tooltips/Resize.d.vue.ts +3 -0
  83. package/dist/runtime/components/Canvas/tools/tooltips/Resize.vue +13 -0
  84. package/dist/runtime/components/Canvas/tools/tooltips/Resize.vue.d.ts +3 -0
  85. package/dist/runtime/components/Canvas/tools/tooltips/Rotate.d.vue.ts +3 -0
  86. package/dist/runtime/components/Canvas/tools/tooltips/Rotate.vue +13 -0
  87. package/dist/runtime/components/Canvas/tools/tooltips/Rotate.vue.d.ts +3 -0
  88. package/dist/runtime/components/Canvas/tools/tooltips/Text.d.vue.ts +3 -0
  89. package/dist/runtime/components/Canvas/tools/tooltips/Text.vue +13 -0
  90. package/dist/runtime/components/Canvas/tools/tooltips/Text.vue.d.ts +3 -0
  91. package/dist/runtime/components/Canvas/tools/tooltips/Transform.d.vue.ts +3 -0
  92. package/dist/runtime/components/Canvas/tools/tooltips/Transform.vue +14 -0
  93. package/dist/runtime/components/Canvas/tools/tooltips/Transform.vue.d.ts +3 -0
  94. package/dist/runtime/components/Canvas/tools/tooltips/Undo.d.vue.ts +3 -0
  95. package/dist/runtime/components/Canvas/tools/tooltips/Undo.vue +13 -0
  96. package/dist/runtime/components/Canvas/tools/tooltips/Undo.vue.d.ts +3 -0
  97. package/dist/runtime/components/Canvas/tools/transformHandles.d.ts +74 -0
  98. package/dist/runtime/components/Canvas/tools/transformHandles.js +191 -0
  99. package/dist/runtime/components/Canvas/tools/transformTool.d.ts +7 -0
  100. package/dist/runtime/components/Canvas/tools/transformTool.js +210 -0
  101. package/dist/runtime/components/Canvas/tools/undoTool.d.ts +1 -0
  102. package/dist/runtime/components/Canvas/tools/undoTool.js +17 -0
  103. package/dist/runtime/components/Canvas/types.d.ts +125 -0
  104. package/dist/runtime/components/Canvas/types.js +3 -0
  105. package/dist/runtime/components/ControlElement.vue +5 -1
  106. package/dist/runtime/components/DateRangePicker.vue +16 -4
  107. package/dist/runtime/components/Icon.vue +2 -2
  108. package/dist/runtime/components/LocaleSwitcher.d.vue.ts +13 -0
  109. package/dist/runtime/components/LocaleSwitcher.vue +43 -0
  110. package/dist/runtime/components/LocaleSwitcher.vue.d.ts +13 -0
  111. package/dist/runtime/components/Selector.vue +14 -5
  112. package/dist/runtime/components/Tooltip.vue +17 -7
  113. package/dist/runtime/components/ZoomableContainer.d.vue.ts +48 -0
  114. package/dist/runtime/components/ZoomableContainer.vue +238 -0
  115. package/dist/runtime/components/ZoomableContainer.vue.d.ts +48 -0
  116. package/dist/runtime/components/gallery/Carousel.vue +1 -1
  117. package/dist/runtime/components/gallery/CarouselPreview.d.vue.ts +31 -0
  118. package/dist/runtime/components/gallery/CarouselPreview.vue +64 -0
  119. package/dist/runtime/components/gallery/CarouselPreview.vue.d.ts +31 -0
  120. package/dist/runtime/components/view/Dates.vue +5 -3
  121. package/dist/runtime/components/view/KeyBinds.d.vue.ts +7 -0
  122. package/dist/runtime/components/view/KeyBinds.vue +36 -0
  123. package/dist/runtime/components/view/KeyBinds.vue.d.ts +7 -0
  124. package/dist/runtime/components/view/Text.vue +4 -4
  125. package/dist/runtime/composables/useInertia.d.ts +10 -0
  126. package/dist/runtime/composables/useInertia.js +49 -0
  127. package/dist/runtime/composables/usePinchZoom.d.ts +13 -0
  128. package/dist/runtime/composables/usePinchZoom.js +66 -0
  129. package/dist/runtime/composables/useValidation.js +11 -1
  130. package/dist/runtime/i18n/en.json +20 -0
  131. package/dist/runtime/i18n/index.d.ts +11 -0
  132. package/dist/runtime/i18n/index.js +19 -0
  133. package/dist/runtime/i18n/uk.json +20 -0
  134. package/dist/runtime/index.d.ts +5 -0
  135. package/dist/runtime/index.js +16 -0
  136. package/dist/runtime/plugins/i18n.d.ts +2 -0
  137. package/dist/runtime/plugins/i18n.js +18 -0
  138. package/dist/runtime/utils/icon-registry.js +13 -1
  139. package/package.json +9 -4
@@ -0,0 +1,191 @@
1
+ import { getNodeBounds, toLocalPoint } from "./hitTest.js";
2
+ const DEFAULT_STYLE = {
3
+ size: 10,
4
+ fill: "transparent",
5
+ stroke: "rgba(31, 122, 236, 1)",
6
+ lineWidth: 1.5,
7
+ rotateOffset: 28
8
+ };
9
+ const HOVER_ALPHA = 0.4;
10
+ export const HANDLE_CURSORS = {
11
+ n: "ns-resize",
12
+ s: "ns-resize",
13
+ e: "ew-resize",
14
+ w: "ew-resize",
15
+ nw: "nwse-resize",
16
+ se: "nwse-resize",
17
+ ne: "nesw-resize",
18
+ sw: "nesw-resize"
19
+ };
20
+ export function cursorForHandle(handle, rotation = 0) {
21
+ if (handle === "rotate") return "grab";
22
+ if (!rotation) return HANDLE_CURSORS[handle];
23
+ const order = ["n", "ne", "e", "se", "s", "sw", "w", "nw"];
24
+ const idx = order.indexOf(handle);
25
+ const step = Math.round(rotation * 4 / Math.PI);
26
+ return HANDLE_CURSORS[order[((idx + step) % 8 + 8) % 8]];
27
+ }
28
+ export function getResizeHandles(b) {
29
+ const cx = b.x + b.width / 2;
30
+ const cy = b.y + b.height / 2;
31
+ return {
32
+ nw: { x: b.x, y: b.y },
33
+ n: { x: cx, y: b.y },
34
+ ne: { x: b.x + b.width, y: b.y },
35
+ e: { x: b.x + b.width, y: cy },
36
+ se: { x: b.x + b.width, y: b.y + b.height },
37
+ s: { x: cx, y: b.y + b.height },
38
+ sw: { x: b.x, y: b.y + b.height },
39
+ w: { x: b.x, y: cy }
40
+ };
41
+ }
42
+ export function getRotationHandle(b, offset = DEFAULT_STYLE.rotateOffset) {
43
+ return { x: b.x + b.width / 2, y: b.y - offset };
44
+ }
45
+ export function hitHandle(node, point, opts = {}) {
46
+ const style = { ...DEFAULT_STYLE, ...opts.style };
47
+ const allowed = new Set(
48
+ opts.handles ?? ["n", "s", "e", "w", "nw", "ne", "sw", "se", "rotate"]
49
+ );
50
+ const bounds = getNodeBounds(node);
51
+ const local = toLocalPoint(node, point);
52
+ const r = style.size / 2 + 2;
53
+ if (allowed.has("rotate")) {
54
+ const h = getRotationHandle(bounds, style.rotateOffset);
55
+ if ((local.x - h.x) ** 2 + (local.y - h.y) ** 2 <= (r + 2) ** 2) {
56
+ return "rotate";
57
+ }
58
+ }
59
+ const resize = getResizeHandles(bounds);
60
+ for (const key of Object.keys(resize)) {
61
+ if (!allowed.has(key)) continue;
62
+ const h = resize[key];
63
+ if (Math.abs(local.x - h.x) <= r && Math.abs(local.y - h.y) <= r) {
64
+ return key;
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ export function renderResizeHandles(ctx, node, opts = {}) {
70
+ const style = { ...DEFAULT_STYLE, ...opts.style };
71
+ const bounds = getNodeBounds(node);
72
+ const all = getResizeHandles(bounds);
73
+ const list = opts.handles ?? Object.keys(all);
74
+ ctx.save();
75
+ if (node.rotation) {
76
+ const cx = bounds.x + bounds.width / 2;
77
+ const cy = bounds.y + bounds.height / 2;
78
+ ctx.translate(cx, cy);
79
+ ctx.rotate(node.rotation);
80
+ ctx.translate(-cx, -cy);
81
+ }
82
+ ctx.fillStyle = style.fill;
83
+ ctx.strokeStyle = style.stroke;
84
+ ctx.lineWidth = style.lineWidth;
85
+ for (const key of list) {
86
+ const p = all[key];
87
+ const half = style.size / 2;
88
+ const hovered = opts.hovered === key;
89
+ if (hovered) {
90
+ ctx.save();
91
+ ctx.globalAlpha = HOVER_ALPHA;
92
+ }
93
+ ctx.beginPath();
94
+ ctx.rect(p.x - half, p.y - half, style.size, style.size);
95
+ ctx.fill();
96
+ ctx.stroke();
97
+ if (hovered) ctx.restore();
98
+ }
99
+ ctx.restore();
100
+ }
101
+ export function renderRotationHandle(ctx, node, opts = {}) {
102
+ const style = { ...DEFAULT_STYLE, ...opts.style };
103
+ const bounds = getNodeBounds(node);
104
+ const top = { x: bounds.x + bounds.width / 2, y: bounds.y };
105
+ const handle = getRotationHandle(bounds, style.rotateOffset);
106
+ const r = style.size / 2 + 1;
107
+ ctx.save();
108
+ if (node.rotation) {
109
+ const cx = bounds.x + bounds.width / 2;
110
+ const cy = bounds.y + bounds.height / 2;
111
+ ctx.translate(cx, cy);
112
+ ctx.rotate(node.rotation);
113
+ ctx.translate(-cx, -cy);
114
+ }
115
+ ctx.strokeStyle = style.stroke;
116
+ ctx.fillStyle = style.fill;
117
+ ctx.lineWidth = style.lineWidth;
118
+ if (opts.hovered) ctx.globalAlpha = HOVER_ALPHA;
119
+ ctx.beginPath();
120
+ ctx.moveTo(top.x, top.y);
121
+ ctx.lineTo(handle.x, handle.y);
122
+ ctx.stroke();
123
+ ctx.beginPath();
124
+ ctx.arc(handle.x, handle.y, r, 0, Math.PI * 2);
125
+ ctx.fill();
126
+ ctx.stroke();
127
+ ctx.restore();
128
+ }
129
+ export function normalizeAngleDelta(delta) {
130
+ const TAU = Math.PI * 2;
131
+ return ((delta + Math.PI) % TAU + TAU) % TAU - Math.PI;
132
+ }
133
+ export function rotationFromPoint(node, point) {
134
+ const b = getNodeBounds(node);
135
+ const cx = b.x + b.width / 2;
136
+ const cy = b.y + b.height / 2;
137
+ return Math.atan2(point.y - cy, point.x - cx) + Math.PI / 2;
138
+ }
139
+ export function computeResizePatch(handle, pointerLocal, ctx, opts = {}) {
140
+ const minSize = opts.minSize ?? 8;
141
+ const ob = ctx.originalBounds;
142
+ let nx = ob.x;
143
+ let ny = ob.y;
144
+ let nw = ob.width;
145
+ let nh = ob.height;
146
+ if (handle.includes("e")) nw = Math.max(minSize, pointerLocal.x - ob.x);
147
+ if (handle.includes("s")) nh = Math.max(minSize, pointerLocal.y - ob.y);
148
+ if (handle.includes("w")) {
149
+ const right = ob.x + ob.width;
150
+ nx = Math.min(pointerLocal.x, right - minSize);
151
+ nw = right - nx;
152
+ }
153
+ if (handle.includes("n")) {
154
+ const bottom = ob.y + ob.height;
155
+ ny = Math.min(pointerLocal.y, bottom - minSize);
156
+ nh = bottom - ny;
157
+ }
158
+ const data = ctx.original.data;
159
+ if (ctx.original.width != null && ctx.original.height != null) {
160
+ return { x: nx, y: ny, width: nw, height: nh };
161
+ }
162
+ if (Array.isArray(data?.points)) {
163
+ const points = data.points;
164
+ const sx = ob.width === 0 ? 1 : nw / ob.width;
165
+ const sy = ob.height === 0 ? 1 : nh / ob.height;
166
+ return {
167
+ x: ctx.original.x,
168
+ y: ctx.original.y,
169
+ data: {
170
+ ...data,
171
+ points: points.map((p) => ({
172
+ x: nx + (p.x - ob.x) * sx,
173
+ y: ny + (p.y - ob.y) * sy
174
+ }))
175
+ }
176
+ };
177
+ }
178
+ if (typeof data?.text === "string" && typeof data?.fontSize === "number") {
179
+ if (!["nw", "ne", "sw", "se"].includes(handle)) return null;
180
+ const sx = ob.width === 0 ? 1 : nw / ob.width;
181
+ const sy = ob.height === 0 ? 1 : nh / ob.height;
182
+ const scale = Math.max(sx, sy);
183
+ const newFont = Math.max(minSize, data.fontSize * scale);
184
+ return {
185
+ x: nx,
186
+ y: ny + (newFont - data.fontSize) / 2,
187
+ data: { ...data, fontSize: newFont }
188
+ };
189
+ }
190
+ return null;
191
+ }
@@ -0,0 +1,7 @@
1
+ export interface TransformToolOptions 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 transformTool(options?: Partial<TransformToolOptions>): import("../types.js").CanvasTool<never, TransformToolOptions>;
@@ -0,0 +1,210 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import {
3
+ findTopNode,
4
+ getNodeBounds,
5
+ hitTestNode,
6
+ renderHighlightRect,
7
+ toLocalPoint
8
+ } from "./hitTest.js";
9
+ import {
10
+ computeResizePatch,
11
+ cursorForHandle,
12
+ hitHandle,
13
+ normalizeAngleDelta,
14
+ renderResizeHandles,
15
+ renderRotationHandle
16
+ } from "./transformHandles.js";
17
+ import TransformTooltip from "./tooltips/Transform.vue";
18
+ const RESIZE_HANDLES = [
19
+ "n",
20
+ "s",
21
+ "e",
22
+ "w",
23
+ "nw",
24
+ "ne",
25
+ "sw",
26
+ "se"
27
+ ];
28
+ const ALL_HANDLES = [...RESIZE_HANDLES, "rotate"];
29
+ export function transformTool(options = {}) {
30
+ let selectedNodeId = null;
31
+ let mode = null;
32
+ let hoveredHandle = null;
33
+ let activeHandle = null;
34
+ let dragCtx = null;
35
+ let prevAngle = 0;
36
+ let accumulatedRotation = 0;
37
+ let lastX = 0;
38
+ let lastY = 0;
39
+ function reset() {
40
+ mode = null;
41
+ activeHandle = null;
42
+ dragCtx = null;
43
+ }
44
+ function clearSelection(api) {
45
+ if (selectedNodeId) {
46
+ selectedNodeId = null;
47
+ hoveredHandle = null;
48
+ api.setCursor(null);
49
+ api.requestRender();
50
+ }
51
+ }
52
+ return defineCanvasTool({
53
+ id: "transform",
54
+ label: "Transform",
55
+ icon: "transform",
56
+ cursor: "default",
57
+ tooltip: TransformTooltip,
58
+ defaultOptions: {
59
+ radius: 10,
60
+ minSize: 8,
61
+ ...options
62
+ },
63
+ onPointerDown(e, api) {
64
+ if (selectedNodeId) {
65
+ const node2 = api.getNode(selectedNodeId);
66
+ if (node2) {
67
+ const handle = hitHandle(node2, e, { handles: ALL_HANDLES });
68
+ const bounds = getNodeBounds(node2);
69
+ const center = {
70
+ x: bounds.x + bounds.width / 2,
71
+ y: bounds.y + bounds.height / 2
72
+ };
73
+ if (handle === "rotate") {
74
+ mode = "rotate";
75
+ accumulatedRotation = node2.rotation ?? 0;
76
+ prevAngle = Math.atan2(e.y - center.y, e.x - center.x);
77
+ api.setCursor("none");
78
+ return;
79
+ }
80
+ if (handle && handle !== "rotate") {
81
+ mode = "resize";
82
+ activeHandle = handle;
83
+ dragCtx = {
84
+ original: {
85
+ ...node2,
86
+ data: JSON.parse(JSON.stringify(node2.data))
87
+ },
88
+ originalBounds: bounds,
89
+ startLocal: toLocalPoint(node2, e)
90
+ };
91
+ api.setCursor("none");
92
+ return;
93
+ }
94
+ const tools = api.getTools();
95
+ const toolMap = new Map(tools.map((t) => [t.id, t]));
96
+ if (hitTestNode(node2, e, api.options.radius, toolMap)) {
97
+ mode = "move";
98
+ lastX = e.x;
99
+ lastY = e.y;
100
+ api.setCursor("grabbing");
101
+ return;
102
+ }
103
+ }
104
+ }
105
+ const node = findTopNode(e, api, api.options.radius);
106
+ selectedNodeId = node ? node.id : null;
107
+ hoveredHandle = null;
108
+ api.requestRender();
109
+ },
110
+ onPointerMove(e, api) {
111
+ if (mode && selectedNodeId && e.buttons !== 0) {
112
+ const node = api.getNode(selectedNodeId);
113
+ if (!node) return;
114
+ if (mode === "resize" && activeHandle && dragCtx) {
115
+ const local = toLocalPoint(dragCtx.original, e);
116
+ const patch = computeResizePatch(activeHandle, local, dragCtx, {
117
+ minSize: api.options.minSize
118
+ });
119
+ if (patch) api.updateNode(selectedNodeId, patch);
120
+ api.setCursor("none");
121
+ return;
122
+ }
123
+ if (mode === "rotate") {
124
+ const b = getNodeBounds(node);
125
+ const angle = Math.atan2(
126
+ e.y - (b.y + b.height / 2),
127
+ e.x - (b.x + b.width / 2)
128
+ );
129
+ accumulatedRotation += normalizeAngleDelta(angle - prevAngle);
130
+ prevAngle = angle;
131
+ api.updateNode(selectedNodeId, { rotation: accumulatedRotation });
132
+ api.setCursor("none");
133
+ return;
134
+ }
135
+ if (mode === "move") {
136
+ const dx = e.x - lastX;
137
+ const dy = e.y - lastY;
138
+ lastX = e.x;
139
+ lastY = e.y;
140
+ const patch = {
141
+ x: node.x + dx,
142
+ y: node.y + dy
143
+ };
144
+ if (Array.isArray(node.data?.points)) {
145
+ const data = node.data;
146
+ patch.data = {
147
+ ...node.data,
148
+ points: data.points.map((p) => ({ x: p.x + dx, y: p.y + dy }))
149
+ };
150
+ }
151
+ api.updateNode(selectedNodeId, patch);
152
+ api.setCursor("grabbing");
153
+ return;
154
+ }
155
+ }
156
+ if (selectedNodeId) {
157
+ const node = api.getNode(selectedNodeId);
158
+ if (node) {
159
+ const handle = hitHandle(node, e, { handles: ALL_HANDLES });
160
+ if (handle !== hoveredHandle) {
161
+ hoveredHandle = handle;
162
+ api.requestRender();
163
+ }
164
+ if (handle) {
165
+ api.setCursor(cursorForHandle(handle, node.rotation));
166
+ return;
167
+ }
168
+ const tools = api.getTools();
169
+ const toolMap = new Map(tools.map((t) => [t.id, t]));
170
+ if (hitTestNode(node, e, api.options.radius, toolMap)) {
171
+ api.setCursor("grab");
172
+ return;
173
+ }
174
+ }
175
+ }
176
+ api.setCursor(null);
177
+ },
178
+ onPointerUp(_e, api) {
179
+ reset();
180
+ api.setCursor(null);
181
+ },
182
+ onDeactivate(api) {
183
+ reset();
184
+ api.setCursor(null);
185
+ clearSelection(api);
186
+ },
187
+ onKeyDown(e, api) {
188
+ if (e.key === "Escape" && selectedNodeId) {
189
+ clearSelection(api);
190
+ e.preventDefault();
191
+ }
192
+ },
193
+ renderOverlay(ctx, api) {
194
+ if (!selectedNodeId) return;
195
+ const node = api.getNode(selectedNodeId);
196
+ if (!node) {
197
+ selectedNodeId = null;
198
+ return;
199
+ }
200
+ renderHighlightRect(ctx, node);
201
+ renderResizeHandles(ctx, node, {
202
+ handles: RESIZE_HANDLES,
203
+ hovered: mode === "resize" ? activeHandle : hoveredHandle && hoveredHandle !== "rotate" ? hoveredHandle : null
204
+ });
205
+ renderRotationHandle(ctx, node, {
206
+ hovered: mode === "rotate" || hoveredHandle === "rotate"
207
+ });
208
+ }
209
+ });
210
+ }
@@ -0,0 +1 @@
1
+ export declare function undoTool(): import("../types.js").CanvasTool<unknown, Record<string, unknown>>;
@@ -0,0 +1,17 @@
1
+ import { defineCanvasTool } from "../types.js";
2
+ import UndoTooltip from "./tooltips/Undo.vue";
3
+ export function undoTool() {
4
+ return defineCanvasTool({
5
+ id: "undo",
6
+ label: "Undo",
7
+ icon: "undo",
8
+ kind: "action",
9
+ tooltip: UndoTooltip,
10
+ action(api) {
11
+ api.undo();
12
+ },
13
+ disabled(api) {
14
+ return !api.canUndo.value;
15
+ }
16
+ });
17
+ }
@@ -0,0 +1,125 @@
1
+ import type { Component, ComputedRef, Ref } from "vue";
2
+ import type { IconName } from "../../utils/icon-registry.js";
3
+ export interface CanvasPoint {
4
+ x: number;
5
+ y: number;
6
+ }
7
+ export interface CanvasNode<TData = unknown> {
8
+ id: string;
9
+ /** Matches the id of the tool that owns (renders) this node. */
10
+ type: string;
11
+ x: number;
12
+ y: number;
13
+ width?: number;
14
+ height?: number;
15
+ rotation?: number;
16
+ /** When true, future interaction tools should skip this node. */
17
+ frozen?: boolean;
18
+ zIndex?: number;
19
+ data: TData;
20
+ }
21
+ export interface CanvasPointerEvent {
22
+ /** Canvas-space x (origin is the top-left of the stage). */
23
+ x: number;
24
+ /** Canvas-space y. */
25
+ y: number;
26
+ /** Viewport-space x — useful for positioning HTML overlays. */
27
+ clientX: number;
28
+ /** Viewport-space y. */
29
+ clientY: number;
30
+ /** Pressed buttons bitmask from the underlying PointerEvent. */
31
+ buttons: number;
32
+ originalEvent: PointerEvent;
33
+ }
34
+ export interface CanvasToolApi<TOptions extends Record<string, unknown> = Record<string, unknown>> {
35
+ /** Reactive, mutable options bag for this tool. */
36
+ options: TOptions;
37
+ /** All current nodes — treat as read-only; use helpers to mutate. */
38
+ nodes: Ref<CanvasNode[]>;
39
+ addNode: (node: Omit<CanvasNode, "id"> & {
40
+ id?: string;
41
+ }) => CanvasNode;
42
+ updateNode: (id: string, patch: Partial<Omit<CanvasNode, "id">>) => void;
43
+ removeNode: (id: string) => void;
44
+ getNode: (id: string) => CanvasNode | undefined;
45
+ clear: () => void;
46
+ /** Schedule a redraw on the next animation frame. */
47
+ requestRender: () => void;
48
+ /** The stage's root element — mount overlay DOM here. */
49
+ stageEl: () => HTMLElement | null;
50
+ /** Current canvas size in CSS pixels. */
51
+ size: () => {
52
+ width: number;
53
+ height: number;
54
+ };
55
+ /** Undo the last action. */
56
+ undo: () => void;
57
+ /** Redo the last undone action. */
58
+ redo: () => void;
59
+ /** Whether there's anything to undo. */
60
+ canUndo: ComputedRef<boolean>;
61
+ /** Whether there's anything to redo. */
62
+ canRedo: ComputedRef<boolean>;
63
+ /** Read/write another tool's reactive options. */
64
+ getToolOptions: <T extends Record<string, unknown>>(id: string) => T;
65
+ /** All registered tools — useful for hit-testing across tool types. */
66
+ getTools: () => CanvasTool[];
67
+ /**
68
+ * Override the stage cursor — pass `null` to fall back to the active tool's
69
+ * static `cursor`. Use for hover-state feedback (resize handles, rotation
70
+ * handle, etc.).
71
+ */
72
+ setCursor: (cursor: string | null) => void;
73
+ }
74
+ /**
75
+ * Tool kind determines how the toolbar renders the tool:
76
+ * - `"interaction"` (default) — activates on click, receives pointer events on the stage.
77
+ * - `"action"` — fires `action()` on click, never becomes the active tool.
78
+ * - `"widget"` — renders a custom `toolbar` component instead of a button.
79
+ */
80
+ export type CanvasToolKind = "interaction" | "action" | "widget";
81
+ export interface CanvasTool<TNodeData = unknown, TOptions extends Record<string, unknown> = Record<string, unknown>> {
82
+ /** Stable id — also used as `node.type` for nodes produced by this tool. */
83
+ id: string;
84
+ label?: string;
85
+ /** Icon name from the Orio icon registry. */
86
+ icon?: IconName | (string & {});
87
+ /** CSS cursor applied to the stage while this tool is active. */
88
+ cursor?: string;
89
+ tooltip?: Component;
90
+ /**
91
+ * Tool kind. Defaults to `"interaction"`.
92
+ * - `"interaction"` — standard pointer tool (draw, text, etc.)
93
+ * - `"action"` — one-shot action (undo, redo, clear)
94
+ * - `"widget"` — renders a custom component in the toolbar
95
+ */
96
+ kind?: CanvasToolKind;
97
+ /** For action tools: callback fired when the button is clicked. */
98
+ action?: (api: CanvasToolApi<TOptions>) => void;
99
+ /** For action tools: reactive disabled state. */
100
+ disabled?: (api: CanvasToolApi<TOptions>) => boolean;
101
+ /** For widget tools: Vue component rendered in the toolbar instead of a button. */
102
+ toolbar?: Component;
103
+ defaultOptions?: TOptions;
104
+ onPointerDown?: (e: CanvasPointerEvent, api: CanvasToolApi<TOptions>) => void;
105
+ onPointerMove?: (e: CanvasPointerEvent, api: CanvasToolApi<TOptions>) => void;
106
+ onPointerUp?: (e: CanvasPointerEvent, api: CanvasToolApi<TOptions>) => void;
107
+ onActivate?: (api: CanvasToolApi<TOptions>) => void;
108
+ onDeactivate?: (api: CanvasToolApi<TOptions>) => void;
109
+ onKeyDown?: (e: KeyboardEvent, api: CanvasToolApi<TOptions>) => void;
110
+ render?: (ctx: CanvasRenderingContext2D, node: CanvasNode<TNodeData>) => void;
111
+ /** Draw overlay graphics after all nodes have been rendered. */
112
+ renderOverlay?: (ctx: CanvasRenderingContext2D, api: CanvasToolApi<TOptions>) => void;
113
+ /**
114
+ * Hit-test a node against a point. Used by the erase tool (and future
115
+ * selection tools) to determine if a pointer intersects this node.
116
+ * Return `true` if the node is "hit".
117
+ *
118
+ * @param node The node to test.
119
+ * @param point The pointer position in canvas-space.
120
+ * @param radius The eraser/selection radius in CSS pixels.
121
+ */
122
+ hitTest?: (node: CanvasNode<TNodeData>, point: CanvasPoint, radius: number) => boolean;
123
+ }
124
+ /** Author-facing helper that preserves generics on a tool definition. */
125
+ export declare function defineCanvasTool<TNodeData = unknown, TOptions extends Record<string, unknown> = Record<string, unknown>>(tool: CanvasTool<TNodeData, TOptions>): CanvasTool<TNodeData, TOptions>;
@@ -0,0 +1,3 @@
1
+ export function defineCanvasTool(tool) {
2
+ return tool;
3
+ }
@@ -54,6 +54,7 @@ const sizeStyle = computed(() => sizeTokens[props.size]);
54
54
  display: flex;
55
55
  flex-direction: column;
56
56
  gap: 0.1rem;
57
+ border-radius: var(--control-radius);
57
58
  }
58
59
  .control .control-label {
59
60
  font-size: var(--control-label-font-size);
@@ -75,7 +76,10 @@ const sizeStyle = computed(() => sizeTokens[props.size]);
75
76
  .control .slot-wrapper :deep(*) {
76
77
  font-size: var(--control-font-size);
77
78
  }
78
- .control.has-error .slot-wrapper {
79
+ .control.has-error .slot-wrapper:not(:has(:deep(.error-fields))) {
80
+ border: 1px solid var(--color-danger);
81
+ }
82
+ .control.has-error .slot-wrapper :deep(.error-fields .slot-wrapper > *) {
79
83
  border: 1px solid var(--color-danger);
80
84
  }
81
85
  .control.has-error .slot-wrapper :deep(*) {
@@ -1,8 +1,10 @@
1
1
  <script setup>
2
2
  import { ref, watch, computed } from "vue";
3
+ import { useI18n } from "vue-i18n";
3
4
  defineProps({
4
5
  month: { type: Boolean, required: false }
5
6
  });
7
+ const { t } = useI18n();
6
8
  const dates = defineModel("dates", { type: Object, ...{ required: true } });
7
9
  const present = ref(dates.value.endDate !== "" && !dates.value.endDate);
8
10
  watch(present, (value) => {
@@ -24,12 +26,22 @@ defineExpose({ dateIsCorrect });
24
26
  <template>
25
27
  <orio-control-element
26
28
  v-bind="$attrs"
27
- :error="!dateIsCorrect && 'Start date must be before end date.'"
29
+ :error="!dateIsCorrect ? t('dateRangePicker.startBeforeEnd') : null"
28
30
  >
29
31
  <div class="date-range-picker">
30
- <orio-date-picker v-model:date="dates.startDate" :month />
31
- <orio-date-picker v-model:date="dates.endDate" :month />
32
- <orio-check-box v-model="present"> Present </orio-check-box>
32
+ <orio-date-picker
33
+ v-model:date="dates.startDate"
34
+ class="error-fields"
35
+ :month
36
+ />
37
+ <orio-date-picker
38
+ v-model:date="dates.endDate"
39
+ class="error-fields"
40
+ :month
41
+ />
42
+ <orio-check-box v-model="present">
43
+ {{ t("dateRangePicker.present") }}
44
+ </orio-check-box>
33
45
  </div>
34
46
  </orio-control-element>
35
47
  </template>
@@ -15,12 +15,12 @@ const sizeValue = computed(() => {
15
15
 
16
16
  <template>
17
17
  <span
18
- class="orio-icon"
18
+ class="icon"
19
19
  :style="{ color, width: sizeValue, height: sizeValue }"
20
20
  v-html="iconSvg"
21
21
  />
22
22
  </template>
23
23
 
24
24
  <style scoped>
25
- .orio-icon{align-items:center;display:inline-flex;flex-shrink:0;height:var(--control-icon-size,1.5em);justify-content:center;width:var(--control-icon-size,1.5em)}.orio-icon :deep(svg){fill:currentColor;height:100%;width:100%}
25
+ .icon{align-items:center;display:inline-flex;flex-shrink:0;height:var(--control-icon-size,1.5em);justify-content:center;width:var(--control-icon-size,1.5em)}.icon :deep(svg){fill:currentColor;height:100%;width:100%}
26
26
  </style>
@@ -0,0 +1,13 @@
1
+ export interface LocaleOption {
2
+ code: string;
3
+ flag: string;
4
+ label: string;
5
+ }
6
+ interface Props {
7
+ locales?: LocaleOption[];
8
+ }
9
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
10
+ locales: LocaleOption[];
11
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;