@widgetic/editor 4.0.11 → 4.1.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/README.md +124 -0
- package/dist/components/WidgetPreview.svelte +58 -59
- 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 +1 -0
- package/dist/index.js +4 -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.
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import { onMount, onDestroy, createEventDispatcher, tick } from 'svelte';
|
|
3
3
|
import { GenerateLoader } from '@widgetic/design-system/components';
|
|
4
4
|
import { lockPointerStyle } from '../utils/shadowDom';
|
|
5
|
+
import { useEditorLanguage } from '../i18n/language';
|
|
6
|
+
|
|
7
|
+
// The words of the editor in the language of the page: the editor above this one chose it (see `provideEditorLanguage`).
|
|
8
|
+
const { t } = useEditorLanguage();
|
|
5
9
|
|
|
6
10
|
const dispatch = createEventDispatcher<{
|
|
7
11
|
load: { src: string | null };
|
|
@@ -88,6 +92,7 @@
|
|
|
88
92
|
*/
|
|
89
93
|
export let resizeMode: 'concentric' | 'corner' = 'concentric';
|
|
90
94
|
/** Initial / reset placement inside the stage. */
|
|
95
|
+
// rtl-ok: 'right-half' names the half of the stage by its place on the screen; the composition editor docks the preview itself
|
|
91
96
|
export let defaultStageAlign: 'center' | 'right-half' = 'center';
|
|
92
97
|
/** Observe stage size and fill it (desktop modal) */
|
|
93
98
|
export let autoFitStage: boolean = false;
|
|
@@ -105,7 +110,6 @@
|
|
|
105
110
|
let internalIframeSrc: string | null = null;
|
|
106
111
|
let iframeLoaded = false;
|
|
107
112
|
let iframeError = false;
|
|
108
|
-
let errorMessage = '';
|
|
109
113
|
let isWidgetPublished = false;
|
|
110
114
|
let triedFallback = false;
|
|
111
115
|
let pendingWidget: any = null;
|
|
@@ -174,6 +178,7 @@
|
|
|
174
178
|
): { left: number; top: number } {
|
|
175
179
|
const maxLeft = Math.max(RESIZE_PAD_X, stageWidth - shellWidth - RESIZE_PAD_X);
|
|
176
180
|
const maxTop = Math.max(RESIZE_PAD_TOP, stageHeight - shellHeight - RESIZE_PAD_BOTTOM);
|
|
181
|
+
// rtl-ok: the half of the stage is named by its place on the screen, see the prop
|
|
177
182
|
if (defaultStageAlign === 'right-half') {
|
|
178
183
|
const rightHalfCenterX = stageWidth * 0.75;
|
|
179
184
|
const left = clampDimension(rightHalfCenterX - shellWidth / 2, RESIZE_PAD_X, maxLeft);
|
|
@@ -378,8 +383,8 @@
|
|
|
378
383
|
: '';
|
|
379
384
|
$: iframeTitle =
|
|
380
385
|
mode === 'controlled'
|
|
381
|
-
? '
|
|
382
|
-
:
|
|
386
|
+
? $t('editor.preview.frameTitle')
|
|
387
|
+
: $t('editor.preview.frameTitleNamed', { name: widget?.name || $t('editor.preview.widgetName') });
|
|
383
388
|
|
|
384
389
|
function buildWidgetUrl(w: any): string | null {
|
|
385
390
|
if (!w) return null;
|
|
@@ -420,7 +425,6 @@
|
|
|
420
425
|
iframeLoaded = false;
|
|
421
426
|
iframeError = false;
|
|
422
427
|
widgetReady = false;
|
|
423
|
-
errorMessage = '';
|
|
424
428
|
pendingMessages = [];
|
|
425
429
|
isResolvingUrl = false;
|
|
426
430
|
|
|
@@ -476,7 +480,6 @@
|
|
|
476
480
|
isWidgetPublished = false;
|
|
477
481
|
iframeLoaded = false;
|
|
478
482
|
iframeError = false;
|
|
479
|
-
errorMessage = '';
|
|
480
483
|
widgetReady = false;
|
|
481
484
|
isResolvingUrl = false;
|
|
482
485
|
}
|
|
@@ -594,7 +597,6 @@
|
|
|
594
597
|
iframeError = true;
|
|
595
598
|
iframeLoaded = false;
|
|
596
599
|
widgetReady = false;
|
|
597
|
-
errorMessage = 'Failed to load widget preview.';
|
|
598
600
|
console.error('[WidgetPreview] Widget iframe onerror:', effectiveSrc);
|
|
599
601
|
dispatch('error', { src: effectiveSrc });
|
|
600
602
|
}
|
|
@@ -810,7 +812,7 @@
|
|
|
810
812
|
<span
|
|
811
813
|
class="preview-url-text flex-1 rounded border border-gray-300 px-2 py-1 text-xs text-gray-500 min-w-0 font-mono overflow-x-auto whitespace-nowrap"
|
|
812
814
|
>
|
|
813
|
-
{effectiveDisplayUrl || effectiveSrc || '
|
|
815
|
+
{effectiveDisplayUrl || effectiveSrc || $t('editor.preview.noUrl')}
|
|
814
816
|
</span>
|
|
815
817
|
<button
|
|
816
818
|
type="button"
|
|
@@ -818,7 +820,7 @@
|
|
|
818
820
|
const url = effectiveDisplayUrl || effectiveSrc;
|
|
819
821
|
if (url) navigator.clipboard.writeText(url);
|
|
820
822
|
}}
|
|
821
|
-
title=
|
|
823
|
+
title={$t('editor.preview.copyUrl')}
|
|
822
824
|
class="shrink-0 w-7 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 text-sm cursor-pointer"
|
|
823
825
|
>
|
|
824
826
|
<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"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
@@ -827,14 +829,14 @@
|
|
|
827
829
|
<button
|
|
828
830
|
type="button"
|
|
829
831
|
onclick={handleRefreshClick}
|
|
830
|
-
title=
|
|
832
|
+
title={$t('editor.preview.refresh')}
|
|
831
833
|
class="shrink-0 w-7 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 text-sm cursor-pointer"
|
|
832
834
|
>🔄</button>
|
|
833
835
|
{#if showViewportButtons && enableResize}
|
|
834
836
|
<button
|
|
835
837
|
type="button"
|
|
836
838
|
onclick={handleFitToAreaClick}
|
|
837
|
-
title=
|
|
839
|
+
title={$t('editor.preview.fitToPanel')}
|
|
838
840
|
class="preview-fit-to-area-btn shrink-0 w-7 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 cursor-pointer"
|
|
839
841
|
>
|
|
840
842
|
<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"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>
|
|
@@ -842,7 +844,7 @@
|
|
|
842
844
|
<button
|
|
843
845
|
type="button"
|
|
844
846
|
onclick={applyMobileViewport}
|
|
845
|
-
title=
|
|
847
|
+
title={$t('editor.preview.mobileViewport')}
|
|
846
848
|
class="preview-mobile-viewport-btn shrink-0 w-7 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 cursor-pointer"
|
|
847
849
|
>
|
|
848
850
|
<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"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
|
|
@@ -850,7 +852,7 @@
|
|
|
850
852
|
<button
|
|
851
853
|
type="button"
|
|
852
854
|
onclick={handleDesktopViewport}
|
|
853
|
-
title=
|
|
855
|
+
title={$t('editor.preview.desktopViewport')}
|
|
854
856
|
class="preview-desktop-viewport-btn shrink-0 w-7 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 cursor-pointer"
|
|
855
857
|
>
|
|
856
858
|
<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"><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>
|
|
@@ -859,9 +861,9 @@
|
|
|
859
861
|
<button
|
|
860
862
|
type="button"
|
|
861
863
|
onclick={handleOpenInTabClick}
|
|
862
|
-
title=
|
|
864
|
+
title={$t('editor.preview.openInNewTab')}
|
|
863
865
|
class="shrink-0 px-2 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 text-xs cursor-pointer whitespace-nowrap"
|
|
864
|
-
|
|
866
|
+
>{$t('editor.preview.newTab')}</button>
|
|
865
867
|
{/if}
|
|
866
868
|
</div>
|
|
867
869
|
{/if}
|
|
@@ -880,7 +882,7 @@
|
|
|
880
882
|
|
|
881
883
|
{:else if mode === 'standalone' && !widget}
|
|
882
884
|
<div class="widget-preview-select-ct flex items-center justify-center h-full text-sm text-gray-400">
|
|
883
|
-
|
|
885
|
+
{$t('editor.preview.selectWidget')}
|
|
884
886
|
</div>
|
|
885
887
|
|
|
886
888
|
{:else if mode === 'standalone' && widget && !effectiveSrc && !isResolvingUrl && !iframeError}
|
|
@@ -892,11 +894,11 @@
|
|
|
892
894
|
</svg>
|
|
893
895
|
</div>
|
|
894
896
|
<div>
|
|
895
|
-
<p class="text-sm font-medium text-gray-600">{widget.name || '
|
|
896
|
-
<p class="text-xs text-gray-400 mt-1">
|
|
897
|
+
<p class="text-sm font-medium text-gray-600">{widget.name || $t('editor.preview.widgetName')}</p>
|
|
898
|
+
<p class="text-xs text-gray-400 mt-1">{$t('editor.preview.notPublished')}</p>
|
|
897
899
|
</div>
|
|
898
900
|
<p class="text-[11px] text-gray-300 max-w-[220px]">
|
|
899
|
-
|
|
901
|
+
{$t('editor.preview.publishToPreview')}
|
|
900
902
|
</p>
|
|
901
903
|
</div>
|
|
902
904
|
</div>
|
|
@@ -909,7 +911,7 @@
|
|
|
909
911
|
<div
|
|
910
912
|
class="preview-iframe-shell relative flex flex-col {enableResize ? 'preview-iframe-shell-positioned' : ''}"
|
|
911
913
|
class:preview-iframe-shell-symmetrical={showSymmetricalStroke}
|
|
912
|
-
style="{enableResize
|
|
914
|
+
style="{enableResize /* rtl-ok: the preview is placed in pixels from the left edge of the stage; the dock of the editor mirrors it */
|
|
913
915
|
? `position: absolute; left: ${previewShellLeft}px; top: ${previewShellTop}px; width: ${resizeWidth ? resizeWidth + 'px' : '100%'};`
|
|
914
916
|
: `width: ${resizeWidth ? resizeWidth + 'px' : '100%'}; height: ${resizeHeight ? resizeHeight + 'px' : '100%'};`} transition: {isResizing || isDraggingPreview ? 'none' : 'width 0.2s, height 0.2s, left 0.15s, top 0.15s'};"
|
|
915
917
|
>
|
|
@@ -921,18 +923,18 @@
|
|
|
921
923
|
onpointerdown={startPreviewDrag}
|
|
922
924
|
>
|
|
923
925
|
{#if enableDrag}
|
|
924
|
-
<span class="preview-chrome-drag-grip shrink-0 text-gray-400 text-xs leading-none" title=
|
|
926
|
+
<span class="preview-chrome-drag-grip shrink-0 text-gray-400 text-xs leading-none" title={$t('editor.preview.dragHandle')} aria-hidden="true">⠿</span>
|
|
925
927
|
{/if}
|
|
926
|
-
<span class="preview-chrome-title text-[11px] font-semibold uppercase tracking-wide text-gray-500 shrink-0">
|
|
928
|
+
<span class="preview-chrome-title text-[11px] font-semibold uppercase tracking-wide text-gray-500 shrink-0">{$t('editor.preview.title')}</span>
|
|
927
929
|
{#if showChromeDeviceToggle}
|
|
928
930
|
<div
|
|
929
|
-
class="preview-chrome-device-toggle
|
|
931
|
+
class="preview-chrome-device-toggle ms-auto flex shrink-0 items-center overflow-hidden rounded border border-gray-300"
|
|
930
932
|
onpointerdown={(event) => event.stopPropagation()}
|
|
931
933
|
>
|
|
932
934
|
<button
|
|
933
935
|
type="button"
|
|
934
936
|
class="preview-chrome-device-desktop flex h-6 w-6 items-center justify-center {!deviceMobile ? 'bg-gray-100 text-gray-800' : 'bg-white text-gray-400'} hover:text-gray-800 focus:outline-none focus-visible:outline-none"
|
|
935
|
-
aria-label=
|
|
937
|
+
aria-label={$t('editor.preview.desktop')}
|
|
936
938
|
aria-pressed={!deviceMobile}
|
|
937
939
|
onclick={() => dispatch('deviceView', { mobile: false })}
|
|
938
940
|
onpointerdown={(event) => event.stopPropagation()}
|
|
@@ -941,8 +943,8 @@
|
|
|
941
943
|
</button>
|
|
942
944
|
<button
|
|
943
945
|
type="button"
|
|
944
|
-
class="preview-chrome-device-mobile flex h-6 w-6 items-center justify-center border-
|
|
945
|
-
aria-label=
|
|
946
|
+
class="preview-chrome-device-mobile flex h-6 w-6 items-center justify-center border-s border-gray-300 {deviceMobile ? 'bg-gray-100 text-gray-800' : 'bg-white text-gray-400'} hover:text-gray-800 focus:outline-none focus-visible:outline-none"
|
|
947
|
+
aria-label={$t('editor.preview.mobile')}
|
|
946
948
|
aria-pressed={deviceMobile}
|
|
947
949
|
onclick={() => dispatch('deviceView', { mobile: true })}
|
|
948
950
|
onpointerdown={(event) => event.stopPropagation()}
|
|
@@ -952,9 +954,9 @@
|
|
|
952
954
|
</div>
|
|
953
955
|
{/if}
|
|
954
956
|
{#if showDimensionInputs}
|
|
955
|
-
<div class="preview-chrome-dimensions flex items-center gap-1.5 {showChromeDeviceToggle ? '' : '
|
|
957
|
+
<div class="preview-chrome-dimensions flex items-center gap-1.5 {showChromeDeviceToggle ? '' : 'ms-auto'}">
|
|
956
958
|
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
957
|
-
<span>
|
|
959
|
+
<span>{$t('editor.preview.widthShort')}</span>
|
|
958
960
|
<input
|
|
959
961
|
type="number"
|
|
960
962
|
min={MIN_WIDTH}
|
|
@@ -969,7 +971,7 @@
|
|
|
969
971
|
</label>
|
|
970
972
|
<span class="text-[10px] text-gray-400">×</span>
|
|
971
973
|
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
972
|
-
<span>
|
|
974
|
+
<span>{$t('editor.preview.heightShort')}</span>
|
|
973
975
|
<input
|
|
974
976
|
type="number"
|
|
975
977
|
min={MIN_HEIGHT}
|
|
@@ -1021,20 +1023,20 @@
|
|
|
1021
1023
|
<svg class="w-8 h-8 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
|
1022
1024
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
|
|
1023
1025
|
</svg>
|
|
1024
|
-
<span class="text-sm text-gray-500">{
|
|
1026
|
+
<span class="text-sm text-gray-500">{$t('editor.preview.loadFailedPreview')}</span>
|
|
1025
1027
|
</div>
|
|
1026
1028
|
</div>
|
|
1027
1029
|
{:else if mode === 'controlled' && previewError && !isGeneratingCode}
|
|
1028
1030
|
<div class="preview-error-overlay absolute inset-0 z-10 flex h-full flex-col items-center justify-center bg-red-50/95 text-gray-700 p-6">
|
|
1029
1031
|
<div class="text-4xl mb-3">⚠️</div>
|
|
1030
|
-
<div class="font-semibold text-lg text-red-600 mb-2">
|
|
1032
|
+
<div class="font-semibold text-lg text-red-600 mb-2">{$t('editor.preview.unavailable')}</div>
|
|
1031
1033
|
<div class="text-sm text-center max-w-md whitespace-pre-line text-gray-600 mb-4">{previewError}</div>
|
|
1032
1034
|
<div class="flex gap-3">
|
|
1033
1035
|
<button type="button" onclick={() => dispatch('retry')} class="px-3 py-1.5 text-xs rounded border border-blue-500 text-blue-600 bg-white hover:bg-blue-50 cursor-pointer">
|
|
1034
|
-
🔄
|
|
1036
|
+
🔄 {$t('editor.preview.retry')} {previewRetryCount > 0 ? `(${previewRetryCount})` : ''}
|
|
1035
1037
|
</button>
|
|
1036
1038
|
<button type="button" onclick={() => dispatch('openInTab')} class="px-3 py-1.5 text-xs rounded border border-gray-300 text-gray-600 bg-white hover:bg-gray-100 cursor-pointer">
|
|
1037
|
-
|
|
1039
|
+
{$t('editor.preview.openInNewTab')}
|
|
1038
1040
|
</button>
|
|
1039
1041
|
</div>
|
|
1040
1042
|
</div>
|
|
@@ -1042,21 +1044,17 @@
|
|
|
1042
1044
|
|
|
1043
1045
|
{#if showStandaloneLoading}
|
|
1044
1046
|
<div class="widget-preview-loading-ct absolute inset-0 z-10 flex items-center justify-center bg-gray-50/80">
|
|
1045
|
-
<GenerateLoader title=
|
|
1047
|
+
<GenerateLoader title={$t('editor.preview.loadingWidget')} subtitle="" size="md" />
|
|
1046
1048
|
</div>
|
|
1047
1049
|
{:else if showControlledLoading && !isGeneratingCode}
|
|
1048
1050
|
<div class="loading-overlay absolute inset-0 z-20 flex items-center justify-center bg-gray-50">
|
|
1049
1051
|
<GenerateLoader
|
|
1050
|
-
title={
|
|
1051
|
-
? 'Loading Preview…'
|
|
1052
|
-
: previewHydrating
|
|
1053
|
-
? 'Loading Preview…'
|
|
1054
|
-
: 'Loading Preview…'}
|
|
1052
|
+
title={$t('editor.preview.loadingPreview')}
|
|
1055
1053
|
subtitle={!src && isLoadingUrl
|
|
1056
|
-
? '
|
|
1054
|
+
? $t('editor.preview.checkingWidget')
|
|
1057
1055
|
: previewHydrating
|
|
1058
|
-
? '
|
|
1059
|
-
: '
|
|
1056
|
+
? $t('editor.preview.applyingSettings')
|
|
1057
|
+
: $t('editor.preview.connecting')}
|
|
1060
1058
|
size="lg"
|
|
1061
1059
|
/>
|
|
1062
1060
|
</div>
|
|
@@ -1067,17 +1065,17 @@
|
|
|
1067
1065
|
<div class="text-5xl mb-4">⚠️</div>
|
|
1068
1066
|
<div class="font-semibold text-xl mb-2">
|
|
1069
1067
|
{previewError && /TypeError|Uncaught|is not a function|is not defined/i.test(previewError)
|
|
1070
|
-
? '
|
|
1071
|
-
: '
|
|
1068
|
+
? $t('editor.preview.runtimeError')
|
|
1069
|
+
: $t('editor.preview.compilationErrors')}
|
|
1072
1070
|
</div>
|
|
1073
1071
|
<div class="text-sm text-center text-red-600 mb-4 max-w-md whitespace-pre-line">
|
|
1074
1072
|
{previewError
|
|
1075
|
-
||
|
|
1073
|
+
|| $t('editor.preview.compilationErrorsDetail', { status: previewRuntimeErrorStatus ?? '?' })}
|
|
1076
1074
|
</div>
|
|
1077
1075
|
<div class="flex gap-3 flex-wrap justify-center">
|
|
1078
|
-
<button type="button" onclick={() => dispatch('fixErrors')} class="px-4 py-2 text-xs rounded bg-red-600 hover:bg-red-700 text-white cursor-pointer border-0">🔧
|
|
1079
|
-
<button type="button" onclick={() => dispatch('viewErrors')} class="px-3 py-1.5 text-xs rounded border border-orange-400 text-orange-600 bg-white hover:bg-orange-50 cursor-pointer">📋
|
|
1080
|
-
<button type="button" onclick={() => dispatch('retryPreview')} class="px-3 py-1.5 text-xs rounded border border-red-300 text-red-600 bg-white hover:bg-red-50 cursor-pointer">🔄
|
|
1076
|
+
<button type="button" onclick={() => dispatch('fixErrors')} class="px-4 py-2 text-xs rounded bg-red-600 hover:bg-red-700 text-white cursor-pointer border-0">🔧 {$t('editor.preview.fixErrors')}</button>
|
|
1077
|
+
<button type="button" onclick={() => dispatch('viewErrors')} class="px-3 py-1.5 text-xs rounded border border-orange-400 text-orange-600 bg-white hover:bg-orange-50 cursor-pointer">📋 {$t('editor.preview.viewErrors')}</button>
|
|
1078
|
+
<button type="button" onclick={() => dispatch('retryPreview')} class="px-3 py-1.5 text-xs rounded border border-red-300 text-red-600 bg-white hover:bg-red-50 cursor-pointer">🔄 {$t('editor.preview.retryPreview')}</button>
|
|
1081
1079
|
</div>
|
|
1082
1080
|
</div>
|
|
1083
1081
|
{/if}
|
|
@@ -1109,7 +1107,7 @@
|
|
|
1109
1107
|
{/if}
|
|
1110
1108
|
|
|
1111
1109
|
{#if mode === 'controlled' && isGeneratingCode}
|
|
1112
|
-
<GenerateLoader overlay title=
|
|
1110
|
+
<GenerateLoader overlay title={$t('editor.preview.generatingCode')} subtitle="" size="lg" />
|
|
1113
1111
|
{/if}
|
|
1114
1112
|
</div>
|
|
1115
1113
|
</div>
|
|
@@ -1119,22 +1117,22 @@
|
|
|
1119
1117
|
{:else if mode === 'controlled'}
|
|
1120
1118
|
{#if isLoadingUrl}
|
|
1121
1119
|
<div class="flex h-full flex-col items-center justify-center p-6">
|
|
1122
|
-
<GenerateLoader title=
|
|
1120
|
+
<GenerateLoader title={$t('editor.preview.loadingPreview')} subtitle="" size="lg" />
|
|
1123
1121
|
</div>
|
|
1124
1122
|
{:else}
|
|
1125
1123
|
<div class="flex h-full flex-col items-center justify-center p-6 text-center text-sm text-gray-600">
|
|
1126
1124
|
{#if isGeneratingCode}
|
|
1127
|
-
<GenerateLoader title=
|
|
1125
|
+
<GenerateLoader title={$t('editor.preview.generatingCode')} subtitle="" size="lg" />
|
|
1128
1126
|
{:else if previewError === 'NO_CODE_YET'}
|
|
1129
|
-
<div class="font-semibold text-lg text-gray-700 mb-2">
|
|
1127
|
+
<div class="font-semibold text-lg text-gray-700 mb-2">{$t('editor.preview.noCodeYet')}</div>
|
|
1130
1128
|
{:else if previewError}
|
|
1131
|
-
<div class="font-semibold text-lg text-red-600 mb-2">
|
|
1129
|
+
<div class="font-semibold text-lg text-red-600 mb-2">{$t('editor.preview.unavailable')}</div>
|
|
1132
1130
|
<div class="text-sm text-gray-600 mb-4">{previewError}</div>
|
|
1133
|
-
<button type="button" onclick={() => dispatch('retry')} class="px-3 py-1.5 text-xs rounded border border-blue-500 text-blue-600 bg-white hover:bg-blue-50 cursor-pointer">🔄
|
|
1131
|
+
<button type="button" onclick={() => dispatch('retry')} class="px-3 py-1.5 text-xs rounded border border-blue-500 text-blue-600 bg-white hover:bg-blue-50 cursor-pointer">🔄 {$t('editor.preview.retryBuild')}</button>
|
|
1134
1132
|
{:else if previewLoading}
|
|
1135
|
-
<div class="text-lg font-medium text-gray-700">
|
|
1133
|
+
<div class="text-lg font-medium text-gray-700">{$t('editor.preview.building')}</div>
|
|
1136
1134
|
{:else}
|
|
1137
|
-
<div class="text-lg text-gray-600 mb-2">🖼️
|
|
1135
|
+
<div class="text-lg text-gray-600 mb-2">🖼️ {$t('editor.preview.noPreview')}</div>
|
|
1138
1136
|
{/if}
|
|
1139
1137
|
</div>
|
|
1140
1138
|
{/if}
|
|
@@ -1178,10 +1176,11 @@
|
|
|
1178
1176
|
box-shadow: none;
|
|
1179
1177
|
}
|
|
1180
1178
|
|
|
1181
|
-
|
|
1182
|
-
.preview-resize-handle-
|
|
1183
|
-
.preview-resize-handle-
|
|
1184
|
-
.preview-resize-handle-
|
|
1179
|
+
/* The four corner handles of the preview are on the four corners of the box, a place on the screen: the set is the same in both directions. */
|
|
1180
|
+
.preview-resize-handle-tl { top: -5px; left: -5px; cursor: nwse-resize; } /* rtl-ok: top left corner */
|
|
1181
|
+
.preview-resize-handle-tr { top: -5px; right: -5px; cursor: nesw-resize; } /* rtl-ok: top right corner */
|
|
1182
|
+
.preview-resize-handle-bl { bottom: -5px; left: -5px; cursor: nesw-resize; } /* rtl-ok: bottom left corner */
|
|
1183
|
+
.preview-resize-handle-br { bottom: -5px; right: -5px; cursor: nwse-resize; } /* rtl-ok: bottom right corner */
|
|
1185
1184
|
|
|
1186
1185
|
.preview-resize-handle:hover {
|
|
1187
1186
|
background: #eff6ff;
|
|
@@ -173,8 +173,10 @@ $: if (rgb && typeof a === 'number') {
|
|
|
173
173
|
|
|
174
174
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
175
175
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
176
|
+
<!-- The picker is a drawing: saturation grows to the right and hue is a strip next to it, on every screen, so it is a left to right island. -->
|
|
176
177
|
<div
|
|
177
178
|
class={$$props.class}
|
|
179
|
+
dir="ltr"
|
|
178
180
|
tabindex="0"
|
|
179
181
|
style="width: {width}px; height: {height}px;"
|
|
180
182
|
on:keydown={onKeydown}
|
|
@@ -184,8 +186,7 @@ $: if (rgb && typeof a === 'number') {
|
|
|
184
186
|
id="{instanceId}_sv"
|
|
185
187
|
width={pickerSize}
|
|
186
188
|
height={pickerSize}
|
|
187
|
-
style
|
|
188
|
-
style:left="{border_size}px"
|
|
189
|
+
style="top: {border_size}px; left: {border_size}px; /* rtl-ok: the saturation square is drawn from the left edge of the picker */"
|
|
189
190
|
use:pointer={update_sv}
|
|
190
191
|
use:render_main_image={color.h}
|
|
191
192
|
/>
|
|
@@ -193,8 +194,7 @@ $: if (rgb && typeof a === 'number') {
|
|
|
193
194
|
<canvas
|
|
194
195
|
width={sliderWidth}
|
|
195
196
|
height={pickerSize}
|
|
196
|
-
style
|
|
197
|
-
style:left="{pickerSize + gap_size}px"
|
|
197
|
+
style="top: {border_size}px; left: {pickerSize + gap_size}px; /* rtl-ok: the hue strip is drawn next to the square, from the left edge of the picker */"
|
|
198
198
|
use:pointer={update_h}
|
|
199
199
|
use:render_slider_image
|
|
200
200
|
/>
|