@pixerate/editor-svelte 0.80.0 → 0.81.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.
@@ -28,7 +28,14 @@
28
28
  pointerPressure,
29
29
  FreeTransformTool,
30
30
  getViewportProjection,
31
+ ShapeDrawGesture,
32
+ TypeToolGesture,
33
+ DEFAULT_SHAPE_TOOL_OPTIONS,
34
+ DEFAULT_TYPE_TOOL_OPTIONS,
31
35
  type SelectionToolOptions,
36
+ type ShapeToolOptions,
37
+ type TextEditSession,
38
+ type TypeToolOptions,
32
39
  } from '@pixerate/editor/image-editor';
33
40
  import ControlSlider from './ControlSlider.svelte';
34
41
  import Histogram from './Histogram.svelte';
@@ -38,6 +45,8 @@
38
45
  import SelectionOptionsBar from './SelectionOptionsBar.svelte';
39
46
  import RefineEdgePanel from './RefineEdgePanel.svelte';
40
47
  import FreeTransformOptionsBar from './FreeTransformOptionsBar.svelte';
48
+ import ShapeOptionsBar from './ShapeOptionsBar.svelte';
49
+ import TypeOptionsBar from './TypeOptionsBar.svelte';
41
50
  import { onDestroy, untrack } from 'svelte';
42
51
  import { createReactiveImageEditor, type ReactiveImageEditor } from './imageEditorState.svelte.js';
43
52
 
@@ -85,6 +94,60 @@
85
94
  const paintGesture = $derived(new PaintGesture(editor.controller));
86
95
  let brushCursor: ImagePoint | null = null;
87
96
 
97
+ // Shape and type tools: their options and gestures; type is edited on the canvas through a hidden textarea.
98
+ let shapeOptions = $state<ShapeToolOptions>({ ...DEFAULT_SHAPE_TOOL_OPTIONS });
99
+ let typeOptions = $state<TypeToolOptions>({ ...DEFAULT_TYPE_TOOL_OPTIONS });
100
+ const shapeGesture = $derived(new ShapeDrawGesture(editor.controller, () => shapeOptions));
101
+ const typeTool = $derived(new TypeToolGesture(editor.controller, () => typeOptions));
102
+ let typeSession = $state<TextEditSession | null>(null);
103
+ let typeInputEl = $state<HTMLTextAreaElement | null>(null);
104
+ let shapeTypeError = $state<string | null>(null);
105
+ $effect(() => {
106
+ const s = typeSession;
107
+ if (!s) return;
108
+ return untrack(() => s.subscribe(() => void renderCanvas()));
109
+ });
110
+ $effect(() => {
111
+ // End the edit when the tool or tab changes.
112
+ const keep = activeTab === 'annotate' && editor.state.activeTool === 'type';
113
+ untrack(() => {
114
+ if (!keep && typeTool.session) finishTyping();
115
+ });
116
+ });
117
+ function finishTyping() {
118
+ typeTool.finish();
119
+ typeSession = null;
120
+ void renderCanvas();
121
+ }
122
+ function handleTypeKeyDown(e: KeyboardEvent) {
123
+ const session = typeTool.session;
124
+ if (!session || e.isComposing) return;
125
+ if (e.key === 'Escape') {
126
+ e.preventDefault();
127
+ finishTyping();
128
+ return;
129
+ }
130
+ if ((e.metaKey || e.ctrlKey) && ['z', 'y', 'c', 'x', 'v'].includes(e.key.toLowerCase())) return;
131
+ if (session.handleKey(e)) {
132
+ e.preventDefault();
133
+ shapeTypeError = session.error;
134
+ }
135
+ }
136
+ function handleTypeInput(e: Event) {
137
+ // Composed (IME) and pasted text arrives here; plain keys were handled on keydown.
138
+ const el = e.currentTarget as HTMLTextAreaElement;
139
+ const session = typeTool.session;
140
+ if (session && el.value && !(e as InputEvent).isComposing) {
141
+ session.insert(el.value);
142
+ el.value = '';
143
+ }
144
+ }
145
+ function handleTypeComposition(e: CompositionEvent) {
146
+ const session = typeTool.session;
147
+ if (session && e.data) session.insert(e.data);
148
+ (e.currentTarget as HTMLTextAreaElement).value = '';
149
+ }
150
+
88
151
  // Free Transform (Ctrl/Cmd+T): the shared tool drives a session; its overlay is drawn by the renderer.
