@olddevs/ui 4.3.0 → 4.4.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/CHANGELOG.md +23 -0
- package/dist/editor/RichTextEditor.d.ts.map +1 -1
- package/dist/editor/RichTextEditor.js +38 -4
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/editor/core/handle.d.ts +1 -5
- package/dist/editor/core/handle.d.ts.map +1 -1
- package/dist/editor/core/handle.js +1 -13
- package/dist/editor/core/handle.js.map +1 -1
- package/dist/editor/core/handleWatchers.d.ts +6 -0
- package/dist/editor/core/handleWatchers.d.ts.map +1 -0
- package/dist/editor/core/handleWatchers.js +14 -0
- package/dist/editor/core/handleWatchers.js.map +1 -0
- package/dist/editor/core/tools.d.ts +7 -2
- package/dist/editor/core/tools.d.ts.map +1 -1
- package/dist/editor/core/tools.js +2 -1
- package/dist/editor/core/tools.js.map +1 -1
- package/dist/editor/features/toc/RichTextToc.d.ts +63 -0
- package/dist/editor/features/toc/RichTextToc.d.ts.map +1 -0
- package/dist/editor/features/toc/RichTextToc.js +372 -0
- package/dist/editor/features/toc/RichTextToc.js.map +1 -0
- package/dist/editor/features/toc/headings.d.ts +31 -0
- package/dist/editor/features/toc/headings.d.ts.map +1 -0
- package/dist/editor/features/toc/headings.js +25 -0
- package/dist/editor/features/toc/headings.js.map +1 -0
- package/dist/editor/features/toc/stickyOffset.d.ts +3 -0
- package/dist/editor/features/toc/stickyOffset.d.ts.map +1 -0
- package/dist/editor/features/toc/stickyOffset.js +14 -0
- package/dist/editor/features/toc/stickyOffset.js.map +1 -0
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +1 -0
- package/dist/editor/index.js.map +1 -1
- package/dist/i18n/defaults.d.ts.map +1 -1
- package/dist/i18n/defaults.js +1 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/messages.d.ts +5 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/locales/ru.d.ts.map +1 -1
- package/dist/locales/ru.js +1 -0
- package/dist/locales/ru.js.map +1 -1
- package/docs/api/messages.en.md +743 -0
- package/docs/api/messages.md +1 -0
- package/docs/api/utilities.en.md +233 -0
- package/docs/components/content/rich-text-toc.en.md +130 -0
- package/docs/components/content/rich-text-toc.md +129 -0
- package/docs/components/index.en.md +2 -1
- package/docs/components/index.md +1 -0
- package/docs/components/inputs/rich-text-editor.en.md +52 -34
- package/docs/components/inputs/rich-text-editor.md +20 -2
- package/docs/conventions.en.md +318 -0
- package/docs/getting-started.en.md +4 -4
- package/docs/getting-started.md +3 -3
- package/docs/glossary.en.md +2 -1
- package/docs/glossary.md +1 -0
- package/docs/index.en.md +53 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/src/editor/RichTextEditor.tsx +107 -3
- package/src/editor/core/handle.ts +2 -23
- package/src/editor/core/handleWatchers.ts +27 -0
- package/src/editor/core/tools.ts +9 -2
- package/src/editor/features/toc/RichTextToc.tsx +712 -0
- package/src/editor/features/toc/headings.ts +70 -0
- package/src/editor/features/toc/stickyOffset.ts +21 -0
- package/src/editor/index.ts +2 -0
- package/src/i18n/defaults.ts +1 -0
- package/src/i18n/messages.ts +5 -0
- package/src/locales/ru.ts +1 -0
|
@@ -37,7 +37,7 @@ category: inputs
|
|
|
37
37
|
status: stable
|
|
38
38
|
source: src/editor/RichTextEditor.tsx
|
|
39
39
|
story: src/editor/RichTextEditor.stories.tsx
|
|
40
|
-
sourceHash:
|
|
40
|
+
sourceHash: 3eaf71d10511
|
|
41
41
|
---
|
|
42
42
|
|
|
43
43
|
# RichTextEditor
|
|
@@ -305,43 +305,44 @@ The `link` Tool: the "Link" button on the Toolbar and `Ctrl/⌘+K` open a popove
|
|
|
305
305
|
|
|
306
306
|
## Tools and Presets
|
|
307
307
|
|
|
308
|
-
`preset="full"` (default) is everything in the release. `preset="basic"` is for comments and short descriptions. `tools` is an exact list and takes precedence over a Preset; descriptions of custom Tools can be mixed in with the kit's names (see the "Custom Tool" section). The type of a `tools` element is `Tool` (a kit name or a `CustomTool`), the type of a kit name is `ToolName`; both are exported from `@olddevs/ui/editor`.
|
|
308
|
+
`preset="full"` (default) is everything in the release except the Table of contents `toc`: it changes the layout of the field, so it is switched on only explicitly. `preset="basic"` is for comments and short descriptions. `tools` is an exact list and takes precedence over a Preset; descriptions of custom Tools can be mixed in with the kit's names (see the "Custom Tool" section). The type of a `tools` element is `Tool` (a kit name or a `CustomTool`), the type of a kit name is `ToolName`; both are exported from `@olddevs/ui/editor`.
|
|
309
309
|
|
|
310
310
|
Presets are also exported as arrays of names — `RICH_TEXT_PRESETS.full` and `RICH_TEXT_PRESETS.basic` — so that you can assemble your own set from them: `tools={[...RICH_TEXT_PRESETS.full, mergeTagTool]}`. The arrays are frozen. A Preset's content is "what the kit considers the full set", not a frozen list: a new built-in Tool arrives in a Preset in a minor version. If you need a stable set, pass an exact list of names.
|
|
311
311
|
|
|
312
312
|
**A disabled Tool removes both the button and the Schema extension.** Its Block or Mark cannot be created with a keyboard shortcut, a Markdown shortcut or a paste, and from `value` they arrive without Block markup, and the text stays.
|
|
313
313
|
|
|
314
|
-
| `Tool` | Button
|
|
315
|
-
| ----------------- |
|
|
316
|
-
| `heading` | "¶": Heading 2–4
|
|
317
|
-
| `heading1` | "¶": adds Heading 1
|
|
318
|
-
| `history` | Undo, Redo
|
|
319
|
-
| `bold` | Bold
|
|
320
|
-
| `italic` | Italic
|
|
321
|
-
| `underline` | "…": Underline
|
|
322
|
-
| `strike` | Strikethrough
|
|
323
|
-
| `code` | "…": Inline code
|
|
324
|
-
| `superscript` | "…": Superscript
|
|
325
|
-
| `subscript` | "…": Subscript
|
|
326
|
-
| `clearFormatting` | "…": Clear formatting
|
|
327
|
-
| `link` | Link (address popover)
|
|
328
|
-
| `color` | Color ▾ (Selection menu): "Text color"
|
|
329
|
-
| `highlight` | Highlight ▾ (Toolbar), "Color ▾"
|
|
330
|
-
| `align` | Alignment ▾ (menu)
|
|
331
|
-
| `bulletList` | Lists ▾: Bulleted list
|
|
332
|
-
| `orderedList` | Lists ▾: Numbered list
|
|
333
|
-
| `taskList` | Lists ▾: Checklist
|
|
334
|
-
| `blockquote` | "…": Quote
|
|
335
|
-
| `codeBlock` | "…": Code block
|
|
336
|
-
| `horizontalRule` | "+": Divider
|
|
337
|
-
| `table` | Table ▾ (menu)
|
|
338
|
-
| `image` | Image (address popover)
|
|
339
|
-
| `emoji` | Emoji (popover with a grid)
|
|
340
|
-
| `source` | HTML (mode toggle)
|
|
341
|
-
| `search` | Find (find and replace panel)
|
|
342
|
-
| `layout` | "+": Layout
|
|
343
|
-
| `wrapper` | "+": Wrapper
|
|
344
|
-
| `insertMenu` | "+" Insert menu
|
|
314
|
+
| `Tool` | Button | Markup | Shortcut | Markdown while typing | `basic` |
|
|
315
|
+
| ----------------- | ---------------------------------------------------- | ----------------------------------------- | ------------------------------------------------- | ---------------------- | ------- |
|
|
316
|
+
| `heading` | "¶": Heading 2–4 | `h2`, `h3`, `h4` | `Ctrl/⌘+Alt+2…4` | `## `, `### `, `#### ` | — |
|
|
317
|
+
| `heading1` | "¶": adds Heading 1 | `h1` | `Ctrl/⌘+Alt+1` | `# ` | — |
|
|
318
|
+
| `history` | Undo, Redo | — | `Ctrl/⌘+Z`, `+Shift+Z` | — | yes |
|
|
319
|
+
| `bold` | Bold | `strong` | `Ctrl/⌘+B` | `**text**` | yes |
|
|
320
|
+
| `italic` | Italic | `em` | `Ctrl/⌘+I` | `*text*` | yes |
|
|
321
|
+
| `underline` | "…": Underline | `u` | `Ctrl/⌘+U` | — | — |
|
|
322
|
+
| `strike` | Strikethrough | `s` | `Ctrl/⌘+Shift+S` | `~~text~~` | yes |
|
|
323
|
+
| `code` | "…": Inline code | `code` | `Ctrl/⌘+E` | `` `text` `` | yes |
|
|
324
|
+
| `superscript` | "…": Superscript | `sup` | `Ctrl/⌘+.` | — | — |
|
|
325
|
+
| `subscript` | "…": Subscript | `sub` | `Ctrl/⌘+,` | — | — |
|
|
326
|
+
| `clearFormatting` | "…": Clear formatting | — (removes Marks) | `Ctrl/⌘+\` | — | — |
|
|
327
|
+
| `link` | Link (address popover) | `a[href]` | `Ctrl/⌘+K` | `[text](url)` on paste | yes |
|
|
328
|
+
| `color` | Color ▾ (Selection menu): "Text color" | `span[data-color]`, `span[style]` | — | — | — |
|
|
329
|
+
| `highlight` | Highlight ▾ (Toolbar), "Color ▾" | `mark[data-color]`, `mark[style]` | — | — | — |
|
|
330
|
+
| `align` | Alignment ▾ (menu) | `p`, `h2`–`h4` with `data-align` | `Ctrl/⌘+Shift+L`, `+E`, `+R` | — | — |
|
|
331
|
+
| `bulletList` | Lists ▾: Bulleted list | `ul`, `li` | `Ctrl/⌘+Shift+8` | `- ` or `* ` | yes |
|
|
332
|
+
| `orderedList` | Lists ▾: Numbered list | `ol`, `li` | `Ctrl/⌘+Shift+7` | `1. ` | yes |
|
|
333
|
+
| `taskList` | Lists ▾: Checklist | `ul[data-type=taskList]` and items | `Ctrl/⌘+Shift+9` | `[ ] ` or `[x] ` | — |
|
|
334
|
+
| `blockquote` | "…": Quote | `blockquote` | `Ctrl/⌘+Shift+B` | `> ` | yes |
|
|
335
|
+
| `codeBlock` | "…": Code block | `pre`, `code class="language-*"` | `Ctrl/⌘+Alt+C` | ` ``` ` | — |
|
|
336
|
+
| `horizontalRule` | "+": Divider | `hr` | — | `---` | — |
|
|
337
|
+
| `table` | Table ▾ (menu) | `table`, `tbody`, `tr`, `th`, `td` | `Tab`, `Shift+Tab`, `Alt+↓` in a table | — | — |
|
|
338
|
+
| `image` | Image (address popover) | `figure`, `img`, `figcaption` | — | `` on paste | — |
|
|
339
|
+
| `emoji` | Emoji (popover with a grid) | — (plain text) | — | — | — |
|
|
340
|
+
| `source` | HTML (mode toggle) | — (edits the Document's HTML) | — | — | — |
|
|
341
|
+
| `search` | Find (find and replace panel) | — (searches the Document) | `Ctrl/⌘+F`, `Ctrl/⌘+Alt+F` | — | yes |
|
|
342
|
+
| `layout` | "+": Layout | `div` with `data-block` and `data-layout` | `Tab`, `Shift+Tab`, arrow keys at the column edge | — | — |
|
|
343
|
+
| `wrapper` | "+": Wrapper | `div[data-block=wrapper]` | `Enter` in an empty last paragraph — exit | — | — |
|
|
344
|
+
| `insertMenu` | "+" Insert menu | — (Blocks of other Tools) | — | `/` | — |
|
|
345
|
+
| `toc` | Table of contents in the left gutter (not in `full`) | — (reads the headings) | — | — | — |
|
|
345
346
|
|
|
346
347
|
A paragraph is always there; a soft line break is `Shift+Enter`. `insertMenu` and `emoji`, like `clearFormatting`, have no markup of their own: `insertMenu` provides access to other Tools' Blocks, and it is disabled where `/` is an ordinary character (paths, "and/or"); `basic` lacks it for the same reason. `emoji` inserts plain text — the Schema, the sanitizer and Markdown do not change because of it (see the "Emoji" section); `basic` lacks it: in a comment the OS's own palette is easier to reach, and a button would take up room in a short row. `source` is the HTML mode (see the "HTML mode" section): it has no markup of its own either, an HTML edit goes through the same Schema as `value`, and it cannot be used to get around the set of Tools; `basic` lacks it — in a comment an "HTML" button is superfluous unless the screen explicitly asks for it.
|
|
347
348
|
|
|
@@ -349,7 +350,7 @@ A paragraph is always there; a soft line break is `Shift+Enter`. `insertMenu` an
|
|
|
349
350
|
|
|
350
351
|
Earlier, "no H1 in the Document" was a kit decision (the page heading sits outside the Editor); now it is the screen's decision — ADR-0053 revises the earlier decision, and ADR-0050 links to ADR-0053.
|
|
351
352
|
|
|
352
|
-
A name enters the `Tool` type together with the Tool itself, not earlier: widening the input type does not break the consumer, whereas a name without an implementation would compile and do nothing. That is how `link` arrived — straight into the `basic` Preset too — and `color`, `highlight`, `align`, `table`, `image`, `emoji`, `layout`, `wrapper`, `source`; there are no reserved names now. Unknown names in `tools` (from JS without types) are dropped silently.
|
|
353
|
+
A name enters the `Tool` type together with the Tool itself, not earlier: widening the input type does not break the consumer, whereas a name without an implementation would compile and do nothing. That is how `link` arrived — straight into the `basic` Preset too — and `color`, `highlight`, `align`, `table`, `image`, `emoji`, `layout`, `wrapper`, `source`, `toc`; there are no reserved names now. Unknown names in `tools` (from JS without types) are dropped silently.
|
|
353
354
|
|
|
354
355
|
`preset` and `tools` are read **when the engine is created**: the Schema of a live ProseMirror cannot be changed. To change the set on a mounted field, remount it with a key (`key`) — the edit history is lost in the process.
|
|
355
356
|
|
|
@@ -1170,6 +1171,23 @@ The `search` Tool (present in `full` and in `basic`) is find and replace over th
|
|
|
1170
1171
|
|
|
1171
1172
|
Message catalog strings: `editor.search` (the button and the `Ctrl/⌘+F` Shortcuts reference row), `searchReplace` (`Ctrl/⌘+Alt+F`), `searchPanel`, `searchField`, `replaceField`, `searchPrevious`, `searchNext`, `searchClose`, `searchMatchCase`, `searchWholeWord`, `searchRegex`, `replaceOne`, `replaceAll`, `searchCount({ current, total })`, `searchNoMatches`, `searchInvalid`.
|
|
1172
1173
|
|
|
1174
|
+
## Table of contents
|
|
1175
|
+
|
|
1176
|
+
The `toc` Tool puts the Table of contents of the Document in the Editor's left gutter: the same strip of strokes and the same list as [`RichTextToc`](../content/rich-text-toc.md), but the Editor holds the place itself (ADR-0068). It is in neither Preset, `full` or `basic`: the Table of contents moves the text, and a kit update does not change the layout of screens that did not ask for it. Switch it on by name:
|
|
1177
|
+
|
|
1178
|
+
```tsx
|
|
1179
|
+
<RichTextEditor tools={[...RICH_TEXT_PRESETS.full, "toc"]} defaultValue={html} />
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
- **Place.** A column to the left of the Document, inside the field frame. It exists only while there is a strip, from two `h1`–`h4` headings at the top level and in Wrappers: with the second heading the text moves right; without it the text stays at the edge, as without the Tool. The first stroke is level with the first line of the first heading.
|
|
1183
|
+
- **Stickiness.** The strip sticks under the fixed Toolbar (and under the app header: `--topbar-h`, ADR-0016); without a Toolbar, to the edge of the window. In a field with `maxHeight` or `resizable` it sticks to the top of the scrolling area, and the highlight and the jump count from there; the page does not scroll.
|
|
1184
|
+
- **The list** works as in `RichTextToc`: hover opens it to the right of the strip, Enter and Space open it from the keyboard, arrows move between items, Escape closes it; the current section is highlighted in the strip and in the list; the width is 240px, and a long heading is cut with an ellipsis. A click on an item does not close the list: moving the pointer away does.
|
|
1185
|
+
- **A jump while editing** puts the caret at the end of the heading, so you can type right away, as in the document outline of Google Docs; the Table of contents does the scrolling, and the heading flashes a fading background. In `readOnly` there is no caret, so focus goes to the heading itself.
|
|
1186
|
+
- **A live Document.** The list is rebuilt shortly after each edit: a new, renamed or removed heading shows in the strip. In HTML mode there is no Table of contents, because the Document is hidden.
|
|
1187
|
+
- **No Toolbar button.** To show or hide the Table of contents, the screen changes `tools` (and remounts the field with a `key`, as for any set of Tools).
|
|
1188
|
+
|
|
1189
|
+
Message catalog string: `editor.toc` ("Table of contents"), the name of the strip and the label of the list.
|
|
1190
|
+
|
|
1173
1191
|
## Status line and Field notice
|
|
1174
1192
|
|
|
1175
1193
|
Under the field inside the frame there are two independent areas. The **Status line** is what the screen puts there itself: "Saved", a word count, a co-author. The **Field notice** is what the kit and Tools report: an upload result, a paste rejection. The first is a React node in the `status` prop, the second is data and an engine command.
|
|
@@ -304,7 +304,7 @@ import { RichTextEditor } from "@olddevs/ui/editor";
|
|
|
304
304
|
|
|
305
305
|
## Инструменты и Пресеты
|
|
306
306
|
|
|
307
|
-
`preset="full"` (по умолчанию) — всё, что есть в
|
|
307
|
+
`preset="full"` (по умолчанию) — всё, что есть в выпуске, кроме Оглавления `toc`: оно меняет раскладку поля, и его включают только явно. `preset="basic"` — для комментариев и коротких описаний. `tools` — точный список, сильнее Пресета; в нём вперемешку с именами кита стоят описания своих Инструментов (раздел «Свой Инструмент»). Тип элемента `tools` — `Tool` (имя кита или `CustomTool`), тип имени кита — `ToolName`; оба экспортируются из `@olddevs/ui/editor`.
|
|
308
308
|
|
|
309
309
|
Пресеты экспортируются и массивами имён — `RICH_TEXT_PRESETS.full` и `RICH_TEXT_PRESETS.basic`, чтобы собирать из них свой набор: `tools={[...RICH_TEXT_PRESETS.full, mergeTagTool]}`. Массивы заморожены. Состав Пресета — «то, что кит считает полным набором», а не замороженный список: новый встроенный Инструмент приходит в Пресет minor-версией. Нужен стабильный состав — передайте точный список имён.
|
|
310
310
|
|
|
@@ -341,6 +341,7 @@ import { RichTextEditor } from "@olddevs/ui/editor";
|
|
|
341
341
|
| `layout` | «+»: Раскладка | `div` с `data-block` и `data-layout` | `Tab`, `Shift+Tab`, стрелки у края колонки | — | — |
|
|
342
342
|
| `wrapper` | «+»: Обёртка | `div[data-block=wrapper]` | `Enter` в пустом последнем абзаце — выход | — | — |
|
|
343
343
|
| `insertMenu` | «+» Меню вставки | — (Блоки других Инструментов) | — | `/` | — |
|
|
344
|
+
| `toc` | Оглавление в левом поле (не в `full`) | — (читает заголовки) | — | — | — |
|
|
344
345
|
|
|
345
346
|
Абзац есть всегда, мягкий перенос строки — `Shift+Enter`. `insertMenu` и `emoji`, как и `clearFormatting`, своей разметки не имеют: `insertMenu` даёт доступ к Блокам других Инструментов, а выключают его там, где `/` — обычный символ (пути, «и/или»); в `basic` его нет по той же причине. `emoji` вставляет обычный текст — Схема, санитайзер и Markdown от него не меняются (раздел «Эмодзи»); в `basic` его нет: в комментарии системная палитра ОС удобнее, а кнопка занимала бы место у короткого ряда. `source` — HTML-режим (раздел «HTML-режим»): своей разметки у него тоже нет, правка HTML проходит ту же Схему, что `value`, и обойти набор Инструментов им нельзя; в `basic` его нет — в комментарии кнопка «HTML» лишняя, если экран её не просил.
|
|
346
347
|
|
|
@@ -348,7 +349,7 @@ import { RichTextEditor } from "@olddevs/ui/editor";
|
|
|
348
349
|
|
|
349
350
|
Раньше «H1 в Документе нет» было решением кита (заголовок страницы стоит вне Редактора); теперь это решение экрана — ADR-0053 пересматривает прежнее решение; ссылка на ADR-0053 стоит в ADR-0050.
|
|
350
351
|
|
|
351
|
-
Имя приходит в тип `Tool` вместе с самим Инструментом, не раньше: расширение входного типа потребителя не ломает, а имя без реализации компилировалось бы и ничего не давало. Так пришли `link` — сразу и в Пресет `basic` — и `color`, `highlight`, `align`, `table`, `image`, `emoji`, `layout`, `wrapper`, `source`; зарезервированных имён сейчас нет. Неизвестные имена в `tools` (из JS без типов) отбрасываются молча.
|
|
352
|
+
Имя приходит в тип `Tool` вместе с самим Инструментом, не раньше: расширение входного типа потребителя не ломает, а имя без реализации компилировалось бы и ничего не давало. Так пришли `link` — сразу и в Пресет `basic` — и `color`, `highlight`, `align`, `table`, `image`, `emoji`, `layout`, `wrapper`, `source`, `toc`; зарезервированных имён сейчас нет. Неизвестные имена в `tools` (из JS без типов) отбрасываются молча.
|
|
352
353
|
|
|
353
354
|
`preset` и `tools` читаются **при создании движка**: Схему живого ProseMirror не поменять. Чтобы сменить набор у смонтированного поля, перемонтируйте его ключом (`key`) — история правок при этом теряется.
|
|
354
355
|
|
|
@@ -1169,6 +1170,23 @@ ARIA сетки — шаблон `grid` из APG: `role="grid"` → `row` → `g
|
|
|
1169
1170
|
|
|
1170
1171
|
Строки каталога сообщений: `editor.search` (кнопка и строка Справочника сочетаний `Ctrl/⌘+F`), `searchReplace` (`Ctrl/⌘+Alt+F`), `searchPanel`, `searchField`, `replaceField`, `searchPrevious`, `searchNext`, `searchClose`, `searchMatchCase`, `searchWholeWord`, `searchRegex`, `replaceOne`, `replaceAll`, `searchCount({ current, total })`, `searchNoMatches`, `searchInvalid`.
|
|
1171
1172
|
|
|
1173
|
+
## Оглавление
|
|
1174
|
+
|
|
1175
|
+
Инструмент `toc` — Оглавление Документа в левом поле Редактора: та же полоса штрихов и тот же список, что у [`RichTextToc`](../content/rich-text-toc.md), но место держит сам Редактор (ADR-0068). В Пресеты он не входит — ни в `full`, ни в `basic`: Оглавление сдвигает текст, и обновление кита не меняет раскладку экранов, которые его не просили. Включают его именем:
|
|
1176
|
+
|
|
1177
|
+
```tsx
|
|
1178
|
+
<RichTextEditor tools={[...RICH_TEXT_PRESETS.full, "toc"]} defaultValue={html} />
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
- **Место.** Колонка слева от Документа внутри рамки поля. Она есть, только пока есть полоса — от двух заголовков `h1`–`h4` на верхнем уровне и в Обёртке: со вторым заголовком текст сдвигается вправо, без него стоит у края, как без Инструмента. Первый штрих стоит на уровне первой строки первого заголовка.
|
|
1182
|
+
- **Липкость.** Полоса липнет под фиксированной Панелью (и под шапкой приложения: `--topbar-h`, ADR-0016), без Панели — к краю окна. В поле с `maxHeight` или `resizable` она липнет к верху прокручиваемой области, а подсветка и переход считают от неё — страница при этом не прокручивается.
|
|
1183
|
+
- **Список** — как у `RichTextToc`: наведение раскрывает его справа от полосы, Enter и Space — с клавиатуры, стрелки ходят по пунктам, Escape закрывает; текущий раздел подсвечен в полосе и в списке; ширина 240px, длинный заголовок обрезан многоточием. Клик по пункту список не закрывает: его закрывает уход курсора.
|
|
1184
|
+
- **Переход в правке** ставит каретку в конец заголовка — можно сразу писать, как в структуре документа Google Docs; прокрутку делает Оглавление, заголовок подсвечивается гаснущим фоном. В `readOnly` каретки нет — фокус встаёт на сам заголовок.
|
|
1185
|
+
- **Живой Документ.** Список пересобирается на правке с короткой задержкой: новый заголовок, переименование и удаление видны в полосе. В HTML-режиме Оглавления нет — Документ скрыт.
|
|
1186
|
+
- **Кнопки на Панели нет.** Показать или спрятать Оглавление — смена `tools` экраном (с перемонтажом ключом, как у любого набора Инструментов).
|
|
1187
|
+
|
|
1188
|
+
Строка каталога сообщений — `editor.toc` («Оглавление»): имя полосы и подпись списка.
|
|
1189
|
+
|
|
1172
1190
|
## Строка статуса и Сообщение поля
|
|
1173
1191
|
|
|
1174
1192
|
Под полем внутри рамки две независимые области. **Строка статуса** — то, что экран кладёт сам: «Сохранено», число слов, соавтор. **Сообщение поля** — то, что сообщают кит и Инструменты: результат загрузки, отказ вставки. Первое — React-узел пропа `status`, второе — данные и команда движка.
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
---
|
|
2
|
+
sourceHash: 84104315608a
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# General conventions for `@olddevs/ui`
|
|
6
|
+
|
|
7
|
+
## Public contract
|
|
8
|
+
|
|
9
|
+
The public API is the runtime exports from [`src/index.ts`](../src/index.ts), the types, the entry points
|
|
10
|
+
described in the documentation, and `styles.css`. The sources in `src/components/` prove the behavior, but they
|
|
11
|
+
are not an import path that consumers may use.
|
|
12
|
+
|
|
13
|
+
## Semantic prop vocabulary
|
|
14
|
+
|
|
15
|
+
Unless a specific page says otherwise, primitives use three independent axes:
|
|
16
|
+
|
|
17
|
+
| Prop | Values | Question it answers |
|
|
18
|
+
| --------- | --------------------------------------------------- | ---------------------------- |
|
|
19
|
+
| `size` | `sm`, `md`, `lg` | How large is the element? |
|
|
20
|
+
| `tone` | `neutral`, `accent`, `success`, `warning`, `danger` | What does the state mean? |
|
|
21
|
+
| `variant` | `solid`, `soft`, `outline`, `ghost` | How prominent is this value? |
|
|
22
|
+
|
|
23
|
+
States are named positively: `selected`, `disabled`, `loading`. A native ARIA state may use its own correct name,
|
|
24
|
+
for example `checked` on a switch. Do not create alternatives such as `kind`, `status` or negative boolean props
|
|
25
|
+
without a separate public decision. `active` already has one meaning, see below.
|
|
26
|
+
|
|
27
|
+
Props outside the three axes repeat across several components. One concept, one name: a second component with the
|
|
28
|
+
same meaning takes the name from here, and does not invent its own.
|
|
29
|
+
|
|
30
|
+
| Prop | Meaning | Where |
|
|
31
|
+
| ------------ | ------------------------------------------------------------------------------------------- | ------------------------------------- |
|
|
32
|
+
| `appearance` | The form of a variant: the markup changes, not the prominence of the tone | `RadioGroup`, `CheckboxGroup` |
|
|
33
|
+
| `active` | The current navigation item: `aria-current="page"` | `NavItem`, `MobileTabBarItem` |
|
|
34
|
+
| `activeKey` | The same for a data collection: the key of the current node, `aria-current="page"` | `TreeView` |
|
|
35
|
+
| `level` | Heading level, `2`–`4`: the component renders a real `<h2>`–`<h4>` | `SectionHeader`, `NotificationCenter` |
|
|
36
|
+
| `kind` | The kind of **data**, not of an element: what value the field describes and how to enter it | `FilterField` (ADR-0063) |
|
|
37
|
+
|
|
38
|
+
- **`kind` on `FilterField` is a public decision (ADR-0063), not a workaround for the axis.**
|
|
39
|
+
`FilterField` describes a data field, not a UI element: `"values"`, `"number-range"` and `"date-range"` say
|
|
40
|
+
what value the field holds and what input sets it. It is not a presentation (`variant`) or a variant form
|
|
41
|
+
(`appearance`): a description of data has no tone and no markup. Components that draw an element still do not
|
|
42
|
+
get `kind`.
|
|
43
|
+
- **`appearance`, not `variant`.** `variant` is the axis for how a tone is presented, with a closed set
|
|
44
|
+
`solid|soft|outline|ghost` across the whole kit. `appearance="card"` changes the form of the variant itself:
|
|
45
|
+
the border, the indicator and the description inside the label. Under `variant`, such a value would give the
|
|
46
|
+
axis a second meaning: `variant="card"` on one component would sit next to `variant="soft"` on another.
|
|
47
|
+
- **`active`, not `selected`.** `selected` is a choice in a set (`aria-selected`, `aria-checked`): the user
|
|
48
|
+
changes it. The current section is `aria-current`: the route sets it, and the item stays a link. `active` has
|
|
49
|
+
no other values, and it does not become a synonym for `selected`. Where there are no item elements but keyed
|
|
50
|
+
data (`TreeView`), the same concept is called `activeKey`, after `selectedKeys` and `expandedKeys`: one value
|
|
51
|
+
instead of a set.
|
|
52
|
+
- **`level`, not `headingLevel`.** The level depends on where the component is placed, and the consumer knows
|
|
53
|
+
that. `SectionHeader` already had a prop called `level`. A second name for the same concept would silently
|
|
54
|
+
drift from it.
|
|
55
|
+
|
|
56
|
+
## Accessible name
|
|
57
|
+
|
|
58
|
+
The rule comes from `Toolbar`, `Carousel`, `MobileTabBar` and `TagGroup`. Before it, these components handled the
|
|
59
|
+
required name in different ways: in one, `label` was shown on screen, and in another it went into an attribute.
|
|
60
|
+
|
|
61
|
+
- **`label` is only a visible caption.** A prop with this name draws text on screen. A name that is not drawn is
|
|
62
|
+
set with its own ARIA attribute.
|
|
63
|
+
- **Without a visible caption, use exactly one of `aria-label` and `aria-labelledby`.** The type is
|
|
64
|
+
[`AccessibleName`](../src/lib/accessible-name.ts), a union with `never`: TypeScript rejects a call with no name
|
|
65
|
+
and a call with both. Two sources of one name drift apart silently when you edit.
|
|
66
|
+
- **A name with a default from the catalog** uses `Partial<AccessibleName>`: you can pass nothing, but not both
|
|
67
|
+
(`MobileTabBar`). The default is announced even when an empty string is passed.
|
|
68
|
+
- **Visible caption or attribute:** `{ label; "aria-label"?: never;
|
|
69
|
+
"aria-labelledby"?: never } | ({ label?: never } & AccessibleName)` (`TagGroup`). A caption together with an
|
|
70
|
+
attribute is not allowed: the attribute would override the visible text, and a screen reader would give the
|
|
71
|
+
component a different name than the one on screen.
|
|
72
|
+
- The test locks the shape with `@ts-expect-error`: for "no name" and for "both at once".
|
|
73
|
+
|
|
74
|
+
Components released before this rule use `label` as the name string without a visible caption (`SideNav`,
|
|
75
|
+
`TopBar`, `FilterBar`, `ScrollArea` and others). Renaming it there is a breaking change and a separate decision.
|
|
76
|
+
A new component follows the rule.
|
|
77
|
+
|
|
78
|
+
## Empty value
|
|
79
|
+
|
|
80
|
+
An empty string is neither a caption nor a name. The rule and its history are in
|
|
81
|
+
[`src/lib/empty.ts`](../src/lib/empty.ts). For a prop of type `string`, write the fallback with `||`, not `??`.
|
|
82
|
+
For `ReactNode`, use `hasContent`, not `!= null`. `??` and `!= null` let `""` through: the component renders an
|
|
83
|
+
empty node, stays without a name, or labels a button "Удалить: ". Each such branch is covered by a test with an
|
|
84
|
+
empty string.
|
|
85
|
+
|
|
86
|
+
## What the tests check
|
|
87
|
+
|
|
88
|
+
A test renders the component as a consumer does (Testing Library and user-event), and checks what can be observed:
|
|
89
|
+
roles, names, ARIA attributes, focus, handler calls and text. Internal state and hooks are not tested.
|
|
90
|
+
|
|
91
|
+
**A class is tested only when it is the observable contract that jsdom would not otherwise see**: jsdom does not
|
|
92
|
+
compute the cascade, the layout or `env()`. Examples are the tone from the dictionary (`TONE_TOKENS`,
|
|
93
|
+
`CHOICE_CARD_SELECTED_BORDER`), the slide width and column count from the component dictionary, and the bar
|
|
94
|
+
background inside and outside the slot (`BACKDROP_CLASS` of `MobileTabBar`). Take the expected value **from the
|
|
95
|
+
dictionary, not as a literal**: a literal checks that nobody touched the string, while the dictionary checks that
|
|
96
|
+
the component picked the right cell.
|
|
97
|
+
|
|
98
|
+
Arbitrary layout classes (`h-7`, `rounded-full`, `ring-bg-1`) are not locked by a test. Visual snapshots guard
|
|
99
|
+
them, and [`tests/class-coverage.test.ts`](../tests/class-coverage.test.ts) checks through a matrix that each
|
|
100
|
+
class reaches the CSS. If such a class has an observable effect (a description inside the label, a node in the
|
|
101
|
+
DOM, an attribute), test the effect.
|
|
102
|
+
|
|
103
|
+
## State management
|
|
104
|
+
|
|
105
|
+
Components mostly take the value and the callback from the consumer. The consumer is the only owner of domain
|
|
106
|
+
state, the request, the route and validation. On a component page, always state:
|
|
107
|
+
|
|
108
|
+
- the shape of the value and of the empty value (`null`, an array, a string, and so on);
|
|
109
|
+
- controlled props and the callback;
|
|
110
|
+
- allowed state transitions;
|
|
111
|
+
- the behavior of disabled and loading;
|
|
112
|
+
- the integration with a native form, if there is one.
|
|
113
|
+
|
|
114
|
+
Do not infer the API from how a story looks: check the type and the source.
|
|
115
|
+
|
|
116
|
+
## How to read a component page
|
|
117
|
+
|
|
118
|
+
Every page must contain:
|
|
119
|
+
|
|
120
|
+
1. A short answer for AI and the limits of use.
|
|
121
|
+
2. The closest alternatives and what not to do.
|
|
122
|
+
3. A minimal working example with the correct import.
|
|
123
|
+
4. An explanation of the public props, states and events.
|
|
124
|
+
5. Accessibility, the keyboard, focus and the boundary with forms.
|
|
125
|
+
6. Anti-patterns, special cases and links to the source and Storybook.
|
|
126
|
+
|
|
127
|
+
## Source layout
|
|
128
|
+
|
|
129
|
+
A component folder in `src/components/<category>/` matches its page folder in `docs/components/<category>/`.
|
|
130
|
+
There are eight categories (`actions`, `inputs`, `date-time`, `overlays`, `navigation`, `content`, `table`,
|
|
131
|
+
`app-shell`). There are no files at the root of `src/components/`. Inside a category, files are flat: an internal
|
|
132
|
+
part lives next to its owner. When a component changes its category, its page changes with it.
|
|
133
|
+
|
|
134
|
+
In `src/editor/`, only the entry points stay at the root: `RichTextEditor`, `RichTextView` and their stories.
|
|
135
|
+
Everything else is in subfolders: `core/` holds the engine and the Tool registry, `ui/` holds the shared glue code,
|
|
136
|
+
`styles/` holds the styling, `hooks/` holds the hooks, and `features/<feature>/` holds the node, commands, Tool and
|
|
137
|
+
UI of one feature.
|
|
138
|
+
|
|
139
|
+
`index.ts` files are not allowed in categories, layers or features: imports go directly to the file. Barrel files
|
|
140
|
+
are allowed only as entry points of the package. `tests/structure.test.ts` checks the layout.
|
|
141
|
+
|
|
142
|
+
## Changing the API
|
|
143
|
+
|
|
144
|
+
When you add or change a runtime export, update its page, its entry in the catalog and the registry in the same
|
|
145
|
+
change set. A breaking change also needs an entry in `docs/api/migration-notes.md` with the action for the
|
|
146
|
+
consumer. Do not hide a change behind behavior that looks similar.
|
|
147
|
+
|
|
148
|
+
## User-visible strings
|
|
149
|
+
|
|
150
|
+
Every label that a user sees or hears goes through the message catalog: `prop ?? messages.family.key`. The prop
|
|
151
|
+
stays: it is a targeted override, not the only way.
|
|
152
|
+
|
|
153
|
+
A string literal in JSX or in a prop default fails `npm test`
|
|
154
|
+
([`tests/i18n-gate.test.ts`](../tests/i18n-gate.test.ts)). The exceptions are an explicit list, with the reason
|
|
155
|
+
given in the gate itself (three reasons are described below), not a regexp.
|
|
156
|
+
|
|
157
|
+
To add a key, see [i18n.md](i18n.md).
|
|
158
|
+
|
|
159
|
+
## Language: what is in Russian, what is in English
|
|
160
|
+
|
|
161
|
+
The boundary follows the audience, not the file type: this is the decision in ADR-0034.
|
|
162
|
+
|
|
163
|
+
| In Russian: the kit explains to itself | In English: the kit shows to the outside |
|
|
164
|
+
| -------------------------------------- | ----------------------------------------------------------- |
|
|
165
|
+
| Comments in the sources | Showcase: sections, story names, demo content, `aria-label` |
|
|
166
|
+
| This documentation and the ADRs | Tests: names, fixtures, data |
|
|
167
|
+
| Text in the Figma mockups | Script output to the console |
|
|
168
|
+
|
|
169
|
+
Comments stay in Russian on purpose, and the i18n gate does not check them. A Russian comment above an English
|
|
170
|
+
story is normal, not unfinished work.
|
|
171
|
+
|
|
172
|
+
Russian is legitimate in tests where it is the **subject** of the check: the plural and case forms and the catalog
|
|
173
|
+
words live in `tests/locale-ru.test.tsx`. In other tests, the expected values come from the English fallback.
|
|
174
|
+
|
|
175
|
+
The gate's allowlist covers **three reasons, not three files**: the keyboard layout as input data (`Keyboard.tsx`
|
|
176
|
+
and its test), the Russian catalog (`locales/ru.ts`), and a Russian word as the subject of a check
|
|
177
|
+
(`locale-ru.test.tsx`, plus the `SortableList` scenario in `tests-visual/interactions.spec.ts`, where react-aria
|
|
178
|
+
localizes the announcements itself). A fourth reason is a case for review, not a reason to change the number.
|
|
179
|
+
|
|
180
|
+
## Showcase story names
|
|
181
|
+
|
|
182
|
+
Section and story names are in Latin script. They make up the story `id`, and the `id` gives the name of the
|
|
183
|
+
snapshot file and its address in `tests-visual`. Renaming costs a baseline regeneration, so pick the name once.
|
|
184
|
+
|
|
185
|
+
A component's section is its category: the folder of its page in the Reference and of its source (ADR-0060). The
|
|
186
|
+
story `src/components/overlays/Dialog.stories.tsx` is named `Overlays/Dialog`. Editor stories take their section
|
|
187
|
+
from the folder of their own page: `Inputs/RichTextEditor`, `Content/RichTextView`.
|
|
188
|
+
|
|
189
|
+
| Section | What is inside |
|
|
190
|
+
| ---------------- | ----------------------------------------------------------- |
|
|
191
|
+
| `Actions/` | Actions and feedback: `Actions/Button`, `Actions/Alert` |
|
|
192
|
+
| `Inputs/` | Fields and choice: `Inputs/Select`, `Inputs/RichTextEditor` |
|
|
193
|
+
| `Date and time/` | Calendars and date and time pickers |
|
|
194
|
+
| `Overlays/` | Dialogs, drawers, popovers, menus, tooltips |
|
|
195
|
+
| `Navigation/` | Tabs, breadcrumbs, page and section headers |
|
|
196
|
+
| `Content/` | Typography, cards, data, states, layout |
|
|
197
|
+
| `Table/` | The data table and its panels |
|
|
198
|
+
| `App shell/` | App shell: `AppShell`, `SideNav`, `TopBar` |
|
|
199
|
+
| `Foundations/` | Basics: icons, motion |
|
|
200
|
+
| `Screens/` | Assembled screens |
|
|
201
|
+
|
|
202
|
+
**Dictionary of repeated names.** A name that appears a second time must be added here. Otherwise the same case
|
|
203
|
+
gets two different names in two files, and they drift apart silently.
|
|
204
|
+
|
|
205
|
+
| Name | When |
|
|
206
|
+
| ----------------- | ------------------------------------------------------ |
|
|
207
|
+
| `Playground` | The main story with controls |
|
|
208
|
+
| `Matrix` | All variants side by side; captured by the visual test |
|
|
209
|
+
| `Empty` | Empty state |
|
|
210
|
+
| `Disabled` | Disabled state |
|
|
211
|
+
| `WithError` | Validation error |
|
|
212
|
+
| `WithAction` | With an action button |
|
|
213
|
+
| `WithSelection` | With a selection |
|
|
214
|
+
| `Sizes` | Size scale |
|
|
215
|
+
| `Horizontal` | Horizontal layout |
|
|
216
|
+
| `Compact` | Dense layout |
|
|
217
|
+
| `SizesAndStates` | Sizes and states side by side |
|
|
218
|
+
| `Collapsed` | Collapsed navigation state |
|
|
219
|
+
| `Submitting` | The form is being submitted |
|
|
220
|
+
| `InForm` | The component inside a form |
|
|
221
|
+
| `AsPage` | Full-screen variant |
|
|
222
|
+
| `WithoutTitle` | Without a title |
|
|
223
|
+
| `States` | A set of states side by side |
|
|
224
|
+
| `Filled` | The field is completely filled |
|
|
225
|
+
| `Open` | An expanded list or calendar |
|
|
226
|
+
| `Tones` | Tone scale |
|
|
227
|
+
| `WithLabel` | With a visible label |
|
|
228
|
+
| `Cards` | Choice variants shown as cards (`appearance="card"`) |
|
|
229
|
+
| `CardsHorizontal` | Choice cards in a row |
|
|
230
|
+
| `CardsStates` | States of choice cards side by side |
|
|
231
|
+
| `CardsTones` | Tone scale of choice cards |
|
|
232
|
+
| `Controlled` | Controlled mode: the story holds the value |
|
|
233
|
+
| `Wrapping` | Wrapping at a narrow width |
|
|
234
|
+
| `Vertical` | Vertical layout |
|
|
235
|
+
| `Badges` | All kinds of badge side by side |
|
|
236
|
+
|
|
237
|
+
Single-word names describe the **case**, not translate a word: a story about how a component differs from its
|
|
238
|
+
neighbors is called `VersusNeighbours`, not a transliteration of a Russian heading.
|
|
239
|
+
|
|
240
|
+
**Snapshot width is set by a story tag.** The visual test takes a snapshot of a story at 1280×900. A story that
|
|
241
|
+
shows a narrow screen sets the tag `tags: ["viewport-phone"]` on itself, and it is captured at 375×812 with an
|
|
242
|
+
emulated screen cutout at the bottom (34px). It is a tag, not a parameter: the spec reads stories from
|
|
243
|
+
`storybook-static/index.json`, which has no parameters. The first such story is `AppShell` → `WithBottomBar`: at
|
|
244
|
+
1280px the slot hides the bar.
|
|
245
|
+
|
|
246
|
+
**A lazy chunk that loads after the story is shown is also set by a tag.** The snapshot waits for two identical
|
|
247
|
+
frames in a row. While a chunk is in transit, the screen does not change, and the snapshot takes a story that is
|
|
248
|
+
not fully loaded for a finished one. A story part of which arrives as a lazy chunk after it is shown sets the tag
|
|
249
|
+
`tags: ["network-idle"]`: the snapshot and the axe audit wait for the network to be quiet. The first such stories
|
|
250
|
+
are `RichTextEditor` → `TextCardOpen` and `TextCardRussian`: the chunk of the Text card is requested only after the
|
|
251
|
+
engine reaches Readiness.
|
|
252
|
+
|
|
253
|
+
**A story name must not match the name of the imported component.** A story about `Separator` in
|
|
254
|
+
`Separator.stories.tsx` must not be called `Separator`: the export shadows the import, and `tsc` fails with
|
|
255
|
+
`cannot be used as a JSX component`. It fails only where the component is used as JSX. Hence the names
|
|
256
|
+
`Separators`, `Scrolling`, `HiddenContent`, `BasicTable`.
|
|
257
|
+
|
|
258
|
+
## Warnings in development mode
|
|
259
|
+
|
|
260
|
+
The first such warning is on `NotificationItem`, which gets both `onAction` and `actions`. The rules come from it,
|
|
261
|
+
and they are written here so the next author does not invent their own.
|
|
262
|
+
|
|
263
|
+
**When to warn.** Only when a contract is broken that **types catch but a consumer in JS does not**, and the
|
|
264
|
+
component silently picks one reading of it (mutually exclusive props, where one of them is ignored). Do not warn
|
|
265
|
+
about style, non-optimal use, deprecation (for that, use `migration-notes.md`), or what React or `react-aria`
|
|
266
|
+
already catches itself. A violation after which the component cannot work is not a warning but an exception (like
|
|
267
|
+
`HoverCardContent` outside `HoverCard`).
|
|
268
|
+
|
|
269
|
+
**How.**
|
|
270
|
+
|
|
271
|
+
- Through [`devWarn(message)`](../src/lib/dev-warn.ts), not a bare `console.warn`. The helper shows the message
|
|
272
|
+
**once** (the key is the text) and **does not write in production**. The call sits in the render body, so
|
|
273
|
+
showing once is required: a list row renders hundreds of times, and `StrictMode` calls the render twice.
|
|
274
|
+
- The text is **in English**, because it is output to the consumer's console (ADR-0034). It does not go into the
|
|
275
|
+
message catalog, because it is not an interface string. The text names the component, the value that was
|
|
276
|
+
passed, the value that won, and how to fix it. Keep changing data out of it: the deduplication key is the text
|
|
277
|
+
itself.
|
|
278
|
+
- The mode is read as `process.env.NODE_ENV !== "production"` inside `try/catch`, on every call. The package
|
|
279
|
+
ships as plain `tsc` output, and the value is not baked into `dist`. The consumer's build sets it (Vite, Next,
|
|
280
|
+
webpack) and removes the dead branch in production. Without a bundler, `process` is not defined: a reference to
|
|
281
|
+
it throws a `ReferenceError`, and the helper stays silent. The check `typeof process !== "undefined"` does not
|
|
282
|
+
work: the bundler does not replace it, so in a Vite browser build the warning would never fire.
|
|
283
|
+
- Test: `vi.spyOn(console, "warn")`, `resetDevWarnings()` in `beforeEach` (the "already shown" state lives at
|
|
284
|
+
module level), and four cases: it warns in development, it warns once, it stays silent in production
|
|
285
|
+
(`vi.stubEnv`), and it stays silent on a correct call.
|
|
286
|
+
|
|
287
|
+
**An error instead of a warning: `devError(message, cause?)`.** Use it when a violation breaks the whole component
|
|
288
|
+
but not the page: the Tool set of `RichTextEditor` is built incorrectly, the loader of an engine part failed,
|
|
289
|
+
`rangeAt` of the Text card threw an exception. The field shows the author a catalog string on its own, and the
|
|
290
|
+
cause goes to the developer through `console.error`, with the same rules as `devWarn`: in development, once per
|
|
291
|
+
text, in English.
|
|
292
|
+
|
|
293
|
+
- The text starts with the component name (`RichTextEditor: …`), because the console is shared by the whole page.
|
|
294
|
+
A Tool name or an entry name in the text is allowed: it comes from the Tool's description and does not change
|
|
295
|
+
from call to call.
|
|
296
|
+
- A caught exception goes in as the second argument (`cause`), not in the text. Its message would make every error
|
|
297
|
+
new for the once-per-text key. The console also shows the stack for an `Error` object.
|
|
298
|
+
- The test follows the same pattern with `vi.spyOn(console, "error")`: an error in development with the exception
|
|
299
|
+
as a separate argument, once; silence in production (`vi.stubEnv`); and silence on a correct call
|
|
300
|
+
(`tests/rich-text-editor-dev-errors.test.tsx`).
|
|
301
|
+
|
|
302
|
+
## `readOnly` and `disabled` are separate axes, not shades of one
|
|
303
|
+
|
|
304
|
+
The rule comes from `Rating` (wave 25). It is written here because the next author would not find it in one
|
|
305
|
+
component's docstring, and a second `readOnly` in the kit would then mean something of its own.
|
|
306
|
+
|
|
307
|
+
- **`readOnly` is data on display.** The value is visible, and it cannot be changed. Such a value must not be
|
|
308
|
+
dimmed: that would hide what is shown on purpose. The control leaves the tab order, but stays at full strength.
|
|
309
|
+
- **`disabled` is an action that is unavailable right now.** It is dimmed, because dimming is the signal that it
|
|
310
|
+
is unavailable.
|
|
311
|
+
|
|
312
|
+
**The role does not change.** A disabled control stays what it was and is marked with `aria-disabled`. Swapping the
|
|
313
|
+
role (for example, to `img`) would be read as a picture, not as an unavailable action. The role is a property of
|
|
314
|
+
the control, not of its availability right now.
|
|
315
|
+
|
|
316
|
+
## Editor glossary
|
|
317
|
+
|
|
318
|
+
The Editor terms moved to the kit's shared [glossary](glossary.md#редактор).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
sourceHash:
|
|
2
|
+
sourceHash: d61bd1f3cca7
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Setting up `@olddevs/ui`
|
|
@@ -133,9 +133,9 @@ peer dependencies are treated as external, and the metric is gzip:
|
|
|
133
133
|
| one `Button` | 1.6 kB |
|
|
134
134
|
| `DataTable` | 9.0 kB |
|
|
135
135
|
| the whole kit (`import *`) | 77.1 kB |
|
|
136
|
-
| `RichTextEditor` from `@olddevs/ui/editor`, without the engine, "start" |
|
|
137
|
-
| `RichTextEditor` with the Tiptap engine, "start" (static graph, `basic`) |
|
|
138
|
-
| `RichTextEditor` with the Tiptap engine, "full" (with all lazy chunks, `full`) |
|
|
136
|
+
| `RichTextEditor` from `@olddevs/ui/editor`, without the engine, "start" | 71.3 kB |
|
|
137
|
+
| `RichTextEditor` with the Tiptap engine, "start" (static graph, `basic`) | 296 kB |
|
|
138
|
+
| `RichTextEditor` with the Tiptap engine, "full" (with all lazy chunks, `full`) | 344.2 kB |
|
|
139
139
|
| `RichTextView` from `@olddevs/ui/editor`, Tiptap engine included in the measurement | 12.0 kB |
|
|
140
140
|
| `sanitizeRichText` and `richTextSanitizeConfig` from `@olddevs/ui/editor/schema`, Tiptap engine included | 3.6 kB |
|
|
141
141
|
| `highlightCodeBlocks` from `@olddevs/ui/editor/highlight`, with lowlight | 22.6 kB |
|
package/docs/getting-started.md
CHANGED
|
@@ -130,9 +130,9 @@ peer-зависимости внешние, метрика gzip:
|
|
|
130
130
|
| одна `Button` | 1,6 кБ |
|
|
131
131
|
| `DataTable` | 9,0 кБ |
|
|
132
132
|
| весь кит целиком (`import *`) | 77,1 кБ |
|
|
133
|
-
| `RichTextEditor` из `@olddevs/ui/editor`, без движка, «старт» |
|
|
134
|
-
| `RichTextEditor` вместе с движком Tiptap, «старт» (статический граф, `basic`) |
|
|
135
|
-
| `RichTextEditor` вместе с движком Tiptap, «полный» (со всеми ленивыми чанками, `full`) |
|
|
133
|
+
| `RichTextEditor` из `@olddevs/ui/editor`, без движка, «старт» | 71,3 кБ |
|
|
134
|
+
| `RichTextEditor` вместе с движком Tiptap, «старт» (статический граф, `basic`) | 296 кБ |
|
|
135
|
+
| `RichTextEditor` вместе с движком Tiptap, «полный» (со всеми ленивыми чанками, `full`) | 344,2 кБ |
|
|
136
136
|
| `RichTextView` из `@olddevs/ui/editor`, движок Tiptap в замер включён | 12,0 кБ |
|
|
137
137
|
| `sanitizeRichText` и `richTextSanitizeConfig` из `@olddevs/ui/editor/schema`, движок Tiptap в замер включён | 3,6 кБ |
|
|
138
138
|
| `highlightCodeBlocks` из `@olddevs/ui/editor/highlight`, вместе с lowlight | 22,6 кБ |
|
package/docs/glossary.en.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
sourceHash:
|
|
2
|
+
sourceHash: 88701a1373b4
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Glossary of `@olddevs/ui`
|
|
@@ -140,6 +140,7 @@ introduces a second meaning.
|
|
|
140
140
|
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
141
141
|
| **Editor** | `RichTextEditor` — a rich text input field | A code editor: the kit has none, code is displayed with `CodeView` |
|
|
142
142
|
| **View** | `RichTextView` — read-only display of a saved Document | The Editor's `readOnly` mode: that one carries the engine, the View does not |
|
|
143
|
+
| **Table of contents** | `RichTextToc`: the list of a Document's headings next to the View or the Editor. Collapsed, it is a strip of strokes; expanded, a list; a click on an item goes to the heading | The kit's `Heading` and the navigation of the documentation site |
|
|
143
144
|
| **Document** | The Editor's value: an HTML string constrained by the Schema | The ProseMirror JSON tree in the engine — it is internal; it comes out only as a JSON envelope |
|
|
144
145
|
| **JSON envelope** | A Document as JSON for a file and a template: `{ format: "olddevs-rich-text", version, doc }` (`getJSON()`, `setJSON()`, `defaultValue`), lossless and through the Schema | The Editor's value — the value stays HTML; the bare tree `editor.getJSON()` without an envelope |
|
|
145
146
|
| **Schema** | An allowlist of Blocks, Marks and attributes that the Editor accepts and returns | Server-side sanitization — the consumer does it (`sanitizeRichText`) |
|
package/docs/glossary.md
CHANGED
|
@@ -135,6 +135,7 @@
|
|
|
135
135
|
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
136
136
|
| **Редактор** | `RichTextEditor` — поле ввода форматированного текста | Редактором кода: его в ките нет, показ кода — `CodeView` |
|
|
137
137
|
| **Просмотр** | `RichTextView` — только чтение сохранённого Документа | `readOnly`-режимом Редактора: тот держит движок, Просмотр — нет |
|
|
138
|
+
| **Оглавление** | `RichTextToc`: список заголовков Документа рядом с Просмотром или Редактором. Свёрнуто — полоса штрихов, раскрыто — список, клик по пункту ведёт к заголовку | `Heading` кита и навигацией сайта документации |
|
|
138
139
|
| **Документ** | Значение Редактора: HTML-строка, ограниченная Схемой | JSON-деревом ProseMirror в движке — оно внутреннее; наружу оно выходит только JSON-конвертом |
|
|
139
140
|
| **JSON-конверт** | Документ как JSON для файла и шаблона: `{ format: "olddevs-rich-text", version, doc }` (`getJSON()`, `setJSON()`, `defaultValue`), без потерь и через Схему | Значением Редактора — значение остаётся HTML; голым деревом `editor.getJSON()` без конверта |
|
|
140
141
|
| **Схема** | Белый список Блоков, Меток и атрибутов, которые Редактор принимает и отдаёт | Санитизацией на сервере — её делает потребитель (`sanitizeRichText`) |
|