@pixerate/editor-svelte 0.184.0 → 0.186.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.
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import StickerPicker from './StickerPicker.svelte';
2
3
  import {
3
4
  BLEND_MENU_GROUPS,
4
5
  BRUSH_ROTATIONS,
@@ -29,7 +30,7 @@
29
30
  toggleDotTexture,
30
31
  STAMP_KINDS,
31
32
  STAMP_LABELS,
32
- toggleStamp,
33
+ toggleStickerRef,
33
34
  type ParticleKind,
34
35
  type ParticleRender,
35
36
  type RibbonPairs,
@@ -198,15 +199,7 @@
198
199
  {/if}
199
200
  {/if}
200
201
  {#if engine.stickerStamps}
201
- <div class="flex flex-wrap gap-1" role="group" aria-label="Sticker stamps">
202
- {#each STAMP_KINDS as k (k)}
203
- <button
204
- type="button"
205
- aria-pressed={brush.sticker.stamps.includes(k)}
206
- class={`px-1.5 py-0.5 rounded text-[10px] ${brush.sticker.stamps.includes(k) ? 'bg-indigo-600 text-white' : 'bg-slate-800 text-slate-300'}`}
207
- onclick={() => controller.setBrush({ sticker: { stamps: toggleStamp(brush.sticker.stamps, k) } })}>{STAMP_LABELS[k]}</button>
208
- {/each}
209
- </div>
202
+ <StickerPicker {controller} label="Sticker stamps" selected={brush.sticker.stamps} onpick={(ref) => controller.setBrush({ sticker: { stamps: toggleStickerRef(brush.sticker.stamps, ref) } })} />
210
203
  {/if}
211
204
  {#if engine.impastoTool}
212
205
  <label class="flex items-center justify-between gap-2">
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
- import { STAMP_KINDS, STAMP_LABELS, STICKER_SLIDERS, readPath, type PaintEditorController, type StampKind } from '@pixerate/editor/paint-editor';
2
+ import { STICKER_SLIDERS, readPath, stickerRefLabel, type PaintEditorController } from '@pixerate/editor/paint-editor';
3
+ import StickerPicker from './StickerPicker.svelte';
3
4
  import PaintSlider from './PaintSlider.svelte';
4
5
  import { attempt, button, paintEditorState, panel } from './paintEditorRune.svelte.js';
5
6
 
@@ -23,17 +24,25 @@
23
24
  <span class="font-medium text-slate-300">Stickers ({stickers.length})</span>
24
25
  <div class="flex max-h-24 flex-wrap gap-1 overflow-y-auto" role="listbox" aria-label="Stickers">
25
26
  {#each stickers as s (s.id)}
26
- <button type="button" role="option" aria-selected={s.id === picked} title={`${STAMP_LABELS[s.stamp]} ${s.id}`} class={`h-5 w-5 rounded ring-1 ${s.id === picked ? 'ring-indigo-400' : 'ring-slate-700'}`} style={`background: ${s.color}`} onclick={() => controller.selectSticker(s.id)}></button>
27
+ <button type="button" role="option" aria-selected={s.id === picked} title={`${stickerRefLabel(s.ref)} ${s.id}`} class={`h-5 w-5 rounded ring-1 ${s.id === picked ? 'ring-indigo-400' : 'ring-slate-700'}`} style={`background: ${s.color}`} onclick={() => controller.selectSticker(s.id)}></button>
27
28
  {/each}
28
29
  </div>
29
30
  {#if sel}
30
31
  <div class="flex flex-col gap-1.5" data-testid="sticker-edit">
31
- <label class="flex items-center justify-between gap-2">
32
- <span class="text-slate-400">Stamp</span>
33
- <select aria-label="Sticker stamp" value={sel.stamp} onchange={(e) => { const stamp = e.currentTarget.value as StampKind; run(() => controller.updateSticker(layerId, sel.id, { stamp })); }} class="rounded bg-slate-800 px-1 py-0.5">
34
- {#each STAMP_KINDS as k (k)}<option value={k}>{STAMP_LABELS[k]}</option>{/each}
35
- </select>
36
- </label>
32
+ <span class="text-slate-400">Sticker</span>
33
+ <!-- The layer is redrawn, so the new sticker's art (and every other's) loads first. -->
34
+ <StickerPicker
35
+ {controller}
36
+ label="Sticker art"
37
+ selected={[sel.ref]}
38
+ onpick={(ref) => {
39
+ const id = sel.id;
40
+ void controller
41
+ .prepareStickers([ref, ...stickers.map((x) => x.ref)])
42
+ .then(() => run(() => controller.updateSticker(layerId, id, { ref })))
43
+ .catch((e: unknown) => run(() => { throw e; }));
44
+ }}
45
+ />
37
46
  <div class="flex items-center justify-between gap-2">
38
47
  <span class="text-slate-400">Position</span>
39
48
  <span class="flex gap-1">
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import type { PaintEditorController, StickerProvider } from '@pixerate/editor/paint-editor';
3
+ import StickerLibraryPicker from '../stickers/StickerLibraryPicker.svelte';
4
+
5
+ interface Props {
6
+ controller: PaintEditorController;
7
+ /** Refs shown as chosen. */
8
+ selected: readonly string[];
9
+ /** Called with a sticker's ref when it is clicked. */
10
+ onpick: (ref: string) => void;
11
+ /** Accessible name of the sticker list (default "Stickers"). */
12
+ label?: string;
13
+ }
14
+ let { controller, selected, onpick, label = 'Stickers' }: Props = $props();
15
+ // The library set later (setStickersProvider) replaces this one; null until the effect runs.
16
+ let changed = $state<StickerProvider | null>(null);
17
+ $effect(() => {
18
+ changed = null;
19
+ return controller.stickers.subscribe((p) => (changed = p));
20
+ });
21
+ const provider = $derived(changed ?? controller.stickersProvider);
22
+ const failed = $derived(selected.map((ref) => ({ ref, ...controller.stickerArtStatus(ref) })).filter((x) => x.status === 'failed'));
23
+ </script>
24
+
25
+ <!-- Stickers from the paint editor's sticker library (followed when it changes); clicking one calls onpick with its ref. Chosen stickers whose art failed to load say why. -->
26
+ <StickerLibraryPicker {provider} {selected} {label} onpick={(it) => onpick(it.ref)}>
27
+ {#snippet footer()}
28
+ {#each failed as f (f.ref)}<p role="alert" class="text-[11px] text-rose-400">{f.ref}: {f.error}</p>{/each}
29
+ {/snippet}
30
+ </StickerLibraryPicker>
@@ -0,0 +1,13 @@
1
+ import type { PaintEditorController } from '@pixerate/editor/paint-editor';
2
+ interface Props {
3
+ controller: PaintEditorController;
4
+ /** Refs shown as chosen. */
5
+ selected: readonly string[];
6
+ /** Called with a sticker's ref when it is clicked. */
7
+ onpick: (ref: string) => void;
8
+ /** Accessible name of the sticker list (default "Stickers"). */
9
+ label?: string;
10
+ }
11
+ declare const StickerPicker: import("svelte").Component<Props, {}, "">;
12
+ type StickerPicker = ReturnType<typeof StickerPicker>;
13
+ export default StickerPicker;
@@ -8,6 +8,7 @@ import type ColorPanelComponent from './ColorPanel.svelte';
8
8
  import type PaintLayersPanelComponent from './PaintLayersPanel.svelte';
9
9
  import type WetPanelComponent from './WetPanel.svelte';
10
10
  import type StickerPanelComponent from './StickerPanel.svelte';
11
+ import type StickerPickerComponent from './StickerPicker.svelte';
11
12
  import type StrokePanelComponent from './StrokePanel.svelte';
12
13
  import type PaintEffectsPanelComponent from './PaintEffectsPanel.svelte';
13
14
  import type PaintGradientEditorComponent from './PaintGradientEditor.svelte';
@@ -20,6 +21,7 @@ export { default as ColorPanel } from './ColorPanel.svelte';
20
21
  export { default as PaintLayersPanel } from './PaintLayersPanel.svelte';
21
22
  export { default as WetPanel } from './WetPanel.svelte';
22
23
  export { default as StickerPanel } from './StickerPanel.svelte';
24
+ export { default as StickerPicker } from './StickerPicker.svelte';
23
25
  export { default as StrokePanel } from './StrokePanel.svelte';
24
26
  export { default as PaintEffectsPanel } from './PaintEffectsPanel.svelte';
25
27
  export { default as PaintGradientEditor } from './PaintGradientEditor.svelte';
@@ -35,6 +37,7 @@ export type ColorPanelProps = ComponentProps<typeof ColorPanelComponent>;
35
37
  export type PaintLayersPanelProps = ComponentProps<typeof PaintLayersPanelComponent>;
36
38
  export type WetPanelProps = ComponentProps<typeof WetPanelComponent>;
37
39
  export type StickerPanelProps = ComponentProps<typeof StickerPanelComponent>;
40
+ export type StickerPickerProps = ComponentProps<typeof StickerPickerComponent>;
38
41
  export type StrokePanelProps = ComponentProps<typeof StrokePanelComponent>;
39
42
  export type PaintEffectsPanelProps = ComponentProps<typeof PaintEffectsPanelComponent>;
40
43
  export type PaintGradientEditorProps = ComponentProps<typeof PaintGradientEditorComponent>;
@@ -7,6 +7,7 @@ export { default as ColorPanel } from './ColorPanel.svelte';
7
7
  export { default as PaintLayersPanel } from './PaintLayersPanel.svelte';
8
8
  export { default as WetPanel } from './WetPanel.svelte';
9
9
  export { default as StickerPanel } from './StickerPanel.svelte';
10
+ export { default as StickerPicker } from './StickerPicker.svelte';
10
11
  export { default as StrokePanel } from './StrokePanel.svelte';
11
12
  export { default as PaintEffectsPanel } from './PaintEffectsPanel.svelte';
12
13
  export { default as PaintGradientEditor } from './PaintGradientEditor.svelte';
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ import { insertSticker, type StickerInsertHost, type StickerProvider } from '@pixerate/editor/stickers';
3
+ import StickerLibraryPicker from './StickerLibraryPicker.svelte';
4
+
5
+ interface Props {
6
+ provider: StickerProvider;
7
+ /** Where stickers go: `createVectorStickerHost`, `createImageStickerHost`, `createCanvasStickerHost` or your own. */
8
+ host: StickerInsertHost;
9
+ /** Called with what was inserted (its id, ref, size and licence). */
10
+ oninserted?: (result: Awaited<ReturnType<typeof insertSticker>>) => void;
11
+ class?: string;
12
+ }
13
+ let { provider, host, oninserted, class: className = '' }: Props = $props();
14
+ let tint = $state<string | null>(null);
15
+ let error = $state<string | null>(null);
16
+ let busy = $state(false);
17
+ </script>
18
+
19
+ <!-- Pick a sticker to insert it into an editor (one undo step), centred at its natural size, tinted when asked: the panel counterpart of insert_sticker. -->
20
+ <div class={`flex flex-col gap-1.5 text-xs ${className}`} data-testid="sticker-insert">
21
+ <StickerLibraryPicker
22
+ {provider}
23
+ label="Insert sticker"
24
+ onpick={(it) => {
25
+ busy = true;
26
+ error = null;
27
+ insertSticker(provider, host, { ref: it.ref, ...(tint && it.tintable ? { color: tint } : {}) })
28
+ .then((r) => oninserted?.(r))
29
+ .catch((e: unknown) => (error = e instanceof Error ? e.message : String(e)))
30
+ .finally(() => (busy = false));
31
+ }}
32
+ />
33
+ <label class="flex items-center gap-2">
34
+ <input type="checkbox" aria-label="Tint stickers" checked={tint !== null} onchange={(e) => (tint = e.currentTarget.checked ? '#2563eb' : null)} /> Tint
35
+ {#if tint !== null}<input type="color" aria-label="Sticker tint" value={tint} oninput={(e) => (tint = e.currentTarget.value)} />{/if}
36
+ {#if busy}<span class="text-slate-500">Inserting…</span>{/if}
37
+ </label>
38
+ {#if error}<p role="alert" class="text-[11px] text-rose-400">{error}</p>{/if}
39
+ </div>
@@ -0,0 +1,12 @@
1
+ import { insertSticker, type StickerInsertHost, type StickerProvider } from '@pixerate/editor/stickers';
2
+ interface Props {
3
+ provider: StickerProvider;
4
+ /** Where stickers go: `createVectorStickerHost`, `createImageStickerHost`, `createCanvasStickerHost` or your own. */
5
+ host: StickerInsertHost;
6
+ /** Called with what was inserted (its id, ref, size and licence). */
7
+ oninserted?: (result: Awaited<ReturnType<typeof insertSticker>>) => void;
8
+ class?: string;
9
+ }
10
+ declare const StickerInsertPanel: import("svelte").Component<Props, {}, "">;
11
+ type StickerInsertPanel = ReturnType<typeof StickerInsertPanel>;
12
+ export default StickerInsertPanel;
@@ -0,0 +1,62 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { StickerBrowser, type StickerBrowserItem, type StickerBrowserState, type StickerProvider } from '@pixerate/editor/stickers';
4
+
5
+ interface Props {
6
+ /** The sticker library (e.g. `editorStickers()`, a host's, or `combineStickerProviders([...])`). */
7
+ provider: StickerProvider;
8
+ /** Refs shown as chosen. */
9
+ selected?: readonly string[];
10
+ /** Called with a sticker when it is clicked. */
11
+ onpick: (sticker: StickerBrowserItem) => void;
12
+ /** Accessible name of the sticker list (default "Stickers"). */
13
+ label?: string;
14
+ /** Shown under the list (e.g. load errors). */
15
+ footer?: Snippet;
16
+ class?: string;
17
+ }
18
+ let { provider, selected = [], onpick, label = 'Stickers', footer, class: className = '' }: Props = $props();
19
+ const browser = $derived(new StickerBrowser(() => provider));
20
+ let s = $state<StickerBrowserState | null>(null);
21
+ $effect(() => {
22
+ const b = browser;
23
+ s = b.state;
24
+ const off = b.subscribe(() => (s = b.state));
25
+ void b.refresh();
26
+ return off;
27
+ });
28
+ </script>
29
+
30
+ <!-- Stickers from a sticker library: searchable, by category and a page at a time, each sticker's licence in its tooltip; clicking one calls onpick. -->
31
+ <div class={`flex flex-col gap-1 ${className}`} data-testid="sticker-picker">
32
+ {#if s}
33
+ <div class="flex items-center gap-1">
34
+ <input type="search" aria-label={`Search ${label.toLowerCase()}`} placeholder={`Search ${s.provider}`} value={s.query} oninput={(e) => void browser.setQuery(e.currentTarget.value)} class="min-w-0 flex-1 rounded bg-slate-800 px-1 py-0.5" />
35
+ {#if s.categories.length > 1}
36
+ <select aria-label="Sticker category" value={s.category} onchange={(e) => void browser.setCategory(e.currentTarget.value)} class="rounded bg-slate-800 px-1 py-0.5">
37
+ <option value="">All</option>
38
+ {#each s.categories as c (c.id)}<option value={c.id}>{c.name}</option>{/each}
39
+ </select>
40
+ {/if}
41
+ </div>
42
+ <div class="flex max-h-32 flex-wrap gap-1 overflow-y-auto" role="group" aria-label={label}>
43
+ {#each s.items as it (it.ref)}
44
+ <button
45
+ type="button"
46
+ aria-pressed={selected.includes(it.ref)}
47
+ title={`${it.name} (${it.ref}) · ${it.licence.name}${it.licence.attribution ? ` · ${it.licence.attribution}` : ''}`}
48
+ class={`flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] ${selected.includes(it.ref) ? 'bg-indigo-600 text-white' : 'bg-slate-800 text-slate-300'}`}
49
+ onclick={() => onpick(it)}
50
+ >
51
+ {#if it.thumbnail}<img src={it.thumbnail} alt="" class="h-4 w-4" />{/if}{it.name}
52
+ </button>
53
+ {/each}
54
+ </div>
55
+ <div class="flex items-center justify-between text-[10px] text-slate-500">
56
+ <span>{s.loading ? 'Loading…' : `${s.items.length} of ${s.total}`}</span>
57
+ {#if s.hasMore}<button type="button" class="text-indigo-300" onclick={() => void browser.more()}>More</button>{/if}
58
+ </div>
59
+ {#if s.error}<p role="alert" class="text-[11px] text-rose-400">{s.error}</p>{/if}
60
+ {/if}
61
+ {@render footer?.()}
62
+ </div>
@@ -0,0 +1,18 @@
1
+ import type { Snippet } from 'svelte';
2
+ import { type StickerBrowserItem, type StickerProvider } from '@pixerate/editor/stickers';
3
+ interface Props {
4
+ /** The sticker library (e.g. `editorStickers()`, a host's, or `combineStickerProviders([...])`). */
5
+ provider: StickerProvider;
6
+ /** Refs shown as chosen. */
7
+ selected?: readonly string[];
8
+ /** Called with a sticker when it is clicked. */
9
+ onpick: (sticker: StickerBrowserItem) => void;
10
+ /** Accessible name of the sticker list (default "Stickers"). */
11
+ label?: string;
12
+ /** Shown under the list (e.g. load errors). */
13
+ footer?: Snippet;
14
+ class?: string;
15
+ }
16
+ declare const StickerLibraryPicker: import("svelte").Component<Props, {}, "">;
17
+ type StickerLibraryPicker = ReturnType<typeof StickerLibraryPicker>;
18
+ export default StickerLibraryPicker;
@@ -0,0 +1,7 @@
1
+ import type { ComponentProps } from 'svelte';
2
+ import type StickerLibraryPickerComponent from './StickerLibraryPicker.svelte';
3
+ import type StickerInsertPanelComponent from './StickerInsertPanel.svelte';
4
+ export { default as StickerLibraryPicker } from './StickerLibraryPicker.svelte';
5
+ export { default as StickerInsertPanel } from './StickerInsertPanel.svelte';
6
+ export type StickerLibraryPickerProps = ComponentProps<typeof StickerLibraryPickerComponent>;
7
+ export type StickerInsertPanelProps = ComponentProps<typeof StickerInsertPanelComponent>;
@@ -0,0 +1,2 @@
1
+ export { default as StickerLibraryPicker } from './StickerLibraryPicker.svelte';
2
+ export { default as StickerInsertPanel } from './StickerInsertPanel.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-svelte",
3
- "version": "0.184.0",
3
+ "version": "0.186.0",
4
4
  "description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -23,6 +23,11 @@
23
23
  "svelte": "./dist/effects/index.js",
24
24
  "default": "./dist/effects/index.js"
25
25
  },
26
+ "./stickers": {
27
+ "types": "./dist/stickers/index.d.ts",
28
+ "svelte": "./dist/stickers/index.js",
29
+ "default": "./dist/stickers/index.js"
30
+ },
26
31
  "./spreadsheet": {
27
32
  "types": "./dist/spreadsheet/index.d.ts",
28
33
  "svelte": "./dist/spreadsheet/index.js",
@@ -165,7 +170,7 @@
165
170
  "**/*.css"
166
171
  ],
167
172
  "dependencies": {
168
- "@pixerate/editor": "0.248.0"
173
+ "@pixerate/editor": "0.250.0"
169
174
  },
170
175
  "peerDependencies": {
171
176
  "@dagrejs/dagre": "^1.1.4",