@widgetic/editor 4.0.10 → 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 +242 -0
- package/dist/components/WidgetPreview.svelte +123 -86
- package/dist/components/WidgetPreview.svelte.d.ts +8 -0
- package/dist/components/color-select/ColorSelect.svelte +4 -4
- package/dist/components/editors/CompositionEditorApp.svelte +562 -0
- package/dist/components/editors/CompositionEditorApp.svelte.d.ts +95 -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 +267 -850
- package/dist/components/editors/PropsEditor.svelte.d.ts +10 -3
- package/dist/components/ic/AngleInputController.svelte +1 -1
- package/dist/components/ic/BrowserInputController.svelte +58 -31
- package/dist/components/ic/ColorPickerInputController.svelte +55 -21
- package/dist/components/ic/DateFieldInputController.svelte +34 -14
- package/dist/components/ic/DatePickerInputController.svelte +37 -21
- package/dist/components/ic/DropdownInputController.svelte +21 -9
- package/dist/components/ic/DropdownInputController.svelte.d.ts +1 -1
- package/dist/components/ic/FixedPositionInputController.svelte +12 -6
- package/dist/components/ic/FontInputController.svelte +100 -76
- package/dist/components/ic/InputControllerHeader.svelte +16 -9
- package/dist/components/ic/NumberControls.svelte +4 -4
- package/dist/components/ic/NumberInputController.svelte +6 -4
- package/dist/components/ic/PositionInputController.svelte +23 -15
- package/dist/components/ic/RangeCalendarInputController.svelte +36 -20
- package/dist/components/ic/RoundedCornersInputController.svelte +39 -26
- 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 +79 -51
- package/dist/components/ic/TextAreaPreviewInputController.svelte +27 -72
- package/dist/components/ic/TextInputController.svelte +17 -1
- package/dist/components/ic/TimeInputController.svelte +11 -4
- package/dist/components/ic/VectorInputController.svelte +101 -70
- package/dist/components/ic/shared/ItemGrid.svelte +12 -4
- package/dist/components/ic/shared/RecentItemsSection.svelte +16 -11
- package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +1 -1
- package/dist/composition/compositionApi.d.ts +95 -0
- package/dist/composition/compositionApi.js +289 -0
- package/dist/composition/compositionEditorController.d.ts +263 -0
- package/dist/composition/compositionEditorController.js +746 -0
- package/dist/composition/compositionHelpers.d.ts +74 -0
- package/dist/composition/compositionHelpers.js +405 -0
- 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 +4 -0
- package/dist/index.js +8 -0
- package/dist/mediaLibraryContext.d.ts +10 -0
- package/dist/mediaLibraryContext.js +30 -0
- package/dist/utils/FontManager.d.ts +21 -5
- package/dist/utils/FontManager.js +62 -67
- package/dist/utils/VectorManager.d.ts +0 -1
- package/dist/utils/VectorManager.js +10 -12
- package/dist/utils/dockPlacement.d.ts +12 -0
- package/dist/utils/dockPlacement.js +13 -0
- package/dist/utils/editorStorage.d.ts +23 -0
- package/dist/utils/editorStorage.js +95 -0
- package/dist/utils/fieldDirection.d.ts +13 -0
- package/dist/utils/fieldDirection.js +25 -0
- package/dist/utils/fontUtils.js +5 -3
- package/dist/utils/htmlSafety.d.ts +21 -0
- package/dist/utils/htmlSafety.js +38 -0
- package/dist/utils/itemReorder.d.ts +90 -0
- package/dist/utils/itemReorder.js +354 -0
- package/dist/utils/recentColors.js +4 -2
- package/dist/utils/richTextPreview.d.ts +38 -0
- package/dist/utils/richTextPreview.js +207 -0
- package/dist/utils/scrollActivity.d.ts +14 -0
- package/dist/utils/scrollActivity.js +63 -0
- package/dist/utils/shadowDom.d.ts +67 -0
- package/dist/utils/shadowDom.js +211 -0
- package/dist/utils/vectorUtils.js +5 -3
- package/dist/utils/visibilityCondition.d.ts +40 -0
- package/dist/utils/visibilityCondition.js +535 -0
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -38,6 +38,243 @@ dispatch('propertyChange', { schema, property, value});
|
|
|
38
38
|
}} />
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## Using your own media library
|
|
42
|
+
|
|
43
|
+
By default the file pickers inside the editor (the controls that open the File Browser) list and upload to the
|
|
44
|
+
user's Widgetic uploads. A platform that keeps its media somewhere else passes a `mediaLibrary` and every picker uses
|
|
45
|
+
it instead:
|
|
46
|
+
|
|
47
|
+
```svelte
|
|
48
|
+
<PropsEditor {designSchema} {contentSchema} mediaLibrary={myMediaLibrary} />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`mediaLibrary` is a `MediaLibraryProvider` (`list`, and optionally `upload`, `rename`, `delete`), exactly as the File
|
|
52
|
+
Browser defines it: see "Using your own media library" in the
|
|
53
|
+
[File Browser README](../file-browser/README.md). The value the editor reports for a picked file is `{ id, file, url }`
|
|
54
|
+
(plus `hash` when the library gives one), taken from the library's file: `id` is the library's own id, `file` its
|
|
55
|
+
name and `url` its address.
|
|
56
|
+
|
|
57
|
+
With a library set, the pickers make no Widgetic upload request, not even when the page address carries `apiUrl` and
|
|
58
|
+
`token`, and the File Browser keeps nothing on the device. Without one, nothing changes.
|
|
59
|
+
|
|
60
|
+
Needs a `@widgetic/file-browser` that supports `mediaLibrary`; an older one ignores the prop and keeps using the
|
|
61
|
+
Widgetic uploads.
|
|
62
|
+
|
|
63
|
+
## Editing a whole composition
|
|
64
|
+
|
|
65
|
+
`CompositionEditorApp` is the editor with everything around it. It loads one Widgetic composition, shows its settings
|
|
66
|
+
(the `PropsEditor`) beside a live preview of the widget, and saves what the user changes. A page that has a token for
|
|
67
|
+
the Widgetic API (a user's own, or an editor session token for a partner's managed user) only has to name the
|
|
68
|
+
composition:
|
|
69
|
+
|
|
70
|
+
```svelte
|
|
71
|
+
<script>
|
|
72
|
+
import { CompositionEditorApp } from '@widgetic/editor';
|
|
73
|
+
|
|
74
|
+
// Called before every request, and again with 'expired' when the API refuses the token.
|
|
75
|
+
async function getAccessToken(reason) {
|
|
76
|
+
const response = await fetch('/my-backend/widgetic-editor-session', { method: 'POST' });
|
|
77
|
+
return (await response.json()).token;
|
|
78
|
+
}
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<!-- It fills the box it is put in, so give the box a height. -->
|
|
82
|
+
<div style="height: 100vh">
|
|
83
|
+
<CompositionEditorApp
|
|
84
|
+
compositionId="comp_123"
|
|
85
|
+
apiBasePath="https://api.widgetic.com/v1"
|
|
86
|
+
{getAccessToken}
|
|
87
|
+
on:saved={(event) => console.log('saved at', event.detail.savedAt)}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Props:
|
|
93
|
+
|
|
94
|
+
- `compositionId`: the composition to edit. One component edits one composition: for another one, make another component.
|
|
95
|
+
- `apiBasePath`: the API's address with its version (`https://api.widgetic.com/v1`). A blank one throws when the
|
|
96
|
+
component is made.
|
|
97
|
+
- `getAccessToken`: `(reason: 'initial' | 'expired', refusedToken?: string) => string | Promise<string>`. See "The
|
|
98
|
+
token" below.
|
|
99
|
+
- `mediaLibrary` (optional): the partner's own `MediaLibraryProvider` for the file pickers (see above). Without it the
|
|
100
|
+
pickers use the Widgetic library the token can see.
|
|
101
|
+
- `googleFontsApiKey` (optional): the key the font pickers list Google Fonts with.
|
|
102
|
+
- `autosaveDelayMs` (optional): how long to wait after the last change before saving by itself (default 2500); `0`
|
|
103
|
+
turns saving by itself off. Read once, when the component is made.
|
|
104
|
+
- `class`, `style`: for the box the editor fills.
|
|
105
|
+
|
|
106
|
+
**The token.** `getAccessToken` is called before every request, so a page that keeps its token fresh is always used
|
|
107
|
+
up to date. When the API answers 401 the function is called again with `'expired'` and the token that was refused
|
|
108
|
+
(`refusedToken`) and, only if the token that comes back is a different one, the request is sent once more (a save is
|
|
109
|
+
sent with the same `Idempotency-Key` and body, so it cannot be applied twice). Several requests that start together with
|
|
110
|
+
the same token are refused together, and each asks: if your function has already replaced `refusedToken`, hand out the
|
|
111
|
+
new token instead of asking your server for another one. If the session is really over, the editor says so and `error`
|
|
112
|
+
reports `failure.sessionEnded`.
|
|
113
|
+
|
|
114
|
+
**Saving.** A change is saved a moment after the last one (2.5 seconds), or at once with the "Save" button. Only one
|
|
115
|
+
save is on its way at a time, and a change made meanwhile is saved by the next one. A failure that may pass (no
|
|
116
|
+
connection, a timeout, 429, 5xx) is tried again after 5, 15 and 45 seconds, the user can press "Try again" at any time,
|
|
117
|
+
and a failure that cannot pass (the session ended, a refusal) is not retried.
|
|
118
|
+
|
|
119
|
+
**The size.** The size the user gives the preview, by dragging its corner or typing it in, is saved with the composition
|
|
120
|
+
(`embedSettings.width` and `height`) and is the size the widget gets where it is embedded. Nothing else changes it: the
|
|
121
|
+
desktop and mobile buttons only change how the preview is looked at, and a box smaller than the preview shrinks it to
|
|
122
|
+
fit without changing what is saved (so opening a composition in a small window does not edit it).
|
|
123
|
+
|
|
124
|
+
Events (the data is in `event.detail`):
|
|
125
|
+
|
|
126
|
+
- `ready` `{ name, widgetId }`: the editor is on the screen.
|
|
127
|
+
- `dirty` `{ dirty }`: there is, or is no longer, work that is not saved.
|
|
128
|
+
- `saved` `{ composition, savedAt }`: a save went through (`composition` is what the API answered).
|
|
129
|
+
- `error` `{ during, failure, fatal }`: `during` is `'load'`, `'schemas'` or `'save'`; `failure` is
|
|
130
|
+
`{ message, code, status, retryable, sessionEnded, correlationId? }`; `fatal` is true when the composition could not
|
|
131
|
+
be loaded at all (the editor then shows its own message with "Try again").
|
|
132
|
+
- `deviceView` `{ deviceView }`: the preview is looked at as `'desktop'` or `'mobile'` now, whether a button of the
|
|
133
|
+
preview, a size the user gave it or `setDeviceView()` did it. Told once per change.
|
|
134
|
+
|
|
135
|
+
The component also has the methods `save()`, `flush()`, `isDirty()`, `getState()` and `reload()`. **Before removing
|
|
136
|
+
the editor, `await flush()`**: removing it cancels whatever is on its way, and changes that were not saved are lost.
|
|
137
|
+
|
|
138
|
+
For a page that has controls of its own around the editor:
|
|
139
|
+
|
|
140
|
+
- `setDeviceView('desktop' | 'mobile')` does what the preview's desktop and mobile buttons do. It is a way of looking,
|
|
141
|
+
not an edit: nothing becomes unsaved.
|
|
142
|
+
- `adoptEmbedSettings(settings)` is for a page that saves the composition's `embedSettings` itself (its own "Embed"
|
|
143
|
+
screen, say). The editor sends back the embed settings it loaded with every save, so without this a later save would put
|
|
144
|
+
them back. Call it after `await flush()` (so no save of the editor is on its way), with the settings the API now holds.
|
|
145
|
+
Nothing becomes unsaved, and the preview takes the size the settings name.
|
|
146
|
+
|
|
147
|
+
What it asks of the API (all of it is on an editor session's allow-list): `GET /compositions/{id}`,
|
|
148
|
+
`GET /compositions/{id}/preview`, `GET /compositions/{id}/schema/design`, `GET /compositions/{id}/schema/content` and
|
|
149
|
+
`PUT /compositions/{id}`, plus `/uploads` for the default file library. A composition whose widget is not published
|
|
150
|
+
has no preview address (`WIDGET_NOT_PUBLISHED`): its settings stay editable and the editor says why there is no preview.
|
|
151
|
+
|
|
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
|
+
plain-JavaScript module that works on any page, inside a shadow root, is a separate step.
|
|
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.
|
|
41
278
|
|
|
42
279
|
## Input Controllers
|
|
43
280
|
|
|
@@ -91,6 +328,11 @@ npm run dev
|
|
|
91
328
|
npm run dev -- --open
|
|
92
329
|
```
|
|
93
330
|
|
|
331
|
+
The dev server also serves three playground pages that need no sign-in:
|
|
332
|
+
`/ic` (an input controller on its own), `/embed` (the Embed editor) and
|
|
333
|
+
`/mock-widget`. They compile the sibling packages from source, so a change in
|
|
334
|
+
`design-system` or `file-browser` shows up there without a build.
|
|
335
|
+
|
|
94
336
|
## Build the project
|
|
95
337
|
|
|
96
338
|
To create a production version of your app:
|