@pilotiq/tiptap 3.19.0 → 3.19.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/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/react/FloatingToolbar.d.ts +6 -4
- package/dist/react/FloatingToolbar.js +11 -20
- package/dist/react/floatingToolbarVisibility.d.ts +20 -0
- package/dist/react/floatingToolbarVisibility.js +39 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @pilotiq/tiptap
|
|
2
2
|
|
|
3
|
+
## 3.19.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ff44b8d: Show the inline mark toolbar on a bare caret inside a formatting mark.
|
|
8
|
+
|
|
9
|
+
The selection-based `FloatingToolbar` only appeared on a non-empty text selection, so a link or bold span couldn't be edited by just clicking into it (#156). Its show/hide decision now lives in a pure, exported `shouldShowFloatingToolbar(state)` predicate: a caret (empty selection) surfaces the toolbar when it sits inside one of the toolbar's marks (`bold` / `italic` / `strike` / `code` / `link`), non-empty ranges behave as before, and the callout/alert suppression from #155 still holds. Pinned by `floatingToolbarVisibility.dom.test.ts` against the real schema.
|
|
10
|
+
|
|
3
11
|
## 3.19.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -12,4 +12,5 @@ export { AiInlineDiffExtension, aiInlineDiffPluginKey, getAiInlineDiffState, typ
|
|
|
12
12
|
export { planReplaceBlock, planInsertBlockBefore, planDeleteBlock, planWrapBlocks, planUpdateBlockMark, summarizeBlockStructure, type BlockMarkRange, type TransactionModifier, } from './surgicalOps.js';
|
|
13
13
|
export { renderRichTextToHtml, isRichTextValue, type RenderRichTextOptions, type TiptapNode, type TiptapMark, } from './render.js';
|
|
14
14
|
export { contentBlockNodes, Intro, Faq, FaqItem, FaqQuestion, FaqAnswer, Alert, AlertTitle, AlertBody, Summary, KeyTakeaways, ProsCons, ProsColumn, ConsColumn, ContentBlockKeymap, LabeledBlockExitKeymap, planExitLabeledBlock, isSelectionInAlert, ALERT_VARIANTS, ALERT_VARIANT_LABEL, coerceAlertType, type AlertType, } from './extensions/contentBlocks.js';
|
|
15
|
+
export { shouldShowFloatingToolbar, TOOLBAR_MARKS } from './react/floatingToolbarVisibility.js';
|
|
15
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -17,3 +17,4 @@ export { renderRichTextToHtml, isRichTextValue, } from './render.js';
|
|
|
17
17
|
// editor — e.g. to parse the content-block HTML or drive the surgical-op
|
|
18
18
|
// planners (`planInsertBlockBefore` & co.) in a test, without mounting React.
|
|
19
19
|
export { contentBlockNodes, Intro, Faq, FaqItem, FaqQuestion, FaqAnswer, Alert, AlertTitle, AlertBody, Summary, KeyTakeaways, ProsCons, ProsColumn, ConsColumn, ContentBlockKeymap, LabeledBlockExitKeymap, planExitLabeledBlock, isSelectionInAlert, ALERT_VARIANTS, ALERT_VARIANT_LABEL, coerceAlertType, } from './extensions/contentBlocks.js';
|
|
20
|
+
export { shouldShowFloatingToolbar, TOOLBAR_MARKS } from './react/floatingToolbarVisibility.js';
|
|
@@ -3,10 +3,12 @@ interface FloatingToolbarProps {
|
|
|
3
3
|
editor: Editor;
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
|
-
* Selection-based formatting toolbar. Visible
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Selection-based formatting toolbar. Visible on a non-empty range inside
|
|
7
|
+
* text content, AND on a bare caret sitting inside a formatting mark (so a
|
|
8
|
+
* link / bold span can be edited without selecting it first — #156). Inline
|
|
9
|
+
* marks (B/I/S/Code) are grouped together; Link sits after a separator since
|
|
10
|
+
* it's a different kind of action. Visibility is decided by the pure
|
|
11
|
+
* `shouldShowFloatingToolbar` predicate; this component only positions.
|
|
10
12
|
*/
|
|
11
13
|
export declare function FloatingToolbar({ editor }: FloatingToolbarProps): import("react").JSX.Element;
|
|
12
14
|
export {};
|
|
@@ -2,12 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Tooltip } from '@base-ui/react/tooltip';
|
|
4
4
|
import { Dialog } from '@base-ui/react/dialog';
|
|
5
|
-
import {
|
|
5
|
+
import { shouldShowFloatingToolbar } from './floatingToolbarVisibility.js';
|
|
6
6
|
/**
|
|
7
|
-
* Selection-based formatting toolbar. Visible
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Selection-based formatting toolbar. Visible on a non-empty range inside
|
|
8
|
+
* text content, AND on a bare caret sitting inside a formatting mark (so a
|
|
9
|
+
* link / bold span can be edited without selecting it first — #156). Inline
|
|
10
|
+
* marks (B/I/S/Code) are grouped together; Link sits after a separator since
|
|
11
|
+
* it's a different kind of action. Visibility is decided by the pure
|
|
12
|
+
* `shouldShowFloatingToolbar` predicate; this component only positions.
|
|
11
13
|
*/
|
|
12
14
|
export function FloatingToolbar({ editor }) {
|
|
13
15
|
const [pos, setPos] = useState(null);
|
|
@@ -15,24 +17,13 @@ export function FloatingToolbar({ editor }) {
|
|
|
15
17
|
const [linkUrl, setLinkUrl] = useState('');
|
|
16
18
|
useEffect(() => {
|
|
17
19
|
const update = () => {
|
|
18
|
-
|
|
19
|
-
if (empty) {
|
|
20
|
-
setPos(null);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
// The callout/alert block owns its content + chrome (the in-block gear
|
|
24
|
-
// menu); the inline mark toolbar shouldn't appear inside it — or when the
|
|
25
|
-
// whole block is picked via the drag handle (a NodeSelection) (#155).
|
|
26
|
-
if (isSelectionInAlert(editor.state.selection)) {
|
|
27
|
-
setPos(null);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
// Don't show on full-block selections (e.g. clicking a custom block).
|
|
31
|
-
const slice = editor.state.doc.slice(from, to);
|
|
32
|
-
if (slice.content.childCount === 0) {
|
|
20
|
+
if (!shouldShowFloatingToolbar(editor.state)) {
|
|
33
21
|
setPos(null);
|
|
34
22
|
return;
|
|
35
23
|
}
|
|
24
|
+
const { from, to } = editor.state.selection;
|
|
25
|
+
// For a bare caret `from === to`, so both coords resolve to the caret
|
|
26
|
+
// point and the toolbar centers above it.
|
|
36
27
|
const start = editor.view.coordsAtPos(from);
|
|
37
28
|
const end = editor.view.coordsAtPos(to);
|
|
38
29
|
// Viewport-relative — pair with `position: fixed` below. The wrapper
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { EditorState } from '@tiptap/pm/state';
|
|
2
|
+
/**
|
|
3
|
+
* Inline marks the `FloatingToolbar` can toggle — bold / italic / strike /
|
|
4
|
+
* code / link. A caret (empty selection) sitting inside any of these surfaces
|
|
5
|
+
* the toolbar so the formatting can be edited in place, without first
|
|
6
|
+
* selecting the text (#156).
|
|
7
|
+
*/
|
|
8
|
+
export declare const TOOLBAR_MARKS: readonly ["bold", "italic", "strike", "code", "link"];
|
|
9
|
+
/**
|
|
10
|
+
* Whether the selection-based `FloatingToolbar` should be visible. A PURE
|
|
11
|
+
* decision (no DOM / coords) so it's unit-testable against the real schema:
|
|
12
|
+
*
|
|
13
|
+
* - never inside a callout/alert block — it owns its own chrome (#155);
|
|
14
|
+
* - a caret (empty selection) shows ONLY when it sits inside a formatting
|
|
15
|
+
* mark (link / bold / …) so the mark can be edited without selecting (#156);
|
|
16
|
+
* - a non-empty range shows whenever it actually spans inline content (the
|
|
17
|
+
* `childCount === 0` guard skips degenerate full-block selections).
|
|
18
|
+
*/
|
|
19
|
+
export declare function shouldShowFloatingToolbar(state: EditorState): boolean;
|
|
20
|
+
//# sourceMappingURL=floatingToolbarVisibility.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { isSelectionInAlert } from '../extensions/contentBlocks.js';
|
|
2
|
+
/**
|
|
3
|
+
* Inline marks the `FloatingToolbar` can toggle — bold / italic / strike /
|
|
4
|
+
* code / link. A caret (empty selection) sitting inside any of these surfaces
|
|
5
|
+
* the toolbar so the formatting can be edited in place, without first
|
|
6
|
+
* selecting the text (#156).
|
|
7
|
+
*/
|
|
8
|
+
export const TOOLBAR_MARKS = ['bold', 'italic', 'strike', 'code', 'link'];
|
|
9
|
+
const TOOLBAR_MARK_SET = new Set(TOOLBAR_MARKS);
|
|
10
|
+
/**
|
|
11
|
+
* True when the caret (an EMPTY selection) sits inside one of the toolbar's
|
|
12
|
+
* formatting marks. Reads `storedMarks` first (so the active mark at a typed
|
|
13
|
+
* boundary still counts), then the marks resolved at the cursor.
|
|
14
|
+
*/
|
|
15
|
+
function caretInToolbarMark(state) {
|
|
16
|
+
const sel = state.selection;
|
|
17
|
+
if (!sel.empty)
|
|
18
|
+
return false;
|
|
19
|
+
const marks = state.storedMarks ?? sel.$from.marks();
|
|
20
|
+
return marks.some((m) => TOOLBAR_MARK_SET.has(m.type.name));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Whether the selection-based `FloatingToolbar` should be visible. A PURE
|
|
24
|
+
* decision (no DOM / coords) so it's unit-testable against the real schema:
|
|
25
|
+
*
|
|
26
|
+
* - never inside a callout/alert block — it owns its own chrome (#155);
|
|
27
|
+
* - a caret (empty selection) shows ONLY when it sits inside a formatting
|
|
28
|
+
* mark (link / bold / …) so the mark can be edited without selecting (#156);
|
|
29
|
+
* - a non-empty range shows whenever it actually spans inline content (the
|
|
30
|
+
* `childCount === 0` guard skips degenerate full-block selections).
|
|
31
|
+
*/
|
|
32
|
+
export function shouldShowFloatingToolbar(state) {
|
|
33
|
+
if (isSelectionInAlert(state.selection))
|
|
34
|
+
return false;
|
|
35
|
+
if (state.selection.empty)
|
|
36
|
+
return caretInToolbarMark(state);
|
|
37
|
+
const { from, to } = state.selection;
|
|
38
|
+
return state.doc.slice(from, to).content.childCount > 0;
|
|
39
|
+
}
|