@gradio/imageeditor 0.18.12 → 0.20.0

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 (61) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/Example.svelte +9 -3
  3. package/ImageEditor.stories.svelte +0 -74
  4. package/Index.svelte +34 -35
  5. package/InteractiveImageEditor.svelte +99 -57
  6. package/dist/Example.svelte +9 -3
  7. package/dist/Example.svelte.d.ts +4 -18
  8. package/dist/Index.svelte +34 -35
  9. package/dist/Index.svelte.d.ts +2 -19
  10. package/dist/InteractiveImageEditor.svelte +99 -57
  11. package/dist/InteractiveImageEditor.svelte.d.ts +20 -37
  12. package/dist/shared/Anchor.svelte +13 -11
  13. package/dist/shared/Anchor.svelte.d.ts +4 -19
  14. package/dist/shared/Controls.svelte +63 -39
  15. package/dist/shared/Controls.svelte.d.ts +13 -28
  16. package/dist/shared/IconButton.svelte +39 -18
  17. package/dist/shared/IconButton.svelte.d.ts +7 -21
  18. package/dist/shared/ImageEditor.svelte +246 -167
  19. package/dist/shared/ImageEditor.svelte.d.ts +55 -2
  20. package/dist/shared/Layers.svelte +43 -29
  21. package/dist/shared/Layers.svelte.d.ts +10 -25
  22. package/dist/shared/Resize.svelte +22 -16
  23. package/dist/shared/Resize.svelte.d.ts +6 -22
  24. package/dist/shared/SecondaryToolbar.svelte +34 -18
  25. package/dist/shared/SecondaryToolbar.svelte.d.ts +10 -25
  26. package/dist/shared/Toolbar.svelte +60 -76
  27. package/dist/shared/Toolbar.svelte.d.ts +10 -25
  28. package/dist/shared/brush/BrushOptions.svelte +49 -29
  29. package/dist/shared/brush/BrushOptions.svelte.d.ts +6 -21
  30. package/dist/shared/brush/BrushSize.svelte +12 -9
  31. package/dist/shared/brush/BrushSize.svelte.d.ts +5 -20
  32. package/dist/shared/brush/ColorField.svelte +21 -16
  33. package/dist/shared/brush/ColorField.svelte.d.ts +6 -22
  34. package/dist/shared/brush/ColorPicker.svelte +22 -16
  35. package/dist/shared/brush/ColorPicker.svelte.d.ts +4 -18
  36. package/dist/shared/brush/ColorSwatch.svelte +41 -21
  37. package/dist/shared/brush/ColorSwatch.svelte.d.ts +10 -24
  38. package/dist/shared/brush/brush.js +7 -8
  39. package/dist/shared/core/editor.d.ts +5 -0
  40. package/dist/shared/core/editor.js +43 -2
  41. package/dist/shared/resize/resize.d.ts +2 -0
  42. package/dist/shared/resize/resize.js +27 -18
  43. package/dist/types.d.ts +1 -1
  44. package/package.json +8 -8
  45. package/shared/Anchor.svelte +13 -11
  46. package/shared/Controls.svelte +63 -39
  47. package/shared/IconButton.svelte +39 -18
  48. package/shared/ImageEditor.svelte +246 -167
  49. package/shared/Layers.svelte +43 -29
  50. package/shared/Resize.svelte +22 -16
  51. package/shared/SecondaryToolbar.svelte +34 -18
  52. package/shared/Toolbar.svelte +60 -76
  53. package/shared/brush/BrushOptions.svelte +49 -29
  54. package/shared/brush/BrushSize.svelte +12 -9
  55. package/shared/brush/ColorField.svelte +21 -16
  56. package/shared/brush/ColorPicker.svelte +22 -16
  57. package/shared/brush/ColorSwatch.svelte +41 -21
  58. package/shared/brush/brush.ts +8 -10
  59. package/shared/core/editor.ts +49 -2
  60. package/shared/resize/resize.ts +28 -20
  61. package/types.ts +1 -1
@@ -6,7 +6,7 @@
6
6
  </script>
7
7
 
8
8
  <script lang="ts">
9
- import { onMount, createEventDispatcher, tick } from "svelte";
9
+ import { onMount, tick, type Snippet } from "svelte";
10
10
  import { get } from "svelte/store";
11
11
  import Toolbar, { type Tool as ToolbarTool } from "./Toolbar.svelte";
12
12
  import { CropTool } from "./crop/crop";
