@pixerate/editor-svelte 0.82.0 → 0.84.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/image-editor/ColorProfilePanel.svelte +123 -0
- package/dist/image-editor/ColorProfilePanel.svelte.d.ts +9 -0
- package/dist/image-editor/ColorProfilePanel.svelte.d.ts.map +1 -0
- package/dist/image-editor/ImageEditor.svelte +3 -0
- package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -1
- package/dist/image-editor/LayersPanel.svelte +12 -0
- package/dist/image-editor/LayersPanel.svelte.d.ts.map +1 -1
- package/dist/image-editor/index.d.ts +3 -0
- package/dist/image-editor/index.d.ts.map +1 -1
- package/dist/image-editor/index.js +1 -0
- package/dist/vector/VectorCanvas.svelte +155 -0
- package/dist/vector/VectorCanvas.svelte.d.ts.map +1 -1
- package/dist/vector/VectorConnectorPanel.svelte +87 -0
- package/dist/vector/VectorConnectorPanel.svelte.d.ts +14 -0
- package/dist/vector/VectorConnectorPanel.svelte.d.ts.map +1 -0
- package/dist/vector/VectorConstraintsPanel.svelte +113 -0
- package/dist/vector/VectorConstraintsPanel.svelte.d.ts +14 -0
- package/dist/vector/VectorConstraintsPanel.svelte.d.ts.map +1 -0
- package/dist/vector/VectorFramePanel.svelte +133 -0
- package/dist/vector/VectorFramePanel.svelte.d.ts +14 -0
- package/dist/vector/VectorFramePanel.svelte.d.ts.map +1 -0
- package/dist/vector/VectorSceneNodes.svelte +4 -1
- package/dist/vector/VectorSceneNodes.svelte.d.ts.map +1 -1
- package/dist/vector/VectorStylePanel.svelte +27 -0
- package/dist/vector/VectorStylePanel.svelte.d.ts.map +1 -1
- package/dist/vector/VectorSymbolsPanel.svelte +93 -0
- package/dist/vector/VectorSymbolsPanel.svelte.d.ts +14 -0
- package/dist/vector/VectorSymbolsPanel.svelte.d.ts.map +1 -0
- package/dist/vector/VectorTextElement.svelte +41 -0
- package/dist/vector/VectorTextElement.svelte.d.ts +8 -0
- package/dist/vector/VectorTextElement.svelte.d.ts.map +1 -0
- package/dist/vector/VectorTextPanel.svelte +170 -0
- package/dist/vector/VectorTextPanel.svelte.d.ts +16 -0
- package/dist/vector/VectorTextPanel.svelte.d.ts.map +1 -0
- package/dist/vector/VectorToolbar.svelte +13 -0
- package/dist/vector/VectorToolbar.svelte.d.ts.map +1 -1
- package/dist/vector/index.d.ts +6 -0
- package/dist/vector/index.d.ts.map +1 -1
- package/dist/vector/index.js +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ImageEditorController, Intent } from '@pixerate/editor/image-editor';
|
|
3
|
+
import { COLOR_MODE_OPTIONS, INTENT_OPTIONS, PROOF_PROFILE_OPTIONS, WORKING_SPACE_OPTIONS, colorPanelView } from '@pixerate/editor/image-editor';
|
|
4
|
+
import { untrack } from 'svelte';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
controller: ImageEditorController;
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { controller, class: className = '' }: Props = $props();
|
|
12
|
+
|
|
13
|
+
let tick = $state(0);
|
|
14
|
+
$effect(() => {
|
|
15
|
+
const c = controller;
|
|
16
|
+
// `subscribe` calls the listener at once: untracked, or the effect would depend on `tick`.
|
|
17
|
+
return untrack(() => c.subscribe(() => tick++));
|
|
18
|
+
});
|
|
19
|
+
const view = $derived.by(() => {
|
|
20
|
+
void tick;
|
|
21
|
+
return colorPanelView(controller);
|
|
22
|
+
});
|
|
23
|
+
let target = $state('display-p3');
|
|
24
|
+
let intent = $state<Intent>('relative');
|
|
25
|
+
let bpc = $state(true);
|
|
26
|
+
let error = $state<string | null>(null);
|
|
27
|
+
|
|
28
|
+
const chip = 'px-2 py-1 rounded text-[11px] bg-slate-800 text-slate-300 hover:text-white';
|
|
29
|
+
const select = 'rounded bg-slate-800 px-1 py-0.5 text-[11px] text-slate-200';
|
|
30
|
+
|
|
31
|
+
function attempt(fn: () => unknown) {
|
|
32
|
+
try {
|
|
33
|
+
error = null;
|
|
34
|
+
fn();
|
|
35
|
+
} catch (err) {
|
|
36
|
+
error = err instanceof Error ? err.message : String(err);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function proofWith(patch: Partial<{ id: string; intent: Intent; simulatePaper: boolean; gamutWarning: boolean }>) {
|
|
41
|
+
const cur = view.proof;
|
|
42
|
+
const id = patch.id ?? cur?.id ?? 'coated-cmyk';
|
|
43
|
+
attempt(() =>
|
|
44
|
+
controller.setProofSetup({
|
|
45
|
+
profile: id,
|
|
46
|
+
intent: patch.intent ?? cur?.intent ?? 'relative',
|
|
47
|
+
simulatePaper: patch.simulatePaper ?? cur?.simulatePaper ?? false,
|
|
48
|
+
gamutWarning: patch.gamutWarning ?? cur?.gamutWarning ?? false,
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<div class="flex flex-col gap-2 text-xs text-slate-200 {className}" data-testid="color-profile-panel">
|
|
55
|
+
<div class="flex items-center gap-2">
|
|
56
|
+
<span class="text-slate-400">Profile</span>
|
|
57
|
+
<span data-testid="color-profile-name">{view.profileName}</span>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="flex flex-wrap items-center gap-1" role="group" aria-label="Colour mode">
|
|
60
|
+
<span class="text-slate-400">Mode</span>
|
|
61
|
+
<select aria-label="Colour mode" class={select} value={view.mode} onchange={(e) => attempt(() => controller.setColorMode((e.target as HTMLSelectElement).value as 'rgb' | 'cmyk' | 'lab'))}>
|
|
62
|
+
{#each COLOR_MODE_OPTIONS as o (o.id)}
|
|
63
|
+
<option value={o.id} title={o.title}>{o.label}</option>
|
|
64
|
+
{/each}
|
|
65
|
+
</select>
|
|
66
|
+
{#if view.cmykProfileName}
|
|
67
|
+
<span class="text-slate-400">{view.cmykProfileName}</span>
|
|
68
|
+
{/if}
|
|
69
|
+
</div>
|
|
70
|
+
<div class="flex flex-wrap items-center gap-1" role="group" aria-label="Assign or convert">
|
|
71
|
+
<select aria-label="Target profile" class={select} value={target} onchange={(e) => (target = (e.target as HTMLSelectElement).value)}>
|
|
72
|
+
{#each WORKING_SPACE_OPTIONS as o (o.id)}
|
|
73
|
+
<option value={o.id}>{o.label}</option>
|
|
74
|
+
{/each}
|
|
75
|
+
</select>
|
|
76
|
+
<select aria-label="Rendering intent" class={select} value={intent} onchange={(e) => (intent = (e.target as HTMLSelectElement).value as Intent)}>
|
|
77
|
+
{#each INTENT_OPTIONS as o (o.id)}
|
|
78
|
+
<option value={o.id} title={o.title}>{o.label}</option>
|
|
79
|
+
{/each}
|
|
80
|
+
</select>
|
|
81
|
+
<label class="flex items-center gap-1">
|
|
82
|
+
<input type="checkbox" aria-label="Black point compensation" checked={bpc} onchange={(e) => (bpc = (e.target as HTMLInputElement).checked)} class="accent-indigo-500" />
|
|
83
|
+
BPC
|
|
84
|
+
</label>
|
|
85
|
+
<button type="button" class={chip} title="Edit › Assign Profile: keep the numbers" onclick={() => attempt(() => controller.assignProfile(target))}>Assign</button>
|
|
86
|
+
<button type="button" class={chip} title="Edit › Convert to Profile: keep the look" onclick={() => attempt(() => controller.convertToProfile(target, { intent, bpc }))}>Convert</button>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="flex flex-wrap items-center gap-1" role="group" aria-label="Proof setup">
|
|
89
|
+
<span class="text-slate-400">Proof</span>
|
|
90
|
+
<select
|
|
91
|
+
aria-label="Proof profile"
|
|
92
|
+
class={select}
|
|
93
|
+
value={view.proof?.id ?? ''}
|
|
94
|
+
onchange={(e) => {
|
|
95
|
+
const v = (e.target as HTMLSelectElement).value;
|
|
96
|
+
if (v) proofWith({ id: v });
|
|
97
|
+
else attempt(() => controller.setProofSetup(null));
|
|
98
|
+
}}>
|
|
99
|
+
<option value="">Off</option>
|
|
100
|
+
{#each PROOF_PROFILE_OPTIONS as o (o.id)}
|
|
101
|
+
<option value={o.id}>{o.label}</option>
|
|
102
|
+
{/each}
|
|
103
|
+
</select>
|
|
104
|
+
{#if view.proof}
|
|
105
|
+
<select aria-label="Proof intent" class={select} value={view.proof.intent} onchange={(e) => proofWith({ intent: (e.target as HTMLSelectElement).value as Intent })}>
|
|
106
|
+
{#each INTENT_OPTIONS as o (o.id)}
|
|
107
|
+
<option value={o.id}>{o.label}</option>
|
|
108
|
+
{/each}
|
|
109
|
+
</select>
|
|
110
|
+
<label class="flex items-center gap-1">
|
|
111
|
+
<input type="checkbox" aria-label="Simulate paper colour" checked={view.proof.simulatePaper} onchange={(e) => proofWith({ simulatePaper: (e.target as HTMLInputElement).checked })} class="accent-indigo-500" />
|
|
112
|
+
Paper
|
|
113
|
+
</label>
|
|
114
|
+
<label class="flex items-center gap-1">
|
|
115
|
+
<input type="checkbox" aria-label="Gamut warning" checked={view.proof.gamutWarning} onchange={(e) => proofWith({ gamutWarning: (e.target as HTMLInputElement).checked })} class="accent-indigo-500" />
|
|
116
|
+
Gamut warning
|
|
117
|
+
</label>
|
|
118
|
+
{/if}
|
|
119
|
+
</div>
|
|
120
|
+
{#if error}
|
|
121
|
+
<p role="alert" class="text-rose-400">{error}</p>
|
|
122
|
+
{/if}
|
|
123
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ImageEditorController } from '@pixerate/editor/image-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
controller: ImageEditorController;
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const ColorProfilePanel: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ColorProfilePanel = ReturnType<typeof ColorProfilePanel>;
|
|
8
|
+
export default ColorProfilePanel;
|
|
9
|
+
//# sourceMappingURL=ColorProfilePanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColorProfilePanel.svelte.d.ts","sourceRoot":"","sources":["../../src/image-editor/ColorProfilePanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAU,MAAM,+BAA+B,CAAC;AAKjF,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAuHH,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
import Histogram from './Histogram.svelte';
|
|
42
42
|
import AdjustmentLayersPanel from './AdjustmentLayersPanel.svelte';
|
|
43
43
|
import LayersPanel from './LayersPanel.svelte';
|
|
44
|
+
import ColorProfilePanel from './ColorProfilePanel.svelte';
|
|
44
45
|
import BrushOptionsBar from './BrushOptionsBar.svelte';
|
|
45
46
|
import SelectionOptionsBar from './SelectionOptionsBar.svelte';
|
|
46
47
|
import RefineEdgePanel from './RefineEdgePanel.svelte';
|
|
@@ -1279,6 +1280,8 @@
|
|
|
1279
1280
|
disabled={transformActive}
|
|
1280
1281
|
onclick={() => transformTool.begin()}>Free Transform</button>
|
|
1281
1282
|
<LayersPanel controller={editor.controller} />
|
|
1283
|
+
<span class="font-semibold text-slate-200 uppercase tracking-wider text-[11px]">Colour</span>
|
|
1284
|
+
<ColorProfilePanel controller={editor.controller} />
|
|
1282
1285
|
</div>
|
|
1283
1286
|
{/if}
|
|
1284
1287
|
|
|
@@ -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;
|
|
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;AAuCzC,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;AAs2CH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ImageEditorController, ImageLayer, LayerMenuAction, LayerPanelRow, PixelBuffer } from '@pixerate/editor/image-editor';
|
|
3
3
|
import {
|
|
4
|
+
BIT_DEPTH_OPTIONS,
|
|
4
5
|
BLEND_MODES,
|
|
5
6
|
PSD_FILE_ACCEPT,
|
|
6
7
|
downloadPsd,
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
rows: layerPanelRows(s.layers, s.activeLayerId),
|
|
51
52
|
active: controller.getActiveLayer() ?? null,
|
|
52
53
|
menu: layerMenu(controller),
|
|
54
|
+
bitDepth: controller.bitDepth,
|
|
53
55
|
};
|
|
54
56
|
});
|
|
55
57
|
|
|
@@ -167,6 +169,16 @@
|
|
|
167
169
|
const r = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
168
170
|
menu = menu ? null : { x: r.left, y: r.bottom };
|
|
169
171
|
}}>⋯</button>
|
|
172
|
+
<select
|
|
173
|
+
aria-label="Bits per channel"
|
|
174
|
+
title="Image › Mode: bits per channel"
|
|
175
|
+
class="rounded bg-slate-800 px-1 py-0.5 text-[11px] text-slate-200"
|
|
176
|
+
value={snapshot.bitDepth}
|
|
177
|
+
onchange={(e) => attempt(() => controller.setBitDepth(Number((e.target as HTMLSelectElement).value) as 8 | 16 | 32))}>
|
|
178
|
+
{#each BIT_DEPTH_OPTIONS as o (o.depth)}
|
|
179
|
+
<option value={o.depth} title={o.title}>{o.label}</option>
|
|
180
|
+
{/each}
|
|
181
|
+
</select>
|
|
170
182
|
{#if psd}
|
|
171
183
|
<button type="button" class={iconButton} onclick={() => fileInput?.click()}>Open PSD…</button>
|
|
172
184
|
<button type="button" class={iconButton} onclick={() => psdTask(async () => (await downloadPsd(controller, psdFileName)).warnings)}>Export PSD</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;
|
|
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;AAqBlI,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;AAsSH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -7,6 +7,7 @@ import type ControlSliderComponent from "./ControlSlider.svelte";
|
|
|
7
7
|
import type SelectionOptionsBarComponent from "./SelectionOptionsBar.svelte";
|
|
8
8
|
import type RefineEdgePanelComponent from "./RefineEdgePanel.svelte";
|
|
9
9
|
import type LayersPanelComponent from "./LayersPanel.svelte";
|
|
10
|
+
import type ColorProfilePanelComponent from "./ColorProfilePanel.svelte";
|
|
10
11
|
import type LayerEffectsPanelComponent from "./LayerEffectsPanel.svelte";
|
|
11
12
|
import type BrushOptionsBarComponent from "./BrushOptionsBar.svelte";
|
|
12
13
|
import type FreeTransformOptionsBarComponent from "./FreeTransformOptionsBar.svelte";
|
|
@@ -20,6 +21,7 @@ export { default as ControlSlider } from './ControlSlider.svelte';
|
|
|
20
21
|
export { default as SelectionOptionsBar } from './SelectionOptionsBar.svelte';
|
|
21
22
|
export { default as RefineEdgePanel } from './RefineEdgePanel.svelte';
|
|
22
23
|
export { default as LayersPanel } from './LayersPanel.svelte';
|
|
24
|
+
export { default as ColorProfilePanel } from './ColorProfilePanel.svelte';
|
|
23
25
|
export { default as LayerEffectsPanel } from './LayerEffectsPanel.svelte';
|
|
24
26
|
export { default as BrushOptionsBar } from './BrushOptionsBar.svelte';
|
|
25
27
|
export { default as FreeTransformOptionsBar } from './FreeTransformOptionsBar.svelte';
|
|
@@ -34,6 +36,7 @@ export type ControlSliderProps = ComponentProps<typeof ControlSliderComponent>;
|
|
|
34
36
|
export type SelectionOptionsBarProps = ComponentProps<typeof SelectionOptionsBarComponent>;
|
|
35
37
|
export type RefineEdgePanelProps = ComponentProps<typeof RefineEdgePanelComponent>;
|
|
36
38
|
export type LayersPanelProps = ComponentProps<typeof LayersPanelComponent>;
|
|
39
|
+
export type ColorProfilePanelProps = ComponentProps<typeof ColorProfilePanelComponent>;
|
|
37
40
|
export type LayerEffectsPanelProps = ComponentProps<typeof LayerEffectsPanelComponent>;
|
|
38
41
|
export type BrushOptionsBarProps = ComponentProps<typeof BrushOptionsBarComponent>;
|
|
39
42
|
export type FreeTransformOptionsBarProps = ComponentProps<typeof FreeTransformOptionsBarComponent>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/image-editor/index.ts"],"names":[],"mappings":"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"}
|
|
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,0BAA0B,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,wBAAwB,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,gCAAgC,MAAM,kCAAkC,CAAC;AACrF,OAAO,KAAK,wBAAwB,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,uBAAuB,MAAM,yBAAyB,CAAC;AAEnE,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,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,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,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,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;AACnG,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,wBAAwB,CAAC,CAAC;AACnF,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAEjF,OAAO,EACN,iBAAiB,EACjB,eAAe,EACf,yBAAyB,EACzB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,MAAM,+BAA+B,CAAC"}
|
|
@@ -6,6 +6,7 @@ export { default as ControlSlider } from './ControlSlider.svelte';
|
|
|
6
6
|
export { default as SelectionOptionsBar } from './SelectionOptionsBar.svelte';
|
|
7
7
|
export { default as RefineEdgePanel } from './RefineEdgePanel.svelte';
|
|
8
8
|
export { default as LayersPanel } from './LayersPanel.svelte';
|
|
9
|
+
export { default as ColorProfilePanel } from './ColorProfilePanel.svelte';
|
|
9
10
|
export { default as LayerEffectsPanel } from './LayerEffectsPanel.svelte';
|
|
10
11
|
export { default as BrushOptionsBar } from './BrushOptionsBar.svelte';
|
|
11
12
|
export { default as FreeTransformOptionsBar } from './FreeTransformOptionsBar.svelte';
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
VECTOR_OVERLAY_THEME as THEME,
|
|
10
10
|
getVectorOverlayRenderData,
|
|
11
11
|
getVectorSceneRenderData,
|
|
12
|
+
getVectorSymbolEditBackground,
|
|
12
13
|
isVectorTextTarget,
|
|
13
14
|
toVectorKeyInput,
|
|
14
15
|
toVectorPointerInput,
|
|
@@ -56,6 +57,8 @@
|
|
|
56
57
|
const view = $derived(store.current);
|
|
57
58
|
const scene = $derived(getVectorSceneRenderData(view, { idPrefix }));
|
|
58
59
|
const overlay = $derived(getVectorOverlayRenderData(view));
|
|
60
|
+
// Isolation mode: the rest of the document, dimmed behind the symbol's art.
|
|
61
|
+
const host = $derived(getVectorSymbolEditBackground(view, { idPrefix: `${idPrefix}host-` }));
|
|
59
62
|
const box = $derived(overlay.selectionBox);
|
|
60
63
|
const edit = $derived(view.widthPointEdit);
|
|
61
64
|
const editAt = $derived(edit ? { x: edit.point.x * view.viewport.zoom + view.viewport.x, y: edit.point.y * view.viewport.zoom + view.viewport.y } : null);
|
|
@@ -64,6 +67,54 @@
|
|
|
64
67
|
const as = THEME.anchorSize;
|
|
65
68
|
|
|
66
69
|
let svg: SVGSVGElement | undefined = $state();
|
|
70
|
+
let textInput: HTMLTextAreaElement | undefined = $state();
|
|
71
|
+
const editingText = $derived(view.textEdit?.nodeId ?? null);
|
|
72
|
+
|
|
73
|
+
// Typing goes into a hidden textarea while text is edited (focused after the click that started it).
|
|
74
|
+
$effect(() => {
|
|
75
|
+
if (!editingText) return;
|
|
76
|
+
const t = setTimeout(() => textInput?.focus({ preventScroll: true }), 0);
|
|
77
|
+
return () => clearTimeout(t);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
function onTextInput(e: Event) {
|
|
81
|
+
const el = e.currentTarget as HTMLTextAreaElement;
|
|
82
|
+
if ((e as InputEvent).isComposing) return;
|
|
83
|
+
if (el.value) controller.textInput(el.value);
|
|
84
|
+
el.value = '';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function onTextCompositionEnd(e: CompositionEvent) {
|
|
88
|
+
const el = e.currentTarget as HTMLTextAreaElement;
|
|
89
|
+
if (el.value) controller.textInput(el.value);
|
|
90
|
+
el.value = '';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function onTextKeyDown(e: KeyboardEvent) {
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
if (e.isComposing) return;
|
|
96
|
+
// Characters come through `input`; editing keys and shortcuts go to the controller.
|
|
97
|
+
if (e.key.length === 1 && !e.metaKey && !e.ctrlKey) return;
|
|
98
|
+
if (controller.keyDown(toVectorKeyInput(e))) e.preventDefault();
|
|
99
|
+
if (!controller.getState().textEdit) svg?.focus({ preventScroll: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function onTextClipboard(kind: 'copy' | 'cut' | 'paste') {
|
|
103
|
+
return (e: ClipboardEvent) => {
|
|
104
|
+
e.stopPropagation();
|
|
105
|
+
if (kind === 'paste') {
|
|
106
|
+
const text = e.clipboardData?.getData('text/plain');
|
|
107
|
+
if (text) controller.textInput(text);
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const text = kind === 'cut' ? controller.cutText() : controller.getSelectedText();
|
|
112
|
+
if (text && e.clipboardData) {
|
|
113
|
+
e.clipboardData.setData('text/plain', text);
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
67
118
|
let fitted = untrack(() => !fitOnMount);
|
|
68
119
|
|
|
69
120
|
$effect(() => {
|
|
@@ -186,9 +237,38 @@
|
|
|
186
237
|
fill={scene.background ?? (checkerboard ? `url(#${idPrefix}checker)` : '#ffffff')}
|
|
187
238
|
stroke="rgba(0,0,0,0.12)"
|
|
188
239
|
/>
|
|
240
|
+
{#if host}
|
|
241
|
+
<g data-vector-symbol-host="" opacity="0.3" pointer-events="none" transform={host.transform}>
|
|
242
|
+
<VectorGradientDefs defs={host.defs} />
|
|
243
|
+
<VectorFilterDefs filters={host.filters} />
|
|
244
|
+
<VectorSceneNodes nodes={host.nodes} />
|
|
245
|
+
</g>
|
|
246
|
+
{/if}
|
|
189
247
|
<g data-vector-scene="" transform={scene.transform}>
|
|
190
248
|
<VectorSceneNodes nodes={scene.nodes} />
|
|
191
249
|
</g>
|
|
250
|
+
{#if overlay.frames.length}
|
|
251
|
+
<g data-vector-frame-labels="">
|
|
252
|
+
{#each overlay.frames as f (f.id)}
|
|
253
|
+
<!-- A frame's name selects it (clicking inside a frame may pick its contents). -->
|
|
254
|
+
<text
|
|
255
|
+
role="button"
|
|
256
|
+
tabindex="-1"
|
|
257
|
+
data-vector-frame-label={f.id}
|
|
258
|
+
x={f.x}
|
|
259
|
+
y={f.y - 6}
|
|
260
|
+
font-size="11"
|
|
261
|
+
font-weight={f.selected ? 600 : 400}
|
|
262
|
+
fill={f.selected ? accentColor : '#737373'}
|
|
263
|
+
style="font-family:system-ui,sans-serif;cursor:default;user-select:none"
|
|
264
|
+
onpointerdown={(e) => {
|
|
265
|
+
e.stopPropagation();
|
|
266
|
+
controller.select([f.id]);
|
|
267
|
+
}}>{f.name}</text
|
|
268
|
+
>
|
|
269
|
+
{/each}
|
|
270
|
+
</g>
|
|
271
|
+
{/if}
|
|
192
272
|
<g data-vector-overlay="" pointer-events="none" fill="none">
|
|
193
273
|
{#each overlay.outlines as o (o.id)}
|
|
194
274
|
<path d={o.d} stroke={accentColor} stroke-width={o.selected ? 1 : 1.5} opacity={o.selected ? 1 : 0.7} />
|
|
@@ -369,6 +449,44 @@
|
|
|
369
449
|
{#if overlay.cutPoint}
|
|
370
450
|
<circle data-vector-cut-point="" cx={overlay.cutPoint.x} cy={overlay.cutPoint.y} r={THEME.handleRadius + 1} fill={THEME.handleFill} stroke={accentColor} />
|
|
371
451
|
{/if}
|
|
452
|
+
{#if overlay.text}
|
|
453
|
+
<g data-vector-text-edit={overlay.text.nodeId} data-editing={overlay.text.editing || undefined}>
|
|
454
|
+
<path d={overlay.text.frame} stroke={accentColor} stroke-dasharray={overlay.text.editing ? '3 2' : undefined} opacity="0.8" />
|
|
455
|
+
{#each overlay.text.selection as d, i (i)}
|
|
456
|
+
<path data-vector-text-selection="" {d} fill={accentColor} fill-opacity="0.25" stroke="none" />
|
|
457
|
+
{/each}
|
|
458
|
+
{#if overlay.text.caret}
|
|
459
|
+
<line data-vector-text-caret="" x1={overlay.text.caret.x1} y1={overlay.text.caret.y1} x2={overlay.text.caret.x2} y2={overlay.text.caret.y2} stroke={accentColor} stroke-width="1.5" />
|
|
460
|
+
{/if}
|
|
461
|
+
{#if overlay.text.overflow}
|
|
462
|
+
<g data-vector-text-overflow="" transform="translate({overlay.text.overflow.x} {overlay.text.overflow.y})">
|
|
463
|
+
<rect x="-5" y="-5" width="10" height="10" fill={THEME.handleFill} stroke="#dc2626" />
|
|
464
|
+
<path d="M-3 0H3M0 -3V3" stroke="#dc2626" />
|
|
465
|
+
</g>
|
|
466
|
+
{/if}
|
|
467
|
+
</g>
|
|
468
|
+
{/if}
|
|
469
|
+
{#each overlay.trackingPoints as t, i (i)}
|
|
470
|
+
<path data-vector-tracking-point="" d="M{t.x - 5} {t.y}H{t.x + 5}M{t.x} {t.y - 5}V{t.y + 5}" stroke={guideColor} stroke-width="1.5" />
|
|
471
|
+
{/each}
|
|
472
|
+
{#if overlay.typedEntry}
|
|
473
|
+
<g data-vector-typed-entry="" transform="translate({overlay.typedEntry.x + 14} {overlay.typedEntry.y + 18})">
|
|
474
|
+
<rect x="-3" y="-12" width={overlay.typedEntry.text.length * 7 + 8} height="17" rx="3" fill={THEME.handleFill} stroke={accentColor} />
|
|
475
|
+
<text x="1" y="1" font-size="11" fill={accentColor} style="font-family:ui-monospace,monospace">{overlay.typedEntry.text}</text>
|
|
476
|
+
</g>
|
|
477
|
+
{/if}
|
|
478
|
+
{#each overlay.connectorSites as s (`${s.nodeId}:${s.side}`)}
|
|
479
|
+
<circle data-vector-connection-site={s.side} data-active={s.active || undefined} cx={s.x} cy={s.y} r={s.active ? 5 : 3.5} fill={s.active ? accentColor : THEME.handleFill} stroke={accentColor} />
|
|
480
|
+
{/each}
|
|
481
|
+
{#each overlay.constraints as c (c.id)}
|
|
482
|
+
<g data-vector-constraint-glyph={c.kind} data-satisfied={c.satisfied ? 'true' : 'false'}>
|
|
483
|
+
{#if c.dimension}<line x1={c.dimension.x1} y1={c.dimension.y1} x2={c.dimension.x2} y2={c.dimension.y2} stroke={c.satisfied ? THEME.guide : '#dc2626'} stroke-dasharray="2 2" />{/if}
|
|
484
|
+
<text x={c.x} y={c.y} font-size="10" text-anchor="middle" fill={c.satisfied ? accentColor : '#dc2626'} stroke="#ffffff" stroke-width="3" paint-order="stroke" style="font-family:system-ui,sans-serif">{c.label}</text>
|
|
485
|
+
</g>
|
|
486
|
+
{/each}
|
|
487
|
+
{#if overlay.textBox}
|
|
488
|
+
<rect data-vector-text-box="" x={overlay.textBox.x} y={overlay.textBox.y} width={overlay.textBox.width} height={overlay.textBox.height} stroke={accentColor} stroke-dasharray="3 2" />
|
|
489
|
+
{/if}
|
|
372
490
|
{#if overlay.marquee}
|
|
373
491
|
<rect
|
|
374
492
|
data-vector-marquee=""
|
|
@@ -405,6 +523,43 @@
|
|
|
405
523
|
<text data-vector-status="" x="8" y="100%" dy="-8" font-size="11" fill={accentColor} style="font-family:system-ui,sans-serif">{overlay.status}</text>
|
|
406
524
|
{/if}
|
|
407
525
|
</g>
|
|
526
|
+
{#if view.textEdit && overlay.text}
|
|
527
|
+
<!-- A hidden textarea at the caret takes typing and composed (IME) text. -->
|
|
528
|
+
<foreignObject x={overlay.text.caret?.x1 ?? 0} y={overlay.text.caret?.y1 ?? 0} width="1" height="1" style="overflow:visible">
|
|
529
|
+
<textarea
|
|
530
|
+
bind:this={textInput}
|
|
531
|
+
data-testid="vector-type-input"
|
|
532
|
+
aria-label="Text"
|
|
533
|
+
autocapitalize="off"
|
|
534
|
+
autocomplete="off"
|
|
535
|
+
spellcheck="false"
|
|
536
|
+
style="position:absolute;width:1px;height:1px;padding:0;border:0;opacity:0;resize:none;overflow:hidden"
|
|
537
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
538
|
+
oninput={onTextInput}
|
|
539
|
+
oncompositionend={onTextCompositionEnd}
|
|
540
|
+
onkeydown={onTextKeyDown}
|
|
541
|
+
onkeyup={(e) => e.stopPropagation()}
|
|
542
|
+
oncopy={onTextClipboard('copy')}
|
|
543
|
+
oncut={onTextClipboard('cut')}
|
|
544
|
+
onpaste={onTextClipboard('paste')}
|
|
545
|
+
></textarea>
|
|
546
|
+
</foreignObject>
|
|
547
|
+
{/if}
|
|
548
|
+
{#if view.symbolEdit}
|
|
549
|
+
<!-- Isolation mode: which symbol, and the way out (Escape with nothing selected works too). -->
|
|
550
|
+
<foreignObject data-vector-symbol-edit={view.symbolEdit.symbolId} x="8" y="8" width="320" height="32" style="overflow:visible">
|
|
551
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
552
|
+
<div
|
|
553
|
+
style="display:inline-flex;align-items:center;gap:8px;padding:4px 8px;border-radius:6px;background:rgba(15,23,42,0.85);color:#fff;font:12px system-ui,sans-serif"
|
|
554
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
555
|
+
ondblclick={(e) => e.stopPropagation()}
|
|
556
|
+
onkeydown={(e) => e.stopPropagation()}
|
|
557
|
+
>
|
|
558
|
+
<span>Symbol: {view.symbolEdit.name}</span>
|
|
559
|
+
<button type="button" aria-label="Exit symbol" style="border:1px solid rgba(255,255,255,0.4);border-radius:4px;padding:0 6px;background:transparent;color:#fff;cursor:pointer" onclick={() => controller.exitSymbolEdit()}>Done</button>
|
|
560
|
+
</div>
|
|
561
|
+
</foreignObject>
|
|
562
|
+
{/if}
|
|
408
563
|
{#if edit && editAt}
|
|
409
564
|
<!-- Width Point Edit (double-click with the width tool), beside the point. -->
|
|
410
565
|
<foreignObject data-vector-width-point-edit="" x={editAt.x + 12} y={editAt.y + 12} width="180" height="140" style="overflow:visible">
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VectorCanvas.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorCanvas.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,
|
|
1
|
+
{"version":3,"file":"VectorCanvas.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorCanvas.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,EASH,KAAK,sBAAsB,EAC5B,MAAM,yBAAyB,CAAC;AASjC,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;AAmaH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
VECTOR_CONNECTOR_ROUTES,
|
|
4
|
+
VECTOR_CONNECTOR_ROUTE_LABELS,
|
|
5
|
+
VECTOR_CONNECTOR_SIDES,
|
|
6
|
+
VECTOR_CONNECTOR_SIDE_LABELS,
|
|
7
|
+
getVectorConnectorPanel,
|
|
8
|
+
type VectorConnectorRoute,
|
|
9
|
+
type VectorConnectorSide,
|
|
10
|
+
type VectorEditorController
|
|
11
|
+
} from '@pixerate/editor/vector';
|
|
12
|
+
import { vectorStateOf } from './subscribe.svelte.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The selected connector: its route (straight, elbow, curved), the elbow corner radius, and for
|
|
16
|
+
* each glued end the side it meets (Auto: the side facing the other end) and Unglue. Each change
|
|
17
|
+
* is one undo step.
|
|
18
|
+
*/
|
|
19
|
+
interface Props {
|
|
20
|
+
controller: VectorEditorController;
|
|
21
|
+
class?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let { controller, class: className = '' }: Props = $props();
|
|
25
|
+
|
|
26
|
+
const store = vectorStateOf(() => controller);
|
|
27
|
+
const view = $derived(store.current);
|
|
28
|
+
const panel = $derived(getVectorConnectorPanel(view));
|
|
29
|
+
const ids = $derived(panel ? [panel.id] : []);
|
|
30
|
+
const ends = $derived(
|
|
31
|
+
panel
|
|
32
|
+
? [
|
|
33
|
+
{ key: 'start' as const, label: 'Start', e: panel.start },
|
|
34
|
+
{ key: 'end' as const, label: 'End', e: panel.end }
|
|
35
|
+
]
|
|
36
|
+
: []
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const buttonClass =
|
|
40
|
+
'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';
|
|
41
|
+
const selectClass = 'h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs';
|
|
42
|
+
const numberClass = 'h-7 w-16 rounded border border-border bg-background px-1.5 text-xs';
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
{#if panel}
|
|
46
|
+
<div class="space-y-2 {className}" data-vector-connector-panel={panel.id} aria-label="Connector">
|
|
47
|
+
<div class="flex items-center gap-1">
|
|
48
|
+
<span class="w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">Connector</span>
|
|
49
|
+
<select aria-label="Connector route" class={selectClass} value={panel.route} onchange={(e) => controller.setConnector({ route: e.currentTarget.value as VectorConnectorRoute }, { ids })}>
|
|
50
|
+
{#each VECTOR_CONNECTOR_ROUTES as r (r)}<option value={r}>{VECTOR_CONNECTOR_ROUTE_LABELS[r]}</option>{/each}
|
|
51
|
+
</select>
|
|
52
|
+
{#if panel.route === 'elbow'}
|
|
53
|
+
<input
|
|
54
|
+
type="number"
|
|
55
|
+
min="0"
|
|
56
|
+
step="1"
|
|
57
|
+
aria-label="Elbow radius"
|
|
58
|
+
title="Corner radius of the bends"
|
|
59
|
+
class={numberClass}
|
|
60
|
+
value={panel.radius}
|
|
61
|
+
oninput={(e) => {
|
|
62
|
+
const n = parseFloat(e.currentTarget.value);
|
|
63
|
+
if (Number.isFinite(n) && n >= 0) controller.setConnector({ radius: n }, { ids });
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
{/if}
|
|
67
|
+
</div>
|
|
68
|
+
{#each ends as { key, label, e } (key)}
|
|
69
|
+
<div class="flex items-center gap-1" data-vector-connector-end={key}>
|
|
70
|
+
<span class="w-16 text-xs text-foreground">{label}</span>
|
|
71
|
+
{#if e}
|
|
72
|
+
<select
|
|
73
|
+
aria-label="{label} side"
|
|
74
|
+
class={selectClass}
|
|
75
|
+
value={e.side ?? 'auto'}
|
|
76
|
+
onchange={(ev) => controller.setConnector({ [key]: { nodeId: e.nodeId, side: ev.currentTarget.value as VectorConnectorSide } }, { ids })}
|
|
77
|
+
>
|
|
78
|
+
{#each VECTOR_CONNECTOR_SIDES as s (s)}<option value={s}>{VECTOR_CONNECTOR_SIDE_LABELS[s]}</option>{/each}
|
|
79
|
+
</select>
|
|
80
|
+
<button type="button" class={buttonClass} title="Unglue: the end stays where it is" aria-label="Unglue {label.toLowerCase()}" onclick={() => controller.setConnector({ [key]: null }, { ids })}>Unglue</button>
|
|
81
|
+
{:else}
|
|
82
|
+
<span class="text-[11px] text-muted-foreground">Free</span>
|
|
83
|
+
{/if}
|
|
84
|
+
</div>
|
|
85
|
+
{/each}
|
|
86
|
+
</div>
|
|
87
|
+
{/if}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type VectorEditorController } from '@pixerate/editor/vector';
|
|
2
|
+
/**
|
|
3
|
+
* The selected connector: its route (straight, elbow, curved), the elbow corner radius, and for
|
|
4
|
+
* each glued end the side it meets (Auto: the side facing the other end) and Unglue. Each change
|
|
5
|
+
* is one undo step.
|
|
6
|
+
*/
|
|
7
|
+
interface Props {
|
|
8
|
+
controller: VectorEditorController;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const VectorConnectorPanel: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type VectorConnectorPanel = ReturnType<typeof VectorConnectorPanel>;
|
|
13
|
+
export default VectorConnectorPanel;
|
|
14
|
+
//# sourceMappingURL=VectorConnectorPanel.svelte.d.ts.map
|
|
@@ -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"}
|