@pixerate/editor-svelte 0.37.0 → 0.39.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.
- package/dist/vector/VectorBlendControls.svelte +69 -0
- package/dist/vector/VectorBlendControls.svelte.d.ts +8 -0
- package/dist/vector/VectorBlendControls.svelte.d.ts.map +1 -0
- package/dist/vector/VectorCanvas.svelte +19 -0
- package/dist/vector/VectorCanvas.svelte.d.ts.map +1 -1
- package/dist/vector/VectorEffectsPanel.svelte +160 -0
- package/dist/vector/VectorEffectsPanel.svelte.d.ts +15 -0
- package/dist/vector/VectorEffectsPanel.svelte.d.ts.map +1 -0
- package/dist/vector/VectorStylePanel.svelte +2 -0
- package/dist/vector/VectorStylePanel.svelte.d.ts.map +1 -1
- package/dist/vector/VectorToolbar.svelte +28 -5
- package/dist/vector/VectorToolbar.svelte.d.ts +5 -0
- package/dist/vector/VectorToolbar.svelte.d.ts.map +1 -1
- package/dist/vector/index.d.ts +1 -0
- package/dist/vector/index.d.ts.map +1 -1
- package/dist/vector/index.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
VECTOR_BLEND_SPACINGS,
|
|
4
|
+
VECTOR_BLEND_SPACING_LABELS,
|
|
5
|
+
applyVectorBlendOptions,
|
|
6
|
+
getVectorBlendControls,
|
|
7
|
+
type VectorBlendSpacing,
|
|
8
|
+
type VectorEditorController
|
|
9
|
+
} from '@pixerate/editor/vector';
|
|
10
|
+
import { vectorStateOf } from './subscribe.svelte.js';
|
|
11
|
+
import { vectorButtonClass } from './styles.js';
|
|
12
|
+
|
|
13
|
+
/** Blend buttons and options: they edit the selected blends and the options new blends get. */
|
|
14
|
+
let { controller }: { controller: VectorEditorController } = $props();
|
|
15
|
+
|
|
16
|
+
const store = vectorStateOf(() => controller);
|
|
17
|
+
const blend = $derived(getVectorBlendControls(store.current));
|
|
18
|
+
const o = $derived(blend.options);
|
|
19
|
+
const selected = $derived(blend.ids.length > 0);
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<div class="ml-1 flex items-center gap-1" role="group" aria-label="Blend" data-vector-blend-controls="">
|
|
23
|
+
<button type="button" data-vector-blend-action="make" class={vectorButtonClass} title="Make Blend" aria-label="Make Blend" disabled={!blend.canBlend} onclick={() => controller.blend()}>⧓</button>
|
|
24
|
+
<select
|
|
25
|
+
aria-label="Blend spacing"
|
|
26
|
+
class="h-7 rounded border border-border bg-background px-1 text-xs"
|
|
27
|
+
value={o.spacing}
|
|
28
|
+
onchange={(e) => applyVectorBlendOptions(controller, { spacing: e.currentTarget.value as VectorBlendSpacing })}
|
|
29
|
+
>
|
|
30
|
+
{#each VECTOR_BLEND_SPACINGS as sp (sp)}
|
|
31
|
+
<option value={sp}>{VECTOR_BLEND_SPACING_LABELS[sp]}</option>
|
|
32
|
+
{/each}
|
|
33
|
+
</select>
|
|
34
|
+
{#if o.spacing !== 'smoothColor'}
|
|
35
|
+
<input
|
|
36
|
+
type="number"
|
|
37
|
+
min={o.spacing === 'steps' ? 1 : 0.5}
|
|
38
|
+
step="1"
|
|
39
|
+
aria-label={o.spacing === 'steps' ? 'Blend steps' : 'Blend distance'}
|
|
40
|
+
class="h-7 w-16 rounded border border-border bg-background px-1.5 text-xs"
|
|
41
|
+
value={o.spacing === 'steps' ? o.steps : o.distance}
|
|
42
|
+
oninput={(e) => {
|
|
43
|
+
const n = parseFloat(e.currentTarget.value);
|
|
44
|
+
if (!Number.isFinite(n) || n <= 0) return;
|
|
45
|
+
applyVectorBlendOptions(controller, o.spacing === 'steps' ? { steps: n } : { distance: n });
|
|
46
|
+
}}
|
|
47
|
+
/>
|
|
48
|
+
{/if}
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
data-vector-blend-action="orient"
|
|
52
|
+
class={vectorButtonClass}
|
|
53
|
+
aria-pressed={o.orientToSpine}
|
|
54
|
+
title="Orient steps to the spine"
|
|
55
|
+
aria-label="Orient to spine"
|
|
56
|
+
onclick={() => applyVectorBlendOptions(controller, { orientToSpine: !o.orientToSpine })}>⤿</button
|
|
57
|
+
>
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
data-vector-blend-action="spine"
|
|
61
|
+
class={vectorButtonClass}
|
|
62
|
+
title="Replace Spine (select a blend and a path)"
|
|
63
|
+
aria-label="Replace Spine"
|
|
64
|
+
disabled={!blend.canReplaceSpine}
|
|
65
|
+
onclick={() => controller.replaceBlendSpine()}>⌇</button
|
|
66
|
+
>
|
|
67
|
+
<button type="button" data-vector-blend-action="expand" class={vectorButtonClass} title="Expand Blend" aria-label="Expand Blend" disabled={!selected} onclick={() => controller.expandBlend()}>⧉</button>
|
|
68
|
+
<button type="button" data-vector-blend-action="release" class={vectorButtonClass} title="Release Blend" aria-label="Release Blend" disabled={!selected} onclick={() => controller.releaseBlend()}>⧄</button>
|
|
69
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type VectorEditorController } from '@pixerate/editor/vector';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
controller: VectorEditorController;
|
|
4
|
+
};
|
|
5
|
+
declare const VectorBlendControls: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type VectorBlendControls = ReturnType<typeof VectorBlendControls>;
|
|
7
|
+
export default VectorBlendControls;
|
|
8
|
+
//# sourceMappingURL=VectorBlendControls.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VectorBlendControls.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorBlendControls.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAMH,KAAK,sBAAsB,EAC5B,MAAM,yBAAyB,CAAC;AAIlC,KAAK,gBAAgB,GAAI;IAAE,UAAU,EAAE,sBAAsB,CAAA;CAAE,CAAC;AAqCjE,QAAA,MAAM,mBAAmB,sDAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
|
|
@@ -228,6 +228,25 @@
|
|
|
228
228
|
<circle cx={w.x} cy={w.y} r={1.5} fill={accentColor} />
|
|
229
229
|
</g>
|
|
230
230
|
{/each}
|
|
231
|
+
{#each overlay.blend.spines as sp (sp.id)}
|
|
232
|
+
<g data-vector-blend-spine={sp.id} data-replaced={sp.replaced || undefined}>
|
|
233
|
+
<path d={sp.d} fill="none" stroke={accentColor} stroke-dasharray="4 3" />
|
|
234
|
+
{#each sp.keys as k, i (i)}
|
|
235
|
+
<circle cx={k.x} cy={k.y} r={THEME.handleRadius} fill={THEME.handleFill} stroke={accentColor} />
|
|
236
|
+
{/each}
|
|
237
|
+
</g>
|
|
238
|
+
{/each}
|
|
239
|
+
{#if overlay.blend.start}
|
|
240
|
+
<g data-vector-blend-start="">
|
|
241
|
+
<path d={overlay.blend.start.d} fill="none" stroke={accentColor} stroke-width="2" />
|
|
242
|
+
{#if overlay.blend.start.anchor}
|
|
243
|
+
<circle cx={overlay.blend.start.anchor.x} cy={overlay.blend.start.anchor.y} r={THEME.handleRadius + 1} fill={accentColor} />
|
|
244
|
+
{/if}
|
|
245
|
+
</g>
|
|
246
|
+
{/if}
|
|
247
|
+
{#each overlay.blend.anchors as a, i (i)}
|
|
248
|
+
<rect data-vector-blend-anchor={i} x={a.x - as / 2} y={a.y - as / 2} width={as} height={as} fill={a.hovered ? accentColor : THEME.handleFill} stroke={accentColor} />
|
|
249
|
+
{/each}
|
|
231
250
|
{#if overlay.widthSpot}
|
|
232
251
|
<g data-vector-width-spot="">
|
|
233
252
|
<line x1={overlay.widthSpot.left.x} y1={overlay.widthSpot.left.y} x2={overlay.widthSpot.right.x} y2={overlay.widthSpot.right.y} stroke={accentColor} stroke-dasharray="3 2" />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VectorCanvas.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorCanvas.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,EAQH,KAAK,sBAAsB,EAC5B,MAAM,yBAAyB,CAAC;AAOjC,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,mFAAmF;IACnF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"VectorCanvas.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorCanvas.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,EAQH,KAAK,sBAAsB,EAC5B,MAAM,yBAAyB,CAAC;AAOjC,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,mFAAmF;IACnF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAqQH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
VECTOR_EFFECT_MENU,
|
|
4
|
+
getVectorEffectsPanel,
|
|
5
|
+
type VectorEditorController,
|
|
6
|
+
type VectorEffectsPanelItem,
|
|
7
|
+
type VectorEffectType
|
|
8
|
+
} from '@pixerate/editor/vector';
|
|
9
|
+
import { vectorStateOf } from './subscribe.svelte.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The live path effects of the selection: add effects (to every selected node), and for one
|
|
13
|
+
* selected node list its stack (applied top to bottom) to turn on and off, reorder, remove and
|
|
14
|
+
* edit. Slider drags and typing are one undo step each. Expand Appearance bakes the effects and
|
|
15
|
+
* live booleans into plain paths.
|
|
16
|
+
*/
|
|
17
|
+
interface Props {
|
|
18
|
+
controller: VectorEditorController;
|
|
19
|
+
class?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let { controller, class: className = '' }: Props = $props();
|
|
23
|
+
|
|
24
|
+
const store = vectorStateOf(() => controller);
|
|
25
|
+
const view = $derived(store.current);
|
|
26
|
+
const panel = $derived(getVectorEffectsPanel(view));
|
|
27
|
+
|
|
28
|
+
const buttonClass =
|
|
29
|
+
'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';
|
|
30
|
+
const numberClass = 'h-7 w-16 rounded border border-border bg-background px-1.5 text-xs';
|
|
31
|
+
const selectClass = 'h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs';
|
|
32
|
+
/** Parameter names sit on their own line in a narrow panel, the controls below. */
|
|
33
|
+
const labelClass = 'w-full truncate text-[11px] text-muted-foreground';
|
|
34
|
+
|
|
35
|
+
const end = () => controller.endMergedEdit();
|
|
36
|
+
function set(e: VectorEffectsPanelItem, key: string, value: unknown, merge: boolean) {
|
|
37
|
+
if (panel?.nodeId) controller.updateEffect(panel.nodeId, e.id, { params: { [key]: value } }, { merge });
|
|
38
|
+
}
|
|
39
|
+
function setNumber(e: VectorEffectsPanelItem, key: string, raw: string) {
|
|
40
|
+
const n = parseFloat(raw);
|
|
41
|
+
if (Number.isFinite(n)) set(e, key, n, true);
|
|
42
|
+
}
|
|
43
|
+
function setAxis(e: VectorEffectsPanelItem, key: string, axis: 'x' | 'y', raw: string) {
|
|
44
|
+
const n = parseFloat(raw);
|
|
45
|
+
const v = vecOf(e, key);
|
|
46
|
+
if (Number.isFinite(n)) set(e, key, { ...v, [axis]: n }, true);
|
|
47
|
+
}
|
|
48
|
+
const vecOf = (e: VectorEffectsPanelItem, key: string) => (e.params[key] as { x: number; y: number } | undefined) ?? { x: 0, y: 0 };
|
|
49
|
+
const round = (n: number) => Math.round(n * 1000) / 1000;
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
{#if panel}
|
|
53
|
+
<div class="space-y-2 {className}" data-vector-effects="" aria-label="Effects">
|
|
54
|
+
<div class="flex items-center gap-2">
|
|
55
|
+
<span class="w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">Effects</span>
|
|
56
|
+
<select
|
|
57
|
+
aria-label="Add effect"
|
|
58
|
+
class={selectClass}
|
|
59
|
+
value=""
|
|
60
|
+
onchange={(ev) => {
|
|
61
|
+
const type = ev.currentTarget.value;
|
|
62
|
+
ev.currentTarget.value = '';
|
|
63
|
+
if (type) controller.addEffect(type as VectorEffectType);
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<option value="">Add effect…</option>
|
|
67
|
+
{#each VECTOR_EFFECT_MENU as m (m.type)}<option value={m.type}>{m.label}</option>{/each}
|
|
68
|
+
</select>
|
|
69
|
+
<button type="button" class={buttonClass} title="Expand Appearance" aria-label="Expand Appearance" disabled={!panel.expandable} onclick={() => controller.expandAppearance()}>
|
|
70
|
+
Expand
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
{#if panel.count > 1}<p class="text-[11px] text-muted-foreground">Select one object to edit its effects.</p>{/if}
|
|
74
|
+
{#if panel.nodeId}
|
|
75
|
+
{@const nodeId = panel.nodeId}
|
|
76
|
+
{#each panel.effects as e, i (e.id)}
|
|
77
|
+
<div class="space-y-1 rounded border border-border p-1.5" data-vector-effect={e.id} data-vector-effect-type={e.type}>
|
|
78
|
+
<div class="flex items-center gap-1">
|
|
79
|
+
<label class="flex min-w-0 flex-1 items-center gap-1.5 text-xs font-medium text-foreground">
|
|
80
|
+
<input type="checkbox" aria-label="{e.label} on" checked={e.enabled} onchange={(ev) => controller.updateEffect(nodeId, e.id, { enabled: ev.currentTarget.checked })} />
|
|
81
|
+
<span class="truncate">{e.label}</span>
|
|
82
|
+
</label>
|
|
83
|
+
<button type="button" class={buttonClass} title="Move up" aria-label="Move {e.label} up" disabled={i === 0} onclick={() => controller.moveEffect(nodeId, e.id, i - 1)}>↑</button>
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
class={buttonClass}
|
|
87
|
+
title="Move down"
|
|
88
|
+
aria-label="Move {e.label} down"
|
|
89
|
+
disabled={i === panel.effects.length - 1}
|
|
90
|
+
onclick={() => controller.moveEffect(nodeId, e.id, i + 1)}>↓</button
|
|
91
|
+
>
|
|
92
|
+
<button type="button" class={buttonClass} title="Remove" aria-label="Remove {e.label}" onclick={() => controller.removeEffect(nodeId, e.id)}>×</button>
|
|
93
|
+
</div>
|
|
94
|
+
{#each e.specs as spec (spec.key)}
|
|
95
|
+
{#if spec.kind === 'number'}
|
|
96
|
+
{@const value = Number(e.params[spec.key] ?? 0)}
|
|
97
|
+
<label class="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground" data-vector-effect-param={spec.key}>
|
|
98
|
+
<span class={labelClass}>{spec.label}</span>
|
|
99
|
+
{#if spec.slider}
|
|
100
|
+
<input
|
|
101
|
+
type="range"
|
|
102
|
+
min={spec.min}
|
|
103
|
+
max={spec.max}
|
|
104
|
+
step={spec.step}
|
|
105
|
+
aria-label="{e.label} {spec.label} slider"
|
|
106
|
+
class="h-4 min-w-0 flex-1 accent-primary"
|
|
107
|
+
{value}
|
|
108
|
+
oninput={(ev) => setNumber(e, spec.key, ev.currentTarget.value)}
|
|
109
|
+
onpointerup={end}
|
|
110
|
+
onkeyup={end}
|
|
111
|
+
onblur={end}
|
|
112
|
+
/>
|
|
113
|
+
{/if}
|
|
114
|
+
<input
|
|
115
|
+
type="number"
|
|
116
|
+
min={spec.min}
|
|
117
|
+
max={spec.max}
|
|
118
|
+
step={spec.step}
|
|
119
|
+
aria-label="{e.label} {spec.label}"
|
|
120
|
+
class={numberClass}
|
|
121
|
+
value={round(value)}
|
|
122
|
+
oninput={(ev) => setNumber(e, spec.key, ev.currentTarget.value)}
|
|
123
|
+
onblur={end}
|
|
124
|
+
/>
|
|
125
|
+
</label>
|
|
126
|
+
{:else if spec.kind === 'vec2'}
|
|
127
|
+
{@const v = vecOf(e, spec.key)}
|
|
128
|
+
<div class="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground" data-vector-effect-param={spec.key}>
|
|
129
|
+
<span class={labelClass}>{spec.label}</span>
|
|
130
|
+
{#each ['x', 'y'] as const as axis (axis)}
|
|
131
|
+
<input
|
|
132
|
+
type="number"
|
|
133
|
+
step={spec.step}
|
|
134
|
+
aria-label="{e.label} {spec.label} {axis}"
|
|
135
|
+
class={numberClass}
|
|
136
|
+
value={round(v[axis])}
|
|
137
|
+
oninput={(ev) => setAxis(e, spec.key, axis, ev.currentTarget.value)}
|
|
138
|
+
onblur={end}
|
|
139
|
+
/>
|
|
140
|
+
{/each}
|
|
141
|
+
</div>
|
|
142
|
+
{:else if spec.kind === 'boolean'}
|
|
143
|
+
<label class="flex items-center gap-2 text-xs text-foreground" data-vector-effect-param={spec.key}>
|
|
144
|
+
<span class="min-w-0 flex-1 truncate">{spec.label}</span>
|
|
145
|
+
<input type="checkbox" aria-label="{e.label} {spec.label}" checked={!!e.params[spec.key]} onchange={(ev) => set(e, spec.key, ev.currentTarget.checked, false)} />
|
|
146
|
+
</label>
|
|
147
|
+
{:else}
|
|
148
|
+
<label class="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground" data-vector-effect-param={spec.key}>
|
|
149
|
+
<span class={labelClass}>{spec.label}</span>
|
|
150
|
+
<select aria-label="{e.label} {spec.label}" class={selectClass} value={String(e.params[spec.key] ?? '')} onchange={(ev) => set(e, spec.key, ev.currentTarget.value, false)}>
|
|
151
|
+
{#each spec.options as o (o.value)}<option value={o.value}>{o.label}</option>{/each}
|
|
152
|
+
</select>
|
|
153
|
+
</label>
|
|
154
|
+
{/if}
|
|
155
|
+
{/each}
|
|
156
|
+
</div>
|
|
157
|
+
{/each}
|
|
158
|
+
{/if}
|
|
159
|
+
</div>
|
|
160
|
+
{/if}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type VectorEditorController } from '@pixerate/editor/vector';
|
|
2
|
+
/**
|
|
3
|
+
* The live path effects of the selection: add effects (to every selected node), and for one
|
|
4
|
+
* selected node list its stack (applied top to bottom) to turn on and off, reorder, remove and
|
|
5
|
+
* edit. Slider drags and typing are one undo step each. Expand Appearance bakes the effects and
|
|
6
|
+
* live booleans into plain paths.
|
|
7
|
+
*/
|
|
8
|
+
interface Props {
|
|
9
|
+
controller: VectorEditorController;
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const VectorEffectsPanel: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type VectorEffectsPanel = ReturnType<typeof VectorEffectsPanel>;
|
|
14
|
+
export default VectorEffectsPanel;
|
|
15
|
+
//# sourceMappingURL=VectorEffectsPanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VectorEffectsPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorEffectsPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAGH,KAAK,sBAAsB,EAG5B,MAAM,yBAAyB,CAAC;AAIjC;;;;;GAKG;AACH,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA0GH,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
} from '@pixerate/editor/vector';
|
|
18
18
|
import { vectorStateOf } from './subscribe.svelte.js';
|
|
19
19
|
import { vectorButtonClass } from './styles.js';
|
|
20
|
+
import VectorEffectsPanel from './VectorEffectsPanel.svelte';
|
|
20
21
|
|
|
21
22
|
interface Props {
|
|
22
23
|
controller: VectorEditorController;
|
|
@@ -217,5 +218,6 @@
|
|
|
217
218
|
oninput={(e) => apply({ opacity: parseFloat(e.currentTarget.value) })}
|
|
218
219
|
/>
|
|
219
220
|
</label>
|
|
221
|
+
<VectorEffectsPanel {controller} />
|
|
220
222
|
{/if}
|
|
221
223
|
</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;
|
|
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;AAMjC,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA2JH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
} from '@pixerate/editor/vector';
|
|
14
14
|
import { vectorStateOf } from './subscribe.svelte.js';
|
|
15
15
|
import { vectorButtonClass } from './styles.js';
|
|
16
|
+
import VectorBlendControls from './VectorBlendControls.svelte';
|
|
16
17
|
|
|
17
18
|
interface Props {
|
|
18
19
|
controller: VectorEditorController;
|
|
@@ -30,10 +31,15 @@
|
|
|
30
31
|
* those tools are active (default `true`).
|
|
31
32
|
*/
|
|
32
33
|
showPathEdit?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Blend: Make Blend, spacing (steps, distance or smooth colour), steps / distance, orient to
|
|
36
|
+
* spine, Replace Spine, Expand and Release (default `true`).
|
|
37
|
+
*/
|
|
38
|
+
showBlend?: boolean;
|
|
33
39
|
class?: string;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
let { controller, tools = VECTOR_TOOLS, showHistory = true, showArrange = true, showSnap = true, showPathEdit = true, class: className = '' }: Props = $props();
|
|
42
|
+
let { controller, tools = VECTOR_TOOLS, showHistory = true, showArrange = true, showSnap = true, showPathEdit = true, showBlend = true, class: className = '' }: Props = $props();
|
|
37
43
|
|
|
38
44
|
const snapTitle = 'Snap to objects';
|
|
39
45
|
const modKey = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform ?? '') ? '⌘' : 'Ctrl';
|
|
@@ -44,11 +50,14 @@
|
|
|
44
50
|
const points = $derived(view.tool === 'direct' ? view.selectedAnchors.length : 0);
|
|
45
51
|
const groupSelected = $derived(view.selection.some((id) => controller.getNode(id)?.type === 'group'));
|
|
46
52
|
|
|
53
|
+
// Live booleans keep the operands editable; a lone live group or its operands take the operation too.
|
|
54
|
+
let live = $state(false);
|
|
55
|
+
const booleanDisabled = $derived(live ? count < 1 : count < 2);
|
|
47
56
|
const combine = $derived([
|
|
48
|
-
{ label: '∪', title: 'Union', run: () => controller.booleanOp('union'), disabled:
|
|
49
|
-
{ label: '−', title: 'Subtract', run: () => controller.booleanOp('subtract'), disabled:
|
|
50
|
-
{ label: '∩', title: 'Intersect', run: () => controller.booleanOp('intersect'), disabled:
|
|
51
|
-
{ label: '⊻', title: 'Exclude', run: () => controller.booleanOp('exclude'), disabled:
|
|
57
|
+
{ label: '∪', title: 'Union', run: () => controller.booleanOp('union', { live }), disabled: booleanDisabled },
|
|
58
|
+
{ label: '−', title: 'Subtract', run: () => controller.booleanOp('subtract', { live }), disabled: booleanDisabled },
|
|
59
|
+
{ label: '∩', title: 'Intersect', run: () => controller.booleanOp('intersect', { live }), disabled: booleanDisabled },
|
|
60
|
+
{ label: '⊻', title: 'Exclude', run: () => controller.booleanOp('exclude', { live }), disabled: booleanDisabled },
|
|
52
61
|
{ label: 'G', title: 'Group', run: () => controller.group(), disabled: count < 1 },
|
|
53
62
|
{ label: 'U', title: 'Ungroup', run: () => controller.ungroup(), disabled: !groupSelected }
|
|
54
63
|
]);
|
|
@@ -182,8 +191,22 @@
|
|
|
182
191
|
{/if}
|
|
183
192
|
</div>
|
|
184
193
|
{/if}
|
|
194
|
+
{#if showBlend}
|
|
195
|
+
<VectorBlendControls {controller} />
|
|
196
|
+
{/if}
|
|
185
197
|
{#if showArrange}
|
|
186
198
|
<div class="ml-1 flex items-center gap-1" role="group" aria-label="Combine">
|
|
199
|
+
<button
|
|
200
|
+
type="button"
|
|
201
|
+
data-vector-live-boolean=""
|
|
202
|
+
class={vectorButtonClass}
|
|
203
|
+
aria-pressed={live}
|
|
204
|
+
title="Live booleans: keep the shapes editable"
|
|
205
|
+
aria-label="Live booleans"
|
|
206
|
+
onclick={() => (live = !live)}
|
|
207
|
+
>
|
|
208
|
+
Live
|
|
209
|
+
</button>
|
|
187
210
|
{#each combine as item (item.title)}{@render action(item)}{/each}
|
|
188
211
|
</div>
|
|
189
212
|
<div class="ml-1 flex items-center gap-1" role="group" aria-label="Pathfinder">
|
|
@@ -15,6 +15,11 @@ interface Props {
|
|
|
15
15
|
* those tools are active (default `true`).
|
|
16
16
|
*/
|
|
17
17
|
showPathEdit?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Blend: Make Blend, spacing (steps, distance or smooth colour), steps / distance, orient to
|
|
20
|
+
* spine, Replace Spine, Expand and Release (default `true`).
|
|
21
|
+
*/
|
|
22
|
+
showBlend?: boolean;
|
|
18
23
|
class?: string;
|
|
19
24
|
}
|
|
20
25
|
declare const VectorToolbar: import("svelte").Component<Props, {}, "">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VectorToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EASH,KAAK,sBAAsB,EAC3B,KAAK,UAAU,EAChB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"VectorToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EASH,KAAK,sBAAsB,EAC3B,KAAK,UAAU,EAChB,MAAM,yBAAyB,CAAC;AAMjC,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,oCAAoC;IACpC,KAAK,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC9B,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgIH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
package/dist/vector/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { default as VectorCanvas } from "./VectorCanvas.svelte";
|
|
|
6
6
|
export { default as VectorWidthPointEditor } from "./VectorWidthPointEditor.svelte";
|
|
7
7
|
export { default as VectorToolbar } from "./VectorToolbar.svelte";
|
|
8
8
|
export { default as VectorStylePanel } from "./VectorStylePanel.svelte";
|
|
9
|
+
export { default as VectorEffectsPanel } from "./VectorEffectsPanel.svelte";
|
|
9
10
|
export { default as VectorSceneNodes } from "./VectorSceneNodes.svelte";
|
|
10
11
|
export { default as VectorGradientDefs } from "./VectorGradientDefs.svelte";
|
|
11
12
|
export * from "./createVectorEditor.svelte.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vector/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,cAAc,gCAAgC,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,cAAc,aAAa,CAAC;AAE5B,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC7E,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vector/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,cAAc,gCAAgC,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,cAAc,aAAa,CAAC;AAE5B,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC7E,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
package/dist/vector/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { default as VectorCanvas } from "./VectorCanvas.svelte";
|
|
|
3
3
|
export { default as VectorWidthPointEditor } from "./VectorWidthPointEditor.svelte";
|
|
4
4
|
export { default as VectorToolbar } from "./VectorToolbar.svelte";
|
|
5
5
|
export { default as VectorStylePanel } from "./VectorStylePanel.svelte";
|
|
6
|
+
export { default as VectorEffectsPanel } from "./VectorEffectsPanel.svelte";
|
|
6
7
|
export { default as VectorSceneNodes } from "./VectorSceneNodes.svelte";
|
|
7
8
|
export { default as VectorGradientDefs } from "./VectorGradientDefs.svelte";
|
|
8
9
|
export * from "./createVectorEditor.svelte.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"@tiptap/extension-bubble-menu": "^2.11.5",
|
|
108
108
|
"@tiptap/extension-placeholder": "^2.11.5",
|
|
109
109
|
"@tiptap/starter-kit": "^2.11.5",
|
|
110
|
-
"@pixerate/editor": "0.
|
|
110
|
+
"@pixerate/editor": "0.42.0"
|
|
111
111
|
},
|
|
112
112
|
"peerDependencies": {
|
|
113
113
|
"@dagrejs/dagre": "^1.1.4",
|