@@ -30,49 +30,92 @@
30
30
  import IconButton from "./IconButton.svelte";
31
31
 
32
32
  const { drag, open_file_upload } = create_drag();
33
- const dispatch = createEventDispatcher<{
34
- clear?: never;
35
- save: void;
36
- change: void;
37
- upload: void;
38
- input: void;
39
- download_error: string;
40
- }>();
41
33
 
42
34
  export const antialias = true;
43
35
 
44
- export let changeable = false;
45
- export let sources: Source[] = ["upload", "webcam", "clipboard"];
46
- export let transforms: Transform[] = ["crop", "resize"];
47
- export let canvas_size: [number, number];
48
- export let is_dragging = false;
49
- export let background_image = false;
50
- export let brush_options: Brush;
51
- export let eraser_options: Eraser;
52
- export let fixed_canvas = false;
53
- export let root: string;
54
- export let i18n: I18nFormatter;
55
- export let upload: Client["upload"];
56
- export let composite: FileData | null;
57
- export let layers: FileData[];
58
- export let background: FileData | null;
59
- export let border_region = 0;
60
- export let layer_options: LayerOptions;
61
- export let current_tool: ToolbarTool;
62
- export let webcam_options: WebcamOptions;
63
- export let show_download_button = false;
64
- export let theme_mode: "dark" | "light";
65
- export let full_history: CommandNode | null = null;
36
+ let {
37
+ changeable = false,
38
+ sources = ["upload", "webcam", "clipboard"],
39
+ transforms = ["crop", "resize"],
40
+ canvas_size,
41
+ is_dragging = $bindable(false),
42
+ background_image = $bindable(false),
43
+ brush_options,
44
+ eraser_options,
45
+ fixed_canvas = false,
46
+ root,
47
+ i18n,
48
+ upload,
49
+ composite,
50
+ layers,
51
+ background,
52
+ border_region = 0,
53
+ layer_options,
54
+ current_tool = $bindable(),
55
+ webcam_options,
56
+ show_download_button = false,
57
+ theme_mode,
58
+ full_history = $bindable(null),
59
+ has_drawn = $bindable(false),
60
+ can_undo = $bindable(false),
61
+ onclear,
62
+ onsave,
63
+ onchange,
64
+ onupload,
65
+ oninput,
66
+ ondownload_error,
67
+ children
68
+ }: {
69
+ changeable?: boolean;
70
+ sources?: Source[];
71
+ transforms?: Transform[];
72
+ canvas_size: [number, number];
73
+ is_dragging?: boolean;
74
+ background_image?: boolean;
75
+ brush_options: Brush | false;
76
+ eraser_options: Eraser | false;
77
+ fixed_canvas?: boolean;
78
+ root: string;
79
+ i18n: I18nFormatter;
80
+ upload: Client["upload"];
81
+ composite: FileData | null;
82
+ layers: FileData[];
83
+ background: FileData | null;
84
+ border_region?: number;
85
+ layer_options: LayerOptions;
86
+ current_tool: ToolbarTool;
87
+ webcam_options: WebcamOptions;
88
+ show_download_button?: boolean;
89
+ theme_mode: "dark" | "light";
90
+ full_history?: CommandNode | null;
91
+ has_drawn?: boolean;
92
+ can_undo?: boolean;
93
+ onclear?: () => void;
94
+ onsave?: () => void;
95
+ onchange?: () => void;
96
+ onupload?: () => void;
97
+ oninput?: () => void;
98
+ ondownload_error?: (error: string) => void;
99
+ children?: Snippet;
100
+ } = $props();
66
101
 
67
102
  let pixi_target: HTMLDivElement;
68
103
  let pixi_target_crop: HTMLDivElement;
69
104
 
70
- $: if (layer_options) {
71
- if (check_if_should_init()) {
72
- editor.set_layer_options(layer_options);
73
- refresh_tools();
74
- }
75
- }
105
+ let layer_options_key: string | undefined | null = null;
106
+ $effect(() => {
107
+ if (!check_if_should_init()) return;
108
+
109
+ const next_layer_options_key = JSON.stringify(layer_options);
110
+ if (next_layer_options_key === layer_options_key) return;
111
+
112
+ layer_options_key = next_layer_options_key;
113
+ editor.set_layer_options(layer_options);
114
+ });
115
+
116
+ $effect(() => {
117
+ refresh_tools();
118
+ });
76
119
 