89
152
  const transformTool = $derived(
90
153
  new FreeTransformTool(editor.controller, {
@@ -292,6 +355,7 @@
292
355
  ? { ...brushCursor, diameter: editor.controller.getState().paint.brush.size }
293
356
  : null,
294
357
  freeTransform: transformTool.overlay(),
358
+ textEdit: typeTool.session?.alive ? typeTool.session.overlay() : null,
295
359
  });
296
360
  }
297
361
 
@@ -434,6 +498,18 @@
434
498
  return;
435
499
  }
436
500
 
501
+ if (activeTab === 'annotate' && (editor.state.activeTool === 'shape' || editor.state.activeTool === 'type')) {
502
+ const imgPt = canvasToImagePoint(canvasPt, editor.state, canvasEl.width, canvasEl.height, false);
503
+ shapeTypeError = null;
504
+ if (editor.state.activeTool === 'shape') shapeGesture.down(imgPt);
505
+ else {
506
+ typeTool.down(imgPt, { shift: e.shiftKey });
507
+ typeSession = typeTool.session;
508
+ void renderCanvas();
509
+ }
510
+ return;
511
+ }
512
+
437
513
  if (activeTab === 'annotate') {
438
514
  const imgPt = canvasToImagePoint(canvasPt, editor.state, canvasEl.width, canvasEl.height, false);
439
515
  dragStartPoint = imgPt;
@@ -530,6 +606,14 @@
530
606
  return;
531
607
  }
532
608
 
