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
package/README.md CHANGED
@@ -8,7 +8,7 @@ A delightful, lightweight component library for Nuxt 3+ applications. Built with
8
8
 
9
9
  ## Features
10
10
 
11
- ✨ **34 Components** - Beautiful, accessible components ready to use
11
+ ✨ **56 Components** - Beautiful, accessible components ready to use
12
12
  🎨 **Themeable** - 5 built-in accent themes with light/dark mode support
13
13
  🚀 **Auto-imported** - Works seamlessly with Nuxt's auto-import system
14
14
  📦 **Tree-shakeable** - Only bundle what you use
@@ -16,6 +16,7 @@ A delightful, lightweight component library for Nuxt 3+ applications. Built with
16
16
  🧪 **Tested** - 29 test suites for reliability
17
17
  📱 **Responsive** - Mobile-first design approach
18
18
  ♿ **Accessible** - ARIA-compliant components
19
+ 🌐 **i18n** - Built-in vue-i18n support with English defaults
19
20
 
20
21
  ## Quick Start
21
22
 
@@ -66,7 +67,7 @@ function handleClick() {
66
67
 
67
68
  ## What's Included
68
69
 
69
- ### Components (34)
70
+ ### Components (56)
70
71
 
71
72
  #### Form Controls
72
73
 
@@ -78,16 +79,22 @@ function handleClick() {
78
79
  - **DatePicker** - Date selection with month/year options
79
80
  - **DateRangePicker** - Start and end date selection
80
81
  - **Selector** - Generic dropdown selector (single/multi-select)
82
+ - **TaggableSelector** - Selector with taggable input
83
+ - **CheckboxGroup** - Group of checkboxes with shared model
84
+ - **RadioButton** - Radio button component
81
85
  - **Tag** - Styled tag/badge component
82
86
  - **Badge** - Status badge with variants
87
+ - **ListItem** - Selectable list item
83
88
 
84
89
  #### Interactive
85
90
 
86
91
  - **Button** - Primary, secondary, subdued variants with loading/icon support
87
92
  - **NavButton** - Navigation button component
93
+ - **Form** - Form wrapper with validation support
88
94
  - **Modal** - Animated modal with origin morphing
89
95
  - **Popover** - Positioned popover with smart placement
90
96
  - **Tooltip** - Hover tooltip component
97
+ - **Banner** - Notification banner component
91
98
 
92
99
  #### Display
93
100
 
@@ -112,7 +119,7 @@ function handleClick() {
112
119
 
113
120
  - **Upload** - File upload component
114
121
 
115
- ### Composables (11)
122
+ ### Composables (13)
116
123
 
117
124
  - **useTheme** - Theme and color mode management
118
125
  - **useModal** - Modal state with animation origin tracking
@@ -187,8 +194,8 @@ npm run docs:dev
187
194
  orio-ui/
188
195
  ├── src/
189
196
  │ ├── runtime/
190
- │ │ ├── components/ # 34 Vue components
191
- │ │ ├── composables/ # 11 composables
197
+ │ │ ├── components/ # 56 Vue components
198
+ │ │ ├── composables/ # 13 composables
192
199
  │ │ ├── assets/css/ # Theme CSS files
193
200
  │ │ └── utils/ # Icon registry
194
201
  │ └── module.ts # Nuxt Module definition
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0 || ^4.0.0"
6
6
  },
7
- "version": "1.20.0",
7
+ "version": "1.23.2",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addComponentsDir, addImportsDir } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addComponentsDir, addImportsDir, addImports, addPlugin } from '@nuxt/kit';
2
2
  import { COOKIE_NAMES, THEME_DEFAULTS } from '../dist/runtime/constants/theme.js';
3
3
 
