@pixerate/editor-svelte 0.140.0 → 0.142.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/SelectionOptionsBar.svelte +40 -0
- package/dist/image-editor/TypeOptionsBar.svelte +42 -6
- package/dist/image-editor/TypeOptionsBar.svelte.d.ts +2 -2
- package/dist/vector/VectorTextPanel.svelte +19 -1
- package/dist/vector/VectorTextPanel.svelte.d.ts +2 -1
- package/package.json +2 -2
|
@@ -203,6 +203,46 @@
|
|
|
203
203
|
{#if options.tool === 'polygon'}
|
|
204
204
|
<p class="text-muted-foreground">Click to add corners (Shift for 45°), drag for a free-hand stretch; double-click, Enter or a click on the first corner closes it, Backspace removes a corner, Escape cancels.</p>
|
|
205
205
|
{/if}
|
|
206
|
+
{#if options.tool === 'type' || options.tool === 'type-vertical'}
|
|
207
|
+
<div data-testid="type-mask-options" class="flex flex-col gap-2">
|
|
208
|
+
<p class="text-muted-foreground">
|
|
209
|
+
{options.tool === 'type-vertical' ? 'Click where the column starts: the letters are stacked down from there, centred on the click.' : 'Click where the text starts (its baseline).'} Shift-click adds, Alt-click subtracts.
|
|
210
|
+
</p>
|
|
211
|
+
<label class="flex items-center gap-1 text-muted-foreground">
|
|
212
|
+
Text
|
|
213
|
+
<input
|
|
214
|
+
type="text"
|
|
215
|
+
aria-label="Type mask text"
|
|
216
|
+
value={options.typeText ?? 'Type'}
|
|
217
|
+
oninput={(e) => set({ typeText: (e.currentTarget as HTMLInputElement).value })}
|
|
218
|
+
class="min-w-0 flex-1 bg-background border border-border rounded px-1 py-0.5 text-foreground"
|
|
219
|
+
/>
|
|
220
|
+
</label>
|
|
221
|
+
<label class="flex items-center gap-1 text-muted-foreground">
|
|
222
|
+
Size
|
|
223
|
+
<input
|
|
224
|
+
type="number"
|
|
225
|
+
min="1"
|
|
226
|
+
step="1"
|
|
227
|
+
aria-label="Type mask size"
|
|
228
|
+
value={options.typeSize ?? 72}
|
|
229
|
+
oninput={(e) => set({ typeSize: Math.max(1, Number((e.currentTarget as HTMLInputElement).value) || 1) })}
|
|
230
|
+
class={numberInput}
|
|
231
|
+
/>
|
|
232
|
+
px
|
|
233
|
+
</label>
|
|
234
|
+
<label class="flex items-center gap-1 text-muted-foreground">
|
|
235
|
+
Font
|
|
236
|
+
<input
|
|
237
|
+
type="text"
|
|
238
|
+
aria-label="Type mask font"
|
|
239
|
+
value={options.typeFamily ?? 'sans-serif'}
|
|
240
|
+
oninput={(e) => set({ typeFamily: (e.currentTarget as HTMLInputElement).value })}
|
|
241
|
+
class="min-w-0 flex-1 bg-background border border-border rounded px-1 py-0.5 text-foreground"
|
|
242
|
+
/>
|
|
243
|
+
</label>
|
|
244
|
+
</div>
|
|
245
|
+
{/if}
|
|
206
246
|
{#if options.tool === 'range'}
|
|
207
247
|
<div data-testid="color-range-options" class="flex flex-col gap-2">
|
|
208
248
|
<p class="text-muted-foreground">Click a colour to select it; Shift-click adds a colour, Alt-click removes one.</p>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import ImageColor from './controls/ImageColor.svelte';
|
|
3
3
|
import { imageChipClass } from './styles.js';
|
|
4
|
-
import type { ImageEditorController, TextAlign, TextEditSession, TextStyle, TypeToolOptions } from '@pixerate/editor/image-editor';
|
|
5
|
-
import { TEXT_ALIGNS } from '@pixerate/editor/image-editor';
|
|
4
|
+
import type { ImageEditorController, TextAlign, TextEditSession, TextOrientation, TextStyle, TypeToolOptions } from '@pixerate/editor/image-editor';
|
|
5
|
+
import { TEXT_ALIGNS, TEXT_ORIENTATIONS } from '@pixerate/editor/image-editor';
|
|
6
6
|
import { untrack } from 'svelte';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* The type tool's options: font family (from the editor's fonts), size, bold, italic, colour
|
|
10
|
-
* alignment. While text is being edited they style the selection (or the whole text); with a
|
|
9
|
+
* The type tool's options: font family (from the editor's fonts), size, bold, italic, colour,
|
|
10
|
+
* alignment and orientation (horizontal or vertical type, with upright Latin in vertical text). While text is being edited they style the selection (or the whole text); with a
|
|
11
11
|
* type layer active they style all of it; otherwise they set what the next text uses.
|
|
12
12
|
*/
|
|
13
13
|
interface Props {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
return untrack(() => s.subscribe(() => tick++));
|
|
33
33
|
});
|
|
34
34
|
const ALIGN_LABELS: Record<TextAlign, string> = { left: 'Left', center: 'Centre', right: 'Right', justify: 'Justify' };
|
|
35
|
+
const ORIENTATION_LABELS: Record<TextOrientation, string> = { horizontal: 'Horizontal', vertical: 'Vertical' };
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
const view = $derived.by(() => {
|
|
@@ -45,9 +46,18 @@
|
|
|
45
46
|
...(here.fontWeight ? { fontWeight: here.fontWeight } : {}),
|
|
46
47
|
...(session?.alive ? { italic: Boolean(here.italic), color: here.color ?? options.color } : {}),
|
|
47
48
|
};
|
|
49
|
+
const typeId = !session && active?.type === 'type' ? active.id : null;
|
|
50
|
+
// The orientation of the text being edited or the active type layer, else the tool's.
|
|
51
|
+
const editedId = session?.alive ? session.layerId : typeId;
|
|
52
|
+
const edited = editedId ? controller.getLayer(editedId) : null;
|
|
53
|
+
const orientation: TextOrientation = edited?.type === 'type' ? (edited.orientation ?? 'horizontal') : (options.orientation ?? 'horizontal');
|
|
54
|
+
const upright = Boolean(session?.alive ? here.upright : edited?.type === 'type' && edited.runs[0]?.style.upright);
|
|
48
55
|
return {
|
|
49
56
|
shown,
|
|
50
|
-
|
|
57
|
+
editedId,
|
|
58
|
+
orientation,
|
|
59
|
+
upright,
|
|
60
|
+
typeId,
|
|
51
61
|
missing: active?.type === 'type' ? (active.missingFonts ?? []) : [],
|
|
52
62
|
fonts: controller.listFonts(),
|
|
53
63
|
};
|
|
@@ -73,6 +83,24 @@
|
|
|
73
83
|
error = e instanceof Error ? e.message : String(e);
|
|
74
84
|
}
|
|
75
85
|
}
|
|
86
|
+
function orient(next: TextOrientation) {
|
|
87
|
+
onchange({ ...options, orientation: next });
|
|
88
|
+
try {
|
|
89
|
+
error = null;
|
|
90
|
+
if (view.editedId) controller.setTextOrientation(view.editedId, next);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
error = e instanceof Error ? e.message : String(e);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function toggleUpright() {
|
|
96
|
+
try {
|
|
97
|
+
error = null;
|
|
98
|
+
if (session?.alive) session.setStyle({ upright: !view.upright });
|
|
99
|
+
else if (view.typeId) controller.setTextStyle(view.typeId, { upright: !view.upright });
|
|
100
|
+
} catch (e) {
|
|
101
|
+
error = e instanceof Error ? e.message : String(e);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
76
104
|
const value = (e: Event) => (e.target as HTMLInputElement).value;
|
|
77
105
|
</script>
|
|
78
106
|
|
|
@@ -108,7 +136,15 @@
|
|
|
108
136
|
<button type="button" aria-pressed={options.align === a} class={imageChipClass(options.align === a)} onclick={() => style({ align: a })}>{ALIGN_LABELS[a]}</button>
|
|
109
137
|
{/each}
|
|
110
138
|
</div>
|
|
111
|
-
<
|
|
139
|
+
<div class="flex gap-1" role="toolbar" aria-label="Orientation">
|
|
140
|
+
{#each TEXT_ORIENTATIONS as o (o)}
|
|
141
|
+
<button type="button" aria-pressed={view.orientation === o} class={imageChipClass(view.orientation === o)} onclick={() => orient(o)}>{ORIENTATION_LABELS[o]}</button>
|
|
142
|
+
{/each}
|
|
143
|
+
{#if view.orientation === 'vertical' && view.editedId}
|
|
144
|
+
<button type="button" aria-pressed={view.upright} class={imageChipClass(view.upright)} title="Set Latin upright, one letter under the other (else turned sideways)" onclick={toggleUpright}>Upright Latin</button>
|
|
145
|
+
{/if}
|
|
146
|
+
</div>
|
|
147
|
+
<p class="text-[10px] text-muted-foreground">Click to type point text (vertical: a column down from the click), drag for a paragraph box, click text to edit it. Esc finishes.</p>
|
|
112
148
|
{#if view.missing.length > 0}
|
|
113
149
|
<p role="status" class="text-amber-600" data-testid="type-missing-fonts">Missing {view.missing.length === 1 ? 'font' : 'fonts'}: {view.missing.join(', ')}</p>
|
|
114
150
|
{/if}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ImageEditorController, TextEditSession, TypeToolOptions } from '@pixerate/editor/image-editor';
|
|
2
2
|
/**
|
|
3
|
-
* The type tool's options: font family (from the editor's fonts), size, bold, italic, colour
|
|
4
|
-
* alignment. While text is being edited they style the selection (or the whole text); with a
|
|
3
|
+
* The type tool's options: font family (from the editor's fonts), size, bold, italic, colour,
|
|
4
|
+
* alignment and orientation (horizontal or vertical type, with upright Latin in vertical text). While text is being edited they style the selection (or the whole text); with a
|
|
5
5
|
* type layer active they style all of it; otherwise they set what the next text uses.
|
|
6
6
|
*/
|
|
7
7
|
interface Props {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
VECTOR_FONT_WEIGHTS,
|
|
4
4
|
VECTOR_TEXT_ALIGN_OPTIONS,
|
|
5
5
|
applyVectorParagraphStyle,
|
|
6
|
+
applyVectorTextOrientation,
|
|
6
7
|
applyVectorTextStyle,
|
|
7
8
|
getVectorTextPanel,
|
|
8
9
|
type VectorEditorController,
|
|
@@ -12,7 +13,8 @@
|
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Character and paragraph style of text: family, weight, size, italic, underline, strike-through,
|
|
15
|
-
* tracking, leading, alignment
|
|
16
|
+
* tracking, leading, alignment, horizontal / vertical orientation (with upright Latin in vertical
|
|
17
|
+
* text) and point / area text. It edits the selected characters while text
|
|
16
18
|
* is being edited, else the selected text, else the defaults for new text. Type on a Path turns a
|
|
17
19
|
* selected path into path text, and Create Outlines turns text into paths (with an outline
|
|
18
20
|
* shaper). Each change is one undo step.
|
|
@@ -108,6 +110,22 @@
|
|
|
108
110
|
<button type="button" class={buttonClass} aria-label="Underline" aria-pressed={f.underline === true} onclick={() => style({ underline: !f.underline })}><u>U</u></button>
|
|
109
111
|
<button type="button" class={buttonClass} aria-label="Strike through" aria-pressed={f.strikethrough === true} onclick={() => style({ strikethrough: !f.strikethrough })}><s>S</s></button>
|
|
110
112
|
</div>
|
|
113
|
+
{#if panel.target !== 'defaults'}
|
|
114
|
+
<div class="flex flex-wrap items-center gap-1" role="group" aria-label="Orientation">
|
|
115
|
+
<button type="button" class={buttonClass} aria-pressed={panel.orientation === 'horizontal'} title="Horizontal text: lines left to right" onclick={() => applyVectorTextOrientation(controller, 'horizontal')}>Horizontal</button>
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
class={buttonClass}
|
|
119
|
+
aria-pressed={panel.orientation === 'vertical'}
|
|
120
|
+
disabled={!panel.canOrient}
|
|
121
|
+
title={panel.canOrient ? 'Vertical text: columns top to bottom, right to left' : 'Text on a path cannot be vertical'}
|
|
122
|
+
onclick={() => applyVectorTextOrientation(controller, 'vertical')}>Vertical</button
|
|
123
|
+
>
|
|
124
|
+
{#if panel.orientation === 'vertical'}
|
|
125
|
+
<button type="button" class={buttonClass} aria-pressed={f.upright === true} title="Set Latin upright, one letter under the other (else turned sideways)" onclick={() => style({ upright: !f.upright })}>Upright Latin</button>
|
|
126
|
+
{/if}
|
|
127
|
+
</div>
|
|
128
|
+
{/if}
|
|
111
129
|
<div class="flex items-center gap-1">
|
|
112
130
|
<label class="flex flex-1 items-center gap-1 text-xs text-foreground" title="Tracking (1/1000 em)">
|
|
113
131
|
<span class="text-[11px] text-muted-foreground">VA</span>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type VectorEditorController } from '@pixerate/editor/vector';
|
|
2
2
|
/**
|
|
3
3
|
* Character and paragraph style of text: family, weight, size, italic, underline, strike-through,
|
|
4
|
-
* tracking, leading, alignment
|
|
4
|
+
* tracking, leading, alignment, horizontal / vertical orientation (with upright Latin in vertical
|
|
5
|
+
* text) and point / area text. It edits the selected characters while text
|
|
5
6
|
* is being edited, else the selected text, else the defaults for new text. Type on a Path turns a
|
|
6
7
|
* selected path into path text, and Create Outlines turns text into paths (with an outline
|
|
7
8
|
* shaper). Each change is one undo step.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.142.0",
|
|
4
4
|
"description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"**/*.css"
|
|
166
166
|
],
|
|
167
167
|
"dependencies": {
|
|
168
|
-
"@pixerate/editor": "0.
|
|
168
|
+
"@pixerate/editor": "0.193.0"
|
|
169
169
|
},
|
|
170
170
|
"peerDependencies": {
|
|
171
171
|
"@dagrejs/dagre": "^1.1.4",
|