@pixerate/editor-svelte 0.81.0 → 0.83.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 (42) hide show
  1. package/dist/image-editor/BrushOptionsBar.svelte +59 -4
  2. package/dist/image-editor/BrushOptionsBar.svelte.d.ts.map +1 -1
  3. package/dist/image-editor/ColorProfilePanel.svelte +112 -0
  4. package/dist/image-editor/ColorProfilePanel.svelte.d.ts +9 -0
  5. package/dist/image-editor/ColorProfilePanel.svelte.d.ts.map +1 -0
  6. package/dist/image-editor/ImageEditor.svelte +4 -1
  7. package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -1
  8. package/dist/image-editor/LayersPanel.svelte +12 -0
  9. package/dist/image-editor/LayersPanel.svelte.d.ts.map +1 -1
  10. package/dist/image-editor/index.d.ts +3 -0
  11. package/dist/image-editor/index.d.ts.map +1 -1
  12. package/dist/image-editor/index.js +1 -0
  13. package/dist/vector/VectorCanvas.svelte +155 -0
  14. package/dist/vector/VectorCanvas.svelte.d.ts.map +1 -1
  15. package/dist/vector/VectorConnectorPanel.svelte +87 -0
  16. package/dist/vector/VectorConnectorPanel.svelte.d.ts +14 -0
  17. package/dist/vector/VectorConnectorPanel.svelte.d.ts.map +1 -0
  18. package/dist/vector/VectorConstraintsPanel.svelte +113 -0
  19. package/dist/vector/VectorConstraintsPanel.svelte.d.ts +14 -0
  20. package/dist/vector/VectorConstraintsPanel.svelte.d.ts.map +1 -0
  21. package/dist/vector/VectorFramePanel.svelte +133 -0
  22. package/dist/vector/VectorFramePanel.svelte.d.ts +14 -0
  23. package/dist/vector/VectorFramePanel.svelte.d.ts.map +1 -0
  24. package/dist/vector/VectorSceneNodes.svelte +4 -1
  25. package/dist/vector/VectorSceneNodes.svelte.d.ts.map +1 -1
  26. package/dist/vector/VectorStylePanel.svelte +27 -0
  27. package/dist/vector/VectorStylePanel.svelte.d.ts.map +1 -1
  28. package/dist/vector/VectorSymbolsPanel.svelte +93 -0
  29. package/dist/vector/VectorSymbolsPanel.svelte.d.ts +14 -0
  30. package/dist/vector/VectorSymbolsPanel.svelte.d.ts.map +1 -0
  31. package/dist/vector/VectorTextElement.svelte +41 -0
  32. package/dist/vector/VectorTextElement.svelte.d.ts +8 -0
  33. package/dist/vector/VectorTextElement.svelte.d.ts.map +1 -0
  34. package/dist/vector/VectorTextPanel.svelte +170 -0
  35. package/dist/vector/VectorTextPanel.svelte.d.ts +16 -0
  36. package/dist/vector/VectorTextPanel.svelte.d.ts.map +1 -0
  37. package/dist/vector/VectorToolbar.svelte +13 -0
  38. package/dist/vector/VectorToolbar.svelte.d.ts.map +1 -1
  39. package/dist/vector/index.d.ts +6 -0
  40. package/dist/vector/index.d.ts.map +1 -1
  41. package/dist/vector/index.js +6 -0
  42. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VectorConnectorPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorConnectorPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAQH,KAAK,sBAAsB,EAC5B,MAAM,yBAAyB,CAAC;AAIjC;;;;GAIG;AACH,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA2DH,QAAA,MAAM,oBAAoB,2CAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,113 @@
