@olddevs/ui 4.6.0 → 4.6.1
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 +8 -0
- package/docs/api/editor-extension.en.md +15 -15
- package/docs/api/migration-notes.en.md +8 -8
- package/docs/api/utilities.en.md +6 -6
- package/docs/components/actions/split-button.en.md +9 -9
- package/docs/components/content/data-display.en.md +1 -1
- package/docs/components/content/layout-primitives.en.md +3 -3
- package/docs/components/content/rich-text-toc.en.md +1 -1
- package/docs/components/content/rich-text-view.en.md +12 -12
- package/docs/components/content/virtual-grid.en.md +2 -2
- package/docs/components/date-time/date-picker.en.md +1 -1
- package/docs/components/inputs/rich-text-editor.en.md +159 -159
- package/docs/components/navigation/mega-menu.en.md +10 -10
- package/docs/components/overlays/menu.en.md +2 -2
- package/docs/components/overlays/shortcuts-help.en.md +1 -1
- package/docs/components/table/data-table.en.md +3 -3
- package/docs/components/table/filter-bar.en.md +1 -1
- package/docs/components/table/toolbar.en.md +1 -1
- package/docs/glossary.en.md +184 -152
- package/docs/glossary.md +182 -151
- package/docs/icons.en.md +9 -9
- package/docs/palette.en.md +3 -3
- package/docs/theming.en.md +6 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @olddevs/ui
|
|
2
2
|
|
|
3
|
+
## 4.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f23fc5b: The English Reference now calls borrowed concepts by the words of their origin, so they can be found by search: the Bubble menu (was Selection menu) and the Slash menu (was Insert menu), as in Tiptap; the table toolbar, menu aim, color scale, filtered rows, menu button, keyboard shortcuts dialog, Keyboard shortcuts popover, Source mode, User edit, Programmatic replacement, Status bar, Column layout and Callout tone. Only the text changes: props, values and exports stay the same.
|
|
8
|
+
|
|
9
|
+
The glossary gets two columns, "Source" (where the English word comes from) and "Also searched as" (synonyms used elsewhere), a `chip` row, and BubbleMenu, FloatingMenu and Suggestion rows in its Tiptap section: `toolbar="floating"` is the floating Toolbar above a selection, built on Tiptap's BubbleMenu, not Tiptap's FloatingMenu.
|
|
10
|
+
|
|
3
11
|
## 4.6.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
|
@@ -9,7 +9,7 @@ sourceHash: 808244893ee6
|
|
|
9
9
|
|
|
10
10
|
## Summary for AI
|
|
11
11
|
|
|
12
|
-
A map of how a product extends `RichTextEditor` from `@olddevs/ui/editor`, and the stability boundary of this API. The unit of extension is a **custom Tool** (`CustomTool`): the description of one Editor capability, passed in `tools` mixed in with the kit's names. It carries Blocks and Marks with Schema rules (as data), Extensions (Tiptap extensions, supplied directly or through a Loader) and UI contributions: buttons, Insert items, Block actions, Text cards. A custom Trigger character (`trigger`) is also available: `@` for mentions opens the same list as `/`. The screen's code controls the field through the Handle (`ref`) and the `useRichTextEditorState` hook, learns about Readiness and Field focus through the `onReady`, `onFocus` and `onBlur` callbacks, puts its own content into the Status
|
|
12
|
+
A map of how a product extends `RichTextEditor` from `@olddevs/ui/editor`, and the stability boundary of this API. The unit of extension is a **custom Tool** (`CustomTool`): the description of one Editor capability, passed in `tools` mixed in with the kit's names. It carries Blocks and Marks with Schema rules (as data), Extensions (Tiptap extensions, supplied directly or through a Loader) and UI contributions: buttons, Insert items, Block actions, Text cards. A custom Trigger character (`trigger`) is also available: `@` for mentions opens the same list as `/`. The screen's code controls the field through the Handle (`ref`) and the `useRichTextEditorState` hook, learns about Readiness and Field focus through the `onReady`, `onFocus` and `onBlur` callbacks, puts its own content into the Status bar (`status`) and reports an error with the `showNotice` command. A Tool has three typed parts, like `options`, `storage` and `commands` of a Tiptap extension: **options** (`tool(name, options)` for the built-in `search` and `toc`, `defineTool` for a custom Tool), **state** (the `useRichTextToolState` hook) and **commands** (`handle.tool(name)`); their types are collected in the extensible `RichTextTools` map, where a custom Tool adds itself through `declare module "@olddevs/ui/editor"`. The same `tools` array is understood by server-side sanitizing (`sanitizeRichText`, `richTextSchema`) and by `RichTextView` with Islands. Everything listed follows the kit's semver from the first release, the HTML convention is frozen, and a Tiptap major is a kit major (section "Stability"). The details of each item live on the [RichTextEditor](../components/inputs/rich-text-editor.md) page; this page covers the map, the contract boundary and the recipes.
|
|
13
13
|
|
|
14
14
|
```tsx
|
|
15
15
|
import {
|
|
@@ -32,7 +32,7 @@ The decision is ADR-0058, which supplements ADR-0050. Capitalized terms come fro
|
|
|
32
32
|
- The Document needs a custom Block, Atom or Mark: a merge tag, an embed, a product card, a comment on a fragment. Their Schema, HTML, Markdown and display in `RichTextView` are described by one Tool.
|
|
33
33
|
- Your own command in the Editor's interface: a button on the Toolbar, an Insert item in "/" and "+", a Block action in the Block handle menu, a Text card next to a fragment of text.
|
|
34
34
|
- The screen controls the field: it inserts a template at the caret, renders its own panel, saves on focus loss, and shows "Saved" and the word count.
|
|
35
|
-
- A built-in Tool needs configuring (`toc` under a header with its own height variable, Find with case matching on) or you need to know what is happening inside it: a match counter in the Status
|
|
35
|
+
- A built-in Tool needs configuring (`toc` under a header with its own height variable, Find with case matching on) or you need to know what is happening inside it: a match counter in the Status bar, an external navigation over the headings, a "Find" button in the screen header.
|
|
36
36
|
- A custom Tool with options whose state and commands the screen needs: `defineTool` instead of a hand-written factory.
|
|
37
37
|
- The server stores a Document with custom Blocks and must sanitize it with the same set of rules as the Editor.
|
|
38
38
|
|
|
@@ -44,7 +44,7 @@ The decision is ADR-0058, which supplements ADR-0050. Capitalized terms come fro
|
|
|
44
44
|
- You cannot change the options of a mounted field: they are read once, when the engine is created, together with `tools`. Remount the field with a key.
|
|
45
45
|
- You cannot put arbitrary React in the Toolbar row or in a menu: the kit renders the UI contributions itself. React is allowed only in the content of a popover (of a button, Insert item or Block action) and in a Text card.
|
|
46
46
|
- You cannot intercept paste and drop through a prop: the Extensions of a custom Tool handle them (["Paste and drop order"](../components/inputs/rich-text-editor.md#порядок-вставки-и-drop)).
|
|
47
|
-
- What is not provided until consumers ask for it (ADR-0058, section 13): `insertImage` and the selection's HTML in the Handle, `onSelectionChange`, `onPaste`, `onDrop`, `onEmpty`, `onDestroy`, history events, a signal about removed images, locked template Blocks, a "menu" UI contribution kind, Text cards and the Status
|
|
47
|
+
- What is not provided until consumers ask for it (ADR-0058, section 13): `insertImage` and the selection's HTML in the Handle, `onSelectionChange`, `onPaste`, `onDrop`, `onEmpty`, `onDestroy`, history events, a signal about removed images, locked template Blocks, a "menu" UI contribution kind, Text cards and the Status bar in `RichTextView`. Until then the screen has `ref.current.editor` and its events.
|
|
48
48
|
- Custom Trigger characters like `@` are no longer on this list: they are supported by the `trigger` field of a Tool (ADR-0064, section "Trigger character" below).
|
|
49
49
|
|
|
50
50
|
## API map
|
|
@@ -64,7 +64,7 @@ The decision is ADR-0058, which supplements ADR-0050. Capitalized terms come fro
|
|
|
64
64
|
| Tool state | `useRichTextToolState(ref, name, selector?)`, `SearchToolState`, `TocToolState`, `TocToolHeading` | ["Tool state: `useRichTextToolState`"](../components/inputs/rich-text-editor.md#состояние-инструмента-userichtexttoolstate) |
|
|
65
65
|
| Tool commands | `handle.tool(name)`, `SearchToolCommands`, `TocToolCommands` | ["Tool commands: `handle.tool()`"](../components/inputs/rich-text-editor.md#команды-инструмента-handletool) |
|
|
66
66
|
| Events | `onReady`, `onFocus`, `onBlur`; paste and drop — by the Extensions | ["Events"](../components/inputs/rich-text-editor.md#события) |
|
|
67
|
-
| Status
|
|
67
|
+
| Status bar and Field notice | the `status` prop; the engine command `showNotice`, the `FieldNotice` type | ["Status bar and Field notice"](../components/inputs/rich-text-editor.md#строка-статуса-и-сообщение-поля) |
|
|
68
68
|
| Loader, Readiness and Skeleton | `engine: () => import(…)` | ["Extensions Loader"](../components/inputs/rich-text-editor.md#загрузчик-extensions), ["Skeleton"](../components/inputs/rich-text-editor.md#скелет) |
|
|
69
69
|
| Atom text: `getText`, `maxLength`, emptiness | `renderText` of the Extensions | ["Atom text"](../components/inputs/rich-text-editor.md#текст-атома) |
|
|
70
70
|
| Server, Markdown, View | `sanitizeRichText(html, DOMPurify, tools)`, `richTextSchema(tools)`, `RichTextView` | ["Outside the Editor"](../components/inputs/rich-text-editor.md#вне-редактора-сервер-markdown-и-просмотр), [RichTextView, "Custom Tools: the third argument"](../components/content/rich-text-view.md#свои-инструменты-третий-аргумент) |
|
|
@@ -91,7 +91,7 @@ The kit renders, the Tool describes. So the ARIA roles, the Toolbar's single Tab
|
|
|
91
91
|
|
|
92
92
|
| UI contribution | Field | Slot |
|
|
93
93
|
| --------------- | -------------- | ---------------------------------------------------------------------------------------------------- |
|
|
94
|
-
| Button | `buttons` | A Toolbar group before "Undo" and "Redo"; with `selectionMenu: true`, also the
|
|
94
|
+
| Button | `buttons` | A Toolbar group before "Undo" and "Redo"; with `selectionMenu: true`, also the Bubble menu |
|
|
95
95
|
| Insert item | `insertItems` | The end of its section ("Add" or "Turn into") in "/", "+", "Add ›" and the Block handle's "Format ›" |
|
|
96
96
|
| Block action | `blockActions` | A section of the Block handle menu before "Delete" — for Blocks from `blocks` |
|
|
97
97
|
| Text card | `textCards` | A frame under the text range where the caret is; no more than one is open |
|
|
@@ -99,21 +99,21 @@ The kit renders, the Tool describes. So the ARIA roles, the Toolbar's single Tab
|
|
|
99
99
|
- **The label** is a string or a function that receives `{ locale }` (the `UiProvider` locale); the `UiProvider` message catalog is not extended for third-party Tools, and overriding is through the Tool factory's options (recipe below).
|
|
100
100
|
- **The icon** is required for a button, item and action: a component with the `IconProps` contract from `@olddevs/ui/icons`, or your own.
|
|
101
101
|
- **The popover** — a button, Insert item and Block action have exactly one of `run` and `popover`. The content is a function that receives `{ editor, close }` (for an action, also the Block's `pos`), and the kit provides the frame: the overlay layer, the Editor's popover Glass, the Escape stack, the name (the UI contribution's label), and returning focus and selection to the Document. A Text card sits on the same Glass, so your content does not need a solid background: it would cover the Glass backing. When the system transparency or contrast setting is on, the backing becomes solid on its own anyway. The popover opens at the button (click), at the caret (the button's shortcut, an item from "/", "+", "Add ›", "Format ›" and its shortcut) or at the Block (an action from the Block handle menu and its shortcut).
|
|
102
|
-
- **The shortcut** (`shortcut`) is the single source: the kit binds the keyboard shortcut and writes it into the tooltip, `aria-keyshortcuts` and the
|
|
102
|
+
- **The shortcut** (`shortcut`) is the single source: the kit binds the keyboard shortcut and writes it into the tooltip, `aria-keyshortcuts` and the Keyboard shortcuts popover. Keyboard shortcuts bound by the Extensions do not reach the Keyboard shortcuts popover.
|
|
103
103
|
- **A disabled Tool** removes its UI contributions from every surface.
|
|
104
104
|
|
|
105
105
|
## Trigger character
|
|
106
106
|
|
|
107
|
-
A Tool's `trigger` is not a UI contribution of a Slot but another way into the same list as "/": typing the `char` character (`@`, `#`) at the start of a Block or after a space opens a list of items at the caret, which the Tool returns on demand (`items(query)`, synchronously or as a promise). The kit renders the list: it is the same component as the
|
|
107
|
+
A Tool's `trigger` is not a UI contribution of a Slot but another way into the same list as "/": typing the `char` character (`@`, `#`) at the start of a Block or after a space opens a list of items at the caret, which the Tool returns on demand (`items(query)`, synchronously or as a promise). The kit renders the list: it is the same component as the Slash menu, with search as you type, arrow keys, `Enter`, `Escape` and a "Nothing found" row. Picking an item calls `onSelect(item, { editor, range, query })`, and the handler replaces `range` (the typed `@query`) with a custom node. Inside a word (`a@b.com`) and in code, the list does not open. The character is one printable character, not `/` and not a space, unique in `tools`; a violation produces an error in the `tools` array (ADR-0064, details on the [RichTextEditor](../components/inputs/rich-text-editor.md#свой-символ-триггер) page).
|
|
108
108
|
|
|
109
|
-
## Handle (`ref`), events, Status
|
|
109
|
+
## Handle (`ref`), events, Status bar
|
|
110
110
|
|
|
111
|
-
- **The Handle** (`ref`) — insertion at the caret (`insertHTML`, `insertText` → `boolean`), reading (`getText`, `getSelectedText`, `isEmpty`, `getHTML`, `getMarkdown`, `getJSON`), focus (`focus("start" | "end" | "restore")`, `blur`), history (`undo`, `redo`, `canUndo`, `canRedo`), plus `clear`, `setMarkdown` and `setJSON` (a JSON envelope → `boolean`) and `tool(name)`, the commands of a Tool (see "Options, state and commands" below). `openSearch({ replace? })` is deprecated: it works as `tool("search")?.open({ replace })`, warns once in development and is removed in 5.0. Insertion is
|
|
111
|
+
- **The Handle** (`ref`) — insertion at the caret (`insertHTML`, `insertText` → `boolean`), reading (`getText`, `getSelectedText`, `isEmpty`, `getHTML`, `getMarkdown`, `getJSON`), focus (`focus("start" | "end" | "restore")`, `blur`), history (`undo`, `redo`, `canUndo`, `canRedo`), plus `clear`, `setMarkdown` and `setJSON` (a JSON envelope → `boolean`) and `tool(name)`, the commands of a Tool (see "Options, state and commands" below). `openSearch({ replace? })` is deprecated: it works as `tool("search")?.open({ replace })`, warns once in development and is removed in 5.0. Insertion is a User edit: a history step, `onChange` after a delay, the `maxLength` limit; past the limit, the insertion does not happen. Before Readiness the methods do not throw and change nothing: `insertHTML` and `insertText` return `false`. `editor` is the raw Tiptap for commands of Marks, Blocks and custom Tools; its API follows the Tiptap major.
|
|
112
112
|
- **`useRichTextEditorState(ref, selector)`** — the state of your own panel: re-renders only when the slice changes, `null` before the engine and on the server.
|
|
113
113
|
- **`useRichTextToolState(ref, name, selector?)`** — the state of a Tool: a data snapshot; the screen re-renders only when it changes, and the hook returns `null` before mount, on the server, after the Editor unmounts and without the Tool in the set.
|
|
114
114
|
- **`onReady()`** — once per mount, when Readiness arrives; **`onFocus()`** and **`onBlur()`** — report focus on the field as a whole (Document, Toolbar, the kit's popovers, the HTML field); before `onBlur`, a pending `onChange` is flushed.
|
|
115
115
|
- **`status`** — the screen's React node in the line under the Document, at the start; the kit's counter is at the end. The kit does not announce the line.
|
|
116
|
-
- **`showNotice({ tone: "status" | "error", text } | null)`** — an engine command: one Field notice, a new one replaces the previous; `status` is only announced, `error` is visible as a `role="alert"` line until the next
|
|
116
|
+
- **`showNotice({ tone: "status" | "error", text } | null)`** — an engine command: one Field notice, a new one replaces the previous; `status` is only announced, `error` is visible as a `role="alert"` line until the next User edit. The kit's image messages go through the same channel.
|
|
117
117
|
|
|
118
118
|
## Options, state and commands
|
|
119
119
|
|
|
@@ -354,11 +354,11 @@ The contract boundary per ADR-0058, item 12. The reviewer checks the changeset t
|
|
|
354
354
|
|
|
355
355
|
### Your own panel outside the field: `preventDefault` on `mousedown`
|
|
356
356
|
|
|
357
|
-
The buttons of your own panel outside the field call `event.preventDefault()` on `mousedown`: focus stays in the Document, and the floating Toolbar, the
|
|
357
|
+
The buttons of your own panel outside the field call `event.preventDefault()` on `mousedown`: focus stays in the Document, and the floating Toolbar, the Bubble menu and the image panel do not hide. The state of the buttons comes from `useRichTextEditorState`, the commands are `ref.current?.editor?.chain().focus()…run()`. The example and details are in [RichTextEditor, "Your own panel"](../components/inputs/rich-text-editor.md#своя-панель-userichtexteditorstate). The rule "a click into your own panel does not clear the selection" is a recipe, not a property of the field.
|
|
358
358
|
|
|
359
|
-
### "Saved" in the Status
|
|
359
|
+
### "Saved" in the Status bar
|
|
360
360
|
|
|
361
|
-
Autosave on focus loss and announcing the result. The live region stays in the Status
|
|
361
|
+
Autosave on focus loss and announcing the result. The live region stays in the Status bar **permanently**, and only its text changes: screen readers often do not read a region that is inserted together with its text. The word count next to it has no `role`, so it is not announced on every edit.
|
|
362
362
|
|
|
363
363
|
```tsx
|
|
364
364
|
import { useRef, useState } from "react";
|
|
@@ -401,7 +401,7 @@ function ArticleBody({
|
|
|
401
401
|
}
|
|
402
402
|
```
|
|
403
403
|
|
|
404
|
-
Show a save error where the screen shows form errors: `showNotice` is for the field's own events (a refused paste, a file upload), and the `error` line goes away with the first
|
|
404
|
+
Show a save error where the screen shows form errors: `showNotice` is for the field's own events (a refused paste, a file upload), and the `error` line goes away with the first User edit. For details about the line, see ["Status bar: `status`"](../components/inputs/rich-text-editor.md#строка-статуса-status).
|
|
405
405
|
|
|
406
406
|
### Removed images: comparing `src` on the server
|
|
407
407
|
|
|
@@ -506,4 +506,4 @@ The kit does not publish preloads. The chunks of Extensions (of custom Loaders a
|
|
|
506
506
|
- Pages: [RichTextEditor](../components/inputs/rich-text-editor.md), [RichTextView](../components/content/rich-text-view.md), [Editor glossary](../glossary.md#редактор), [migration-notes](migration-notes.md).
|
|
507
507
|
- Decisions: ADR-0058 — the extension API, ADR-0069 — Tool options, state and commands, ADR-0050 — the Editor.
|
|
508
508
|
- Source: the entry point `src/editor/index.ts`, the public form of a custom Tool `src/editor/core/customTool.ts`, validation of the `tools` array and wiring `src/editor/core/customToolEngine.ts`, the Handle `src/editor/core/handle.ts`, the hooks `src/editor/hooks/useRichTextEditorState.ts` and `src/editor/hooks/useRichTextToolState.ts`, options and the map `src/editor/core/toolOptions.ts`, the command types `src/editor/core/toolCommands.ts` and the state types `src/editor/core/toolState.ts`, the Field notice `src/editor/core/fieldNotice.ts`, the Schema and sanitizing `src/editor/core/schema.ts`, Islands `src/editor/core/viewIsland.ts`.
|
|
509
|
-
- Tests: `tests/rich-text-editor-custom-tool.test.tsx` (description, Schema, `tools` array validation), `tests/rich-text-editor-custom-button.test.tsx`, `tests/rich-text-editor-custom-ui.test.tsx` and `tests/rich-text-editor-text-card.test.tsx` (UI contributions), `tests/rich-text-editor-handle.test.tsx` and `tests/rich-text-editor-handle-ssr.test.tsx` (the Handle and the hook), `tests/rich-text-editor-events.test.tsx` (callbacks, paste and drop order), `tests/rich-text-editor-notice.test.tsx` (Status
|
|
509
|
+
- Tests: `tests/rich-text-editor-custom-tool.test.tsx` (description, Schema, `tools` array validation), `tests/rich-text-editor-custom-button.test.tsx`, `tests/rich-text-editor-custom-ui.test.tsx` and `tests/rich-text-editor-text-card.test.tsx` (UI contributions), `tests/rich-text-editor-handle.test.tsx` and `tests/rich-text-editor-handle-ssr.test.tsx` (the Handle and the hook), `tests/rich-text-editor-events.test.tsx` (callbacks, paste and drop order), `tests/rich-text-editor-notice.test.tsx` (Status bar and Field notice), `tests/rich-text-editor-loaders.test.tsx`, `tests/rich-text-editor-skeleton.test.tsx` and `tests/rich-text-editor-lazy-builtins.test.tsx` (Loaders, Skeleton, lazy built-ins), `tests/rich-text-editor-atom-text.test.tsx` (Atom text), `tests/rich-text-editor-custom-markdown.test.tsx`, `tests/editor-sanitize-tools.test.tsx`, `tests/rich-text-view-island.test.tsx`, `tests/editor-entry.test.ts`, `tests/rich-text-editor-tool-options.test.tsx`, `tests/rich-text-editor-tool-state.test.tsx`, `tests/rich-text-editor-tool-commands.test.tsx` and `tests/rich-text-editor-define-tool.test.tsx` (options, state, commands, `defineTool`), `tests/editor-tool-options-types.test.ts`, `tests/editor-tool-state-types.test.ts`, `tests/editor-tool-commands-types.test.ts` and `tests/editor-define-tool-types.test.ts` (types and the map); test Tools — `tests/editor-test-tools.ts`.
|
|
@@ -22,7 +22,7 @@ became relevant.
|
|
|
22
22
|
- When changing a default, accessibility, keyboard behavior or the shape of a
|
|
23
23
|
controlled value: TypeScript may not detect such a migration.
|
|
24
24
|
- When replacing one component with another, for example, when the old API goes away in favor of
|
|
25
|
-
an existing family.
|
|
25
|
+
an existing component family.
|
|
26
26
|
|
|
27
27
|
## When not to use
|
|
28
28
|
|
|
@@ -110,7 +110,7 @@ ref.current?.tool("search")?.open({ replace: true });
|
|
|
110
110
|
|
|
111
111
|
Unchanged: the same panel, the same focus, the same shortcuts. The differences are small: `tool("search")` is `null`
|
|
112
112
|
without the `search` Tool in `tools` and before the engine exists, so a call through `?.` does nothing, just as
|
|
113
|
-
`openSearch` did; `open` returns a `boolean` (`false` in
|
|
113
|
+
`openSearch` did; `open` returns a `boolean` (`false` in Source mode) instead of `void`.
|
|
114
114
|
|
|
115
115
|
### Action before updating
|
|
116
116
|
|
|
@@ -282,7 +282,7 @@ the same page.
|
|
|
282
282
|
`onChange`, `onUploadImage` and `focus()` do not change, and the previous code builds.
|
|
283
283
|
The note is needed because five changes are visible to current consumers
|
|
284
284
|
of `@olddevs/ui/editor`, and one requires installing packages with pnpm. The new API
|
|
285
|
-
(a custom Tool, the Handle, events, the Status
|
|
285
|
+
(a custom Tool, the Handle, events, the Status bar) requires no action — it is on the
|
|
286
286
|
[extension API page](editor-extension.md).
|
|
287
287
|
|
|
288
288
|
### 1. Two new peer packages
|
|
@@ -381,7 +381,7 @@ arrive with the page, add `modulepreload` for them
|
|
|
381
381
|
**What changed.** "Uploading…", "uploaded", an upload refusal, "not an image" and
|
|
382
382
|
"files are not accepted" go through the same channel as the new engine command
|
|
383
383
|
`showNotice`: one place under the field, a new message replaces the previous one, and an error
|
|
384
|
-
(`role="alert"`) stays until the next
|
|
384
|
+
(`role="alert"`) stays until the next User edit. The texts and roles are the same, but the
|
|
385
385
|
live region `role="status"` is now present in **every** Editor, not only with the
|
|
386
386
|
`image` Tool.
|
|
387
387
|
|
|
@@ -578,7 +578,7 @@ is parameterized by your state (`SavedView<T>`) and is not tied to
|
|
|
578
578
|
`TableState`; storage, sharing and the URL stay entirely with you. For details, see the
|
|
579
579
|
[component page](../components/table/saved-views.md).
|
|
580
580
|
|
|
581
|
-
**The `FormField` hint and error reach the react-aria families.** For
|
|
581
|
+
**The `FormField` hint and error reach the react-aria component families.** For
|
|
582
582
|
`Select`, `Combobox`, `DatePicker` and `TagInput`, the description from `FormField` is now tied to
|
|
583
583
|
the field through `aria-describedby`. **This is an accessibility fix, and it changes
|
|
584
584
|
what is announced:** where a hint was visible on screen but did not reach a screen reader, a
|
|
@@ -646,7 +646,7 @@ above came from: `npm run check:dist`, `npm run check:consumer`,
|
|
|
646
646
|
with changesets.
|
|
647
647
|
|
|
648
648
|
**Deliberately postponed:** Figma Code Connect (priority P2 in the specification, only after
|
|
649
|
-
the names and axes stabilize); binding the description for the react-aria families (`Select`,
|
|
649
|
+
the names and axes stabilize); binding the description for the react-aria component families (`Select`,
|
|
650
650
|
`Combobox`, `DatePicker`, `TagInput`) — they have their own description slots; merging
|
|
651
651
|
`UploadState` and `TimelineStatus` — it breaks a public type and waits for a major;
|
|
652
652
|
widening the peer to React 18 — it doubles the consumer's CI build.
|
|
@@ -735,7 +735,7 @@ the description is **supplemented, not replaced**: the attribute holds a list.
|
|
|
735
735
|
|
|
736
736
|
`TextControl` and everything built on it (`MaskedInput`,
|
|
737
737
|
`PhoneInput`, `PasswordInput`, `SearchControl`), plus `NumberInput`, pick up the description. The react-aria
|
|
738
|
-
families (`Select`, `Combobox`, `DatePicker`, `TagInput`) carry their own description slots
|
|
738
|
+
component families (`Select`, `Combobox`, `DatePicker`, `TagInput`) carry their own description slots
|
|
739
739
|
and do not pick up the `FormField` hint yet.
|
|
740
740
|
|
|
741
741
|
**3. A character that does not fit the mask is announced.** A character that did not fit the mask token
|
|
@@ -817,7 +817,7 @@ something read it; `DataTable` became that reader.
|
|
|
817
817
|
`tableStateToParams`, `tableStateFromParams`, `DEFAULT_TABLE_STATE`.
|
|
818
818
|
|
|
819
819
|
**5. Selection is within a page.** "Select all" marks the `items` that arrived;
|
|
820
|
-
the kit does not support selecting the
|
|
820
|
+
the kit does not support selecting all the filtered rows, and the labels about it do not lie.
|
|
821
821
|
If you need selection beyond the page, that is your code, not the kit's.
|
|
822
822
|
|
|
823
823
|
**6. State is carried into the URL without selection and expansion.** The list of keys
|
package/docs/api/utilities.en.md
CHANGED
|
@@ -156,10 +156,10 @@ nothing outside React** and subscribes to nothing. The kit provides the shape, a
|
|
|
156
156
|
### Invariants the hook exists to keep
|
|
157
157
|
|
|
158
158
|
- changing **filters, sorting or the page size resets the page** to the first one. Otherwise the user looks at
|
|
159
|
-
page four of
|
|
159
|
+
page four of ten filtered rows, and reads the empty space as "nothing found";
|
|
160
160
|
- **selection is not reset.** The reverse looks tidier, but it breaks a bulk action exactly when that action is
|
|
161
161
|
needed most;
|
|
162
|
-
- density and column visibility do **not** touch the page. A filter narrows the
|
|
162
|
+
- density and column visibility do **not** touch the page. A filter narrows the rows, not the look;
|
|
163
163
|
- an explicit `page` in the same call cancels the reset. This is how the state is restored from the address,
|
|
164
164
|
in full and in one go.
|
|
165
165
|
|
|
@@ -190,7 +190,7 @@ router.replace(`?${tableStateToParams(state)}`);
|
|
|
190
190
|
state. There are two reasons. The list of keys has no length limit. And a link with a selection, opened the next
|
|
191
191
|
day, marks records that no longer exist. Selection is session state, not URL state.
|
|
192
192
|
|
|
193
|
-
The restored state is passed to the hook as a whole, in one `set` call. The invariant "narrowing the
|
|
193
|
+
The restored state is passed to the hook as a whole, in one `set` call. The invariant "narrowing the filtered rows
|
|
194
194
|
resets the page" is cancelled by an explicit page in the same call. Otherwise a link would always open the first
|
|
195
195
|
page.
|
|
196
196
|
|
|
@@ -207,7 +207,7 @@ For how to build a range in the UI, see [FilterBar](../components/table/filter-b
|
|
|
207
207
|
|
|
208
208
|
## Constants, classes and public types: where they are documented
|
|
209
209
|
|
|
210
|
-
These are public exports, but not standalone components. The page of the family they belong to describes them,
|
|
210
|
+
These are public exports, but not standalone components. The page of the component family they belong to describes them,
|
|
211
211
|
so that AI does not treat a class as a general-purpose API:
|
|
212
212
|
|
|
213
213
|
| Exports | Documentation |
|
|
@@ -221,8 +221,8 @@ so that AI does not treat a class as a general-purpose API:
|
|
|
221
221
|
|
|
222
222
|
Each `*Props` type and each component-specific type exported with a component (for example `TableProps`,
|
|
223
223
|
`CommandItem`, `SelectItem`, `KeyboardSelection` or `DrawerSide`) stays part of that component's public API. It
|
|
224
|
-
is described on the page of its family. The [component catalog](../components/index.md) links the public exports
|
|
225
|
-
to the family pages. `src/index.ts` is the final source for all exports.
|
|
224
|
+
is described on the page of its component family. The [component catalog](../components/index.md) links the public exports
|
|
225
|
+
to the component family pages. `src/index.ts` is the final source for all exports.
|
|
226
226
|
|
|
227
227
|
## Related sources and checks
|
|
228
228
|
|
|
@@ -12,7 +12,7 @@ sourceHash: 982b0da00a2f
|
|
|
12
12
|
|
|
13
13
|
## Summary for AI
|
|
14
14
|
|
|
15
|
-
`SplitButton` is a primary action with a menu
|
|
15
|
+
`SplitButton` is a primary action with a menu button for its variants next to it, as **two separate buttons**. The accessible name of the menu button is built from `label`, so `label` is a string, not a node. Menu items go in `children`.
|
|
16
16
|
|
|
17
17
|
## When to use
|
|
18
18
|
|
|
@@ -38,9 +38,9 @@ sourceHash: 982b0da00a2f
|
|
|
38
38
|
|
|
39
39
|
## Public API
|
|
40
40
|
|
|
41
|
-
- `label` is a **string**: the label of the primary action and the source of the name of the menu
|
|
41
|
+
- `label` is a **string**: the label of the primary action and the source of the name of the menu button.
|
|
42
42
|
- `icon` is the icon on the left of the primary action. If a button can enter `loading`, give it an icon from the start: otherwise the loading indicator adds a slot, and the label shifts.
|
|
43
|
-
- `onClick` is called only by the primary action; the menu
|
|
43
|
+
- `onClick` is called only by the primary action; the menu button does not call it.
|
|
44
44
|
- `variant`, `tone`, `size` are the same axes as on `Button` and mean the same thing.
|
|
45
45
|
- `disabled` and `loading` disable **both** buttons and close a menu that is already open — and **do not bring it back** when `disabled` or `loading` is lifted: otherwise the menu would open by itself once a submission ended.
|
|
46
46
|
- `placement` is the side the panel opens to, `bottom end` by default. The side values of `Menu` are narrowed here: a panel that slides sideways lands on top of the primary action.
|
|
@@ -57,20 +57,20 @@ two. These are two separate controls.
|
|
|
57
57
|
|
|
58
58
|
## Why `label` is a string
|
|
59
59
|
|
|
60
|
-
The name of the menu
|
|
60
|
+
The name of the menu button is built from the label. A node would give a name
|
|
61
61
|
that does not exist — or would require a second prop with the same label as a
|
|
62
62
|
string. One label would live in two places and drift apart at the first edit.
|
|
63
63
|
|
|
64
64
|
## Keyboard
|
|
65
65
|
|
|
66
|
-
- **Two tab stops, not one.** Tab goes to the primary action, the next Tab to the menu
|
|
67
|
-
- The menu opens from the menu
|
|
68
|
-
- Escape closes the menu and **returns focus to the menu
|
|
66
|
+
- **Two tab stops, not one.** Tab goes to the primary action, the next Tab to the menu button. The whole rationale of the component rests on this: a single button whose right part opens a menu would leave the second outcome unreachable from the keyboard.
|
|
67
|
+
- The menu opens from the menu button with Enter, Space and Down Arrow — `MenuTrigger` does this.
|
|
68
|
+
- Escape closes the menu and **returns focus to the menu button** instead of sending it to `<body>`.
|
|
69
69
|
- Disabling the component while the menu is open closes the panel: otherwise it would look disabled while the items kept working.
|
|
70
70
|
|
|
71
71
|
## Accessibility and semantics
|
|
72
72
|
|
|
73
|
-
- The menu
|
|
74
|
-
- The menu
|
|
73
|
+
- The menu button's name comes from the message catalog: `splitButton.more({ action })` → "Другие действия: Экспорт" with `@olddevs/ui/locales/ru`, "More actions: Export" without a provider. Without the action's label, several menu buttons on a page are indistinguishable when read aloud by a screen reader.
|
|
74
|
+
- The menu button carries `aria-haspopup` and `aria-expanded` — `MenuTrigger` sets them.
|
|
75
75
|
- The group does not get `role="group"`: an unnamed group is an extra screen reader announcement, and a name would duplicate the button's label.
|
|
76
76
|
- See `src/components/actions/SplitButton.tsx`, `src/components/actions/SplitButton.stories.tsx`, `tests/split-button.test.tsx`, [Menu](../overlays/menu.md).
|
|
@@ -117,7 +117,7 @@ the row: a manual `stopPropagation` is not needed. A detail row and the expander
|
|
|
117
117
|
button do not call the action. A row with an action gets a pointer cursor.
|
|
118
118
|
|
|
119
119
|
**Expansion is the only axis that could not be added as a layer on top.**
|
|
120
|
-
`DataTable` adds column visibility, order, density and the table
|
|
120
|
+
`DataTable` adds column visibility, order, density and the table toolbar (`toolbar`)
|
|
121
121
|
from the outside: they change what goes into `Table` through props. A detail row
|
|
122
122
|
lives **inside** `<tbody>`, between the rows of the react-aria collection, and
|
|
123
123
|
cannot be inserted from the outside. The analysis is in
|
|
@@ -77,9 +77,9 @@ import { AspectRatio, Kbd, ScrollArea, Separator } from "@olddevs/ui";
|
|
|
77
77
|
|
|
78
78
|
### `Kbd`
|
|
79
79
|
|
|
80
|
-
| Prop | Default | Meaning
|
|
81
|
-
| ------ | ------- |
|
|
82
|
-
| `size` | `sm` | `sm` — inside a menu, `md` — in a standalone shortcuts
|
|
80
|
+
| Prop | Default | Meaning |
|
|
81
|
+
| ------ | ------- | ----------------------------------------------------------------------- |
|
|
82
|
+
| `size` | `sm` | `sm` — inside a menu, `md` — in a standalone keyboard shortcuts dialog. |
|
|
83
83
|
|
|
84
84
|
`Kbd` renders the Apple modifier symbols (`⌘ ⌥ ⇧ ⌃`) in a string label in a
|
|
85
85
|
proportional font and everything else in a monospace one. The kit's monospace
|
|
@@ -67,7 +67,7 @@ export function Article({ html }: { html: string }) {
|
|
|
67
67
|
### Which headings are included
|
|
68
68
|
|
|
69
69
|
- `h1`–`h4` at the top level of the Document and inside a Wrapper.
|
|
70
|
-
- Headings in the columns of a
|
|
70
|
+
- Headings in the columns of a Column layout are not included: two headings at the same height in neighboring columns confuse the highlight of the current section. Headings inside custom Blocks and their Islands are not included either: their content is part of the Block, not of the Document's structure.
|
|
71
71
|
- The `levels` prop narrows the set: `levels={[2, 3]}` keeps only `h2` and `h3`. Other levels are in neither the strip nor the list; an empty array leaves no headings at all.
|
|
72
72
|
- With fewer than two headings (after `levels`) the component renders nothing, and the screen's column stays empty.
|
|
73
73
|
|
|
@@ -93,17 +93,17 @@ The allowlist of tags and attributes (the table mirrors `src/editor/core/schema.
|
|
|
93
93
|
|
|
94
94
|
<!-- allow-list -->
|
|
95
95
|
|
|
96
|
-
| What | Allowed
|
|
97
|
-
| ---------- |
|
|
98
|
-
| Blocks | `p`, `h1`, `h2`, `h3`, `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `pre`, `table`, `tbody`, `tr`, `th`, `td`; `label`, `input`, `span` — only inside a checklist item; `div` — there too, plus the
|
|
99
|
-
| Marks | `strong`, `em`, `u`, `s`, `code`, `sup`, `sub`, `mark` (only with a color), `br`, `a`
|
|
100
|
-
| Attributes | `ol[start]` — only when numbering does not start at 1; `ul[data-type]`, `li[data-type]`, `li[data-checked]` — checklist; `input[type]`, `input[checked]` — its checkbox; `code[class]` — the code block language, only "language-python", "language-typescript", "language-javascript", "language-go", "language-html", "language-json", "language-yaml" or "language-markdown" (no language, no class); `span[data-color]`, `mark[data-color]` — text color and marker, only names from that tag's palette: text colors on `span`, markers on `mark`; `span[style]`, `mark[style]` — the author's custom color, only "color:#rrggbb" and "background-color:#rrggbb", and not together with "data-color"; `p[data-align]`, `h1[data-align]`, `h2[data-align]`, `h3[data-align]`, `h4[data-align]` — Block alignment, center or right; `th[colspan]`, `td[colspan]` — horizontal merge, an integer from 2 to 20; `th[rowspan]`, `td[rowspan]` — vertical, from 2 to 50; `th[data-align]`, `td[data-align]` — column alignment, center or right, on each of its cells; `a[href]` — only "http", "https" or "mailto"; `a[rel]` — always "noopener noreferrer"; `img[src]` — only absolute "http" or "https", no "data:" or "blob:"; `img[alt]` — always, empty for an image without a description; `img[data-width]` — 50, 75 or 100; `div[data-block]` — the
|
|
96
|
+
| What | Allowed |
|
|
97
|
+
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| Blocks | `p`, `h1`, `h2`, `h3`, `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `pre`, `table`, `tbody`, `tr`, `th`, `td`; `label`, `input`, `span` — only inside a checklist item; `div` — there too, plus the Column layout and its columns (a Column layout only at the top level, a column only directly inside it) and the Wrapper (at the top level and directly in a column, not in another Wrapper); `details` — a collapsible Block, with `summary` and a content `div` inside it (both only directly in `details`; outside it the tags are removed); `figure` — an image, with `img` and an optional `figcaption` inside (they do not exist outside `figure`) |
|
|
99
|
+
| Marks | `strong`, `em`, `u`, `s`, `code`, `sup`, `sub`, `mark` (only with a color), `br`, `a` |
|
|
100
|
+
| Attributes | `ol[start]` — only when numbering does not start at 1; `ul[data-type]`, `li[data-type]`, `li[data-checked]` — checklist; `input[type]`, `input[checked]` — its checkbox; `code[class]` — the code block language, only "language-python", "language-typescript", "language-javascript", "language-go", "language-html", "language-json", "language-yaml" or "language-markdown" (no language, no class); `span[data-color]`, `mark[data-color]` — text color and marker, only names from that tag's palette: text colors on `span`, markers on `mark`; `span[style]`, `mark[style]` — the author's custom color, only "color:#rrggbb" and "background-color:#rrggbb", and not together with "data-color"; `p[data-align]`, `h1[data-align]`, `h2[data-align]`, `h3[data-align]`, `h4[data-align]` — Block alignment, center or right; `th[colspan]`, `td[colspan]` — horizontal merge, an integer from 2 to 20; `th[rowspan]`, `td[rowspan]` — vertical, from 2 to 50; `th[data-align]`, `td[data-align]` — column alignment, center or right, on each of its cells; `a[href]` — only "http", "https" or "mailto"; `a[rel]` — always "noopener noreferrer"; `img[src]` — only absolute "http" or "https", no "data:" or "blob:"; `img[alt]` — always, empty for an image without a description; `img[data-width]` — 50, 75 or 100; `div[data-block]` — the Column layout ("layout"), its column ("column") and the Wrapper ("wrapper"); `div[data-layout]` — the Column layout variant, column shares in percent: "100", "50-50", "33-33-33", "25-25-25-25", "60-40" or "40-60"; `div[data-tone]` — the Callout tone, only on it: "neutral", "accent", "success", "warning" or "danger" (no tone, no attribute); `div[data-type]` — the content wrapper of a collapsible Block, only the value "detailsContent" and only directly in `details`; `details[open]` — an open Block, only an empty "open" |
|
|
101
101
|
|
|
102
102
|
Tiptap writes a checklist item like this: `<li data-type="taskItem" data-checked="true"><label><input type="checkbox" checked><span></span></label><div><p>…</p></div></li>`. `sanitizeRichText` lets `input`, `label`, `span` and `div` through only inside such an item, and `input` only with `type="checkbox"`.
|
|
103
103
|
|
|
104
104
|
The Editor writes a table like this: `<table><tbody><tr><th><p>…</p></th></tr><tr><td colspan="2" data-align="center"><p>…</p></td></tr></tbody></table>`. The header row is the first row made of `th`, and a cell has only three attributes: `colspan` and `rowspan` (merging, an integer from 2 to `RICH_TEXT_SCHEMA.tableSpans`: 20 horizontally and 50 vertically; 1 is not written) and `data-align` (column alignment, `center` or `right`, on every cell of the column). A row fully covered by a merge from above stays an empty `<tr></tr>`. `thead`, `tfoot`, `colgroup` and `caption` are not in the Schema, and **by default DOMPurify removes the contents of `thead` and `colgroup` along with the tags**: the header of a third-party table written in `thead` disappears. The Editor does not write `thead`, so for its HTML this is no loss. `tbody`, `tr`, `th` and `td` outside a table are dropped by the HTML parser before sanitizing (the text stays), `style` and `colwidth` are not allowed through, and `colspan` and `rowspan` are let through only by the `sanitizeRichText` hook: on `th` and `td`, and only as digits within the Schema's limits; the bare config does not know them at all. Without such a check, `colspan="10000"` in third-party HTML would expand into 10,000 empty cells for everyone who opens the Document.
|
|
105
105
|
|
|
106
|
-
There are no other classes, `style` (except the custom color, see below), `aria-*` or `data-*` (except eight Schema names: `data-type` of the checklist and of the content wrapper of a collapsible Block, `data-checked` of the checklist, `data-color` for color, `data-align` for alignment, `data-width` for image width, `data-block` and `data-layout` of the
|
|
106
|
+
There are no other classes, `style` (except the custom color, see below), `aria-*` or `data-*` (except eight Schema names: `data-type` of the checklist and of the content wrapper of a collapsible Block, `data-checked` of the checklist, `data-color` for color, `data-align` for alignment, `data-width` for image width, `data-block` and `data-layout` of the Column layout, and `data-tone` of the Wrapper) in the output HTML. An `h1` appears in a Document only if the Editor has the `heading1` Tool (on by default in `full`); a screen that puts an H1 above the Editor turns this Tool off.
|
|
107
107
|
|
|
108
108
|
**An image** is written by the Editor like this: `<figure><img src="https://…" alt="…" data-width="50"><figcaption>…</figcaption></figure>`. `figcaption` is present only when the caption is not empty, `data-width` only on an image with a set width (`50`, `75` or `100` percent of the Document width, `RICH_TEXT_SCHEMA.imageWidths`; without it, the image keeps its own size, no wider than the Document), and `alt` is always present and empty for an image without a description. The URL must be an absolute `http` or `https` URL (`RICH_TEXT_SCHEMA.imageProtocols`): the Editor never writes `data:` (base64) or `blob:`, and an image never has `style`, `width`, `height` or `class` — neither on `img` nor on `figure`. The Editor does not output a bare `<img>` outside `figure`; `sanitizeRichText` removes it.
|
|
109
109
|
|
|
@@ -167,7 +167,7 @@ export function sanitizeDocument(html: string): string {
|
|
|
167
167
|
- **A custom Block** comes through as `div[data-block="<name>"]`, an inline Atom as `span[data-block="<name>"]`, a custom Mark as `span[data-mark="<name>"]`. A Block of the wrong kind (an Atom in a `div`, a block-level Block in a `span`) and an unknown name lose their tag; the text stays.
|
|
168
168
|
- **Custom attributes** `data-<name>` — only on their own element and only with a value that follows the rule: from a list, matching a pattern, or a URL that follows the link policy (absolute `http`, `https`, `mailto`). A value with markup (`<`) is never allowed through. An invalid value is stripped together with the attribute, and the element itself stays — the Editor does the same.
|
|
169
169
|
- **Anything extra on a custom Block's element** — `class`, `style`, `data-color`, handlers, an attribute of another Block — are stripped.
|
|
170
|
-
- **The `top` slot** — only the Document's top level: a nested Block (in a quote, a Wrapper, another Block) loses its tag, like a
|
|
170
|
+
- **The `top` slot** — only the Document's top level: a nested Block (in a quote, a Wrapper, another Block) loses its tag, like a Column layout.
|
|
171
171
|
- **Names of the kit's Tools do not narrow the allowlist.** `["bold", acmeCard]` sanitizes the same as `[...RICH_TEXT_PRESETS.full, acmeCard]`: screens with different sets write one Document, and the server must not strip a table that a `full` screen wrote. The server ignores rules with a name that lacks a Consumer prefix: the Editor rejects such a set with a field error, and on the server it must not create a way around the kit's rules.
|
|
172
172
|
- **Without the third argument** sanitizing is the same as before custom Tools: custom Blocks are stripped like any other third-party HTML.
|
|
173
173
|
|
|
@@ -231,11 +231,11 @@ The cost of this choice:
|
|
|
231
231
|
- **Table semantics.** On a `table` with `display: block`, some screen readers lose rows and columns. This was fixed long ago in Chrome and Firefox; in Safari with VoiceOver it depends on the version; check with a manual run on your versions. The alternative — `table-layout: fixed` without scrolling — keeps the semantics, but makes all columns equal and narrow and breaks long words, which is worse on a phone.
|
|
232
232
|
- **Focus on a scrollable region.** The axe rule `scrollable-region-focusable` requires that a scrollable block be reachable from the keyboard. In the Editor every cell has a caret, but `RichTextView` has no focusable content: the rule fires on a table that is wider than the screen. This can be fixed only with a container that has `tabindex="0"`, and such a container cannot be put into the saved HTML. If such tables occur on your screen, wrap `RichTextView` in your own container with `overflow-x: auto` and `tabindex="0"`, or keep tables narrow.
|
|
233
233
|
|
|
234
|
-
##
|
|
234
|
+
## Column layout
|
|
235
235
|
|
|
236
|
-
The Editor's
|
|
236
|
+
The Editor's Column layout (`div[data-block="layout"]` with the variant in `data-layout` and columns `div[data-block="column"]`, section "Column layout" of the [RichTextEditor](../inputs/rich-text-editor.md#раскладка) page) is laid out on the same grid as in the Editor, by `prose.ts` classes on the container, with no `style` in the markup. Columns have no borders: the dashed outline is a cue for the author in the Editor, and the reader needs no boundary. Columns sit side by side when the Document itself is wide (two from 30 rem, three from 40 rem, four from 48 rem), and in a single column in reading order, left to right, when it is narrower. The threshold is a container query on the Document's width: the `RichTextView` root with a Column layout becomes a container (`container-type: inline-size`), so in a shrinking context (a flex row item with no width) give it a width. On Paper it is the same grid.
|
|
237
237
|
|
|
238
|
-
`sanitizeRichText` keeps a
|
|
238
|
+
`sanitizeRichText` keeps a Column layout where the Editor writes it: only at the top level, with a variant from `RICH_TEXT_SCHEMA.layouts`, and columns only directly inside it; otherwise the wrappers are stripped and the Blocks stay one after another.
|
|
239
239
|
|
|
240
240
|
## Wrapper
|
|
241
241
|
|
|
@@ -243,13 +243,13 @@ The Editor's Wrapper (`div[data-block="wrapper"]` with Blocks inside, section "W
|
|
|
243
243
|
|
|
244
244
|
**A Wrapper with a tone** (`data-tone`: `accent`, `success`, `neutral`, `warning` or `danger`) is visible to the reader too — **in the same look as in the Editor**: a soft tone backing and a 3 px stripe on the left, the text is ordinary Document text, with inner padding like `Alert`. The colors are the `--rte-tone-*` roles of the `prose.ts` classes, with no `style` in the markup; on the Paper they are the light theme's values. The tone adds no ARIA role or `aria-*`: the meaning of the note is carried by a word in its text ("Warning: …"), not by color. `RichTextView` does not style a tone outside these five names — `sanitizeRichText` strips it on the server. The palette, contrast and Markdown are in the section ["Wrapper", "Tone"](../inputs/rich-text-editor.md#тон).
|
|
245
245
|
|
|
246
|
-
`sanitizeRichText` keeps a Wrapper where the Editor writes it: at the top level and directly in a
|
|
246
|
+
`sanitizeRichText` keeps a Wrapper where the Editor writes it: at the top level and directly in a column of a Column layout, not in another Wrapper, list, quote or table; otherwise the Wrapper is stripped and the Blocks stay one after another. A Column layout inside a Wrapper is unwrapped into its Blocks.
|
|
247
247
|
|
|
248
248
|
## Collapsible block
|
|
249
249
|
|
|
250
250
|
A collapsible Block of the Editor (the `details` Tool, the "Collapsible block" section of the [RichTextEditor](../inputs/rich-text-editor.md#сворачиваемый-блок) page) is shown as **a native `details` and `summary`**: it needs neither the engine nor JavaScript, the browser itself opens and closes it, from the mouse and from the keyboard (Tab, Enter, Space), and a screen reader reads it as a disclosure element — the kit adds no roles or `aria-*` to the markup. The Block arrives closed; it is open only if it was saved with `open` (the Editor's `persist` option). The frame, the padding and the semibold title are the same as in the Editor, the `summary` marker is the browser's native one, and the Blocks in the content keep the Document's rhythm. The content wrapper `div[data-type="detailsContent"]` has no look of its own.
|
|
251
251
|
|
|
252
|
-
`sanitizeRichText` keeps the Block in the shape the Editor writes: `summary` and the content wrapper only directly in `details` (otherwise the tag is removed and the Blocks stay in a row), `open` only empty and only on `details`; `iframe` is not opened this way. A
|
|
252
|
+
`sanitizeRichText` keeps the Block in the shape the Editor writes: `summary` and the content wrapper only directly in `details` (otherwise the tag is removed and the Blocks stay in a row), `open` only empty and only on `details`; `iframe` is not opened this way. A Column layout or a Wrapper inside the content unwraps into Blocks.
|
|
253
253
|
|
|
254
254
|
## Custom Block
|
|
255
255
|
|
|
@@ -234,7 +234,7 @@ leaves the cell selected.
|
|
|
234
234
|
|
|
235
235
|
The cell's `contextMenu` field (`items[n].contextMenu`) holds the menu items: `ContextMenuItem`,
|
|
236
236
|
`ContextMenuSeparator`, `ContextMenuSub` and the rest of the
|
|
237
|
-
[`ContextMenu`](../overlays/context-menu.md) family. The grid sets the panel, the trigger and the
|
|
237
|
+
[`ContextMenu`](../overlays/context-menu.md) component family. The grid sets the panel, the trigger and the
|
|
238
238
|
isolation from selection itself, so there is no need to wrap the cell in `ContextMenu` by hand.
|
|
239
239
|
|
|
240
240
|
```tsx
|
|
@@ -276,7 +276,7 @@ and the drag does not start.
|
|
|
276
276
|
|
|
277
277
|
**Why not `ContextMenu` around `content`.** That is how it was done before the field appeared, and it
|
|
278
278
|
produced two failures. First: a right click works, but Shift+F10 does not, because focus is on the
|
|
279
|
-
cell while the menu
|
|
279
|
+
cell while the menu button is inside it. Second: events from the panel, which is rendered in a
|
|
280
280
|
portal, bubble up the React tree to the cell and select it. Both failures are now closed inside the
|
|
281
281
|
grid.
|
|
282
282
|
|
|
@@ -44,7 +44,7 @@ sourceHash: 389a684cb62f
|
|
|
44
44
|
- **The `FormField` hint and error are announced together with the field.** The
|
|
45
45
|
description is passed through context, and `aria-describedby` is set on the
|
|
46
46
|
trigger; the consumer's `aria-describedby` value is not overridden but merged
|
|
47
|
-
with it. Before version 3.0.0 the react-aria families did not pick up the form description — it was on
|
|
47
|
+
with it. Before version 3.0.0 the react-aria component families did not pick up the form description — it was on
|
|
48
48
|
screen but never reached the screen reader
|
|
49
49
|
(ADR-0031).
|
|
50
50
|
- The popover uses `Calendar` with its grid and keyboard contract; it closes on selection.
|