609
+ if (activeTab === 'annotate' && editor.state.activeTool === 'shape') {
610
+ if (shapeGesture.active) {
611
+ shapeGesture.move(canvasToImagePoint(canvasPt, editor.state, canvasEl.width, canvasEl.height, false), { shift: e.shiftKey, alt: e.altKey });
612
+ if (shapeGesture.error) shapeTypeError = shapeGesture.error;
613
+ }
614
+ return;
615
+ }
616
+
533
617
  if (activeTab === 'annotate') {
534
618
  if (annotationDragInfo) {
535
619
  const currentImgPt = canvasToImagePoint(canvasPt, editor.state, canvasEl.width, canvasEl.height, false);
@@ -660,6 +744,20 @@
660
744
  return;
661
745
  }
662
746
 
747
+ if (activeTab === 'annotate' && editor.state.activeTool === 'shape') {
748
+ shapeGesture.up();
749
+ return;
750
+ }
751
+ if (activeTab === 'annotate' && editor.state.activeTool === 'type') {
752
+ typeTool.up(canvasToImagePoint(canvasPt, editor.state, canvasEl.width, canvasEl.height, false));
753
+ typeSession = typeTool.session;
754
+ if (typeTool.error) shapeTypeError = typeTool.error;
755
+ void renderCanvas();
756
+ // Keystrokes go to the hidden textarea while text is being edited.
757
+ if (typeTool.session) setTimeout(() => typeInputEl?.focus(), 0);
758
+ return;
759
+ }
760
+
663
761
  if (activeTab === 'annotate') {
664
762
  if (annotationDragInfo) {
665
763
  annotationDragInfo = null;
@@ -977,8 +1075,21 @@
977
1075
  onpointercancel={() => paintGesture.cancel()}
978
1076
  data-testid="image-editor-canvas"
979
1077
  style={transformActive ? `cursor: ${transformCursor}` : undefined}
980
- class="absolute inset-0 w-full h-full block {transformActive ? 'touch-none' : activeTab === 'paint' ? 'touch-none cursor-crosshair' : ''}"
1078
+ class="absolute inset-0 w-full h-full block {transformActive ? 'touch-none' : activeTab === 'paint' ? 'touch-none cursor-crosshair' : activeTab === 'annotate' && editor.state.activeTool === 'type' ? 'cursor-text' : activeTab === 'annotate' && editor.state.activeTool === 'shape' ? 'cursor-crosshair' : ''}"
981
1079
  ></canvas>
1080
+ {#if typeSession}
1081
+ <textarea
1082
+ bind:this={typeInputEl}
1083
+ aria-label="Edit text"
1084
+ data-testid="type-input"
1085
+ autocapitalize="off"
1086
+ spellcheck={false}
1087
+ onkeydown={handleTypeKeyDown}
1088
+ oninput={handleTypeInput}
1089
+ oncompositionend={handleTypeComposition}
1090
+ class="absolute left-0 top-0 h-px w-px opacity-0"
1091
+ ></textarea>
1092
+ {/if}
982
1093
 
983
1094
  </div>
984
1095
 
@@ -1192,6 +1303,32 @@
1192
1303
 
1193
1304
  {#if activeTab === 'annotate'}
1194
1305
  <div class="flex flex-col gap-3 text-xs">
1306
+ <span class="font-semibold text-slate-200 uppercase tracking-wider text-[11px]">Shape & Type Layers</span>
1307
+ <div class="grid grid-cols-2 gap-1.5">
1308
+ {#each ['shape', 'type'] as const as t (t)}
1309
+ <button
1310
+ type="button"
1311
+ aria-pressed={editor.state.activeTool === t}
1312
+ onclick={() => editor.setTool(t)}
1313
+ class="py-1.5 px-2 rounded-lg font-medium border transition-colors {editor.state.activeTool === t
1314
+ ? 'bg-indigo-600 border-indigo-500 text-white'
1315
+ : 'bg-slate-800 border-slate-700 text-slate-300 hover:bg-slate-700'}">{t === 'shape' ? 'Shape' : 'Type'}</button>
1316
+ {/each}
1317
+ </div>
1318
+ {#if editor.state.activeTool === 'shape'}
1319
+ <ShapeOptionsBar controller={editor.controller} options={shapeOptions} onchange={(o) => (shapeOptions = o)} />
1320
+ {/if}
1321
+ {#if editor.state.activeTool === 'type'}
1322
+ <TypeOptionsBar controller={editor.controller} options={typeOptions} onchange={(o) => (typeOptions = o)} session={typeSession} />
1323
+ {#if typeSession}
1324
+ <button type="button" onclick={finishTyping} class="self-start rounded bg-slate-800 px-2 py-1 text-[11px] text-slate-200 hover:bg-slate-700">Done editing text</button>
1325
+ {/if}
1326
+ {/if}
1327
+ {#if shapeTypeError}
1328
+ <p role="alert" class="text-rose-400" data-testid="shape-type-error">{shapeTypeError}</p>
1329
+ {/if}
1330
+ <div class="h-px bg-slate-800 my-1"></div>
1331
+
1195
1332
  <span class="font-semibold text-slate-200 uppercase tracking-wider text-[11px]">
1196
1333
  Draw & Annotate
1197
1334
  </span>
@@ -1 +1 @@
1
- {"version":3,"file":"ImageEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/image-editor/ImageEditor.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAIR,aAAa,EAGb,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,EACf,MAAM,+BAA+B,CAAC;AA6BzC,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAGjG,UAAU,KAAK;IACb,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB;AA+uCH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"ImageEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/image-editor/ImageEditor.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAIR,aAAa,EAGb,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,EACf,MAAM,+BAA+B,CAAC;AAsCzC,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAGjG,UAAU,KAAK;IACb,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB;AAm2CH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -8,6 +8,8 @@
8
8
  dropZone,
9
9
  layerBadge,
10
10
  layerEffectsBadge,
11
+ layerHasThumbnail,
12
+ layerWarning,
11
13
  layerMenu,
12
14
  layerPanelRows,
13
15
  openPsdFile,
@@ -136,7 +138,7 @@
136
138
 
137
139
  function thumb(layer: ImageLayer): PixelBuffer | null {
138
140
  void tick;
139
- return layer.type === 'pixel' ? controller.getLayerThumbnail(layer.id, { maxSide: 64 }) : null;
141
+ return layerHasThumbnail(layer) ? controller.getLayerThumbnail(layer.id, { maxSide: 64 }) : null;
140
142
  }
141
143
  function maskThumb(layer: ImageLayer): PixelBuffer | null {
142
144
  void tick;
@@ -314,13 +316,20 @@
314
316
  }}>{layer.collapsed ? '▸' : '▾'}</button>
315
317
  {/if}
316
318
  {#if row.clipped}<span class="text-slate-500" aria-label="Clipped">↳</span>{/if}
317
- {#if layer.type === 'pixel'}
318
- <canvas
319
- use:paint={thumb(layer)}
320
- aria-label={`${row.label} pixels`}
321
- style="width: {thumbnailSize}px; height: {thumbnailSize}px"
322
- class={thumbClass(row.active && snapshot.activeTarget === 'pixels' && Boolean(layer.mask))}
323
- ></canvas>
319
+ {#if layerHasThumbnail(layer)}
320
+ <span class="relative shrink-0">
321
+ <canvas
322
+ use:paint={thumb(layer)}
323
+ aria-label={`${row.label} pixels`}
324
+ style="width: {thumbnailSize}px; height: {thumbnailSize}px"
325
+ class={thumbClass(row.active && snapshot.activeTarget === 'pixels' && Boolean(layer.mask))}
326
+ ></canvas>
327
+ {#if layer.type !== 'pixel'}
328
+ <span
329
+ data-testid="layer-kind-badge"
330
+ class="absolute -bottom-1 -right-1 rounded bg-slate-700 px-0.5 text-[8px] font-bold leading-tight text-slate-100 ring-1 ring-slate-900">{layerBadge(layer)}</span>
331
+ {/if}
332
+ </span>
324
333
  {:else}
325
334
  <span
326
335
  class="shrink-0 grid place-items-center rounded bg-slate-800 text-[9px] text-slate-400 ring-1 ring-slate-700"
@@ -382,6 +391,9 @@
382
391
  renaming = layer.id;
383
392
  }}>{row.label}</span>
384
393
  {/if}
394
+ {#if layerWarning(layer)}
395
+ <span role="img" data-testid="layer-warning" aria-label={layerWarning(layer)} title={layerWarning(layer)} class="text-[10px] text-amber-400">⚠</span>
396
+ {/if}
385
397
  {#if layerEffectsBadge(layer)}
386
398
  {@const fxOn = layerEffectsBadge(layer) === 'on'}
387
399
  <button
@@ -1 +1 @@
1
- {"version":3,"file":"LayersPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/image-editor/LayersPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAA2D,MAAM,+BAA+B,CAAC;AAkBlI,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,yCAAyC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwRH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"LayersPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/image-editor/LayersPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAA2D,MAAM,+BAA+B,CAAC;AAoBlI,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,yCAAyC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgSH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,120 @@
1
+ <script lang="ts">
2
+ import type { ImageEditorController, ShapeToolOptions } from '@pixerate/editor/image-editor';
3
+ import { LIVE_SHAPE_KINDS, SHAPE_KIND_LABELS, applyShapeOptions } from '@pixerate/editor/image-editor';
4
+ import { untrack } from 'svelte';
5
+
6
+ /**
7
+ * The shape tool's options: the kind to draw, fill and stroke (colour or none), stroke width and
8
+ * the kind's own settings (corner radius, sides, points, line weight and arrowhead). Changes also
9
+ * apply to the active shape layer, as one undo step each.
10
+ */
11
+ interface Props {
12
+ controller: ImageEditorController;
13
+ options: ShapeToolOptions;
14
+ onchange: (options: ShapeToolOptions) => void;
15
+ class?: string;
16
+ }
17
+
18
+ let { controller, options, onchange, class: className = '' }: Props = $props();
19
+
20
+ let tick = $state(0);
21
+ $effect(() => {
22
+ const c = controller;
23
+ return untrack(() => c.subscribe(() => tick++));
24
+ });
25
+ const shapeId = $derived.by(() => {
26
+ void tick;
27
+ const a = controller.getActiveLayer();
28
+ return a?.type === 'shape' ? a.id : null;
29
+ });
30
+ let error = $state<string | null>(null);
31
+
32
+ const chip = (on: boolean) => `px-2 py-1 rounded text-[11px] ${on ? 'bg-indigo-600 text-white' : 'bg-slate-800 text-slate-300 hover:text-white'}`;
33
+ const num = (v: string, min: number, max: number) => Math.max(min, Math.min(max, Number(v) || min));
34
+
35
+ function set(change: Partial<ShapeToolOptions>) {
36
+ onchange({ ...options, ...change });
37
+ if (!shapeId) return;
38
+ try {
39
+ error = null;
40
+ applyShapeOptions(controller, shapeId, change);
41
+ } catch (e) {
42
+ error = e instanceof Error ? e.message : String(e);
43
+ }
44
+ }
45
+ const value = (e: Event) => (e.target as HTMLInputElement).value;
46
+ const checked = (e: Event) => (e.target as HTMLInputElement).checked;
47
+ </script>
48
+
49
+ <div class="flex flex-col gap-2 text-xs text-slate-200 {className}" data-testid="shape-options">
50
+ <div class="flex flex-wrap gap-1" role="toolbar" aria-label="Shape kind">
51
+ {#each LIVE_SHAPE_KINDS as k (k)}
52
+ <button type="button" aria-pressed={options.kind === k} class={chip(options.kind === k)} onclick={() => onchange({ ...options, kind: k })}>{SHAPE_KIND_LABELS[k]}</button>
53
+ {/each}
54
+ </div>
55
+ <div class="flex flex-wrap items-center gap-3">
56
+ <label class="flex items-center gap-1">
57
+ <input type="checkbox" aria-label="Fill" checked={options.fill !== null} onchange={(e) => set({ fill: checked(e) ? '#4f46e5' : null })} />
58
+ Fill
59
+ <input
60
+ type="color"
61
+ aria-label="Fill colour"
62
+ disabled={options.fill === null}
63
+ value={options.fill ?? '#4f46e5'}
64
+ oninput={(e) => set({ fill: value(e) })}
65
+ class="h-6 w-8 rounded border border-slate-700 bg-transparent"
66
+ />
67
+ </label>
68
+ <label class="flex items-center gap-1">
69
+ <input type="checkbox" aria-label="Stroke" checked={options.stroke !== null} onchange={(e) => set({ stroke: checked(e) ? '#000000' : null })} />
70
+ Stroke
71
+ <input
72
+ type="color"
73
+ aria-label="Stroke colour"
74
+ disabled={options.stroke === null}
75
+ value={options.stroke ?? '#000000'}
76
+ oninput={(e) => set({ stroke: value(e) })}
77
+ class="h-6 w-8 rounded border border-slate-700 bg-transparent"
78
+ />
79
+ </label>
80
+ <label class="flex items-center gap-1">
81
+ Width
82
+ <input type="number" aria-label="Stroke width" min="0" max="200" value={options.strokeWidth} oninput={(e) => set({ strokeWidth: num(value(e), 0, 200) })} class="w-14 rounded border border-slate-700 bg-slate-950 px-1 py-0.5" />
83
+ </label>
84
+ </div>
85
+ <div class="flex flex-wrap items-center gap-3">
86
+ {#if options.kind === 'rect'}
87
+ <label class="flex items-center gap-1">
88
+ Radius
89
+ <input type="number" aria-label="Corner radius" min="0" max="1000" value={options.radius} oninput={(e) => set({ radius: num(value(e), 0, 1000) })} class="w-14 rounded border border-slate-700 bg-slate-950 px-1 py-0.5" />
90
+ </label>
91
+ {:else if options.kind === 'polygon'}
92
+ <label class="flex items-center gap-1">
93
+ Sides
94
+ <input type="number" aria-label="Sides" min="3" max="100" value={options.sides} oninput={(e) => set({ sides: num(value(e), 3, 100) })} class="w-14 rounded border border-slate-700 bg-slate-950 px-1 py-0.5" />
95
+ </label>
96
+ {:else if options.kind === 'star'}
97
+ <label class="flex items-center gap-1">
98
+ Points
99
+ <input type="number" aria-label="Points" min="3" max="100" value={options.points} oninput={(e) => set({ points: num(value(e), 3, 100) })} class="w-14 rounded border border-slate-700 bg-slate-950 px-1 py-0.5" />
100
+ </label>
101
+ <label class="flex items-center gap-1">
102
+ Inner
103
+ <input type="range" aria-label="Inner radius" min="0.05" max="1" step="0.05" value={options.innerRatio} oninput={(e) => set({ innerRatio: Number(value(e)) })} class="accent-indigo-500" />
104
+ </label>
105
+ {:else if options.kind === 'line'}
106
+ <label class="flex items-center gap-1">
107
+ Weight
108
+ <input type="number" aria-label="Line weight" min="1" max="200" value={options.weight} oninput={(e) => set({ weight: num(value(e), 1, 200) })} class="w-14 rounded border border-slate-700 bg-slate-950 px-1 py-0.5" />
109
+ </label>
110
+ <label class="flex items-center gap-1">
111
+ <input type="checkbox" aria-label="Arrowhead" checked={options.arrow} onchange={(e) => set({ arrow: checked(e) })} />
112
+ Arrowhead
113
+ </label>
114
+ {/if}
115
+ </div>
116
+ <p class="text-[10px] text-slate-500">Drag on the image to draw. Shift: square, circle or 45° line. Alt: from the centre.</p>
117
+ {#if error}
118
+ <p role="alert" class="text-rose-400">{error}</p>
119
+ {/if}
120
+ </div>
@@ -0,0 +1,16 @@
1
+ import type { ImageEditorController, ShapeToolOptions } from '@pixerate/editor/image-editor';
2
+ /**
3
+ * The shape tool's options: the kind to draw, fill and stroke (colour or none), stroke width and
4
+ * the kind's own settings (corner radius, sides, points, line weight and arrowhead). Changes also
5
+ * apply to the active shape layer, as one undo step each.
6
+ */
7
+ interface Props {
8
+ controller: ImageEditorController;
9
+ options: ShapeToolOptions;
10
+ onchange: (options: ShapeToolOptions) => void;
11
+ class?: string;
12
+ }
13
+ declare const ShapeOptionsBar: import("svelte").Component<Props, {}, "">;
14
+ type ShapeOptionsBar = ReturnType<typeof ShapeOptionsBar>;
15
+ export default ShapeOptionsBar;
16
+ //# sourceMappingURL=ShapeOptionsBar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShapeOptionsBar.svelte.d.ts","sourceRoot":"","sources":["../../src/image-editor/ShapeOptionsBar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAK3F;;;;GAIG;AACH,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,QAAQ,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmGH,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -0,0 +1,116 @@
1
+ <script lang="ts">
2
+ import type { ImageEditorController, TextAlign, TextEditSession, TextStyle, TypeToolOptions } from '@pixerate/editor/image-editor';
3
+ import { TEXT_ALIGNS } from '@pixerate/editor/image-editor';
4
+ import { untrack } from 'svelte';
5
+
6
+ /**
7
+ * The type tool's options: font family (from the editor's fonts), size, bold, italic, colour and
8
+ * alignment. While text is being edited they style the selection (or the whole text); with a
9
+ * type layer active they style all of it; otherwise they set what the next text uses.
10
+ */
11
+ interface Props {
12
+ controller: ImageEditorController;
13
+ options: TypeToolOptions;
14
+ onchange: (options: TypeToolOptions) => void;
15
+ /** The text being edited on the canvas: changes style its selection (or all of it). */
16
+ session?: TextEditSession | null;
17
+ class?: string;
18
+ }
19
+
20
+ let { controller, options, onchange, session = null, class: className = '' }: Props = $props();
21
+
22
+ let tick = $state(0);
23
+ $effect(() => {
24
+ const c = controller;
25
+ return untrack(() => c.subscribe(() => tick++));
26
+ });
27
+ $effect(() => {
28
+ const s = session;
29
+ if (!s) return;
30
+ return untrack(() => s.subscribe(() => tick++));
31
+ });
32
+ const ALIGN_LABELS: Record<TextAlign, string> = { left: 'Left', center: 'Centre', right: 'Right', justify: 'Justify' };
33
+ const chip = (on: boolean) => `px-2 py-1 rounded text-[11px] ${on ? 'bg-indigo-600 text-white' : 'bg-slate-800 text-slate-300 hover:text-white'}`;
34
+
35
+ const view = $derived.by(() => {
36
+ void tick;
37
+ const active = controller.getActiveLayer();
38
+ const here: Partial<TextStyle> = session?.alive ? session.styleHere() : {};
39
+ const shown: TypeToolOptions = {
40
+ ...options,
41
+ ...(here.fontFamily ? { fontFamily: here.fontFamily } : {}),
42
+ ...(here.fontSize ? { fontSize: here.fontSize } : {}),
43
+ ...(here.fontWeight ? { fontWeight: here.fontWeight } : {}),
44
+ ...(session?.alive ? { italic: Boolean(here.italic), color: here.color ?? options.color } : {}),
45
+ };
46
+ return {
47
+ shown,
48
+ typeId: !session && active?.type === 'type' ? active.id : null,
49
+ missing: active?.type === 'type' ? (active.missingFonts ?? []) : [],
50
+ fonts: controller.listFonts(),
51
+ };
52
+ });
53
+ let error = $state<string | null>(null);
54
+
55
+ function style(change: Partial<TypeToolOptions>) {
56
+ onchange({ ...options, ...change });
57
+ const s: Record<string, unknown> = {};
58
+ for (const k of ['fontFamily', 'fontSize', 'fontWeight', 'italic', 'color'] as const) if (k in change) s[k] = change[k];
59
+ const p = change.align ? { align: change.align } : null;
60
+ try {
61
+ error = null;
62
+ if (session?.alive) {
63
+ if (Object.keys(s).length) session.setStyle(s as Partial<TextStyle>);
64
+ if (p) session.setParagraph(p);
65
+ if (session.error) error = session.error;
66
+ } else if (view.typeId) {
67
+ if (Object.keys(s).length) controller.setTextStyle(view.typeId, s);
68
+ if (p) controller.setParagraphStyle(view.typeId, p);
69
+ }
70
+ } catch (e) {
71
+ error = e instanceof Error ? e.message : String(e);
72
+ }
73
+ }
74
+ const value = (e: Event) => (e.target as HTMLInputElement).value;
75
+ </script>
76
+
77
+ <div class="flex flex-col gap-2 text-xs text-slate-200 {className}" data-testid="type-options">
78
+ <div class="flex flex-wrap items-center gap-2">
79
+ <label class="flex items-center gap-1">
80
+ Font
81
+ <input list="pixerate-type-fonts" aria-label="Font family" value={view.shown.fontFamily} oninput={(e) => value(e).trim() && style({ fontFamily: value(e) })} class="w-32 rounded border border-slate-700 bg-slate-950 px-1 py-0.5" />
82
+ <datalist id="pixerate-type-fonts">
83
+ {#each view.fonts as f (f)}
84
+ <option value={f}></option>
85
+ {/each}
86
+ </datalist>
87
+ </label>
88
+ <label class="flex items-center gap-1">
89
+ Size
90
+ <input
91
+ type="number"
92
+ aria-label="Font size"
93
+ min="1"
94
+ max="2000"
95
+ value={Math.round(view.shown.fontSize * 10) / 10}
96
+ oninput={(e) => Number(value(e)) > 0 && style({ fontSize: Number(value(e)) })}
97
+ class="w-16 rounded border border-slate-700 bg-slate-950 px-1 py-0.5"
98
+ />
99
+ </label>
100
+ <button type="button" aria-pressed={view.shown.fontWeight >= 600} class="{chip(view.shown.fontWeight >= 600)} font-bold" onclick={() => style({ fontWeight: view.shown.fontWeight >= 600 ? 400 : 700 })}>B</button>
101
+ <button type="button" aria-pressed={view.shown.italic} class="{chip(view.shown.italic)} italic" onclick={() => style({ italic: !view.shown.italic })}>I</button>
102
+ <input type="color" aria-label="Text colour" value={view.shown.color} oninput={(e) => style({ color: value(e) })} class="h-6 w-8 rounded border border-slate-700 bg-transparent" />
103
+ </div>
104
+ <div class="flex gap-1" role="toolbar" aria-label="Alignment">
105
+ {#each TEXT_ALIGNS as a (a)}
106
+ <button type="button" aria-pressed={options.align === a} class={chip(options.align === a)} onclick={() => style({ align: a })}>{ALIGN_LABELS[a]}</button>
107
+ {/each}
108
+ </div>
109
+ <p class="text-[10px] text-slate-500">Click to type point text, drag for a paragraph box, click text to edit it. Esc finishes.</p>
110
+ {#if view.missing.length > 0}
111
+ <p role="status" class="text-amber-400" data-testid="type-missing-fonts">Missing {view.missing.length === 1 ? 'font' : 'fonts'}: {view.missing.join(', ')}</p>
112
+ {/if}
113
+ {#if error}
114
+ <p role="alert" class="text-rose-400">{error}</p>
115
+ {/if}
116
+ </div>
@@ -0,0 +1,18 @@
1
+ import type { ImageEditorController, TextEditSession, TypeToolOptions } from '@pixerate/editor/image-editor';
2
+ /**
3
+ * The type tool's options: font family (from the editor's fonts), size, bold, italic, colour and
4
+ * alignment. While text is being edited they style the selection (or the whole text); with a
5
+ * type layer active they style all of it; otherwise they set what the next text uses.
6
+ */
7
+ interface Props {
8
+ controller: ImageEditorController;
9
+ options: TypeToolOptions;
10
+ onchange: (options: TypeToolOptions) => void;
11
+ /** The text being edited on the canvas: changes style its selection (or all of it). */
12
+ session?: TextEditSession | null;
13
+ class?: string;
14
+ }
15
+ declare const TypeOptionsBar: import("svelte").Component<Props, {}, "">;
16
+ type TypeOptionsBar = ReturnType<typeof TypeOptionsBar>;
17
+ export default TypeOptionsBar;
18
+ //# sourceMappingURL=TypeOptionsBar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TypeOptionsBar.svelte.d.ts","sourceRoot":"","sources":["../../src/image-editor/TypeOptionsBar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAa,eAAe,EAAa,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAKjI;;;;GAIG;AACH,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAC7C,uFAAuF;IACvF,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmGH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -10,6 +10,8 @@ import type LayersPanelComponent from "./LayersPanel.svelte";
10
10
  import type LayerEffectsPanelComponent from "./LayerEffectsPanel.svelte";
11
11
  import type BrushOptionsBarComponent from "./BrushOptionsBar.svelte";
12
12
  import type FreeTransformOptionsBarComponent from "./FreeTransformOptionsBar.svelte";
13
+ import type ShapeOptionsBarComponent from "./ShapeOptionsBar.svelte";
14
+ import type TypeOptionsBarComponent from "./TypeOptionsBar.svelte";
13
15
  export { default as ImageEditor } from './ImageEditor.svelte';
14
16
  export { default as Histogram } from './Histogram.svelte';
15
17
  export { default as AdjustmentLayersPanel } from './AdjustmentLayersPanel.svelte';
@@ -21,6 +23,8 @@ export { default as LayersPanel } from './LayersPanel.svelte';
21
23
  export { default as LayerEffectsPanel } from './LayerEffectsPanel.svelte';
22
24
  export { default as BrushOptionsBar } from './BrushOptionsBar.svelte';
23
25
  export { default as FreeTransformOptionsBar } from './FreeTransformOptionsBar.svelte';
26
+ export { default as ShapeOptionsBar } from './ShapeOptionsBar.svelte';
27
+ export { default as TypeOptionsBar } from './TypeOptionsBar.svelte';
24
28
  export * from './imageEditorState.svelte.js';
25
29
  export type ImageEditorProps = ComponentProps<typeof ImageEditorComponent>;
26
30
  export type HistogramProps = ComponentProps<typeof HistogramComponent>;
@@ -33,5 +37,7 @@ export type LayersPanelProps = ComponentProps<typeof LayersPanelComponent>;
33
37
  export type LayerEffectsPanelProps = ComponentProps<typeof LayerEffectsPanelComponent>;
34
38
  export type BrushOptionsBarProps = ComponentProps<typeof BrushOptionsBarComponent>;
35
39
  export type FreeTransformOptionsBarProps = ComponentProps<typeof FreeTransformOptionsBarComponent>;
40
+ export type ShapeOptionsBarProps = ComponentProps<typeof ShapeOptionsBarComponent>;
41
+ export type TypeOptionsBarProps = ComponentProps<typeof TypeOptionsBarComponent>;
36
42
  export { applyRegionMosaic, applyRegionBlur, resolveMosaicRegionBounds, type MosaicRegion, type MosaicImageDataLike } from '@pixerate/editor/image-editor';
37
43
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/image-editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,kBAAkB,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,8BAA8B,MAAM,gCAAgC,CAAC;AACjF,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,sBAAsB,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,4BAA4B,MAAM,8BAA8B,CAAC;AAC7E,OAAO,KAAK,wBAAwB,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,0BAA0B,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,wBAAwB,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,gCAAgC,MAAM,kCAAkC,CAAC;AAErF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AACtF,cAAc,8BAA8B,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC3E,MAAM,MAAM,cAAc,GAAG,cAAc,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACvE,MAAM,MAAM,0BAA0B,GAAG,cAAc,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAC/F,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC7E,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC/E,MAAM,MAAM,wBAAwB,GAAG,cAAc,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAC3F,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,wBAAwB,CAAC,CAAC;AACnF,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC3E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,0BAA0B,CAAC,CAAC;AACvF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,wBAAwB,CAAC,CAAC;AACnF,MAAM,MAAM,4BAA4B,GAAG,cAAc,CAAC,OAAO,gCAAgC,CAAC,CAAC;AAEnG,OAAO,EACN,iBAAiB,EACjB,eAAe,EACf,yBAAyB,EACzB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,MAAM,+BAA+B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/image-editor/index.ts"],"names":[],"mappings":"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"}
@@ -9,5 +9,7 @@ export { default as LayersPanel } from './LayersPanel.svelte';
9
9
  export { default as LayerEffectsPanel } from './LayerEffectsPanel.svelte';
10
10
  export { default as BrushOptionsBar } from './BrushOptionsBar.svelte';
11
11
  export { default as FreeTransformOptionsBar } from './FreeTransformOptionsBar.svelte';
12
+ export { default as ShapeOptionsBar } from './ShapeOptionsBar.svelte';
13
+ export { default as TypeOptionsBar } from './TypeOptionsBar.svelte';
12
14
  export * from './imageEditorState.svelte.js';
13
15
  export { applyRegionMosaic, applyRegionBlur, resolveMosaicRegionBounds } from '@pixerate/editor/image-editor';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-svelte",
3
- "version": "0.80.0",
3
+ "version": "0.81.0",
4
4
  "description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -139,7 +139,7 @@
139
139
  "@tiptap/extension-bubble-menu": "^2.11.5",
140
140
  "@tiptap/extension-placeholder": "^2.11.5",
141
141
  "@tiptap/starter-kit": "^2.11.5",
142
- "@pixerate/editor": "0.110.0"
142
+ "@pixerate/editor": "0.112.0"
143
143
  },
144
144
  "peerDependencies": {
145
145
  "@dagrejs/dagre": "^1.1.4",