orio-ui 1.20.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 (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 +49 -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 +38 -0
  23. package/dist/runtime/components/Canvas/context.js +11 -0
  24. package/dist/runtime/components/Canvas/index.d.vue.ts +77 -0
  25. package/dist/runtime/components/Canvas/index.vue +208 -0
  26. package/dist/runtime/components/Canvas/index.vue.d.ts +77 -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 +18 -0
  41. package/dist/runtime/components/Canvas/tools/exportTool.js +89 -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,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;
@@ -0,0 +1,43 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { useI18n } from "vue-i18n";
4
+ const props = defineProps({
5
+ locales: { type: Array, required: false, default: () => [
6
+ { code: "en", flag: "\u{1F1EC}\u{1F1E7}", label: "English" },
7
+ { code: "uk", flag: "\u{1F1FA}\u{1F1E6}", label: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430" }
8
+ ] }
9
+ });
10
+ const { locale } = useI18n();
11
+ const selected = computed({
12
+ get: () => props.locales.find((l) => l.code === locale.value) ?? props.locales[0],
13
+ set: (val) => {
14
+ locale.value = val.code;
15
+ }
16
+ });
17
+ </script>
18
+
19
+ <template>
20
+ <orio-selector
21
+ v-model="selected"
22
+ :options="locales"
23
+ field="code"
24
+ option-name="label"
25
+ >
26
+ <template #trigger-label>
27
+ <span class="locale-trigger">
28
+ <span class="locale-flag">{{ selected.flag }}</span>
29
+ <span class="locale-label">{{ selected.label }}</span>
30
+ </span>
31
+ </template>
32
+ <template #option="{ option }">
33
+ <span class="locale-option">
34
+ <span class="locale-flag">{{ option.flag }}</span>
35
+ <span>{{ option.label }}</span>
36
+ </span>
37
+ </template>
38
+ </orio-selector>
39
+ </template>
40
+
41
+ <style scoped>
42
+ .locale-option,.locale-trigger{align-items:center;display:flex;gap:.5rem}.locale-flag{font-size:1.25em;line-height:1}
43
+ </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;
@@ -2,12 +2,14 @@
2
2
  import { computed, ref, toRef, toRefs } from "vue";
3
3
  import { useControlTokens } from "../composables/useControlSize";
4
4
  import { useListKeyboard } from "../composables/useListKeyboard";
5
+ import { useI18n } from "vue-i18n";
6
+ const { t } = useI18n();
5
7
  const props = defineProps({
6
8
  options: { type: Array, required: true },
7
9
  multiple: { type: Boolean, required: false },
8
10
  field: { type: String, required: false, default: "id" },
9
11
  optionName: { type: String, required: false },
10
- placeholder: { type: String, required: false, default: "Select an option" },
12
+ placeholder: { type: String, required: false },
11
13
  appearance: { type: String, required: false },
12
14
  error: { type: [String, null], required: false },
13
15
  group: { type: Boolean, required: false },
@@ -17,7 +19,10 @@ const props = defineProps({
17
19
  size: { type: String, required: false },
18
20
  fill: { type: Boolean, required: false }
19
21
  });
20
- const { field, optionName, placeholder } = toRefs(props);
22
+ const { field, optionName } = toRefs(props);
23
+ const resolvedPlaceholder = computed(
24
+ () => props.placeholder ?? t("selector.placeholder")
25
+ );
21
26
  const modelValue = defineModel({ type: null, ...{
22
27
  required: true
23
28
  } });
@@ -60,7 +65,7 @@ function isOptionSelected(option) {
60
65
  }
61
66
  }
62
67
  function getOptionLabel(option) {
63
- if (!option) return placeholder.value;
68
+ if (!option) return resolvedPlaceholder.value;
64
69
  if (typeof option === "string") return option;
65
70
  if (optionName.value) return String(option[label.value]);
66
71
  return JSON.stringify(option);
@@ -134,7 +139,11 @@ const {
134
139
  {{ getOptionLabel(modelValue) }}
135
140
  </template>
136
141
  <template v-else-if="Array.isArray(modelValue)">
137
- <span> {{ modelValue.length }} selected </span>
142
+ <span>
143
+ {{
144
+ t("selector.selected", { count: modelValue.length })
145
+ }}
146
+ </span>
138
147
  </template>
139
148
  </slot>
140
149
  </div>
@@ -175,7 +184,7 @@ const {
175
184
  </orio-list-item>
176
185
  </ul>
177
186
  <slot v-else name="no-options">
178
- <orio-empty-state title="No options found" size="small" />
187
+ <orio-empty-state :title="t('selector.noOptions')" size="small" />
179
188
  </slot>
180
189
  <slot name="options-addon" />
181
190
  </div>
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { ref, computed, nextTick } from "vue";
2
+ import { ref, computed, nextTick, toRefs, watch } from "vue";
3
3
  import { useEventListener } from "@vueuse/core";
4
4
  const props = defineProps({
5
5
  text: { type: String, required: false, default: void 0 },
@@ -8,6 +8,10 @@ const props = defineProps({
8
8
  disabled: { type: Boolean, required: false, default: false },
9
9
  offset: { type: Number, required: false, default: 8 }
10
10
  });
11
+ const { disabled } = toRefs(props);
12
+ watch(disabled, () => {
13
+ if (disabled.value) hideTooltip();
14
+ });
11
15
  const isVisible = ref(false);
12
16
  const trigger = ref(null);
13
17
  const tooltip = ref(null);
@@ -42,7 +46,7 @@ function calculatePosition() {
42
46
  position.value = { top, left };
43
47
  }
44
48
  function showTooltip() {
45
- if (props.disabled) return;
49
+ if (disabled.value) return;
46
50
  if (props.delay > 0) {
47
51
  showTimeout = setTimeout(() => {
48
52
  isVisible.value = true;
@@ -75,7 +79,9 @@ const tooltipStyle = computed(() => ({
75
79
  top: `${position.value.top}px`,
76
80
  left: `${position.value.left}px`
77
81
  }));
78
- const arrowClass = computed(() => `arrow-${props.placement}`);
82
+ const arrowClass = computed(
83
+ () => `orio-tooltip-arrow orio-tooltip-arrow-${props.placement}`
84
+ );
79
85
  </script>
80
86
 
81
87
  <template>
@@ -91,22 +97,26 @@ const arrowClass = computed(() => `arrow-${props.placement}`);
91
97
  <Teleport v-if="isVisible" to="body">
92
98
  <div
93
99
  ref="tooltip"
94
- class="tooltip"
100
+ class="orio-tooltip"
95
101
  :style="tooltipStyle"
96
102
  role="tooltip"
97
103
  :aria-hidden="!isVisible"
98
104
  >
99
- <div class="content">
105
+ <div class="orio-tooltip-content">
100
106
  <slot name="content">
101
107
  {{ text }}
102
108
  </slot>
103
109
  </div>
104
- <div :class="['arrow', arrowClass]" />
110
+ <div :class="arrowClass" />
105
111
  </div>
106
112
  </Teleport>
107
113
  </div>
108
114
  </template>
109
115
 
110
116
  <style scoped>
111
- .trigger{align-items:center;display:inline-flex;justify-content:center}.tooltip{pointer-events:none;position:absolute;z-index:9999}.content{background-color:rgba(0,0,0,.9);border-radius:var(--border-radius-sm,4px);box-shadow:0 2px 8px rgba(0,0,0,.15);color:#fff;font-size:var(--font-md);padding:.5rem .75rem;white-space:nowrap}.arrow{border-style:solid;height:0;position:absolute;width:0}.arrow-top{border-color:rgba(0,0,0,.9) transparent transparent;border-width:4px 4px 0;bottom:-4px}.arrow-bottom,.arrow-top{left:50%;transform:translateX(-50%)}.arrow-bottom{border-color:transparent transparent rgba(0,0,0,.9);border-width:0 4px 4px;top:-4px}.arrow-left{border-color:transparent transparent transparent rgba(0,0,0,.9);border-width:4px 0 4px 4px;right:-4px}.arrow-left,.arrow-right{top:50%;transform:translateY(-50%)}.arrow-right{border-color:transparent rgba(0,0,0,.9) transparent transparent;border-width:4px 4px 4px 0;left:-4px}
117
+ .trigger{align-items:center;display:inline-flex;justify-content:center}
118
+ </style>
119
+
120
+ <style>
121
+ .orio-tooltip{pointer-events:none;position:absolute;z-index:9999}.orio-tooltip-content{background-color:rgba(0,0,0,.9);border-radius:var(--border-radius-sm,4px);box-shadow:0 2px 8px rgba(0,0,0,.15);color:#fff;font-size:var(--font-md);padding:.5rem .75rem;white-space:nowrap}.orio-tooltip-arrow{border-style:solid;height:0;position:absolute;width:0}.orio-tooltip-arrow-top{border-color:rgba(0,0,0,.9) transparent transparent;border-width:4px 4px 0;bottom:-4px;left:50%;transform:translateX(-50%)}.orio-tooltip-arrow-bottom{border-color:transparent transparent rgba(0,0,0,.9);border-width:0 4px 4px;left:50%;top:-4px;transform:translateX(-50%)}.orio-tooltip-arrow-left{border-color:transparent transparent transparent rgba(0,0,0,.9);border-width:4px 0 4px 4px;right:-4px;top:50%;transform:translateY(-50%)}.orio-tooltip-arrow-right{border-color:transparent rgba(0,0,0,.9) transparent transparent;border-width:4px 4px 4px 0;left:-4px;top:50%;transform:translateY(-50%)}
112
122
  </style>