@gradio/imageeditor 0.10.0 → 0.11.0-beta.1

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 (63) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/Index.svelte +5 -2
  3. package/dist/Example.svelte +41 -0
  4. package/dist/Example.svelte.d.ts +19 -0
  5. package/dist/Index.svelte +184 -0
  6. package/dist/Index.svelte.d.ts +192 -0
  7. package/dist/shared/Controls.svelte +73 -0
  8. package/dist/shared/Controls.svelte.d.ts +24 -0
  9. package/dist/shared/ImageEditor.svelte +329 -0
  10. package/dist/shared/ImageEditor.svelte.d.ts +79 -0
  11. package/dist/shared/InteractiveImageEditor.svelte +273 -0
  12. package/dist/shared/InteractiveImageEditor.svelte.d.ts +58 -0
  13. package/dist/shared/layers/Layers.svelte +216 -0
  14. package/dist/shared/layers/Layers.svelte.d.ts +18 -0
  15. package/dist/shared/layers/utils.d.ts +69 -0
  16. package/dist/shared/layers/utils.js +131 -0
  17. package/dist/shared/tools/Brush.svelte +208 -0
  18. package/dist/shared/tools/Brush.svelte.d.ts +29 -0
  19. package/dist/shared/tools/BrushOptions.svelte +162 -0
  20. package/dist/shared/tools/BrushOptions.svelte.d.ts +29 -0
  21. package/dist/shared/tools/BrushSize.svelte +60 -0
  22. package/dist/shared/tools/BrushSize.svelte.d.ts +20 -0
  23. package/dist/shared/tools/ColorField.svelte +159 -0
  24. package/dist/shared/tools/ColorField.svelte.d.ts +20 -0
  25. package/dist/shared/tools/ColorPicker.svelte +179 -0
  26. package/dist/shared/tools/ColorPicker.svelte.d.ts +16 -0
  27. package/dist/shared/tools/ColorSwatch.svelte +183 -0
  28. package/dist/shared/tools/ColorSwatch.svelte.d.ts +30 -0
  29. package/dist/shared/tools/Crop.svelte +155 -0
  30. package/dist/shared/tools/Crop.svelte.d.ts +16 -0
  31. package/dist/shared/tools/Cropper.svelte +271 -0
  32. package/dist/shared/tools/Cropper.svelte.d.ts +41 -0
  33. package/dist/shared/tools/Handle.svelte +260 -0
  34. package/dist/shared/tools/Handle.svelte.d.ts +29 -0
  35. package/dist/shared/tools/Sources.svelte +250 -0
  36. package/dist/shared/tools/Sources.svelte.d.ts +32 -0
  37. package/dist/shared/tools/Tools.svelte +101 -0
  38. package/dist/shared/tools/Tools.svelte.d.ts +48 -0
  39. package/dist/shared/tools/brush.d.ts +62 -0
  40. package/dist/shared/tools/brush.js +133 -0
  41. package/dist/shared/tools/crop.d.ts +15 -0
  42. package/dist/shared/tools/crop.js +154 -0
  43. package/dist/shared/tools/index.d.ts +5 -0
  44. package/dist/shared/tools/index.js +5 -0
  45. package/dist/shared/tools/sources.d.ts +40 -0
  46. package/dist/shared/tools/sources.js +69 -0
  47. package/dist/shared/tools/types.d.ts +1 -0
  48. package/dist/shared/tools/types.js +1 -0
  49. package/dist/shared/utils/commands.d.ts +96 -0
  50. package/dist/shared/utils/commands.js +65 -0
  51. package/dist/shared/utils/events.d.ts +7 -0
  52. package/dist/shared/utils/events.js +22 -0
  53. package/dist/shared/utils/parse_placeholder.d.ts +1 -0
  54. package/dist/shared/utils/parse_placeholder.js +19 -0
  55. package/dist/shared/utils/pixi.d.ts +76 -0
  56. package/dist/shared/utils/pixi.js +152 -0
  57. package/package.json +23 -12
  58. package/shared/ImageEditor.svelte +8 -4
  59. package/shared/InteractiveImageEditor.svelte +9 -2
  60. package/shared/tools/Brush.svelte +4 -6
  61. package/shared/tools/BrushOptions.svelte +1 -1
  62. package/shared/tools/Crop.svelte +2 -1
  63. package/shared/utils/pixi.ts +0 -1