77
120
  function refresh_tools(): void {
78
121
  if (!editor || !ready) return;
@@ -80,13 +123,14 @@
80
123
  editor.set_subtool(current_subtool);
81
124
  }
82
125
 
83
- $: if (editor && ready && editor.layers) {
126
+ $effect(() => {
127
+ if (!(editor && ready && editor.layers)) return;
84
128
  const current_layers = get(editor.layers);
85
129
 
86
130
  if (current_layers.layers.length > 0 && !current_layers.active_layer) {
87
131
  refresh_tools_for_layer_changes(current_layers);
88
132
  }
89
- }
133
+ });
90
134
 
91
135
  /**
92
136
  * Refreshes tools when layer state changes
@@ -115,8 +159,6 @@
115
159
  return layer_options && editor && ready;
116
160
  }
117
161
 
118
- export let has_drawn = false;
119
-
120
162
  /**
121
163
  * Gets the image blobs from the editor
122
164
  * @returns {Promise<ImageBlobs>} Object containing background, layers, and composite image blobs
@@ -176,8 +218,8 @@
176
218
  crop.set_subtool("crop");
177
219
  background_image = true;
178
220
  zoom.set_zoom("fit");
179
- dispatch("upload");
180
- dispatch("input");
221
+ onupload?.();
222
+ oninput?.();
181
223
  } catch (error) {
182
224
  console.error("Error adding image from URL:", error);
183
225
  }
@@ -200,8 +242,8 @@
200
242
  try {
201
243
  await pending_bg;
202
244
  await editor.add_layers_from_url(source.map((item) => item.url));
203
- dispatch("change");
204
- dispatch("input");
245
+ onchange?.();
246
+ oninput?.();
205
247
  } catch (error) {
206
248
  console.error("Error adding layer from URL:", error);
207
249
  }
@@ -210,10 +252,10 @@
210
252
 
211
253
  let brush: BrushTool;
212
254
  let zoom: ZoomTool;
213
- let zoom_level = 1;
214
- let ready = false;
215
- let mounted = false;
216
- let min_zoom = true;
255
+ let zoom_level = $state(1);
256
+ let ready = $state(false);
257
+ let mounted = $state(false);
258
+ let min_zoom = $state(true);
217
259
 
218
260
  let last_dimensions = { width: 0, height: 0 };
219
261
 
@@ -221,9 +263,11 @@
221
263
  * Handles visibility changes and resets zoom if dimensions have changed
222
264
  */
223
265
  async function handle_visibility_change(): Promise<void> {
224
- if (!editor || !ready || !zoom) return;
266
+ if (!editor || !ready || !zoom || !pixi_target) return;
225
267
  await tick();
226
268
 
269
+ if (!pixi_target) return;
270
+
227
271
  const is_visible = pixi_target.offsetParent !== null;
228
272
 
229
273
  if (is_visible) {
@@ -316,8 +360,7 @@
316
360
 
317
361
  let crop: ImageEditor;
318
362
  let crop_zoom: ZoomTool;
319
- export let can_undo = false;
320
- let can_redo = false;
363
+ let can_redo = $state(false);
321
364
  async function init_image_editor(): Promise<void> {
322
365
  brush = new BrushTool();
323
366
  zoom = new ZoomTool();
@@ -382,8 +425,8 @@
382
425
  });
383
426
 
384
427
  editor.on("change", () => {
385
- dispatch("change");
386
- dispatch("input");
428
+ onchange?.();
429
+ oninput?.();
387
430
  full_history = editor.command_manager.history;
388
431
  });
389
432
 
@@ -403,23 +446,27 @@
403
446
  refresh_tools();
404
447
  }
405
448
 
406
- $: if (
407
- background == null &&
408
- layers.length == 0 &&
409
- composite == null &&
410
- editor &&
411
- ready
412
- ) {
413
- editor.reset_canvas();
414
- zoom.set_zoom("fit");
415
- handle_tool_change({ tool: "image" });
416
- background_image = false;
417
- has_drawn = false;
418
- }
449
+ $effect(() => {
450
+ if (
451
+ background == null &&
452
+ layers.length == 0 &&
453
+ composite == null &&
454
+ editor &&
455
+ ready
456
+ ) {
457
+ editor.reset_canvas();
458
+ zoom.set_zoom("fit");
459
+ handle_tool_change({ tool: "image" });
460
+ background_image = false;
461
+ has_drawn = false;
462
+ }
463
+ });
419
464
 