1
+ <script lang="ts">
2
+ import { getVectorConstraintPanel, type VectorConstraintAction, type VectorEditorController } from '@pixerate/editor/vector';
3
+ import { vectorStateOf } from './subscribe.svelte.js';
4
+
5
+ /**
6
+ * Constraints: the degrees of freedom left, the constraints the selection allows (pick anchors
7
+ * with the direct tool, or round paths), Auto Constrain, the constraints of the selection with
8
+ * their values, and the named parameters. Each change is one undo step.
9
+ */
10
+ interface Props {
11
+ controller: VectorEditorController;
12
+ class?: string;
13
+ }
14
+
15
+ let { controller, class: className = '' }: Props = $props();
16
+
17
+ const store = vectorStateOf(() => controller);
18
+ const view = $derived(store.current);
19
+ const panel = $derived(getVectorConstraintPanel(view));
20
+ const shown = $derived(panel.actions.length > 0 || panel.constraints.length > 0 || !!view.document.constraints?.length || view.tool === 'direct');
21
+ let error: string | null = $state(null);
22
+ let paramName = $state('');
23
+ let paramValue = $state('');
24
+ const report = (r: { ok: boolean; message?: string }) => {
25
+ error = r.ok ? null : (r.message ?? null);
26
+ };
27
+ const add = (a: VectorConstraintAction) => report(controller.addConstraint({ kind: a.kind, refs: a.refs }));
28
+ function addParameter(e: SubmitEvent) {
29
+ e.preventDefault();
30
+ if (!paramName.trim()) return;
31
+ const r = controller.setConstraintValue(paramName.trim(), paramValue);
32
+ report(r);
33
+ if (r.ok) {
34
+ paramName = '';
35
+ paramValue = '';
36
+ }
37
+ }
38
+ const blurOnEnter = (e: KeyboardEvent) => {
39
+ if (e.key === 'Enter') (e.currentTarget as HTMLInputElement).blur();
40
+ };
41
+
42
+ const buttonClass =
43
+ 'inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40';
44
+ const inputClass = 'h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs';
45
+ const labelClass = 'w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground';
46
+ </script>
47
+
48
+ {#if shown}
49
+ <div class="space-y-2 {className}" data-vector-constraints-panel="" data-dof-status={panel.dof.status} aria-label="Constraints">
50
+ <div class="flex items-center gap-1">
51
+ <span class={labelClass}>Constraints</span>
52
+ <span class="flex-1 truncate text-[11px] text-muted-foreground" data-vector-dof="">{panel.dof.message}</span>
53
+ <button type="button" class={buttonClass} title="Auto Constrain: add what the selection (or the drawing) nearly satisfies" onclick={() => report(controller.autoConstrain())}>Auto</button>
54
+ </div>
55
+ {#if panel.actions.length}
56
+ <div class="flex flex-wrap gap-1" role="group" aria-label="Add constraint">
57
+ {#each panel.actions as a (a.kind)}
58
+ <button type="button" class={buttonClass} data-vector-constraint-action={a.kind} title="Add {a.label}" onclick={() => add(a)}>{a.label}</button>
59
+ {/each}
60
+ </div>
61
+ {/if}
62
+ {#if panel.constraints.length}
63
+ <ul class="space-y-1">
64
+ {#each panel.constraints as c (c.id)}
65
+ <li data-vector-constraint={c.id} data-satisfied={c.satisfied && !c.conflict ? 'true' : 'false'} class="flex items-center gap-1 text-xs text-foreground">
66
+ <span class="min-w-0 flex-1 truncate {c.conflict || !c.satisfied ? 'text-destructive' : ''}">{c.label}{c.name ? ` ${c.name}` : ''}</span>
67
+ {#if c.value !== undefined}
68
+ {#key `${c.id}:${c.value}`}
69
+ <input
70
+ aria-label="Value of {c.name ?? c.id}"
71
+ class="{inputClass} w-16 flex-none"
72
+ value={c.value}
73
+ onkeydown={blurOnEnter}
74
+ onblur={(e) => {
75
+ const v = (e.currentTarget as HTMLInputElement).value;
76
+ if (v.trim() !== c.value) report(controller.setConstraintValue(c.id, v));
77
+ }}
78
+ />
79
+ {/key}
80
+ {/if}
81
+ <button type="button" class={buttonClass} aria-label="Remove {c.label} constraint" onclick={() => report(controller.removeConstraints([c.id]))}>×</button>
82
+ </li>
83
+ {/each}
84
+ </ul>
85
+ {/if}
86
+ <div class="space-y-1" aria-label="Parameters">
87
+ {#each panel.parameters as p (p.name)}
88
+ <div class="flex items-center gap-1 text-xs">
89
+ <span class="w-12 truncate font-mono">{p.name}</span>
90
+ {#key `${p.name}:${p.value}`}
91
+ <input
92
+ aria-label="Parameter {p.name}"
93
+ class={inputClass}
94
+ value={p.value}
95
+ onkeydown={blurOnEnter}
96
+ onblur={(e) => {
97
+ const v = (e.currentTarget as HTMLInputElement).value;
98
+ if (v.trim() !== p.value) report(controller.setConstraintValue(p.name, v));
99
+ }}
100
+ />
101
+ {/key}
102
+ <span class="w-10 text-right text-muted-foreground">{p.result === null ? '!' : Math.round(p.result * 100) / 100}</span>
103
+ </div>
104
+ {/each}
105
+ <form class="flex items-center gap-1" onsubmit={addParameter}>
106
+ <input aria-label="New parameter name" placeholder="name" class="{inputClass} w-12 flex-none" bind:value={paramName} />
107
+ <input aria-label="New parameter value" placeholder="value" class={inputClass} bind:value={paramValue} />
108
+ <button type="submit" class={buttonClass} aria-label="Add parameter">+</button>
109
+ </form>
110
+ </div>
111
+ {#if error}<p class="text-[11px] text-destructive">{error}</p>{/if}
112
+ </div>
113
+ {/if}
@@ -0,0 +1,14 @@
1
+ import { type VectorEditorController } from '@pixerate/editor/vector';
2
+ /**
3
+ * Constraints: the degrees of freedom left, the constraints the selection allows (pick anchors
4
+ * with the direct tool, or round paths), Auto Constrain, the constraints of the selection with
5
+ * their values, and the named parameters. Each change is one undo step.
6
+ */
7
+ interface Props {
8
+ controller: VectorEditorController;
9
+ class?: string;
10
+ }
11
+ declare const VectorConstraintsPanel: import("svelte").Component<Props, {}, "">;
12
+ type VectorConstraintsPanel = ReturnType<typeof VectorConstraintsPanel>;
13
+ export default VectorConstraintsPanel;
14
+ //# sourceMappingURL=VectorConstraintsPanel.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VectorConstraintsPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorConstraintsPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAyD,KAAK,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAI3H;;;;GAIG;AACH,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgGH,QAAA,MAAM,sBAAsB,2CAAwC,CAAC;AACrE,KAAK,sBAAsB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACxE,eAAe,sBAAsB,CAAC"}
@@ -0,0 +1,133 @@
1
+ <script lang="ts">
2
+ import {
3
+ VECTOR_HORIZONTAL_RESIZING,
4
+ VECTOR_HORIZONTAL_RESIZING_LABELS,
5
+ VECTOR_VERTICAL_RESIZING,
6
+ VECTOR_VERTICAL_RESIZING_LABELS,
7
+ getVectorFramePanel,
8
+ vectorPaintToHex,
9
+ type VectorEditorController,
10
+ type VectorHorizontalResizing,
11
+ type VectorVerticalResizing
12
+ } from '@pixerate/editor/vector';
13
+ import { vectorStateOf } from './subscribe.svelte.js';
14
+
15
+ /**
16
+ * Frames (artboards): Frame Selection; for a selected frame its position, size (its children
17
+ * follow their constraints), background and clipping; for objects inside a frame their resizing
18
+ * constraints. Each change is one undo step (sizes apply on Enter or blur).
19
+ */
20
+ interface Props {
21
+ controller: VectorEditorController;
22
+ class?: string;
23
+ }
24
+
25
+ let { controller, class: className = '' }: Props = $props();
26
+
27
+ const store = vectorStateOf(() => controller);
28
+ const view = $derived(store.current);
29
+ const panel = $derived(getVectorFramePanel(view));
30
+ const f = $derived(panel.frame);
31
+ const ids = $derived(f ? [f.id] : undefined);
32
+ const fillHex = $derived(f ? vectorPaintToHex(f.frame.fill ?? null) : null);
33
+ const hasFill = $derived(!!f && f.frame.fill !== undefined && f.frame.fill !== null);
34
+ const fields = [
35
+ { key: 'x', label: 'X' },
36
+ { key: 'y', label: 'Y' },
37
+ { key: 'width', label: 'W' },
38
+ { key: 'height', label: 'H' }
39
+ ] as const;
40
+
41
+ const buttonClass =
42
+ 'inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40';
43
+ const selectClass = 'h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs';
44
+ const numberClass = 'h-7 w-16 rounded border border-border bg-background px-1.5 text-xs';
45
+ </script>
46
+
47
+ <div class="space-y-2 {className}" data-vector-frame-panel="" aria-label="Frame">
48
+ <div class="flex flex-wrap items-center gap-1">
49
+ <span class="w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">Frame</span>
50
+ <button
51
+ type="button"
52
+ data-vector-frame-action="frame-selection"
53
+ class={buttonClass}
54
+ title="Frame Selection: put the selection in a frame fitted to it"
55
+ aria-label="Frame Selection"
56
+ disabled={!view.selection.length}
57
+ onclick={() => controller.frameSelection()}
58
+ >
59
+ Frame selection
60
+ </button>
61
+ </div>
62
+ {#if f}
63
+ <div class="space-y-2" data-vector-frame={f.id}>
64
+ <div class="grid grid-cols-2 gap-1">
65
+ {#each fields as { key, label } (key)}
66
+ <label class="flex items-center gap-1 text-xs text-foreground">
67
+ <span class="w-4 text-muted-foreground">{label}</span>
68
+ <input
69
+ type="number"
70
+ step="1"
71
+ min={key === 'width' || key === 'height' ? 1 : undefined}
72
+ aria-label="Frame {key}"
73
+ class={numberClass}
74
+ value={Math.round(f.frame[key] * 100) / 100}
75
+ onchange={(e) => {
76
+ // On commit (Enter or blur): each resize lays the children out again, so the digits typed on the way must not.
77
+ const n = parseFloat(e.currentTarget.value);
78
+ if (Number.isFinite(n) && (key === 'x' || key === 'y' || n > 0)) controller.setFrame({ [key]: n }, { ids });
79
+ }}
80
+ />
81
+ </label>
82
+ {/each}
83
+ </div>
84
+ <div class="flex items-center gap-2">
85
+ <label class="flex w-16 items-center gap-1.5 text-xs text-foreground">
86
+ <input type="checkbox" aria-label="Frame fill on" checked={hasFill} onchange={(e) => controller.setFrame({ fill: e.currentTarget.checked ? (fillHex ?? '#ffffff') : null }, { ids })} />
87
+ Fill
88
+ </label>
89
+ <input
90
+ type="color"
91
+ aria-label="Frame fill colour"
92
+ class="h-7 w-10 cursor-pointer rounded border border-border bg-background disabled:opacity-40"
93
+ disabled={!hasFill}
94
+ value={fillHex ?? '#ffffff'}
95
+ oninput={(e) => controller.setFrame({ fill: e.currentTarget.value }, { ids, merge: 'frame-fill' })}
96
+ />
97
+ <label class="flex items-center gap-1 text-xs text-foreground" title="Hide what lies outside the frame">
98
+ <input type="checkbox" aria-label="Clip content" checked={f.frame.clip !== false} onchange={(e) => controller.setFrame({ clip: e.currentTarget.checked }, { ids })} />
99
+ Clip
100
+ </label>
101
+ </div>
102
+ </div>
103
+ {/if}
104
+ {#if panel.resizing}
105
+ {@const resizing = panel.resizing}
106
+ <div class="space-y-1" data-vector-resizing="">
107
+ <label class="flex items-center gap-2 text-xs text-foreground">
108
+ <span class="w-16">Horizontal</span>
109
+ <select
110
+ aria-label="Horizontal constraint"
111
+ class={selectClass}
112
+ value={resizing.horizontal}
113
+ onchange={(e) => controller.setResizing({ horizontal: e.currentTarget.value as VectorHorizontalResizing }, { ids: resizing.ids })}
114
+ >
115
+ {#if resizing.horizontal === 'mixed'}<option value="mixed" disabled>Mixed</option>{/if}
116
+ {#each VECTOR_HORIZONTAL_RESIZING as h (h)}<option value={h}>{VECTOR_HORIZONTAL_RESIZING_LABELS[h]}</option>{/each}
117
+ </select>
118
+ </label>
119
+ <label class="flex items-center gap-2 text-xs text-foreground">
120
+ <span class="w-16">Vertical</span>
121
+ <select
122
+ aria-label="Vertical constraint"
123
+ class={selectClass}
124
+ value={resizing.vertical}
125
+ onchange={(e) => controller.setResizing({ vertical: e.currentTarget.value as VectorVerticalResizing }, { ids: resizing.ids })}
126
+ >
127
+ {#if resizing.vertical === 'mixed'}<option value="mixed" disabled>Mixed</option>{/if}
128
+ {#each VECTOR_VERTICAL_RESIZING as v (v)}<option value={v}>{VECTOR_VERTICAL_RESIZING_LABELS[v]}</option>{/each}
129
+ </select>
130
+ </label>
131
+ </div>
132
+ {/if}
133
+ </div>
@@ -0,0 +1,14 @@
1
+ import { type VectorEditorController } from '@pixerate/editor/vector';
2
+ /**
3
+ * Frames (artboards): Frame Selection; for a selected frame its position, size (its children
4
+ * follow their constraints), background and clipping; for objects inside a frame their resizing
5
+ * constraints. Each change is one undo step (sizes apply on Enter or blur).
6
+ */
7
+ interface Props {
8
+ controller: VectorEditorController;
9
+ class?: string;
10
+ }
11
+ declare const VectorFramePanel: import("svelte").Component<Props, {}, "">;
12
+ type VectorFramePanel = ReturnType<typeof VectorFramePanel>;
13
+ export default VectorFramePanel;
14
+ //# sourceMappingURL=VectorFramePanel.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VectorFramePanel.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorFramePanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAOH,KAAK,sBAAsB,EAG5B,MAAM,yBAAyB,CAAC;AAIjC;;;;GAIG;AACH,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqFH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -3,6 +3,7 @@
3
3
  import Self from './VectorSceneNodes.svelte';
4
4
  import VectorPaintPath from './VectorPaintPath.svelte';
5
5
  import VectorMaskedNodes from './VectorMaskedNodes.svelte';
6
+ import VectorTextElement from './VectorTextElement.svelte';
6
7
 
7
8
  interface Props {
8
9
  nodes: readonly VectorRenderNode[];
@@ -13,7 +14,9 @@
13
14
  </script>
14
15
 
15
16
  {#each nodes as node (node.id)}
16
- {#if node.kind === 'group'}
17
+ {#if node.kind === 'text'}
18
+ <VectorTextElement {node} />
19
+ {:else if node.kind === 'group'}
17
20
  <g
18
21
  data-vector-id={node.id}
19
22
  data-vector-mask={node.mask?.type}
@@ -1 +1 @@
1
- {"version":3,"file":"VectorSceneNodes.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorSceneNodes.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAM9D,UAAU,KAAK;IACb,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACpC;AAmCH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"VectorSceneNodes.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorSceneNodes.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAO9D,UAAU,KAAK;IACb,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACpC;AAsCH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -21,6 +21,11 @@
21
21
  import VectorAppearancePanel from './VectorAppearancePanel.svelte';
22
22
  import VectorEnvelopePanel from './VectorEnvelopePanel.svelte';
23
23
  import VectorMaskPanel from './VectorMaskPanel.svelte';
24
+ import VectorTextPanel from './VectorTextPanel.svelte';
25
+ import VectorSymbolsPanel from './VectorSymbolsPanel.svelte';
26
+ import VectorConstraintsPanel from './VectorConstraintsPanel.svelte';
27
+ import VectorFramePanel from './VectorFramePanel.svelte';
28
+ import VectorConnectorPanel from './VectorConnectorPanel.svelte';
24
29
  import VectorGradientEditor from './VectorGradientEditor.svelte';
25
30
 
26
31
  interface Props {
@@ -50,6 +55,7 @@
50
55
  widthProfile: widthProfilePresetId(d.strokeWidthProfile),
51
56
  cornerRadius: d.cornerRadius,
52
57
  cornerKind: d.cornerKind ?? 'round',
58
+ cornerSmoothing: 0,
53
59
  corners: 1,
54
60
  hasOpenPath: true
55
61
  };
@@ -209,6 +215,23 @@
209
215
  {#if style?.mixed.cornerRadius}<span class="text-[11px] text-muted-foreground">Mixed</span>{/if}
210
216
  </div>
211
217
  {/if}
218
+ {#if style && source.corners > 0 && source.cornerKind === 'round' && source.cornerRadius > 0}
219
+ <label class="flex items-center gap-2 text-xs text-foreground" data-vector-style="corner-smoothing" title="Corner smoothing: squircle corners that ease into the curve (0.6 is iOS-like)">
220
+ <span class="w-16">Smoothing</span>
221
+ <input
222
+ type="range"
223
+ min="0"
224
+ max="1"
225
+ step="0.05"
226
+ aria-label="Corner smoothing"
227
+ class="h-4 flex-1 accent-primary"
228
+ value={source.cornerSmoothing}
229
+ oninput={(e) => controller.setCornerSmoothing(parseFloat(e.currentTarget.value), { merge: 'corner-smoothing' })}
230
+ onpointerup={() => controller.endMergedEdit()}
231
+ />
232
+ </label>
233
+ {/if}
234
+ <VectorTextPanel {controller} />
212
235
  {#if style}
213
236
  <label class="flex items-center gap-2 text-xs text-foreground">
214
237
  <span class="w-16">Opacity</span>
@@ -228,4 +251,8 @@
228
251
  <VectorEnvelopePanel {controller} />
229
252
  <VectorMaskPanel {controller} />
230
253
  {/if}
254
+ <VectorFramePanel {controller} />
255
+ <VectorConnectorPanel {controller} />
256
+ <VectorConstraintsPanel {controller} />
257
+ <VectorSymbolsPanel {controller} />
231
258
  </div>
@@ -1 +1 @@
1
- {"version":3,"file":"VectorStylePanel.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorStylePanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAYH,KAAK,sBAAsB,EAG5B,MAAM,yBAAyB,CAAC;AAUjC,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmKH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"VectorStylePanel.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorStylePanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAYH,KAAK,sBAAsB,EAG5B,MAAM,yBAAyB,CAAC;AAejC,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoLH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,93 @@
1
+ <script lang="ts">
2
+ import { getVectorSymbolsPanel, vectorPaintToHex, type VectorEditorController } from '@pixerate/editor/vector';
3
+ import VectorGradientDefs from './VectorGradientDefs.svelte';
4
+ import VectorFilterDefs from './VectorFilterDefs.svelte';
5
+ import VectorSceneNodes from './VectorSceneNodes.svelte';
6
+ import { vectorStateOf } from './subscribe.svelte.js';
7
+
8
+ /**
9
+ * Symbols of the document with previews: New Symbol from the selection, Place, Edit (isolation
10
+ * mode) and Delete per symbol; for a selected instance, Replace, Scale strokes, a fill tint and
11
+ * Detach. Each change is one undo step.
12
+ */
13
+ interface Props {
14
+ controller: VectorEditorController;
15
+ class?: string;
16
+ }
17
+
18
+ let { controller, class: className = '' }: Props = $props();
19
+
20
+ const prefix = `vsym${Math.random().toString(36).slice(2, 8)}-`;
21
+ const store = vectorStateOf(() => controller);
22
+ const view = $derived(store.current);
23
+ const panel = $derived(getVectorSymbolsPanel(view, { idPrefix: prefix }));
24
+ const inst = $derived(panel.instance);
25
+ let error: string | null = $state(null);
26
+ const report = (r: { ok: boolean; message?: string }) => {
27
+ error = r.ok ? null : (r.message ?? null);
28
+ };
29
+
30
+ const buttonClass =
31
+ 'inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground';
32
+ const selectClass = 'h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs';
33
+ const labelClass = 'w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground';
34
+ </script>
35
+
36
+ <div class="space-y-2 {className}" data-vector-symbols-panel="" aria-label="Symbols">
37
+ <div class="flex items-center gap-1">
38
+ <span class={labelClass}>Symbols</span>
39
+ <button type="button" class={buttonClass} data-vector-symbol-action="make" disabled={!panel.canMake} title="New Symbol from the selection (F8)" onclick={() => report(controller.makeSymbol())}>New</button>
40
+ {#if panel.editing}
41
+ <button type="button" class={buttonClass} data-vector-symbol-action="exit" title="Leave the symbol (Escape)" onclick={() => controller.exitSymbolEdit()}>Done</button>
42
+ {/if}
43
+ </div>
44
+ {#if panel.symbols.length}
45
+ <ul class="space-y-1">
46
+ {#each panel.symbols as s (s.id)}
47
+ <li data-vector-symbol={s.id} data-editing={s.editing || undefined} class="flex items-center gap-1">
48
+ <svg viewBox={s.viewBox} width="28" height="28" class="shrink-0 rounded border border-border bg-background" aria-hidden="true">
49
+ <VectorGradientDefs defs={s.scene.defs} />
50
+ <VectorFilterDefs filters={s.scene.filters} />
51
+ <VectorSceneNodes nodes={s.scene.nodes} />
52
+ </svg>
53
+ <span class="min-w-0 flex-1 truncate text-xs text-foreground" title="{s.name} · {s.instances} instance{s.instances === 1 ? '' : 's'}">{s.name} <span class="text-muted-foreground">· {s.instances}</span></span>
54
+ <button type="button" class={buttonClass} aria-label="Place {s.name}" title="Place an instance" onclick={() => report(controller.placeSymbol(s.id))}>+</button>
55
+ <button type="button" class={buttonClass} aria-label="Edit {s.name}" aria-pressed={s.editing} title="Edit Symbol (isolation mode)" onclick={() => report(controller.editSymbol(s.id))}>✎</button>
56
+ <button type="button" class={buttonClass} aria-label="Delete {s.name}" title="Delete the symbol (its instances become groups)" onclick={() => report(controller.deleteSymbol(s.id, { detach: true }))}>×</button>
57
+ </li>
58
+ {/each}
59
+ </ul>
60
+ {/if}
61
+ {#if inst}
62
+ <div class="space-y-1" data-vector-instance={inst.id}>
63
+ <div class="flex items-center gap-1">
64
+ <select aria-label="Instance symbol" class={selectClass} value={inst.symbolId} onchange={(e) => report(controller.replaceSymbol((e.currentTarget as HTMLSelectElement).value, { ids: [inst.id] }))}>
65
+ {#each panel.symbols as s (s.id)}
66
+ <option value={s.id}>{s.name}</option>
67
+ {/each}
68
+ </select>
69
+ <button type="button" class={buttonClass} title="Break Link to Symbol: a plain group of its art" onclick={() => report(controller.detachInstances({ ids: [inst.id] }))}>Detach</button>
70
+ </div>
71
+ <div class="flex items-center gap-2">
72
+ <label class="flex items-center gap-1 text-xs text-foreground">
73
+ <input type="checkbox" aria-label="Scale strokes" checked={inst.scaleStrokes} onchange={(e) => report(controller.setInstanceOptions({ scaleStrokes: (e.currentTarget as HTMLInputElement).checked }, { ids: [inst.id] }))} />
74
+ Scale strokes
75
+ </label>
76
+ <label class="flex items-center gap-1 text-xs text-foreground" title="Paint every fill of the art in this instance">
77
+ Tint
78
+ <input
79
+ type="color"
80
+ aria-label="Instance fill"
81
+ class="h-6 w-8 rounded border border-border"
82
+ value={vectorPaintToHex(inst.fill ?? undefined) ?? '#000000'}
83
+ onchange={(e) => report(controller.setInstanceOptions({ fill: (e.currentTarget as HTMLInputElement).value }, { ids: [inst.id] }))}
84
+ />
85
+ </label>
86
+ {#if inst.fill !== null}
87
+ <button type="button" class={buttonClass} aria-label="Clear tint" onclick={() => report(controller.setInstanceOptions({ fill: null }, { ids: [inst.id] }))}>×</button>
88
+ {/if}
89
+ </div>
90
+ </div>
91
+ {/if}
92
+ {#if error}<p class="text-[11px] text-destructive">{error}</p>{/if}
93
+ </div>
@@ -0,0 +1,14 @@
1
+ import { type VectorEditorController } from '@pixerate/editor/vector';
2
+ /**
3
+ * Symbols of the document with previews: New Symbol from the selection, Place, Edit (isolation
4
+ * mode) and Delete per symbol; for a selected instance, Replace, Scale strokes, a fill tint and
5
+ * Detach. Each change is one undo step.
6
+ */
7
+ interface Props {
8
+ controller: VectorEditorController;
9
+ class?: string;
10
+ }
11
+ declare const VectorSymbolsPanel: import("svelte").Component<Props, {}, "">;
12
+ type VectorSymbolsPanel = ReturnType<typeof VectorSymbolsPanel>;
13
+ export default VectorSymbolsPanel;
14
+ //# sourceMappingURL=VectorSymbolsPanel.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VectorSymbolsPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorSymbolsPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAA2C,KAAK,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAO7G;;;;GAIG;AACH,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAkFH,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ import type { VectorRenderText } from '@pixerate/editor/vector';
3
+
4
+ interface Props {
5
+ node: VectorRenderText;
6
+ }
7
+
8
+ /**
9
+ * Text drawn as SVG text: one `<text>` per paint layer, every span a `<tspan>` placed character by
10
+ * character (the layout is the editor's, not the browser's).
11
+ */
12
+ let { node }: Props = $props();
13
+ const blend = (mode: string | undefined) => (mode ? `mix-blend-mode: ${mode}` : undefined);
14
+ </script>
15
+
16
+ <g data-vector-id={node.id} data-vector-text="" transform={node.transform} opacity={node.opacity < 1 ? node.opacity : undefined} filter={node.filter} style={blend(node.blendMode)}>
17
+ {#each node.layers as layer, i (i)}
18
+ <text
19
+ data-vector-layer={layer.kind}
20
+ xml:space="preserve"
21
+ fill={layer.fill}
22
+ stroke={layer.stroke}
23
+ stroke-width={layer.stroke === 'none' ? undefined : layer.strokeWidth}
24
+ stroke-linejoin={layer.strokeLinejoin as "miter" | "round" | "bevel" | undefined}
25
+ stroke-dasharray={layer.strokeDasharray}
26
+ opacity={layer.opacity < 1 ? layer.opacity : undefined}
27
+ style="white-space:pre;{blend(layer.blendMode) ?? ''}"
28
+ >{#each node.spans as sp, k (k)}<tspan
29
+ x={sp.x}
30
+ y={sp.y}
31
+ rotate={sp.rotate}
32
+ font-family={sp.fontFamily}
33
+ font-size={sp.fontSize}
34
+ font-weight={sp.fontWeight}
35
+ font-style={sp.fontStyle}
36
+ text-decoration={sp.textDecoration}
37
+ fill={layer.runFills && sp.fill ? sp.fill : undefined}>{sp.text}</tspan
38
+ >{/each}</text
39
+ >
40
+ {/each}
41
+ </g>
@@ -0,0 +1,8 @@
1
+ import type { VectorRenderText } from '@pixerate/editor/vector';
2
+ interface Props {
3
+ node: VectorRenderText;
4
+ }
5
+ declare const VectorTextElement: import("svelte").Component<Props, {}, "">;
6
+ type VectorTextElement = ReturnType<typeof VectorTextElement>;
7
+ export default VectorTextElement;
8
+ //# sourceMappingURL=VectorTextElement.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VectorTextElement.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorTextElement.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,UAAU,KAAK;IACb,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAqBH,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}