@@ -0,0 +1,62 @@
1
+ import { Container, type ColorSource, type IRenderer } from "pixi.js";
2
+ import type { LayerScene } from "../layers/utils";
3
+ import { type Command } from "../utils/commands";
4
+ export interface DrawCommand extends Command {
5
+ /**
6
+ * sets the initial state to begin drawing
7
+ * @param options the options for drawing
8
+ * @returns
9
+ */
10
+ start: (options: DrawOptions) => void;
11
+ /**
12
+ * continues drawing, smoothly interpolating between points where necessary
13
+ * @param options the options for drawing
14
+ * @returns
15
+ */
16
+ continue: (options: Points) => void;
17
+ /**
18
+ * stops drawing
19
+ * @returns
20
+ */
21
+ stop: () => void;
22
+ /**
23
+ * Whether or not the user is currently drawing.
24
+ */
25
+ drawing: boolean;
26
+ }
27
+ /**
28
+ * Coordinates for a draw or erase path.
29
+ */
30
+ interface Points {
31
+ /**
32
+ * The x coordinate of the path.
33
+ */
34
+ x: number;
35
+ /**
36
+ * The y coordinate of the path.
37
+ */
38
+ y: number;
39
+ }
40
+ /**
41
+ * Options for drawing a path.
42
+ */
43
+ interface DrawOptions extends Points {
44
+ /**
45
+ * The size of the brush.
46
+ */
47
+ size: number;
48
+ /**
49
+ * The color of the brush.
50
+ */
51
+ color?: ColorSource;
52
+ /**
53
+ * The opacity of the brush.
54
+ */
55
+ opacity: number;
56
+ /**
57
+ * Whether or not to set the initial texture.
58
+ */
59
+ set_initial_texture?: boolean;
60
+ }
61
+ export declare function draw_path(renderer: IRenderer, stage: Container, layer: LayerScene, mode: "draw" | "erase"): DrawCommand;
62
+ export {};
@@ -0,0 +1,133 @@
1
+ import { Container, Graphics, Color, RenderTexture, Sprite } from "pixi.js";
2
+ import {} from "../utils/commands";
3
+ import { make_graphics } from "../utils/pixi";
4
+ /**
5
+ * Draws a circle on the given graphics object.
6
+ * @param graphics the graphics object to draw on
7
+ * @param x the x coordinate of the circle
8
+ * @param y the y coordinate of the circle
9
+ * @param brush_color the color of the circle
10
+ * @param brush_size the radius of the circle
11
+ */
12
+ function drawCircle(graphics, x, y, brush_color = new Color("black"), brush_size) {
13
+ const color = new Color(brush_color);
14
+ graphics.beginFill(color);
15
+ graphics.drawCircle(x, y, brush_size);
16
+ graphics.endFill();
17
+ }
18
+ /**
19
+ * Interpolates between two points.
20
+ * @param point1 the first point
21
+ * @param point2 the second point
22
+ * @returns an array of points between the two points
23
+ */
24
+ function interpolate(point1, point2) {
25
+ let points = [];
26
+ const dx = point2.x - point1.x;
27
+ const dy = point2.y - point1.y;
28
+ const distance = Math.sqrt(dx * dx + dy * dy);
29
+ const steps = Math.ceil(distance / 2);
30
+ const stepX = dx / steps;
31
+ const stepY = dy / steps;
32
+ for (let j = 0; j < steps; j++) {
33
+ const x = point1.x + j * stepX;
34
+ const y = point1.y + j * stepY;
35
+ points.push({ x, y });
36
+ }
37
+ return points;
38
+ }
39
+ export function draw_path(renderer, stage, layer, mode) {
40
+ const paths = [];
41
+ let initial_path;
42
+ let graphics;
43
+ let InitialTexture;
44
+ let has_drawn = false;
45
+ let id = 0;
46
+ return {
47
+ drawing: false,
48
+ start: function ({ x, y, size, color = new Color("black"), opacity, set_initial_texture = true }) {
49
+ if (set_initial_texture) {
50
+ InitialTexture = RenderTexture.create({
51
+ width: layer.draw_texture.width,
52
+ height: layer.draw_texture.height
53
+ });
54
+ renderer.render(layer.composite, {
55
+ renderTexture: InitialTexture
56
+ });
57
+ }
58
+ initial_path = { x, y, size, color, opacity };
59
+ paths.push({ x, y });
60
+ graphics = make_graphics(id++);
61
+ drawCircle(graphics, x, y, color, size);
62
+ renderer.render(graphics, {
63
+ renderTexture: mode === "draw" ? layer.draw_texture : layer.erase_texture,
64
+ clear: false
65
+ });
66
+ this.drawing = true;
67
+ },
68
+ continue: function ({ x, y }) {
69
+ const last_point = paths[paths.length - 1];
70
+ const new_points = interpolate(last_point, { x, y });
71
+ for (let i = 0; i < new_points.length; i++) {
72
+ const { x, y } = new_points[i];
73
+ drawCircle(graphics, x, y, initial_path.color, initial_path.size);
74
+ paths.push({ x, y });
75
+ }
76
+ renderer.render(graphics, {
77
+ renderTexture: mode === "draw" ? layer.draw_texture : layer.erase_texture,
78
+ clear: false
79
+ });
80
+ graphics.clear();
81
+ },
82
+ stop: function () {
83
+ const current_sketch = RenderTexture.create({
84
+ width: layer.draw_texture.width,
85
+ height: layer.draw_texture.height
86
+ });
87
+ renderer.render(layer.composite, {
88
+ renderTexture: current_sketch
89
+ });
90
+ renderer.render(new Sprite(current_sketch), {
91
+ renderTexture: layer.draw_texture
92
+ });
93
+ const clear_graphics = new Graphics()
94
+ .beginFill(0x000000, 0) // Use a fill color with 0 alpha for transparency
95
+ .drawRect(0, 0, layer.erase_texture.width, layer.erase_texture.height)
96
+ .endFill();
97
+ renderer.render(clear_graphics, {
98
+ renderTexture: layer.erase_texture,
99
+ clear: true
100
+ });
101
+ has_drawn = true;
102
+ this.drawing = false;
103
+ },
104
+ execute: function () {
105
+ if (!has_drawn) {
106
+ for (let i = 1; i < paths.length; i++) {
107
+ const { x, y } = paths[i];
108
+ drawCircle(graphics, x, y, initial_path.color, initial_path.size);
109
+ }
110
+ renderer.render(graphics, {
111
+ renderTexture: mode === "draw" ? layer.draw_texture : layer.erase_texture,
112
+ clear: false
113
+ });
114
+ this.stop();
115
+ }
116
+ },
117
+ undo: function () {
118
+ const clear_graphics = new Graphics()
119
+ .beginFill(0x000000, 0)
120
+ .drawRect(0, 0, layer.erase_texture.width, layer.erase_texture.height)
121
+ .endFill();
122
+ renderer.render(new Sprite(InitialTexture), {
123
+ renderTexture: layer.draw_texture
124
+ });
125
+ renderer.render(clear_graphics, {
126
+ renderTexture: layer.erase_texture,
127
+ clear: true
128
+ });
129
+ this.stop();
130
+ has_drawn = false;
131
+ }
132
+ };
133
+ }
@@ -0,0 +1,15 @@
1
+ import { type IRenderer, type Container } from "pixi.js";
2
+ import { type Command } from "../utils/commands";
3
+ import type { Writable } from "svelte/store";
4
+ export interface CropCommand extends Command {
5
+ start: (width: number, height: number, previous_crop: [number, number, number, number], preview?: boolean, set_previous?: boolean) => void;
6
+ stop: () => number;
7
+ continue: (crop_size: [number, number, number, number], preview?: boolean) => void;
8
+ }
9
+ export declare function crop_canvas(renderer: IRenderer, mask_container: Container, crop: Writable<[number, number, number, number]>, current_opacity?: number): CropCommand;
10
+ export declare function resize_and_reposition(original_width: number, original_height: number, anchor: "t" | "r" | "l" | "b" | "tl" | "tr" | "bl" | "br" | "c", aspect_ratio: number, max_width: number, max_height: number): {
11
+ new_width: number;
12
+ new_height: number;
13
+ x_offset: number;
14
+ y_offset: number;
15
+ };
@@ -0,0 +1,154 @@
1
+ import { Graphics, Sprite, RenderTexture } from "pixi.js";
2
+ import {} from "../utils/commands";
3
+ import { spring } from "svelte/motion";
4
+ export function crop_canvas(renderer, mask_container, crop, current_opacity = 0) {
5
+ let text;
6
+ let sprite;
7
+ const mask_graphics = new Graphics();
8
+ let previous_crop;
9
+ let final_crop;
10
+ let width;
11
+ let height;
12
+ let alpha_spring = spring(current_opacity, {
13
+ stiffness: 0.1,
14
+ damping: 0.5
15
+ });
16
+ let spring_value = current_opacity;
17
+ alpha_spring.subscribe((value) => {
18
+ if (!final_crop)
19
+ return;
20
+ spring_value = value;
21
+ crop_mask(width, height, final_crop, true);
22
+ });
23
+ function crop_mask(width, height, crop_size, preview) {
24
+ mask_graphics.clear();
25
+ if (preview) {
26
+ mask_graphics.beginFill(0xffffff, spring_value);
27
+ mask_graphics.drawRect(0, 0, width, height);
28
+ mask_graphics.endFill();
29
+ }
30
+ mask_graphics.beginFill(0xffffff, 1);
31
+ mask_graphics.drawRect(...crop_size);
32
+ mask_graphics.endFill();
33
+ renderer.render(mask_graphics, {
34
+ renderTexture: text
35
+ });
36
+ }
37
+ let clean = true;
38
+ let stopped = false;
39
+ return {
40
+ start: (_width, _height, _previous_crop, _preview = true, set_previous = true) => {
41
+ clean = false;
42
+ text = RenderTexture.create({
43
+ width: _width,
44
+ height: _height
45
+ });
46
+ crop_mask(_width, _height, _previous_crop, _preview);
47
+ sprite = new Sprite(text);
48
+ mask_container.mask = sprite;
49
+ width = _width;
50
+ height = _height;
51
+ if (set_previous)
52
+ previous_crop = JSON.parse(JSON.stringify(_previous_crop));
53
+ },
54
+ continue: (crop_size, preview = true) => {
55
+ final_crop = JSON.parse(JSON.stringify(crop_size));
56
+ if (spring_value === 0.2) {
57
+ crop_mask(width, height, final_crop, preview);
58
+ }
59
+ else {
60
+ alpha_spring.set(0.2);
61
+ }
62
+ },
63
+ undo() {
64
+ this.start(width, height, previous_crop, false);
65
+ crop.set([
66
+ previous_crop[0] / width,
67
+ previous_crop[1] / height,
68
+ previous_crop[2] / width,
69
+ previous_crop[3] / height
70
+ ]);
71
+ clean = true;
72
+ },
73
+ stop() {
74
+ stopped = true;
75
+ return spring_value;
76
+ },
77
+ execute() {
78
+ if (clean) {
79
+ this.start(width, height, final_crop, false);
80
+ crop.set([
81
+ final_crop[0] / width,
82
+ final_crop[1] / height,
83
+ final_crop[2] / width,
84
+ final_crop[3] / height
85
+ ]);
86
+ clean = true;
87
+ }
88
+ else {
89
+ if (!stopped) {
90
+ alpha_spring.set(0);
91
+ }
92
+ crop.set([
93
+ final_crop[0] / width,
94
+ final_crop[1] / height,
95
+ final_crop[2] / width,
96
+ final_crop[3] / height
97
+ ]);
98
+ clean = true;
99
+ }
100
+ }
101
+ };
102
+ }
103
+ export function resize_and_reposition(original_width, original_height, anchor, aspect_ratio, max_width, max_height) {
104
+ let new_width = original_width;
105
+ let new_height = original_height;
106
+ // Calculate new dimensions based on aspect ratio
107
+ if (anchor.includes("t") || anchor.includes("b") || anchor == "c") {
108
+ new_width = original_height * aspect_ratio;
109
+ }
110
+ if (anchor.includes("l") || anchor.includes("r") || anchor == "c") {
111
+ new_height = original_width / aspect_ratio;
112
+ }
113
+ // Ensure aspect ratio is maintained
114
+ new_height = new_height || new_width / aspect_ratio;
115
+ new_width = new_width || new_height * aspect_ratio;
116
+ // Apply max width/height constraints and adjust dimensions
117
+ if (new_width > max_width) {
118
+ new_width = max_width;
119
+ new_height = new_width / aspect_ratio;
120
+ }
121
+ if (new_height > max_height) {
122
+ new_height = max_height;
123
+ new_width = new_height * aspect_ratio;
124
+ }
125
+ // Calculate offsets to ensure anchor position is maintained
126
+ let x_offset = 0;
127
+ let y_offset = 0;
128
+ if (anchor.includes("r")) {
129
+ x_offset = original_width - new_width;
130
+ }
131
+ else if (anchor.includes("l")) {
132
+ x_offset = 0;
133
+ }
134
+ else {
135
+ // Center or top/bottom center
136
+ x_offset = (original_width - new_width) / 2;
137
+ }
138
+ if (anchor.includes("b")) {
139
+ y_offset = original_height - new_height;
140
+ }
141
+ else if (anchor.includes("t")) {
142
+ y_offset = 0;
143
+ }
144
+ else {
145
+ // Center or left/right center
146
+ y_offset = (original_height - new_height) / 2;
147
+ }
148
+ return {
149
+ new_width: new_width,
150
+ new_height: new_height,
151
+ x_offset: x_offset,
152
+ y_offset: y_offset
153
+ };
154
+ }
@@ -0,0 +1,5 @@
1
+ export { default as Tools } from "./Tools.svelte";
2
+ export { default as Brush } from "./Brush.svelte";
3
+ export { default as Crop } from "./Crop.svelte";
4
+ export { default as Sources } from "./Sources.svelte";
5
+ export * from "./types";
@@ -0,0 +1,5 @@
1
+ export { default as Tools } from "./Tools.svelte";
2
+ export { default as Brush } from "./Brush.svelte";
3
+ export { default as Crop } from "./Crop.svelte";
4
+ export { default as Sources } from "./Sources.svelte";
5
+ export * from "./types";
@@ -0,0 +1,40 @@
1
+ import { type Container, type IRenderer, type ColorSource } from "pixi.js";
2
+ import { type Command } from "../utils/commands";
3
+ interface BgImageCommand extends Command {
4
+ /**
5
+ * Initial setup for the bg command
6
+ * @returns
7
+ */
8
+ start: () => Promise<[number, number]>;
9
+ }
10
+ /**
11
+ * Adds a background image to the canvas.
12
+ * @param container The container to add the image to.
13
+ * @param renderer The renderer to use for the image.
14
+ * @param background The background image to add.
15
+ * @param resize The function to resize the canvas.
16
+ * @returns A command that can be used to undo the action.
17
+ */
18
+ export declare function add_bg_image(container: Container, renderer: IRenderer, background: Blob | File, resize: (width: number, height: number) => void): BgImageCommand;
19
+ /**
20
+ * Command that sets a background
21
+ */
22
+ interface BgColorCommand extends Command {
23
+ /**
24
+ * Initial setup for the bg command
25
+ * @returns
26
+ */
27
+ start: () => [number, number];
28
+ }
29
+ /**
30
+ * Adds a background color to the canvas.
31
+ * @param container The container to add the image to.
32
+ * @param renderer The renderer to use for the image.
33
+ * @param color The background color to add.
34
+ * @param width The width of the background.
35
+ * @param height The height of the background.
36
+ * @param resize The function to resize the canvas.
37
+ * @returns A command that can be used to undo the action.
38
+ */
39
+ export declare function add_bg_color(container: Container, renderer: IRenderer, color: ColorSource, width: number, height: number, resize: (width: number, height: number) => void): BgColorCommand;
40
+ export {};
@@ -0,0 +1,69 @@
1
+ import { Sprite, Texture, DisplayObject, Color, RenderTexture } from "pixi.js";
2
+ import {} from "../utils/commands";
3
+ import { make_graphics } from "../utils/pixi";
4
+ /**
5
+ * Adds a background image to the canvas.
6
+ * @param container The container to add the image to.
7
+ * @param renderer The renderer to use for the image.
8
+ * @param background The background image to add.
9
+ * @param resize The function to resize the canvas.
10
+ * @returns A command that can be used to undo the action.
11
+ */
12
+ export function add_bg_image(container, renderer, background, resize) {
13
+ let sprite;
14
+ return {
15
+ async start() {
16
+ container.removeChildren();
17
+ const img = await createImageBitmap(background);
18
+ const bitmap_texture = Texture.from(img);
19
+ sprite = new Sprite(bitmap_texture);
20
+ return [sprite.width, sprite.height];
21
+ },
22
+ async execute() {
23
+ // renderer.resize(sprite.width, sprite.height);
24
+ resize(sprite.width, sprite.height);
25
+ sprite.zIndex = 0;
26
+ container.addChild(sprite);
27
+ },
28
+ undo() {
29
+ container.removeChildAt(0);
30
+ }
31
+ };
32
+ }
33
+ /**
34
+ * Adds a background color to the canvas.
35
+ * @param container The container to add the image to.
36
+ * @param renderer The renderer to use for the image.
37
+ * @param color The background color to add.
38
+ * @param width The width of the background.
39
+ * @param height The height of the background.
40
+ * @param resize The function to resize the canvas.
41
+ * @returns A command that can be used to undo the action.
42
+ */
43
+ export function add_bg_color(container, renderer, color, width, height, resize) {
44
+ let sprite;
45
+ return {
46
+ start() {
47
+ container.removeChildren();
48
+ const graphics = make_graphics(1);
49
+ const texture = RenderTexture.create({
50
+ width,
51
+ height
52
+ });
53
+ graphics.beginFill(new Color(color));
54
+ graphics.drawRect(0, 0, width, height);
55
+ graphics.endFill();
56
+ renderer.render(graphics, { renderTexture: texture });
57
+ sprite = new Sprite(texture);
58
+ return [sprite.width, sprite.height];
59
+ },
60
+ async execute() {
61
+ resize(sprite.width, sprite.height);
62
+ sprite.zIndex = 1;
63
+ container.addChild(sprite);
64
+ },
65
+ undo() {
66
+ container.removeChildren();
67
+ }
68
+ };
69
+ }
@@ -0,0 +1 @@
1
+ export type tool = "draw" | "erase" | "crop" | "bg";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,96 @@
1
+ import { type Writable } from "svelte/store";
2
+ /**
3
+ * Base command interface that is added to the command_managers history
4
+ */
5
+ export interface Command {
6
+ /**
7
+ * Optionally called when the command is first executed for multi-step commands
8
+ * @param args arguments to pass to the command
9
+ */
10
+ start?: (...args: any) => any | Promise<any>;
11
+ /**
12
+ * Optionally called when the command is continued for multi-step commands
13
+ * @param args arguments to pass to the command
14
+ */
15
+ continue?: (...args: any) => any | Promise<any>;
16
+ /**
17
+ * Optionally called when the command is stopped for multi-step commands
18
+ * @param args arguments to pass to the command
19
+ */
20
+ stop?: (...args: any) => any | Promise<any>;
21
+ /**
22
+ * Called by the command manager to execute the command, can act as a no-op if the work has already been done
23
+ * This function must be able to recreate the command if the command is undone and redone (`stop`/`start`/`continue` will not be called again)
24
+ */
25
+ execute(): any | Promise<any>;
26
+ /**
27
+ * Called by the command manager to undo the command
28
+ * This function must be able to undo the work done by the execute function
29
+ */
30
+ undo(): any | Promise<any>;
31
+ }
32
+ /**
33
+ * Command manager interface that handles the undo/redo history
34
+ */
35
+ export interface CommandManager {
36
+ /**
37
+ * Undo the last command
38
+ */
39
+ undo(): void;
40
+ /**
41
+ * Redo the last undone command
42
+ */
43
+ redo(): void;
44
+ /**
45
+ * Execute a command and add it to the history
46
+ * @param command command to execute
47
+ */
48
+ execute(command: Command): void;
49
+ /**
50
+ * Whether or not there are commands that can be undone
51
+ * Observable store that you can subscribe to for updates
52
+ */
53
+ readonly can_undo: Writable<boolean>;
54
+ /**
55
+ * Whether or not there are commands that can be redone
56
+ * Observable store that you can subscribe to for updates
57
+ */
58
+ readonly can_redo: Writable<boolean>;
59
+ /**
60
+ * Resets the history
61
+ */
62
+ reset(): void;
63
+ /**
64
+ * The current history node
65
+ * Observable store that you can subscribe to for updates
66
+ */
67
+ readonly current_history: Writable<CommandNode>;
68
+ }
69
+ /**
70
+ * Command node interface that is used to create the undo/redo history
71
+ */
72
+ interface CommandNode {
73
+ /**
74
+ * Command that the node holds
75
+ */
76
+ command: Command | null;
77
+ /**
78
+ * Next command in the history
79
+ */
80
+ next: CommandNode | null;
81
+ /**
82
+ * Previous command in the history
83
+ */
84
+ previous: CommandNode | null;
85
+ /**
86
+ * Push a command onto the history
87
+ * @param command command to push onto the history
88
+ */
89
+ push(command: Command): void;
90
+ }
91
+ /**
92
+ * Creates a command manager
93
+ * @returns a command manager
94
+ */
95
+ export declare function command_manager(): CommandManager;
96
+ export {};
@@ -0,0 +1,65 @@
1
+ import { writable } from "svelte/store";
2
+ /**
3
+ * Creates a command node
4
+ * @param command command to add to the node
5
+ * @returns a command node
6
+ */
7
+ function command_node(command) {
8
+ return {
9
+ command: command || null,
10
+ next: null,
11
+ previous: null,
12
+ push: function (command) {
13
+ const node = command_node(command);
14
+ node.previous = this;
15
+ this.next = node;
16
+ }
17
+ };
18
+ }
19
+ /**
20
+ * Creates a command manager
21
+ * @returns a command manager
22
+ */
23
+ export function command_manager() {
24
+ let history = command_node();
25
+ const can_undo = writable(false);
26
+ const can_redo = writable(false);
27
+ const current_history = writable(history);
28
+ return {
29
+ undo: function () {
30
+ if (history.previous) {
31
+ history.command?.undo();
32
+ history = history.previous;
33
+ }
34
+ can_undo.set(!!history.previous);
35
+ can_redo.set(!!history.next);
36
+ current_history.set(history);
37
+ },
38
+ redo: function () {
39
+ if (history.next) {
40
+ history.next.command?.execute();
41
+ history = history.next;
42
+ }
43
+ can_undo.set(!!history.previous);
44
+ can_redo.set(!!history.next);
45
+ current_history.set(history);
46
+ },
47
+ execute: function (command) {
48
+ command.execute();
49
+ history.push(command);
50
+ history = history.next;
51
+ can_undo.set(!!history.previous);
52
+ can_redo.set(!!history.next);
53
+ current_history.set(history);
54
+ },
55
+ can_undo,
56
+ can_redo,
57
+ current_history,
58
+ reset: function () {
59
+ history = command_node();
60
+ can_undo.set(false);
61
+ can_redo.set(false);
62
+ current_history.set(history);
63
+ }
64
+ };
65
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Svelte action to handle clicks outside of a DOM node
3
+ * @param node DOM node to check the click is outside of
4
+ * @param callback callback function to call if click is outside
5
+ * @returns svelte action return object with destroy method to remove event listener
6
+ */
7
+ export declare function click_outside(node: Node, callback: (arg: MouseEvent) => void): any;
@@ -0,0 +1,22 @@
1
+ import { tick } from "svelte";
2
+ /**
3
+ * Svelte action to handle clicks outside of a DOM node
4
+ * @param node DOM node to check the click is outside of
5
+ * @param callback callback function to call if click is outside
6
+ * @returns svelte action return object with destroy method to remove event listener
7
+ */
8
+ export function click_outside(node, callback) {
9
+ const handle_click = (event) => {
10
+ if (node &&
11
+ !node.contains(event.target) &&
12
+ !event.defaultPrevented) {
13
+ callback(event);
14
+ }
15
+ };
16
+ document.addEventListener("mousedown", handle_click, true);
17
+ return {
18
+ destroy() {
19
+ document.removeEventListener("mousedown", handle_click, true);
20
+ }
21
+ };
22
+ }