420
- $: current_tool === "image" &&
421
- current_subtool === "crop" &&
422
- crop_zoom.set_zoom("fit");
465
+ $effect(() => {
466
+ if (current_tool === "image" && current_subtool === "crop") {
467
+ crop_zoom.set_zoom("fit");
468
+ }
469
+ });
423
470
 
424
471
  /**
425
472
  * Handles file uploads
@@ -439,10 +486,15 @@
439
486
  background_image = true;
440
487
  zoom.set_zoom("fit");
441
488
  handle_tool_change({ tool: "draw" });
442
- dispatch("upload");
489
+ onupload?.();
443
490
  }
444
491
 
445
- $: background_image = can_undo && editor.command_manager.contains("AddImage");
492
+ $effect(() => {
493
+ if (editor) {
494
+ background_image =
495
+ can_undo && editor.command_manager.contains("AddImage");
496
+ }
497
+ });
446
498
 
447
499
  /**
448
500
  * Handles tool change events
@@ -500,13 +552,13 @@
500
552
  }
501
553
  }
502
554
 
503
- let eraser_size_visible = false;
504
- let selected_color: ColorInput | string;
505
- let selected_size: number;
506
- let selected_opacity = 1;
507
- let selected_eraser_size: number;
555
+ let eraser_size_visible = $state(false);
556
+ let selected_color = $state<ColorInput | string>();
557
+ let selected_size = $state<number>();
558
+ let selected_opacity = $state(1);
559
+ let selected_eraser_size = $state<number>();
508
560
 
509
- $: {
561
+ $effect(() => {
510
562
  if (brush_options) {
511
563
  update_brush_options();
512
564
  }
@@ -514,13 +566,15 @@
514
566
  if (eraser_options) {
515
567
  update_eraser_options();
516
568
  }
517
- }
569
+ });
518
570
 
519
571
  function update_brush_options(): void {
572
+ if (!brush_options) return;
573
+ const options = brush_options;
520
574
  const default_color =
521
- brush_options.default_color === "auto"
522
- ? brush_options.colors[0]
523
- : brush_options.default_color;
575
+ options.default_color === "auto"
576
+ ? options.colors[0]
577
+ : options.default_color;
524
578
 
525
579
  // color is already a tuple [color, opacity]
526
580
  if (Array.isArray(default_color)) {
@@ -539,59 +593,86 @@
539
593
  }
540
594
 
541
595
  selected_size =
542
- typeof brush_options.default_size === "number"
543
- ? brush_options.default_size
544
- : 25;
596
+ typeof options.default_size === "number" ? options.default_size : 25;
545
597
  }
546
598
 
547
599
  function update_eraser_options(): void {
600
+ if (!eraser_options) return;
601
+ const options = eraser_options;
548
602
  selected_eraser_size =
549
- eraser_options.default_size === "auto" ? 25 : eraser_options.default_size;
550
- }
551
-
552
- let brush_size_visible = false;
553
-
554
- let brush_color_visible = false;
603
+ options.default_size === "auto" ? 25 : options.default_size;
604
+ }
605
+
606
+ let brush_size_visible = $state(false);
607
+
608
+ let brush_color_visible = $state(false);
609
+
610
+ $effect(() => {
611
+ if (!brush) return;
612
+ brush.set_brush_color(
613
+ (() => {
614
+ let color_value: ColorInput = "black";
615
+ if (selected_color === "auto") {
616
+ if (!brush_options) return "black";
617
+ const default_color =
618
+ brush_options.colors.find((color) =>
619
+ Array.isArray(color)
620
+ ? color[0] === brush_options.default_color
621
+ : color === brush_options.default_color
622
+ ) || brush_options.colors[0];
623
+
624
+ color_value = Array.isArray(default_color)
625
+ ? default_color[0]
626
+ : default_color;
627
+ } else {
628
+ color_value = selected_color ?? "black";
629
+ }
630
+ return color_value;
631
+ })()
632
+ );
633
+ });
555
634
 
556
- $: brush?.set_brush_color(
557
- (() => {
558
- let color_value;
559
- if (selected_color === "auto") {
560
- const default_color =
561
- brush_options.colors.find((color) =>
562
- Array.isArray(color)
563
- ? color[0] === brush_options.default_color
564
- : color === brush_options.default_color
565
- ) || brush_options.colors[0];
635
+ $effect(() => {
636
+ brush?.set_brush_size(
637
+ typeof selected_size === "number" ? selected_size : 25
638
+ );
639
+ });
566
640
 
567
- color_value = Array.isArray(default_color)
568
- ? default_color[0]
569
- : default_color;
570
- } else {
571
- color_value = selected_color;
572
- }
573
- return color_value;
574
- })()
575
- );
641
+ $effect(() => {
642
+ brush?.set_eraser_size(
643
+ typeof selected_eraser_size === "number" ? selected_eraser_size : 25
644
+ );
645
+ });
576
646
 
577
- $: brush?.set_brush_size(
578
- typeof selected_size === "number" ? selected_size : 25
579
- );
647
+ let disable_click = $state(true);
648
+ $effect(() => {
649
+ disable_click =
650
+ current_tool !== "image" ||
651
+ (current_tool === "image" && background_image) ||
652
+ (current_tool === "image" && current_subtool === "webcam") ||
653
+ !sources.includes("upload");
654
+ });
580
655
 
581
- $: brush?.set_eraser_size(
582
- typeof selected_eraser_size === "number" ? selected_eraser_size : 25
583
- );
656
+ let current_subtool = $state<Subtool | null>(null);
657
+ let preview = $state(false);
658
+ $effect(() => {
659
+ brush?.preview_brush(preview);
660
+ });
661
+ $effect(() => {
662
+ brush?.set_brush_opacity(selected_opacity);
663
+ });
584
664
 
585
- $: disable_click =
586
- current_tool !== "image" ||
587
- (current_tool === "image" && background_image) ||
588
- (current_tool === "image" && current_subtool === "webcam") ||
589
- !sources.includes("upload");
665
+ function remove_image(): void {
666
+ onclear?.();
667
+ editor.reset_canvas();
668
+ handle_tool_change({ tool: "image" });
669
+ background_image = false;
670
+ has_drawn = false;
671
+ }
590
672
 
591
- let current_subtool: Subtool | null = null;
592
- let preview = false;
593
- $: brush?.preview_brush(preview);
594
- $: brush?.set_brush_opacity(selected_opacity);
673
+ export function handle_remove(): void {
674
+ remove_image();
675
+ }
595
676
 
596
677
  function handle_zoom_change(zoom_level: number | "fit"): void {
597
678
  zoom.set_zoom(zoom_level);
@@ -618,19 +699,23 @@
618
699
  }
619
700
  }
620
701
 
621
- function handle_capture(e: CustomEvent): void {
622
- if (e.detail !== null) {
623
- handle_files(e.detail as Blob);
702
+ function handle_capture(value: FileData | Blob | null): void {
703
+ if (value !== null) {
704
+ handle_files(value as Blob);
624
705
  }
625
706
  handle_subtool_change({ tool: current_tool, subtool: null });
626
707
  }
627
708
 
628
709
  function handle_save(): void {
629
- dispatch("save");
710
+ onsave?.();
630
711
  }
631
712
 
632
- $: add_image_from_url(composite || background);
633
- $: add_layers_from_url(layers);
713
+ $effect(() => {
714
+ add_image_from_url(composite || background);
715
+ });
716
+ $effect(() => {
717
+ add_layers_from_url(layers);
718
+ });
634
719
 
635
720
  async function handle_crop_confirm(): Promise<void> {
636
721
  const { image } = await crop.get_crop_bounds();
@@ -641,8 +726,8 @@
641
726
  resize: false
642
727
  });
643
728
  handle_subtool_change({ tool: "image", subtool: null });
644
- dispatch("change");
645
- dispatch("input");
729
+ onchange?.();
730
+ oninput?.();
646
731
  }
647
732
 
648
733
  async function handle_download(): Promise<void> {
@@ -650,7 +735,7 @@
650
735
 
651
736
  const blob = blobs.composite;
652
737
  if (!blob) {
653
- dispatch("download_error", "Unable to generate image to download.");
738
+ ondownload_error?.("Unable to generate image to download.");
654
739
  return;
655
740
  }
656
741
  const url = URL.createObjectURL(blob);
@@ -671,12 +756,13 @@
671
756
 
672
757
  let old_background = background;
673
758
 
674
- $: if (old_background !== background) {
759
+ $effect(() => {
760
+ if (old_background === background) return;
675
761
  handle_tool_change({ tool: "draw" });
676
762
  handle_subtool_change({ tool: "draw", subtool: null });
677
763
  current_tool = "draw";
678
764
  old_background = background;
679
- }
765
+ });
680
766
  </script>
681
767
 
682
768
  <div
@@ -687,7 +773,8 @@
687
773
  on_drag_change: (dragging) => (is_dragging = dragging),
688
774
  on_files: handle_files,
689
775
  accepted_types: "image/*",
690
- disable_click: disable_click
776
+ disable_click: disable_click,
777
+ ignore_click_selector: ".toolbar-wrap"
691
778
  }}
692
779
  aria-label={"Click to upload or drop files"}
693
780
  aria-dropeffect="copy"
@@ -696,30 +783,24 @@
696
783
  {#if current_subtool !== "crop"}
697
784
  <Controls
698
785
  {changeable}
699
- on:set_zoom={(e) => handle_zoom_change(e.detail)}
700
- on:zoom_in={() => zoom_in_out("in")}
701
- on:zoom_out={() => zoom_in_out("out")}
786
+ onset_zoom={(zoom) => handle_zoom_change(zoom)}
787
+ onzoom_in={() => zoom_in_out("in")}
788
+ onzoom_out={() => zoom_in_out("out")}
702
789
  {min_zoom}
703
790
  current_zoom={zoom_level}
704
- on:remove_image={() => {
705
- dispatch("clear");
706
- editor.reset_canvas();
707
- handle_tool_change({ tool: "image" });
708
- background_image = false;
709
- has_drawn = false;
710
- }}
791
+ onremove_image={remove_image}
711
792
  tool={current_tool}
712
793
  can_save={true}
713
- on:save={handle_save}
714
- on:pan={(e) => {
794
+ onsave={handle_save}
795
+ onpan={() => {
715
796
  handle_tool_change({ tool: "pan" });
716
797
  }}
717
798
  enable_download={show_download_button}
718
- on:download={() => handle_download()}
799
+ ondownload={() => handle_download()}
719
800
  {can_undo}
720
801
  {can_redo}
721
- on:undo={handle_undo}
722
- on:redo={handle_redo}
802
+ onundo={handle_undo}
803
+ onredo={handle_redo}
723
804
  />
724
805
  {/if}
725
806
 
@@ -728,8 +809,8 @@
728
809
  {sources}
729
810
  {transforms}
730
811
  background={background_image}
731
- on:tool_change={(e) => handle_tool_change(e.detail)}
732
- on:subtool_change={(e) => handle_subtool_change(e.detail)}
812
+ ontool_change={(detail) => handle_tool_change(detail)}
813
+ onsubtool_change={(detail) => handle_subtool_change(detail)}
733
814
  show_brush_size={brush_size_visible}
734
815
  show_brush_color={brush_color_visible}
735
816
  show_eraser_size={eraser_size_visible}
@@ -751,9 +832,7 @@
751
832
  <Webcam
752
833
  {upload}
753
834
  {root}
754
- on:capture={handle_capture}
755
- on:error
756
- on:drag
835
+ oncapture={handle_capture}
757
836
  streaming={false}
758
837
  mode="image"
759
838
  include_audio={false}
@@ -769,23 +848,23 @@
769
848
  <SecondaryToolbar
770
849
  enable_additional_layers={layer_options.allow_additional_layers}
771
850
  layers={editor.layers}
772
- on:new_layer={() => {
851
+ onnew_layer={() => {
773
852
  editor.add_layer();
774
853
  }}
775
- on:change_layer={(e) => {
776
- editor.set_layer(e.detail);
854
+ onchange_layer={(id) => {
855
+ editor.set_layer(id);
777
856
  if (current_tool === "draw") {
778
857
  handle_tool_change({ tool: "draw" });
779
858
  }
780
859
  }}
781
- on:move_layer={(e) => {
782
- editor.move_layer(e.detail.id, e.detail.direction);
860
+ onmove_layer={({ id, direction }) => {
861
+ editor.move_layer(id, direction);
783
862
  }}
784
- on:delete_layer={(e) => {
785
- editor.delete_layer(e.detail);
863
+ ondelete_layer={(id) => {
864
+ editor.delete_layer(id);
786
865
  }}
787
- on:toggle_layer_visibility={(e) => {
788
- editor.toggle_layer_visibility(e.detail);
866
+ ontoggle_layer_visibility={(id) => {
867
+ editor.toggle_layer_visibility(id);
789
868
  }}
790
869
  />
791
870
  {/if}
@@ -816,7 +895,7 @@
816
895
  />
817
896
  </div>
818
897
  {/if}
819
- <slot></slot>
898
+ {#if children}{@render children()}{/if}
820
899
  </div>
821
900
 
822
901
  <style>