orio-ui 1.23.2 → 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.
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.23.2",
7
+ "version": "1.23.3",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -18,4 +18,4 @@ export { rotateTool, type RotateToolOptions, } from "./components/Canvas/tools/r
18
18
  export { resizeTool, type ResizeToolOptions, } from "./components/Canvas/tools/resizeTool.js";
19
19
  export { transformTool, type TransformToolOptions, } from "./components/Canvas/tools/transformTool.js";
20
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";
21
+ export { exportTool, performExport, renderCanvasSnapshot, DEFAULT_EXPORT_OPTIONS, type ExportToolOptions, type ExportOptions, type ExportResult, type ExportFormat, type CanvasSnapshot, } from "./components/Canvas/tools/exportTool.js";
@@ -45,5 +45,8 @@ export {
45
45
  imageTool
46
46
  } from "./components/Canvas/tools/imageTool.js";
47
47
  export {
48
- exportTool
48
+ exportTool,
49
+ performExport,
50
+ renderCanvasSnapshot,
51
+ DEFAULT_EXPORT_OPTIONS
49
52
  } from "./components/Canvas/tools/exportTool.js";
@@ -1,5 +1,6 @@
1
1
  import { type ComputedRef, type InjectionKey, type Ref } from "vue";
2
2
  import type { CanvasNode, CanvasTool, CanvasToolApi } from "./types.js";
