@pixerate/editor-svelte 0.185.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,5 +1,6 @@
1
1
  <script lang="ts">
2
- import { StickerBrowser, type PaintEditorController } from '@pixerate/editor/paint-editor';
2
+ import type { PaintEditorController, StickerProvider } from '@pixerate/editor/paint-editor';
3
+ import StickerLibraryPicker from '../stickers/StickerLibraryPicker.svelte';
3
4
 
4
5
  interface Props {
5
6
  controller: PaintEditorController;
@@ -11,50 +12,19 @@
11
12
  label?: string;
12
13
  }
13
14
  let { controller, selected, onpick, label = 'Stickers' }: Props = $props();
14
- const browser = $derived(new StickerBrowser(() => controller.stickersProvider));
15
- let s = $state(new StickerBrowser(() => controller.stickersProvider).state);
15
+ // The library set later (setStickersProvider) replaces this one; null until the effect runs.
16
+ let changed = $state<StickerProvider | null>(null);
16
17
  $effect(() => {
17
- const b = browser;
18
- s = b.state;
19
- const off = b.subscribe(() => (s = b.state));
20
- void b.refresh();
21
- const offProvider = controller.stickers.subscribe(() => void b.refresh());
22
- return () => {
23
- off();
24
- offProvider();
25
- };
18
+ changed = null;
19
+ return controller.stickers.subscribe((p) => (changed = p));
26
20
  });
21
+ const provider = $derived(changed ?? controller.stickersProvider);
27
22
  const failed = $derived(selected.map((ref) => ({ ref, ...controller.stickerArtStatus(ref) })).filter((x) => x.status === 'failed'));
28
23
  </script>
29
24
 
30
- <!-- Stickers from the paint editor's sticker library, searchable, by category and a page at a time; clicking one calls onpick with its ref. -->
31
- <div class="flex flex-col gap-1" data-testid="sticker-picker">
32
- <div class="flex items-center gap-1">
33
- <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" />
34
- {#if s.categories.length > 1}
35
- <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">
36
- <option value="">All</option>
37
- {#each s.categories as c (c.id)}<option value={c.id}>{c.name}</option>{/each}
38
- </select>
39
- {/if}
40
- </div>
41
- <div class="flex max-h-32 flex-wrap gap-1 overflow-y-auto" role="group" aria-label={label}>
42
- {#each s.items as it (it.ref)}
43
- <button
44
- type="button"
45
- aria-pressed={selected.includes(it.ref)}
46
- title={`${it.name} (${it.ref}) · ${it.licence.name}`}
47
- 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'}`}
48
- onclick={() => onpick(it.ref)}
49
- >
50
- {#if it.thumbnail}<img src={it.thumbnail} alt="" class="h-4 w-4" />{/if}{it.name}
51
- </button>
52
- {/each}
53
- </div>
54
- <div class="flex items-center justify-between text-[10px] text-slate-500">
55
- <span>{s.loading ? 'Loading…' : `${s.items.length} of ${s.total}`}</span>
56
- {#if s.hasMore}<button type="button" class="text-indigo-300" onclick={() => void browser.more()}>More</button>{/if}
57
- </div>
58
- {#if s.error}<p role="alert" class="text-[11px] text-rose-400">{s.error}</p>{/if}
59
- {#each failed as f (f.ref)}<p role="alert" class="text-[11px] text-rose-400">{f.ref}: {f.error}</p>{/each}
60
- </div>
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>
@@ -1,4 +1,4 @@
1
- import { type PaintEditorController } from '@pixerate/editor/paint-editor';
1
+ import type { PaintEditorController } from '@pixerate/editor/paint-editor';
2
2
  interface Props {
3
3
  controller: PaintEditorController;
4
4
  /** Refs shown as chosen. */
@@ -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.185.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.249.0"
173
+ "@pixerate/editor": "0.250.0"
169
174
  },
170
175
  "peerDependencies": {
171
176
  "@dagrejs/dagre": "^1.1.4",