4
4
  const themeScript = `
@@ -34,9 +34,12 @@ const module$1 = defineNuxtModule({
34
34
  addComponentsDir({
35
35
  path: resolver.resolve("./runtime/components"),
36
36
  prefix: "Orio",
37
- pathPrefix: true
37
+ pathPrefix: true,
38
+ ignore: ["**/components/**"]
38
39
  });
39
40
  addImportsDir(resolver.resolve("./runtime/composables"));
41
+ addImports({ name: "useI18n", from: "vue-i18n" });
42
+ addPlugin(resolver.resolve("./runtime/plugins/i18n"));
40
43
  }
41
44
  });
42
45
 
@@ -0,0 +1,21 @@
1
+ export { default as Canvas, type CanvasProps, } from "./components/Canvas/index.vue.js";
2
+ export { default as CanvasStage } from "./components/Canvas/components/Stage.vue.js";
3
+ export { default as CanvasToolbar } from "./components/Canvas/components/Toolbar.vue.js";
4
+ export { default as CanvasToolButton } from "./components/Canvas/components/ToolButton.vue.js";
5
+ export { defineCanvasTool, type CanvasTool, type CanvasToolKind, type CanvasNode, type CanvasToolApi, type CanvasPoint, type CanvasPointerEvent, } from "./components/Canvas/types.js";
6
+ export { useCanvasContext, type CanvasContext, } from "./components/Canvas/context.js";
7
+ export { canvasRegistry } from "./components/Canvas/registry.js";
8
+ export { drawTool, type DrawToolOptions, type DrawNodeData, } from "./components/Canvas/tools/drawTool.js";
9
+ export { textTool, type TextToolOptions, type TextNodeData, } from "./components/Canvas/tools/textTool.js";
10
+ export { undoTool } from "./components/Canvas/tools/undoTool.js";
11
+ export { redoTool } from "./components/Canvas/tools/redoTool.js";
12
+ export { clearTool } from "./components/Canvas/tools/clearTool.js";
13
+ export { colorPickerTool, type ColorPickerToolOptions, } from "./components/Canvas/tools/colorPickerTool.js";
14
+ export { eraseTool, type EraseToolOptions, } from "./components/Canvas/tools/eraseTool.js";
15
+ export { moveTool, type MoveToolOptions, } from "./components/Canvas/tools/moveTool.js";
16
+ export { highlightTool, type HighlightToolOptions, } from "./components/Canvas/tools/highlightTool.js";
17
+ export { rotateTool, type RotateToolOptions, } from "./components/Canvas/tools/rotateTool.js";
18
+ export { resizeTool, type ResizeToolOptions, } from "./components/Canvas/tools/resizeTool.js";
19
+ export { transformTool, type TransformToolOptions, } from "./components/Canvas/tools/transformTool.js";
20
+ export { imageTool, type ImageToolOptions, type ImageNodeData, } from "./components/Canvas/tools/imageTool.js";
21
+ export { exportTool, type ExportToolOptions, type ExportFormat, } from "./components/Canvas/tools/exportTool.js";
@@ -0,0 +1,49 @@
1
+ export {
2
+ default as Canvas
3
+ } from "./components/Canvas/index.vue";
4
+ export { default as CanvasStage } from "./components/Canvas/components/Stage.vue";
5
+ export { default as CanvasToolbar } from "./components/Canvas/components/Toolbar.vue";
6
+ export { default as CanvasToolButton } from "./components/Canvas/components/ToolButton.vue";
7
+ export {
8
+ defineCanvasTool
9
+ } from "./components/Canvas/types.js";
10
+ export {
11
+ useCanvasContext
12
+ } from "./components/Canvas/context.js";
13
+ export { canvasRegistry } from "./components/Canvas/registry.js";
14
+ export {
15
+ drawTool
16
+ } from "./components/Canvas/tools/drawTool.js";
17
+ export {
18
+ textTool
19
+ } from "./components/Canvas/tools/textTool.js";
20
+ export { undoTool } from "./components/Canvas/tools/undoTool.js";
21
+ export { redoTool } from "./components/Canvas/tools/redoTool.js";
22
+ export { clearTool } from "./components/Canvas/tools/clearTool.js";
23
+ export {
24
+ colorPickerTool
25
+ } from "./components/Canvas/tools/colorPickerTool.js";
26
+ export {
27
+ eraseTool
28
+ } from "./components/Canvas/tools/eraseTool.js";
29
+ export {
30
+ moveTool
31
+ } from "./components/Canvas/tools/moveTool.js";
32
+ export {
33
+ highlightTool
34
+ } from "./components/Canvas/tools/highlightTool.js";
35
+ export {
36
+ rotateTool
37
+ } from "./components/Canvas/tools/rotateTool.js";
38
+ export {
39
+ resizeTool
40
+ } from "./components/Canvas/tools/resizeTool.js";
41
+ export {
42
+ transformTool
43
+ } from "./components/Canvas/tools/transformTool.js";
44
+ export {
45
+ imageTool
46
+ } from "./components/Canvas/tools/imageTool.js";
47
+ export {
48
+ exportTool
49
+ } from "./components/Canvas/tools/exportTool.js";
@@ -0,0 +1,174 @@
1
+ # Canvas — Internal Requirements
2
+
3
+ Scratchpad for the Canvas component. Not shipped to users, not imported anywhere.
4
+ Update as scope evolves.
5
+
6
+ ## Vision
7
+
8
+ A **tiptap-flavored, headless-ish canvas playground**. The core owns the
9
+ `<canvas>`, node storage, rendering loop and pointer dispatch. Everything
10
+ else — tools, toolbars, node kinds, UI chrome — is pluggable by the consumer.
11
+
12
+ ## Extensibility goals
13
+
14
+ - Consumers can define a tool in userland (`defineCanvasTool({...})`) and drop
15
+ it into `<orio-canvas :tools="[...]" />`.
16
+ - Tools own:
17
+ - their id (also the node type they produce),
18
+ - pointer handlers,
19
+ - the render function for nodes of their type,
20
+ - reactive per-tool options (brush size, color, font family, ...),
21
+ - optional icon and label used by the default toolbar.
22
+ - Consumers can replace the toolbar UI entirely via the `toolbar` slot, or
23
+ replace individual tool buttons via the default slot of
24
+ `<orio-canvas-tool-button>`.
25
+ - Consumers can replace the stage too — `<orio-canvas-stage>` is a standalone
26
+ child; advanced users could write their own stage using `useCanvasContext`.
27
+
28
+ ## Data model
29
+
30
+ - `CanvasNode` = `{ id, type, x, y, width?, height?, rotation?, frozen?, zIndex?, data }`
31
+ - `nodes` is the single source of truth (reactive array), v-modelable as
32
+ `v-model:nodes`.
33
+ - Rendering order is `zIndex` ascending (stable). New nodes get an increasing
34
+ zIndex by default.
35
+ - `frozen` is a flag only. Interaction tools (`moveTool`, `eraseTool`,
36
+ `highlightTool`) skip frozen nodes; strokes and text are painted regardless.
37
+
38
+ ## Rendering
39
+
40
+ - Single `<canvas>` element. 2D context. DPR-scaled.
41
+ - Full redraw on every `requestRender()`, batched through `requestAnimationFrame`.
42
+ - Tools render their own node type via `render(ctx, node)`. Unknown node types
43
+ are silently skipped — lets consumers hydrate a saved doc gradually as tools
44
+ register themselves.
45
+
46
+ ## Tool API surface
47
+
48
+ ```ts
49
+ CanvasTool<TNodeData, TOptions> = {
50
+ id: string
51
+ label?: string
52
+ icon?: string // orio-icon registry name
53
+ cursor?: string // CSS cursor applied to stage when active
54
+ kind?: "interaction" | "action" | "widget"
55
+ action?(api) // for action tools — fired on button click
56
+ disabled?(api) => boolean // for action tools — reactive disabled state
57
+ toolbar?: Component // for widget tools — rendered instead of button
58
+ defaultOptions?: TOptions
59
+ onPointerDown?(e, api)
60
+ onPointerMove?(e, api)
61
+ onPointerUp?(e, api)
62
+ onActivate?(api)
63
+ onDeactivate?(api)
64
+ render?(ctx, node)
65
+ }
66
+ ```
67
+
68
+ `api: CanvasToolApi<TOptions>` = `{ options, nodes, addNode, updateNode,
69
+ removeNode, getNode, clear, requestRender, stageEl(), size(), undo, redo,
70
+ canUndo, canRedo, getToolOptions }`.
71
+
72
+ Tool options live on the context keyed by tool id. Mutating `api.options`
73
+ should be reactive so overlays or settings panels can reflect changes.
74
+
75
+ ### Tool kinds
76
+
77
+ - **interaction** (default) — standard pointer tool, becomes active on click.
78
+ - **action** — fires `action(api)` on click, never becomes active. Button
79
+ shows disabled state from `disabled(api)`.
80
+ - **widget** — renders a custom `toolbar` Vue component instead of a button.
81
+
82
+ ## Scope v1 (SHIPPING)
83
+
84
+ - Core canvas component + Stage + Toolbar + ToolButton.
85
+ - No default tools — consumer specifies everything explicitly.
86
+ - `drawTool()` — freehand pen, per-tool color/size/opacity/brush. Uses
87
+ quadratic smoothing between points.
88
+ - `textTool()` — click to place, inline `<input>` overlay for editing, commits
89
+ on Enter/blur, Escape cancels. Options: `fontSize`, `fontFamily`, `color`,
90
+ `weight`.
91
+ - `eraseTool()` — erases non-frozen nodes under pointer. Uses per-tool
92
+ `hitTest` for accurate detection, falls back to bounding box.
93
+ - `moveTool()` — drags non-frozen nodes; `[`/`]` keys reorder zIndex of
94
+ hovered/dragged node. Shares hit-testing via `tools/hitTest.ts`.
95
+ - `highlightTool()` — hover overlay showing the top-most hit node's bounds.
96
+ - `undoTool()`, `redoTool()`, `clearTool()` — action tools.
97
+ - `colorPickerTool()` — widget tool, native color input, syncs to targets.
98
+ - `setup` prop — callback on mount to seed initial nodes. Frozen nodes are
99
+ protected from eraser and future selection tools.
100
+ - `hitTest` optional method on CanvasTool for spatial queries.
101
+ - Undo / redo — history stack on the nodes array, Ctrl+Z / Ctrl+Shift+Z
102
+ keyboard support, `maxHistory` prop (default 50). drawTool finalizes stroke
103
+ on pointerUp so the full stroke is captured as one snapshot.
104
+ - Vitepress docs covering usage, slots, and how to author a custom tool.
105
+ - Icons added to `icon-registry.ts`: `pencil`, `text`, `undo`, `redo`,
106
+ `eraser`.
107
+
108
+ ## Out of scope v1 (stubbed in types / docs only)
109
+
110
+ - Image tool — image nodes need loading/caching + upload hookup. Add as
111
+ `imageTool()` next iteration.
112
+ - ~~Move~~ — shipped in v1 via `moveTool` (reuses shared `hitTest.ts`).
113
+ - Selection / resize / rotate — needs a marquee-select tool and bounds
114
+ handles. Not in v1.
115
+ - Freeze toggle UI — data model supports `frozen`, but there is no tool to
116
+ toggle it yet. Add when selection lands.
117
+ - Custom font loading — doc will explain using the `FontFace` API; no bundled
118
+ loader.
119
+ - ~~Undo/redo~~ — shipped in v1.
120
+ - Save / load — `v-model:nodes` plus JSON is enough for now; real persistence
121
+ (including bitmap snapshots) comes later.
122
+ - Zoom / pan — stage is fixed-size in v1. Add a viewport transform layer
123
+ later; tools should keep working because they already operate on
124
+ canvas-space coordinates.
125
+
126
+ ## Decisions / rationale
127
+
128
+ - **Per-tool factory (`drawTool()` vs a singleton object):** each call creates
129
+ fresh closure state (e.g. the "currently drawing" node id), so two canvases
130
+ on the same page don't trample each other.
131
+ - **`activeId` stored in closure, not context:** it's a transient interaction
132
+ state owned by the tool, not something any other component cares about.
133
+ - **`stageEl` as a context ref:** text tool needs a DOM parent to mount its
134
+ inline editor, and future tools (image crop, handles) will need it too.
135
+ - **Not using `<canvas>` per node:** single canvas is simpler, renders faster,
136
+ and matches the "raster playground" feel. Trade-off: no layer-level hit
137
+ testing — we'll need to maintain an index when selection arrives.
138
+
139
+ ## File layout
140
+
141
+ ```text
142
+ Canvas/
143
+ ├── REQUIREMENTS.md <- this file
144
+ ├── index.vue <- <orio-canvas>
145
+ ├── types.ts <- CanvasNode, CanvasTool, defineCanvasTool
146
+ ├── context.ts <- provide/inject + useCanvasContext
147
+ ├── registry.ts <- module-level Map<name, CanvasContext> for
148
+ │ detached toolbars (toolbar in a different
149
+ │ subtree binds via `canvas="<name>"`)
150
+ ├── components/
151
+ │ ├── Stage.vue <- <orio-canvas-stage>
152
+ │ ├── Toolbar.vue <- <orio-canvas-toolbar>
153
+ │ └── ToolButton.vue <- <orio-canvas-tool-button>
154
+ ├── composables/
155
+ │ ├── useCanvasHistory.ts <- undo/redo stack
156
+ │ ├── useCanvasNodes.ts <- add/update/remove/clear
157
+ │ └── useCanvasSetup.ts <- onMounted setup() + baseline
158
+ └── tools/
159
+ ├── drawTool.ts
160
+ ├── textTool.ts
161
+ ├── eraseTool.ts
162
+ ├── moveTool.ts
163
+ ├── highlightTool.ts
164
+ ├── undoTool.ts
165
+ ├── redoTool.ts
166
+ ├── clearTool.ts
167
+ ├── colorPickerTool.ts
168
+ ├── hitTest.ts <- shared findTopNode / renderHighlightRect
169
+ ├── ColorPickerWidget.vue
170
+ └── tooltips/ <- per-tool <orio-view-text> + key binds
171
+ ```
172
+
173
+ Auto-import prefix is `Orio`, so nested files become `<orio-canvas-stage>`,
174
+ `<orio-canvas-toolbar>`, `<orio-canvas-tool-button>`.
@@ -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,150 @@
1
+ <script setup>
2
+ import { computed, onMounted, onUnmounted, useTemplateRef, watch } from "vue";
3
+ import { useEventListener } from "@vueuse/core";
4
+ import { useCanvasContext } from "../context";
5
+ import { getNodeBounds } from "../tools/hitTest";
6
+ const ctx = useCanvasContext();
7
+ const rootEl = useTemplateRef("rootEl");
8
+ const canvasEl = useTemplateRef("canvasEl");
9
+ let renderQueued = false;
10
+ let rafId = 0;
11
+ function render() {
12
+ renderQueued = false;
13
+ const canvas = canvasEl.value;
14
+ if (!canvas) return;
15
+ const c = canvas.getContext("2d");
16
+ if (!c) return;
17
+ const dpr = window.devicePixelRatio || 1;
18
+ const { width, height } = ctx.size.value;
19
+ const pxW = Math.round(width * dpr);
20
+ const pxH = Math.round(height * dpr);
21
+ if (canvas.width !== pxW || canvas.height !== pxH) {
22
+ canvas.width = pxW;
23
+ canvas.height = pxH;
24
+ }
25
+ c.setTransform(dpr, 0, 0, dpr, 0, 0);
26
+ c.clearRect(0, 0, width, height);
27
+ const toolMap = new Map(
28
+ ctx.tools.value.map((t) => [t.id, t])
29
+ );
30
+ const ordered = [...ctx.nodes.value].sort(
31
+ (a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0)
32
+ );
33
+ for (const node of ordered) {
34
+ const renderFn = toolMap.get(node.type)?.render;
35
+ if (!renderFn) continue;
36
+ if (node.rotation) {
37
+ const b = getNodeBounds(node);
38
+ const cx = b.x + b.width / 2;
39
+ const cy = b.y + b.height / 2;
40
+ c.save();
41
+ c.translate(cx, cy);
42
+ c.rotate(node.rotation);
43
+ c.translate(-cx, -cy);
44
+ renderFn(c, node);
45
+ c.restore();
46
+ } else {
47
+ renderFn(c, node);
48
+ }
49
+ }
50
+ const active = ctx.tools.value.find((t) => t.id === ctx.activeToolId.value);
51
+ if (active?.renderOverlay) {
52
+ active.renderOverlay(c, ctx.getToolApi(active.id));
53
+ }
54
+ }
55
+ function requestRender() {
56
+ if (renderQueued) return;
57
+ renderQueued = true;
58
+ rafId = requestAnimationFrame(render);
59
+ }
60
+ ctx.installRenderer(requestRender);
61
+ useEventListener(rootEl, "keydown", ctx.onKeyDown);
62
+ onMounted(() => {
63
+ ctx.stageEl.value = rootEl.value;
64
+ requestRender();
65
+ });
66
+ onUnmounted(() => {
67
+ cancelAnimationFrame(rafId);
68
+ if (ctx.stageEl.value === rootEl.value) {
69
+ ctx.stageEl.value = null;
70
+ }
71
+ });
72
+ watch(
73
+ () => ctx.nodes.value,
74
+ () => requestRender(),
75
+ { deep: true }
76
+ );
77
+ watch(() => ctx.size.value, requestRender, { deep: true });
78
+ const activeTool = computed(
79
+ () => ctx.tools.value.find((t) => t.id === ctx.activeToolId.value)
80
+ );
81
+ const cursor = computed(
82
+ () => ctx.cursorOverride.value ?? activeTool.value?.cursor ?? "default"
83
+ );
84
+ function toCanvasEvent(e) {
85
+ const rect = rootEl.value.getBoundingClientRect();
86
+ return {
87
+ x: e.clientX - rect.left,
88
+ y: e.clientY - rect.top,
89
+ clientX: e.clientX,
90
+ clientY: e.clientY,
91
+ buttons: e.buttons,
92
+ originalEvent: e
93
+ };
94
+ }
95
+ function onPointerDown(e) {
96
+ rootEl.value?.focus();
97
+ const tool = activeTool.value;
98
+ if (!tool) return;
99
+ ctx.beginAction();
100
+ try {
101
+ e.currentTarget.setPointerCapture(e.pointerId);
102
+ } catch {
103
+ }
104
+ tool.onPointerDown?.(toCanvasEvent(e), ctx.getToolApi(tool.id));
105
+ }
106
+ function onPointerMove(e) {
107
+ const tool = activeTool.value;
108
+ if (!tool) return;
109
+ tool.onPointerMove?.(toCanvasEvent(e), ctx.getToolApi(tool.id));
110
+ }
111
+ function onPointerUp(e) {
112
+ const tool = activeTool.value;
113
+ if (!tool) return;
114
+ tool.onPointerUp?.(toCanvasEvent(e), ctx.getToolApi(tool.id));
115
+ ctx.endAction();
116
+ try {
117
+ e.currentTarget.releasePointerCapture(e.pointerId);
118
+ } catch {
119
+ }
120
+ }
121
+ </script>
122
+
123
+ <template>
124
+ <div
125
+ ref="rootEl"
126
+ class="canvas-stage"
127
+ tabindex="0"
128
+ :style="{
129
+ width: ctx.size.value.width + 'px',
130
+ height: ctx.size.value.height + 'px',
131
+ cursor
132
+ }"
133
+ @pointerdown="onPointerDown"
134
+ @pointermove="onPointerMove"
135
+ @pointerup="onPointerUp"
136
+ @pointercancel="onPointerUp"
137
+ >
138
+ <canvas
139
+ ref="canvasEl"
140
+ :style="{
141
+ width: ctx.size.value.width + 'px',
142
+ height: ctx.size.value.height + 'px'
143
+ }"
144
+ />
145
+ </div>
146
+ </template>
147
+
148
+ <style scoped>
149
+ .canvas-stage{outline:none;position:relative;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.canvas-stage>canvas{display:block}
150
+ </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,24 @@
1
+ import type { CanvasTool } from "../types.js";
2
+ interface Props {
3
+ tool: CanvasTool;
4
+ }
5
+ declare function onClick(): void;
6
+ declare var __VLS_24: {
7
+ tool: CanvasTool<unknown, Record<string, unknown>>;
8
+ isActive: boolean;
9
+ activate: typeof onClick;
10
+ }, __VLS_32: {};
11
+ type __VLS_Slots = {} & {
12
+ default?: (props: typeof __VLS_24) => any;
13
+ } & {
14
+ tooltip?: (props: typeof __VLS_32) => any;
15
+ };
16
+ declare const __VLS_base: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
20
+ type __VLS_WithSlots<T, S> = T & {
21
+ new (): {
22
+ $slots: S;
23
+ };
24
+ };
@@ -0,0 +1,62 @@
1
+ <script setup>
2
+ import { ref, computed, h } from "vue";
3
+ import { useCanvasContext } from "../context";
4
+ const props = defineProps({
5
+ tool: { type: Object, required: true }
6
+ });
7
+ const ctx = useCanvasContext();
8
+ const kind = computed(() => props.tool.kind ?? "interaction");
9
+ const isActive = computed(
10
+ () => kind.value === "interaction" && ctx.activeToolId.value === props.tool.id
11
+ );
12
+ const showTooltip = ref(true);
13
+ const isDisabled = computed(() => {
14
+ if (!props.tool.disabled) return false;
15
+ return props.tool.disabled(ctx.getToolApi(props.tool.id));
16
+ });
17
+ function onClick() {
18
+ showTooltip.value = false;
19
+ if (kind.value === "action") {
20
+ props.tool.action?.(ctx.getToolApi(props.tool.id));
21
+ } else if (kind.value === "interaction") {
22
+ ctx.setActiveTool(props.tool.id);
23
+ }
24
+ }
25
+ const tooltip = computed(() => {
26
+ if (props.tool.tooltip) {
27
+ return h(props.tool.tooltip);
28
+ }
29
+ return h("span", props.tool.label ?? props.tool.id);
30
+ });
31
+ </script>
32
+
33
+ <template>
34
+ <component
35
+ :is="tool.toolbar"
36
+ v-if="kind === 'widget' && tool.toolbar"
37
+ :tool
38
+ />
39
+
40
+ <orio-tooltip v-else :disabled="!showTooltip">
41
+ <template #default>
42
+ <orio-button
43
+ appearance="minimal"
44
+ :variant="isActive ? 'primary' : 'subdued'"
45
+ :aria-pressed="kind === 'interaction' ? isActive : void 0"
46
+ :disabled="isDisabled"
47
+ @click="onClick"
48
+ @mouseleave="showTooltip = true"
49
+ >
50
+ <slot :tool :is-active :activate="onClick">
51
+ <orio-icon v-if="tool.icon" :name="tool.icon" />
52
+ <span v-else>{{ tool.label ?? tool.id }}</span>
53
+ </slot>
54
+ </orio-button>
55
+ </template>
56
+ <template #content>
57
+ <slot name="tooltip">
58
+ <component :is="tooltip" />
59
+ </slot>
60
+ </template>
61
+ </orio-tooltip>
62
+ </template>
@@ -0,0 +1,24 @@
1
+ import type { CanvasTool } from "../types.js";
2
+ interface Props {
3
+ tool: CanvasTool;
4
+ }
5
+ declare function onClick(): void;
6
+ declare var __VLS_24: {
7
+ tool: CanvasTool<unknown, Record<string, unknown>>;
8
+ isActive: boolean;
9
+ activate: typeof onClick;
10
+ }, __VLS_32: {};
11
+ type __VLS_Slots = {} & {
12
+ default?: (props: typeof __VLS_24) => any;
13
+ } & {
14
+ tooltip?: (props: typeof __VLS_32) => any;
15
+ };
16
+ declare const __VLS_base: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
20
+ type __VLS_WithSlots<T, S> = T & {
21
+ new (): {
22
+ $slots: S;
23
+ };
24
+ };
@@ -0,0 +1,24 @@
1
+ interface Props {
2
+ /**
3
+ * Name of the `<orio-canvas>` instance to bind to. Lets the toolbar live
4
+ * anywhere in the app — header, sidebar, modal — not just inside the canvas.
5
+ */
6
+ canvas: string;
7
+ }
8
+ declare var __VLS_1: {
9
+ tools: any;
10
+ activeToolId: any;
11
+ setActiveTool: any;
12
+ };
13
+ type __VLS_Slots = {} & {
14
+ default?: (props: typeof __VLS_1) => any;
15
+ };
16
+ declare const __VLS_base: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
20
+ type __VLS_WithSlots<T, S> = T & {
21
+ new (): {
22
+ $slots: S;
23
+ };
24
+ };