3
+ import type { ExportOptions, ExportResult } from "./tools/exportTool.js";
3
4
  export interface CanvasContext {
4
5
  tools: Ref<CanvasTool[]>;
5
6
  activeToolId: Ref<string | null>;
@@ -33,6 +34,14 @@ export interface CanvasContext {
33
34
  endAction: () => void;
34
35
  /** Keyboard handler for undo/redo shortcuts. Attach to the stage element. */
35
36
  onKeyDown: (e: KeyboardEvent) => void;
37
+ /**
38
+ * Export the current canvas as an image. Overrides merge over the registered
39
+ * `exportTool` options (if any) and the built-in defaults. Without
40
+ * `onExport`/`download` overrides the call triggers a browser download for
41
+ * backwards compatibility; pass `{ download: false }` or supply `onExport`
42
+ * to consume the result instead (e.g. push to a cart, upload to a backend).
43
+ */
44
+ exportCanvas: (overrides?: ExportOptions) => Promise<ExportResult>;
36
45
  }
37
46
  export declare const CANVAS_CONTEXT: InjectionKey<CanvasContext>;
38
47
  export declare function useCanvasContext(): CanvasContext;
@@ -1,4 +1,5 @@
1
1
  import type { CanvasNode, CanvasTool, CanvasToolApi } from "./types.js";
2
+ import { type ExportOptions, type ExportResult } from "./tools/exportTool.js";
2
3
  export interface CanvasProps {
3
4
  /**
4
5
  * Unique name for this canvas instance. Required so detached toolbars and
@@ -31,6 +32,7 @@ export interface CanvasProps {
31
32
  type __VLS_Props = CanvasProps;
32
33
  declare function getToolOptions<T extends Record<string, unknown>>(id: string): T;
33
34
  declare function setActiveTool(id: string | null): void;
35
+ declare function exportCanvas(overrides?: ExportOptions): Promise<ExportResult>;
34
36
  type __VLS_ModelProps = {
35
37
  "nodes"?: CanvasNode[];
36
38
  };
@@ -54,6 +56,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
54
56
  redo: () => void;
55
57
  canUndo: import("vue").ComputedRef<boolean>;
56
58
  canRedo: import("vue").ComputedRef<boolean>;
59
+ exportCanvas: typeof exportCanvas;
57
60
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
58
61
  "update:nodes": (value: CanvasNode<unknown>[]) => any;
59
62
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
@@ -15,6 +15,9 @@ import { useCanvasNodes } from "./composables/useCanvasNodes";
15
15
  import { useCanvasSetup } from "./composables/useCanvasSetup";
16
16
  import Stage from "./components/Stage.vue";
17
17
  import Toolbar from "./components/Toolbar.vue";
18
+ import {
19
+ performExport
20
+ } from "./tools/exportTool";
18
21
  const props = defineProps({
19
22
  name: { type: String, required: true },
20
23
  tools: { type: Array, required: false, default: () => [] },
@@ -122,6 +125,18 @@ function handleKeyDown(e) {
122
125
  }
123
126
  onKeyDown(e);
124
127
  }
128
+ function exportCanvas(overrides = {}) {
129
+ const toolOpts = getToolOptions("export");
130
+ return performExport(
131
+ {
132
+ nodes: nodes.value,
133
+ tools: props.tools,
134
+ width: props.width,
135
+ height: props.height
136
+ },
137
+ { ...toolOpts, ...overrides }
138
+ );
139
+ }
125
140
  const context = {
126
141
  tools: toolsRef,
127
142
  activeToolId,
@@ -146,7 +161,8 @@ const context = {
146
161
  endAction,
147
162
  onKeyDown: handleKeyDown,
148
163
  cursorOverride,
149
- setCursor
164
+ setCursor,
165
+ exportCanvas
150
166
  };
151
167
  provide(CANVAS_CONTEXT, context);
152
168
  let registeredName = null;
@@ -190,7 +206,8 @@ defineExpose({
190
206
  undo,
191
207
  redo,
192
208
  canUndo,
193
- canRedo
209
+ canRedo,
210
+ exportCanvas
194
211
  });
195
212
  </script>
196
213
 
@@ -1,4 +1,5 @@
1
1
  import type { CanvasNode, CanvasTool, CanvasToolApi } from "./types.js";
2
+ import { type ExportOptions, type ExportResult } from "./tools/exportTool.js";
2
3
  export interface CanvasProps {
3
4
  /**
4
5
  * Unique name for this canvas instance. Required so detached toolbars and
@@ -31,6 +32,7 @@ export interface CanvasProps {
31
32
  type __VLS_Props = CanvasProps;
32
33
  declare function getToolOptions<T extends Record<string, unknown>>(id: string): T;
33
34
  declare function setActiveTool(id: string | null): void;
35
+ declare function exportCanvas(overrides?: ExportOptions): Promise<ExportResult>;
34
36
  type __VLS_ModelProps = {
35
37
  "nodes"?: CanvasNode[];
36
38
  };
@@ -54,6 +56,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
54
56
  redo: () => void;
55
57
  canUndo: import("vue").ComputedRef<boolean>;
56
58
  canRedo: import("vue").ComputedRef<boolean>;
59
+ exportCanvas: typeof exportCanvas;
57
60
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
58
61
  "update:nodes": (value: CanvasNode<unknown>[]) => any;
59
62
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
@@ -1,18 +1,65 @@
1
- import type { CanvasTool } from "../types.js";
1
+ import type { CanvasNode, CanvasTool } from "../types.js";
2
2
  /**
3
3
  * Image formats Canvas2D's `toBlob`/`toDataURL` natively supports across
4
4
  * modern browsers. Anything else (e.g. AVIF, TIFF) is encoder-dependent and
5
5
  * not guaranteed to round-trip.
6
6
  */
7
7
  export type ExportFormat = "png" | "jpeg" | "webp";
8
- export interface ExportToolOptions extends Record<string, unknown> {
9
- /** Output format. Defaults to PNG. */
8
+ export interface ExportResult {
9
+ blob: Blob;
10
+ dataURL: string;
11
+ /** Same bytes as `blob`, wrapped as a `File` with the configured filename. */
12
+ file: File;
10
13
  format: ExportFormat;
14
+ /** Output pixel width (already multiplied by `scale`). */
15
+ width: number;
16
+ /** Output pixel height (already multiplied by `scale`). */
17
+ height: number;
18
+ filename: string;
19
+ }
20
+ export interface ExportOptions {
21
+ /** Output format. Defaults to PNG. */
22
+ format?: ExportFormat;
11
23
  /** Quality for lossy formats (jpeg, webp), 0–1. Ignored for PNG. */
12
- quality: number;
24
+ quality?: number;
13
25
  /** File name without extension. */
14
- filename: string;
26
+ filename?: string;
15
27
  /** Pixel scale relative to the visible canvas (1 = same size). */
16
- scale: number;
28
+ scale?: number;
29
+ /**
30
+ * If provided, receives the export result instead of (or alongside) the
31
+ * download. Use to push the image into application state (e.g. a cart) or
32
+ * upload to a backend. Async — the canvas awaits the returned Promise.
33
+ */
34
+ onExport?: (result: ExportResult) => void | Promise<void>;
35
+ /**
36
+ * Trigger a browser download. Defaults to `true` when `onExport` is not
37
+ * provided, `false` when it is.
38
+ */
39
+ download?: boolean;
40
+ }
41
+ export interface ExportToolOptions extends Required<Pick<ExportOptions, "format" | "quality" | "filename" | "scale">>, Pick<ExportOptions, "onExport" | "download">, Record<string, unknown> {
17
42
  }
43
+ export declare const DEFAULT_EXPORT_OPTIONS: ExportToolOptions;
44
+ export interface CanvasSnapshot {
45
+ nodes: CanvasNode[];
46
+ tools: CanvasTool[];
47
+ width: number;
48
+ height: number;
49
+ }
50
+ /**
51
+ * Render the snapshot to an offscreen canvas and produce a Blob/dataURL/File.
52
+ * Pure: does not download or invoke callbacks.
53
+ *
54
+ * The offscreen canvas is sized exactly to `snapshot.width × snapshot.height`
55
+ * (× `scale`), so any node geometry outside the canvas is naturally clipped —
56
+ * the result captures only what would be visible on the live canvas.
57
+ */
58
+ export declare function renderCanvasSnapshot(snapshot: CanvasSnapshot, options?: ExportOptions): Promise<ExportResult>;
59
+ /**
60
+ * Render the snapshot, then optionally hand the result to `onExport` and/or
61
+ * trigger a browser download. Resolves with the result so callers can chain
62
+ * additional work (uploads, state updates).
63
+ */
64
+ export declare function performExport(snapshot: CanvasSnapshot, options?: ExportOptions): Promise<ExportResult>;
18
65
  export declare function exportTool(options?: Partial<ExportToolOptions>): CanvasTool<never, ExportToolOptions>;
@@ -6,11 +6,15 @@ const MIME_TYPES = {
6
6
  jpeg: "image/jpeg",
7
7
  webp: "image/webp"
8
8
  };
9
+ export const DEFAULT_EXPORT_OPTIONS = {
10
+ format: "png",
11
+ quality: 0.92,
12
+ filename: "canvas",
13
+ scale: 1
14
+ };
9
15
  function renderNodesTo(target, nodes, tools) {
10
16
  const toolMap = new Map(tools.map((t) => [t.id, t]));
11
- const ordered = [...nodes].sort(
12
- (a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0)
13
- );
17
+ const ordered = [...nodes].sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));
14
18
  for (const node of ordered) {
15
19
  const renderFn = toolMap.get(node.type)?.render;
16
20
  if (!renderFn) continue;
@@ -39,6 +43,67 @@ function downloadBlob(blob, filename) {
39
43
  a.remove();
40
44
  setTimeout(() => URL.revokeObjectURL(url), 1e3);
41
45
  }
46
+ function resolveFormat(format) {
47
+ return MIME_TYPES[format] ? format : "png";
48
+ }
49
+ function sanitizeFilename(name) {
50
+ return (name ?? "canvas").replace(/[^A-Za-z0-9._-]/g, "_") || "canvas";
51
+ }
52
+ export async function renderCanvasSnapshot(snapshot, options = {}) {
53
+ const format = resolveFormat(options.format);
54
+ const quality = options.quality ?? DEFAULT_EXPORT_OPTIONS.quality;
55
+ const scale = options.scale ?? DEFAULT_EXPORT_OPTIONS.scale;
56
+ if (!Number.isFinite(scale) || scale <= 0) {
57
+ throw new Error(
58
+ `[orio-canvas] invalid export option: scale must be a finite number > 0 (got ${scale})`
59
+ );
60
+ }
61
+ if (format !== "png" && options.quality !== void 0 && (!Number.isFinite(quality) || quality < 0 || quality > 1)) {
62
+ throw new Error(
63
+ `[orio-canvas] invalid export option: quality must be a finite number in [0, 1] (got ${quality})`
64
+ );
65
+ }
66
+ const mime = MIME_TYPES[format];
67
+ const ext = format === "jpeg" ? "jpg" : format;
68
+ const filename = `${sanitizeFilename(options.filename)}.${ext}`;
69
+ const out = document.createElement("canvas");
70
+ const w = Math.max(1, Math.round(snapshot.width * scale));
71
+ const h = Math.max(1, Math.round(snapshot.height * scale));
72
+ out.width = w;
73
+ out.height = h;
74
+ const c = out.getContext("2d");
75
+ if (!c) {
76
+ throw new Error("[orio-canvas] failed to acquire 2D context for export");
77
+ }
78
+ c.scale(scale, scale);
79
+ if (format === "jpeg") {
80
+ c.fillStyle = "#ffffff";
81
+ c.fillRect(0, 0, snapshot.width, snapshot.height);
82
+ }
83
+ renderNodesTo(c, snapshot.nodes, snapshot.tools);
84
+ const encoderQuality = format === "png" ? void 0 : quality;
85
+ const blob = await new Promise((resolve, reject) => {
86
+ out.toBlob(
87
+ (b) => b ? resolve(b) : reject(new Error("[orio-canvas] toBlob returned null")),
88
+ mime,
89
+ encoderQuality
90
+ );
91
+ });
92
+ const dataURL = out.toDataURL(mime, encoderQuality);
93
+ const file = new File([blob], filename, { type: mime });
94
+ return { blob, dataURL, file, format, width: w, height: h, filename };
95
+ }
96
+ export async function performExport(snapshot, options = {}) {
97
+ const result = await renderCanvasSnapshot(snapshot, options);
98
+ if (options.onExport) {
99
+ await options.onExport(result);
100
+ }
101
+ const shouldDownload = options.download ?? !options.onExport;
102
+ if (shouldDownload) {
103
+ downloadBlob(result.blob, result.filename);
104
+ }
105
+ return result;
106
+ }
42
107
  export function exportTool(options = {}) {
43
108
  return defineCanvasTool({
44
109
  id: "export",
@@ -47,43 +112,25 @@ export function exportTool(options = {}) {
47
112
  kind: "action",
48
113
  tooltip: ExportTooltip,
49
114
  defaultOptions: {
50
- format: "png",
51
- quality: 0.92,
52
- filename: "canvas",
53
- scale: 1,
115
+ ...DEFAULT_EXPORT_OPTIONS,
54
116
  ...options
55
117
  },
56
118
  disabled(api) {
57
119
  return api.nodes.value.length === 0;
58
120
  },
59
121
  action(api) {
60
- const { quality, scale } = api.options;
61
122
  const { width, height } = api.size();
62
- const knownFormat = MIME_TYPES[api.options.format] ? api.options.format : "png";
63
- const mime = MIME_TYPES[knownFormat];
64
- const ext = knownFormat === "jpeg" ? "jpg" : knownFormat;
65
- const sanitized = api.options.filename.replace(/[^A-Za-z0-9._-]/g, "_") || "canvas";
66
- const out = document.createElement("canvas");
67
- const w = Math.max(1, Math.round(width * scale));
68
- const h = Math.max(1, Math.round(height * scale));
69
- out.width = w;
70
- out.height = h;
71
- const c = out.getContext("2d");
72
- if (!c) return;
73
- c.scale(scale, scale);
74
- if (knownFormat === "jpeg") {
75
- c.fillStyle = "#ffffff";
76
- c.fillRect(0, 0, width, height);
77
- }
78
- renderNodesTo(c, api.nodes.value, api.getTools());
79
- out.toBlob(
80
- (blob) => {
81
- if (!blob) return;
82
- downloadBlob(blob, `${sanitized}.${ext}`);
123
+ performExport(
124
+ {
125
+ nodes: api.nodes.value,
126
+ tools: api.getTools(),
127
+ width,
128
+ height
83
129
  },
84
- mime,
85
- knownFormat === "png" ? void 0 : quality
86
- );
130
+ api.options
131
+ ).catch((err) => {
132
+ console.error("[orio-canvas] export failed", err);
133
+ });
87
134
  }
88
135
  });
89
136
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orio-ui",
3
- "version": "1.23.2",
3
+ "version": "1.23.3",
4
4
  "description": "Modern Nuxt component library with theme support",
5
5
  "type": "module",
6
6
  "main": "./dist/module.mjs",