@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 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 line (`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.
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 line, an external navigation over the headings, a "Find" button in the screen header.
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 line in `RichTextView`. Until then the screen has `ref.current.editor` and its events.
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 line and Field notice | the `status` prop; the engine command `showNotice`, the `FieldNotice` type | ["Status line and Field notice"](../components/inputs/rich-text-editor.md#строка-статуса-и-сообщение-поля) |
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 Selection menu |
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 Shortcuts reference. Keyboard shortcuts bound by the Extensions do not reach the reference.
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 Insert 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).
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 line
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 an Author 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.
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 Author edit. The kit's image messages go through the same channel.
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 Selection 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.
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 line
359
+ ### "Saved" in the Status bar
360
360
 
361
- Autosave on focus loss and announcing the result. The live region stays in the Status line **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.
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 Author edit. For details about the line, see ["Status line: `status`"](../components/inputs/rich-text-editor.md#строка-статуса-status).
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 line 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`.
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 HTML mode) instead of `void`.
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 line) requires no action — it is on the
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 Author edit. The texts and roles are the same, but the
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 whole result set, and the labels about it do not lie.
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
@@ -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 a result set of ten rows, and reads the empty space as "nothing found";
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 result set, not the look;
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 result set
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 trigger for its variants next to it, as **two separate buttons**. The accessible name of the menu trigger is built from `label`, so `label` is a string, not a node. Menu items go in `children`.
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 trigger.
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 trigger does not call it.
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 trigger is built from the label. A node would give a name
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 trigger. 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 trigger with Enter, Space and Down Arrow — `MenuTrigger` does this.
68
- - Escape closes the menu and **returns focus to the menu trigger** instead of sending it to `<body>`.
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 trigger'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 triggers on a page are indistinguishable when read aloud by a screen reader.
74
- - The menu trigger carries `aria-haspopup` and `aria-expanded` — `MenuTrigger` sets them.
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 bar (`toolbar`)
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 cheat sheet. |
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 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.
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 Layout and its columns (a 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 Layout ("layout"), its column ("column") and the Wrapper ("wrapper"); `div[data-layout]` — the 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 Wrapper 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" |
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 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.
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 Layout.
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
- ## Layout
234
+ ## Column layout
235
235
 
236
- The Editor's Layout (`div[data-block="layout"]` with the variant in `data-layout` and columns `div[data-block="column"]`, section "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 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.
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 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.
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 Layout column, not in another Wrapper, list, quote or table; otherwise the Wrapper is stripped and the Blocks stay one after another. A Layout inside a Wrapper is unwrapped into its Blocks.
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 Layout or a Wrapper inside the content unwraps into Blocks.
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 trigger is inside it. Second: events from the panel, which is rendered in a
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.