@widgetic/editor 4.0.11 → 4.1.2
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/README.md +124 -0
- package/dist/components/PreviewChromeControls.svelte +84 -0
- package/dist/components/PreviewChromeControls.svelte.d.ts +35 -0
- package/dist/components/WidgetPreview.svelte +90 -129
- package/dist/components/WidgetPreview.svelte.d.ts +1 -0
- package/dist/components/color-select/ColorSelect.svelte +4 -4
- package/dist/components/editors/CompositionEditorApp.svelte +69 -15
- package/dist/components/editors/CompositionEditorApp.svelte.d.ts +20 -0
- package/dist/components/editors/EmbedEditor.svelte +33 -22
- package/dist/components/editors/EmbedEditor.svelte.d.ts +1 -1
- package/dist/components/editors/PropsEditor.svelte +74 -58
- package/dist/components/editors/PropsEditor.svelte.d.ts +3 -3
- package/dist/components/ic/AngleInputController.svelte +1 -1
- package/dist/components/ic/BrowserInputController.svelte +37 -29
- package/dist/components/ic/ColorPickerInputController.svelte +31 -15
- package/dist/components/ic/DateFieldInputController.svelte +27 -11
- package/dist/components/ic/DatePickerInputController.svelte +27 -17
- package/dist/components/ic/DropdownInputController.svelte +10 -5
- package/dist/components/ic/DropdownInputController.svelte.d.ts +1 -1
- package/dist/components/ic/FixedPositionInputController.svelte +12 -6
- package/dist/components/ic/FontInputController.svelte +32 -27
- package/dist/components/ic/InputControllerHeader.svelte +5 -1
- package/dist/components/ic/NumberControls.svelte +4 -4
- package/dist/components/ic/NumberInputController.svelte +2 -2
- package/dist/components/ic/PositionInputController.svelte +9 -4
- package/dist/components/ic/RangeCalendarInputController.svelte +26 -16
- package/dist/components/ic/RoundedCornersInputController.svelte +22 -17
- package/dist/components/ic/SliderInputController.svelte +7 -2
- package/dist/components/ic/SwitchInputController.svelte +12 -3
- package/dist/components/ic/TagsInputController.svelte +19 -4
- package/dist/components/ic/TextAreaInputController.svelte +59 -44
- package/dist/components/ic/TextAreaPreviewInputController.svelte +8 -3
- package/dist/components/ic/TextInputController.svelte +17 -1
- package/dist/components/ic/TimeInputController.svelte +2 -1
- package/dist/components/ic/VectorInputController.svelte +21 -16
- package/dist/components/ic/shared/ItemGrid.svelte +7 -3
- package/dist/components/ic/shared/RecentItemsSection.svelte +16 -11
- package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +1 -1
- package/dist/composition/compositionEditorController.d.ts +11 -2
- package/dist/composition/compositionEditorController.js +33 -21
- package/dist/composition/compositionHelpers.d.ts +3 -1
- package/dist/composition/compositionHelpers.js +4 -2
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.js +4 -0
- package/dist/i18n/labels.d.ts +15 -0
- package/dist/i18n/labels.js +57 -0
- package/dist/i18n/language.d.ts +228 -0
- package/dist/i18n/language.js +34 -0
- package/dist/i18n/languageKit.d.ts +54 -0
- package/dist/i18n/languageKit.js +102 -0
- package/dist/i18n/messages.d.ts +241 -0
- package/dist/i18n/messages.js +253 -0
- package/dist/i18n/translate.d.ts +6 -0
- package/dist/i18n/translate.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/utils/dockPlacement.d.ts +12 -0
- package/dist/utils/dockPlacement.js +13 -0
- package/dist/utils/fieldDirection.d.ts +13 -0
- package/dist/utils/fieldDirection.js +25 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -152,6 +152,130 @@ has no preview address (`WIDGET_NOT_PUBLISHED`): its settings stay editable and
|
|
|
152
152
|
It is a Svelte component for a page that can import it, and it takes the page's styles and fonts as they are. A
|
|
153
153
|
plain-JavaScript module that works on any page, inside a shadow root, is a separate step.
|
|
154
154
|
|
|
155
|
+
## Languages
|
|
156
|
+
|
|
157
|
+
The editor shows English until it is told otherwise. `CompositionEditorApp` takes two props, and both can change while
|
|
158
|
+
the editor is open:
|
|
159
|
+
|
|
160
|
+
```svelte
|
|
161
|
+
<CompositionEditorApp
|
|
162
|
+
{compositionId}
|
|
163
|
+
{apiBasePath}
|
|
164
|
+
{getAccessToken}
|
|
165
|
+
locale="de"
|
|
166
|
+
messages={{
|
|
167
|
+
'editor.props.save': 'Speichern',
|
|
168
|
+
'editor.app.tryAgain': 'Erneut versuchen',
|
|
169
|
+
'action.cancel': 'Abbrechen',
|
|
170
|
+
'shadEditor.toolbar.bold': 'Fett (⌘B)'
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
- **`locale`** is a BCP 47 language tag (`de`, `pt-BR`, `ar-EG`). It decides the plural rules and how numbers, dates and the
|
|
176
|
+
names of months and weekdays are written (all through `Intl`). It translates nothing. Blank or left out is English.
|
|
177
|
+
- **`messages`** replaces words, and it is ONE flat object for everything inside the editor: the editor's own words
|
|
178
|
+
(keys that start with `editor.`), the File Browser that opens from its file pickers (`action.`, `upload.`, `dialog.` ...,
|
|
179
|
+
see the File Browser's README, "Languages") and the rich text editor of its text fields (`shadEditor.`). Give the keys
|
|
180
|
+
you want to change; every key you leave out stays English, so a language can be added one screen at a time. A key that
|
|
181
|
+
does not exist is ignored, and the console says `[Editor] ...` once, with the key you probably meant.
|
|
182
|
+
|
|
183
|
+
`ALL_EDITOR_MESSAGES` is the list to hand to a translator: every key of the three catalogs with its English text.
|
|
184
|
+
`EDITOR_MESSAGES` is the editor's own part of it.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { ALL_EDITOR_MESSAGES, EDITOR_MESSAGES } from '@widgetic/editor';
|
|
188
|
+
|
|
189
|
+
console.log(Object.keys(ALL_EDITOR_MESSAGES).length); // every word a person can read in the editor
|
|
190
|
+
console.log(EDITOR_MESSAGES['editor.preview.compilationErrorsDetail']); // 'The generated code has syntax or compilation errors (HTTP {status}).'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
A message is text. `{name}` puts a value in, and `{count, plural, one {# file} other {# files}}` picks a form for a
|
|
194
|
+
number in the page's language (the format is the design system's: `@widgetic/design-system/i18n`). Each English message
|
|
195
|
+
names the values it is given; a translation may leave one out or use them in any order, but it cannot ask for a value the
|
|
196
|
+
message is never given.
|
|
197
|
+
|
|
198
|
+
**Finding a mistake before a person does.** `checkEditorMessages(messages)` checks a `messages` object without showing
|
|
199
|
+
anything: a key that does not exist (with the nearest real one), a value that is not text, a template that does not parse,
|
|
200
|
+
a message that asks for a value it is never given. An empty list means every message will be used. Put it in a test of your
|
|
201
|
+
page; `describeMessageProblem` (from `@widgetic/design-system/i18n`) turns each problem into a sentence:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { checkEditorMessages } from '@widgetic/editor';
|
|
205
|
+
import { describeMessageProblem } from '@widgetic/design-system/i18n';
|
|
206
|
+
import german from './editor.de.json';
|
|
207
|
+
|
|
208
|
+
expect(checkEditorMessages(german).map(describeMessageProblem)).toEqual([]);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
While the editor runs, a message that cannot be used is never thrown: the English text of that key is shown.
|
|
212
|
+
|
|
213
|
+
**Parts used on their own.** `PropsEditor`, `EmbedEditor` and `WidgetPreview` read their words from the language of the
|
|
214
|
+
`CompositionEditorApp` they sit in. A page that mounts one of them without it says English, unless a component above it
|
|
215
|
+
calls `provideEditorLanguage({ locale, messages })` while it is being made (it gives back a handle whose `update(...)`
|
|
216
|
+
changes the language later). `useEditorLanguage()` is what the parts read it with.
|
|
217
|
+
|
|
218
|
+
**What stays as it is**
|
|
219
|
+
|
|
220
|
+
- **The widget's own words.** The names, descriptions and options of a widget's settings are written by the person who made
|
|
221
|
+
the widget, and are shown as written.
|
|
222
|
+
- **What is saved with the composition.** The title of a new item ("Item 03"), "Untitled Composition" and the names a font
|
|
223
|
+
weight or a position is saved under ("ExtraBold", "top-left") are kept in English whatever the language. The editor shows
|
|
224
|
+
the weights, the tabs of the font and vector lists and the positions in the language; it saves the English name.
|
|
225
|
+
- **What the editor tells the page's code.** `error` events, `failure.message` and the other event details stay English: they
|
|
226
|
+
are for your logs. The person is told by the editor's own screen, in the language.
|
|
227
|
+
- **What comes from outside.** A sentence the Widgetic API sends back, or a media library you give the file pickers, is
|
|
228
|
+
shown as it arrived.
|
|
229
|
+
- **A sentence the editor said once** (why there is no preview) keeps the language it was said in until the editor says
|
|
230
|
+
something new. Every label and button changes at once, and so do the banners of a composition that could not be loaded and
|
|
231
|
+
of a save that failed: they are written when they are drawn.
|
|
232
|
+
- **Dates.** Months and weekdays follow `locale`, and so does the way the range control shows the chosen dates: 7 October 2026
|
|
233
|
+
is `10/07/26` with `en` (and with no `locale`), `07/10/26` with `en-GB` and `07.10.26` with `de`. In a language written right
|
|
234
|
+
to left a calendar writes each weekday in one letter (Arabic `ح ن ث ر خ ج س`) and gives a screen reader the whole name;
|
|
235
|
+
where the browser has no one-letter names (Urdu) the short names stay.
|
|
236
|
+
- **Right to left.** The layout turns around when the editor is given `dir="rtl"` (see "Right to left" below). For a language written right to left a name that is put inside a sentence sits between two invisible marks (Unicode isolates U+2068 and U+2069, made by the shared translator), so the words around it cannot turn it around; a test that compares text has to leave them out.
|
|
237
|
+
- **The design system.** The editor needs `@widgetic/design-system` **0.5.16 or newer** and a `@widgetic/file-browser` that
|
|
238
|
+
takes `locale` and `messages`: its words come from the design system's `@widgetic/design-system/i18n`.
|
|
239
|
+
|
|
240
|
+
The `@widgetic/ui-sdk` module of the composition editor takes the same `locale` and `messages` and adds `setLanguage()`.
|
|
241
|
+
|
|
242
|
+
### Right to left
|
|
243
|
+
|
|
244
|
+
Arabic, Hebrew, Persian and Urdu start a line on the right. `CompositionEditorApp` takes one more prop for that, and it can
|
|
245
|
+
change while the editor is open:
|
|
246
|
+
|
|
247
|
+
```svelte
|
|
248
|
+
<CompositionEditorApp {compositionId} {apiBasePath} {getAccessToken} locale="ar" messages={arabic} dir="rtl" />
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
- **`dir`** is `'rtl'` or `'ltr'`. It is not taken from `locale`: it says which way the screen is read, and a page decides
|
|
252
|
+
that (`resolveTextDirection(locale)` from `@widgetic/design-system/i18n` answers it for a language tag; the `@widgetic/ui-sdk`
|
|
253
|
+
module does exactly this). Left out, the editor follows the direction of the screen it sits in (a component above it that
|
|
254
|
+
calls `provideTextDirection(store)` from `@widgetic/design-system/components/textDirection`), and with neither it is left
|
|
255
|
+
to right, as it always was. The editor writes `dir` on its own root, so it is read the right way wherever it is put.
|
|
256
|
+
- **What turns around.** The settings editor and the preview float over the editor's area and are put in place in pixels
|
|
257
|
+
when the editor appears and again when `dir` changes: on a right to left screen the settings editor is docked at the
|
|
258
|
+
right of the middle and the preview left of it, with the same gaps (`leftOnThisScreen`, `src/lib/utils/dockPlacement.ts`).
|
|
259
|
+
The Design and Content tabs, the label and the control of a row, the calendars (their arrows are mirrored), the sliders,
|
|
260
|
+
the switches and the file picker (the File Browser, which opens inside the editor's own root and is read the way the root
|
|
261
|
+
is) all start at the start of the line; the arrow keys of the date fields follow it, the help tip opens at the end of
|
|
262
|
+
the line, and each tab panel slides in from the side its tab is on.
|
|
263
|
+
- **What a person types** is read by its own first letter on a right to left screen (`directionOfFieldValue`,
|
|
264
|
+
`src/lib/utils/fieldDirection.ts`): a sentence in Latin letters is set from the left and one in Arabic from the right,
|
|
265
|
+
in the same form. A field with nothing in it yet has no first letter (`dir="auto"` on an empty field reads left to right,
|
|
266
|
+
which would put the hint and the caret at the wrong edge), so it takes the direction of the screen. The kinds `email` and
|
|
267
|
+
`url` are addresses and stay left to right on every screen. Note that the kind a schema gives a plain text field is
|
|
268
|
+
`textInput`, not an HTML `type`: only those two names are addresses.
|
|
269
|
+
- **Left to right on every screen.** The colour code, the drawing of the colour picker (saturation and hue), the grid that
|
|
270
|
+
places a box and the picker of the corners of a rounded box are drawings, not lines of text, and say `dir="ltr"` themselves.
|
|
271
|
+
- **Keeping it so.** `tests/editor-placement.test.mjs` reads the source of the editor with `auditPhysicalPlacement` from
|
|
272
|
+
`@widgetic/design-system/rtl/audit` and fails on a class or a style that puts something on a physical side (`left`,
|
|
273
|
+
`right`, `ml-`, `pr-`, `text-left`, four-value shorthands such as `padding: 4px 6px 4px 0` ...): write `start`/`end`
|
|
274
|
+
(`ms-`, `pe-`, `text-start`, `border-s`) or say why with the note `rtl-ok` on the same line or the line above.
|
|
275
|
+
`tests/editor-direction.test.mjs` pins the rest. A panel that went off the screen in Arabic (a `position: relative` box
|
|
276
|
+
is moved from where it sits in the flow, which on a right to left screen is the right edge) and a field that never read
|
|
277
|
+
its text by its own letter were not found by the audit but by looking at a screen: look at one.
|
|
278
|
+
|
|
155
279
|
## Input Controllers
|
|
156
280
|
|
|
157
281
|
The component uses the input controllers to edit the widget properties.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let className = '';
|
|
3
|
+
export { className as class };
|
|
4
|
+
export let style = '';
|
|
5
|
+
/** Desktop is the unpressed state of the phone icon. */
|
|
6
|
+
export let mobile = false;
|
|
7
|
+
export let showDevices = true;
|
|
8
|
+
export let showDimensions = true;
|
|
9
|
+
export let widthValue = '';
|
|
10
|
+
export let heightValue = '';
|
|
11
|
+
export let minWidth = 120;
|
|
12
|
+
export let minHeight = 80;
|
|
13
|
+
export let onDevice: (mobile: boolean) => void = () => {};
|
|
14
|
+
export let onWidth: (value: string) => void = () => {};
|
|
15
|
+
export let onHeight: (value: string) => void = () => {};
|
|
16
|
+
export let desktopLabel = 'Desktop preview';
|
|
17
|
+
export let mobileLabel = 'Mobile preview';
|
|
18
|
+
export let widthLabel = 'W';
|
|
19
|
+
export let heightLabel = 'H';
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<div class="preview-chrome-controls flex items-center gap-2 {className}" style={style}>
|
|
23
|
+
{#if showDevices}
|
|
24
|
+
<div class="preview-chrome-device-toggle flex shrink-0 items-center overflow-hidden rounded border border-gray-300">
|
|
25
|
+
<button
|
|
26
|
+
type="button"
|
|
27
|
+
class="preview-chrome-device-desktop flex h-6 w-6 items-center justify-center {!mobile ? 'bg-gray-100 text-gray-800' : 'bg-white text-gray-400'} hover:text-gray-800 focus:outline-none focus-visible:outline-none"
|
|
28
|
+
aria-label={desktopLabel}
|
|
29
|
+
aria-pressed={!mobile}
|
|
30
|
+
onclick={() => onDevice(false)}
|
|
31
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
32
|
+
>
|
|
33
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
34
|
+
</button>
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
class="preview-chrome-device-mobile flex h-6 w-6 items-center justify-center border-s border-gray-300 {mobile ? 'bg-gray-100 text-gray-800' : 'bg-white text-gray-400'} hover:text-gray-800 focus:outline-none focus-visible:outline-none"
|
|
38
|
+
aria-label={mobileLabel}
|
|
39
|
+
aria-pressed={mobile}
|
|
40
|
+
onclick={() => onDevice(true)}
|
|
41
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
42
|
+
>
|
|
43
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
{/if}
|
|
47
|
+
{#if showDimensions}
|
|
48
|
+
<div class="preview-chrome-dimensions flex items-center gap-1.5">
|
|
49
|
+
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
50
|
+
<span>{widthLabel}</span>
|
|
51
|
+
<input
|
|
52
|
+
type="number"
|
|
53
|
+
min={minWidth}
|
|
54
|
+
class="preview-dim-input w-14 h-6 rounded border border-gray-300 bg-white px-1 text-[11px] text-gray-800 focus:outline-none focus:border-blue-400 focus-visible:outline-none"
|
|
55
|
+
value={widthValue}
|
|
56
|
+
oninput={(event) => onWidth(event.currentTarget.value)}
|
|
57
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
58
|
+
/>
|
|
59
|
+
</label>
|
|
60
|
+
<span class="text-[10px] text-gray-400">×</span>
|
|
61
|
+
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
62
|
+
<span>{heightLabel}</span>
|
|
63
|
+
<input
|
|
64
|
+
type="number"
|
|
65
|
+
min={minHeight}
|
|
66
|
+
class="preview-dim-input w-14 h-6 rounded border border-gray-300 bg-white px-1 text-[11px] text-gray-800 focus:outline-none focus:border-blue-400 focus-visible:outline-none"
|
|
67
|
+
value={heightValue}
|
|
68
|
+
oninput={(event) => onHeight(event.currentTarget.value)}
|
|
69
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
70
|
+
/>
|
|
71
|
+
</label>
|
|
72
|
+
</div>
|
|
73
|
+
{/if}
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<style>
|
|
77
|
+
.preview-chrome-controls button:focus,
|
|
78
|
+
.preview-chrome-controls button:focus-visible,
|
|
79
|
+
.preview-chrome-controls input:focus,
|
|
80
|
+
.preview-chrome-controls input:focus-visible {
|
|
81
|
+
outline: none;
|
|
82
|
+
box-shadow: none;
|
|
83
|
+
}
|
|
84
|
+
</style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: Props & {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const PreviewChromeControls: $$__sveltets_2_IsomorphicComponent<{
|
|
15
|
+
class?: string;
|
|
16
|
+
style?: string;
|
|
17
|
+
/** Desktop is the unpressed state of the phone icon. */ mobile?: boolean;
|
|
18
|
+
showDevices?: boolean;
|
|
19
|
+
showDimensions?: boolean;
|
|
20
|
+
widthValue?: string;
|
|
21
|
+
heightValue?: string;
|
|
22
|
+
minWidth?: number;
|
|
23
|
+
minHeight?: number;
|
|
24
|
+
onDevice?: (mobile: boolean) => void;
|
|
25
|
+
onWidth?: (value: string) => void;
|
|
26
|
+
onHeight?: (value: string) => void;
|
|
27
|
+
desktopLabel?: string;
|
|
28
|
+
mobileLabel?: string;
|
|
29
|
+
widthLabel?: string;
|
|
30
|
+
heightLabel?: string;
|
|
31
|
+
}, {
|
|
32
|
+
[evt: string]: CustomEvent<any>;
|
|
33
|
+
}, {}, {}, string>;
|
|
34
|
+
type PreviewChromeControls = InstanceType<typeof PreviewChromeControls>;
|
|
35
|
+
export default PreviewChromeControls;
|