@domternal/react 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +52 -11
- package/dist/index.js.map +1 -1
- package/package.json +10 -12
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ and React 19.
|
|
|
19
19
|
pnpm add @domternal/react @domternal/core react react-dom
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
`react` (>=18), `react-dom` (>=18), and `@domternal/core` (>=0.
|
|
22
|
+
`react` (>=18), `react-dom` (>=18), and `@domternal/core` (>=0.15.0) are peer dependencies. Import the theme
|
|
23
23
|
([`@domternal/theme`](https://www.npmjs.com/package/@domternal/theme)) in your entry CSS for default styling:
|
|
24
24
|
|
|
25
25
|
```css
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { DependencyList, ReactNode, HTMLAttributes, Ref, RefObject, KeyboardEvent, ElementType, RefCallback } from 'react';
|
|
3
|
-
import { AnyExtension, Content, FocusPosition, Editor, JSONContent, IconSet, ToolbarLayoutEntry, BubbleMenuOptions, FloatingMenuOptions, FloatingMenuItemsOverride, FloatingMenuKeymap } from '@domternal/core';
|
|
3
|
+
import { AnyExtension, Content, EditorPreset, FocusPosition, Editor, JSONContent, IconSet, ToolbarLayoutEntry, BubbleMenuOptions, FloatingMenuOptions, FloatingMenuItemsOverride, FloatingMenuKeymap } from '@domternal/core';
|
|
4
4
|
export { AnyExtension, Content, Editor, FocusPosition, GenerateHTMLOptions, GenerateJSONOptions, GenerateTextOptions, JSONContent, generateHTML, generateJSON, generateText } from '@domternal/core';
|
|
5
5
|
|
|
6
6
|
declare const DEFAULT_EXTENSIONS: AnyExtension[];
|
|
@@ -17,6 +17,12 @@ interface UseEditorOptions {
|
|
|
17
17
|
content?: Content;
|
|
18
18
|
/** Whether the editor is editable. @default true */
|
|
19
19
|
editable?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Editing experience preset. `'notion'` paints `dm-notion-mode` on the
|
|
22
|
+
* `.dm-editor` wrapper and switches preset-aware extensions to their
|
|
23
|
+
* Notion behavior, replacing the hand-written class. Create-time only.
|
|
24
|
+
*/
|
|
25
|
+
preset?: EditorPreset;
|
|
20
26
|
/** Where to autofocus on mount. @default false */
|
|
21
27
|
autofocus?: FocusPosition;
|
|
22
28
|
/** Output format for content comparison. @default 'html' */
|
package/dist/index.js
CHANGED
|
@@ -53,7 +53,8 @@ function useEditor(options = {}, deps) {
|
|
|
53
53
|
extensions: [...defaults, ...extensions],
|
|
54
54
|
content: initialContent,
|
|
55
55
|
editable,
|
|
56
|
-
autofocus: focus
|
|
56
|
+
autofocus: focus,
|
|
57
|
+
...options.preset ? { preset: options.preset } : {}
|
|
57
58
|
});
|
|
58
59
|
wireEvents(ed);
|
|
59
60
|
instanceRef.current = ed;
|
|
@@ -96,6 +97,7 @@ function useEditor(options = {}, deps) {
|
|
|
96
97
|
const mount = editorRef.current;
|
|
97
98
|
if (mount && existing.view.dom.parentElement !== mount) {
|
|
98
99
|
mount.appendChild(existing.view.dom);
|
|
100
|
+
existing.adoptPresetClass();
|
|
99
101
|
}
|
|
100
102
|
callbacksRef.current.onCreate?.(existing);
|
|
101
103
|
return () => {
|
|
@@ -260,6 +262,7 @@ function useToolbarController(editor, layout) {
|
|
|
260
262
|
const toolbarRef = useRef(null);
|
|
261
263
|
const cleanupFloatingRef = useRef(null);
|
|
262
264
|
const clickOutsideRef = useRef(null);
|
|
265
|
+
const escapeKeydownRef = useRef(null);
|
|
263
266
|
const dismissOverlayRef = useRef(null);
|
|
264
267
|
const editorElRef = useRef(null);
|
|
265
268
|
const syncStateRafRef = useRef(0);
|
|
@@ -295,6 +298,18 @@ function useToolbarController(editor, layout) {
|
|
|
295
298
|
};
|
|
296
299
|
clickOutsideRef.current = clickOutside;
|
|
297
300
|
document.addEventListener("mousedown", clickOutside);
|
|
301
|
+
const escapeKeydown = (e) => {
|
|
302
|
+
if (!controller.openDropdown) return;
|
|
303
|
+
if (e.key !== "Escape") return;
|
|
304
|
+
if (toolbarRef.current?.contains(document.activeElement)) return;
|
|
305
|
+
cleanupFloatingRef.current?.();
|
|
306
|
+
cleanupFloatingRef.current = null;
|
|
307
|
+
controller.closeDropdown();
|
|
308
|
+
syncState();
|
|
309
|
+
e.preventDefault();
|
|
310
|
+
};
|
|
311
|
+
escapeKeydownRef.current = escapeKeydown;
|
|
312
|
+
document.addEventListener("keydown", escapeKeydown);
|
|
298
313
|
const editorEl = editor.view.dom.closest(".dm-editor");
|
|
299
314
|
editorElRef.current = editorEl;
|
|
300
315
|
if (editorEl) {
|
|
@@ -317,6 +332,10 @@ function useToolbarController(editor, layout) {
|
|
|
317
332
|
document.removeEventListener("mousedown", clickOutsideRef.current);
|
|
318
333
|
clickOutsideRef.current = null;
|
|
319
334
|
}
|
|
335
|
+
if (escapeKeydownRef.current) {
|
|
336
|
+
document.removeEventListener("keydown", escapeKeydownRef.current);
|
|
337
|
+
escapeKeydownRef.current = null;
|
|
338
|
+
}
|
|
320
339
|
if (dismissOverlayRef.current && editorElRef.current) {
|
|
321
340
|
editorElRef.current.removeEventListener("dm:dismiss-overlays", dismissOverlayRef.current);
|
|
322
341
|
dismissOverlayRef.current = null;
|
|
@@ -603,6 +622,19 @@ function getInlineStyleAtCursor(editor, prop) {
|
|
|
603
622
|
return null;
|
|
604
623
|
}
|
|
605
624
|
}
|
|
625
|
+
function useInnerHtml() {
|
|
626
|
+
const cache = useRef(null);
|
|
627
|
+
cache.current ??= /* @__PURE__ */ new Map();
|
|
628
|
+
return (html) => {
|
|
629
|
+
const store = cache.current;
|
|
630
|
+
let prop = store.get(html);
|
|
631
|
+
if (prop === void 0) {
|
|
632
|
+
prop = { __html: html };
|
|
633
|
+
store.set(html, prop);
|
|
634
|
+
}
|
|
635
|
+
return prop;
|
|
636
|
+
};
|
|
637
|
+
}
|
|
606
638
|
function ToolbarButton({
|
|
607
639
|
item,
|
|
608
640
|
isActive,
|
|
@@ -614,6 +646,7 @@ function ToolbarButton({
|
|
|
614
646
|
onClick,
|
|
615
647
|
onFocus
|
|
616
648
|
}) {
|
|
649
|
+
const innerHtml = useInnerHtml();
|
|
617
650
|
return /* @__PURE__ */ jsx(
|
|
618
651
|
"button",
|
|
619
652
|
{
|
|
@@ -625,7 +658,7 @@ function ToolbarButton({
|
|
|
625
658
|
title: tooltip,
|
|
626
659
|
tabIndex,
|
|
627
660
|
disabled: isDisabled,
|
|
628
|
-
dangerouslySetInnerHTML:
|
|
661
|
+
dangerouslySetInnerHTML: innerHtml(iconHtml),
|
|
629
662
|
onMouseDown: (e) => {
|
|
630
663
|
e.preventDefault();
|
|
631
664
|
},
|
|
@@ -644,6 +677,7 @@ function ToolbarDropdownPanel({
|
|
|
644
677
|
getCachedItemContent,
|
|
645
678
|
onItemClick
|
|
646
679
|
}) {
|
|
680
|
+
const innerHtml = useInnerHtml();
|
|
647
681
|
if (dropdown.layout === "grid") {
|
|
648
682
|
return /* @__PURE__ */ jsx(
|
|
649
683
|
"div",
|
|
@@ -678,7 +712,7 @@ function ToolbarDropdownPanel({
|
|
|
678
712
|
role: "menuitem",
|
|
679
713
|
tabIndex: -1,
|
|
680
714
|
"aria-label": sub.label,
|
|
681
|
-
dangerouslySetInnerHTML:
|
|
715
|
+
dangerouslySetInnerHTML: innerHtml(getCachedItemContent(sub.icon, sub.label)),
|
|
682
716
|
onMouseDown: (e) => {
|
|
683
717
|
e.preventDefault();
|
|
684
718
|
},
|
|
@@ -709,7 +743,7 @@ function ToolbarDropdownPanel({
|
|
|
709
743
|
ref: (el) => {
|
|
710
744
|
if (el && sub.style) el.setAttribute("style", sub.style);
|
|
711
745
|
},
|
|
712
|
-
dangerouslySetInnerHTML:
|
|
746
|
+
dangerouslySetInnerHTML: innerHtml(getCachedItemContent(sub.icon, sub.label, dropdown.displayMode)),
|
|
713
747
|
onMouseDown: (e) => {
|
|
714
748
|
e.preventDefault();
|
|
715
749
|
},
|
|
@@ -735,6 +769,7 @@ function ToolbarDropdown({
|
|
|
735
769
|
onItemClick,
|
|
736
770
|
onFocus
|
|
737
771
|
}) {
|
|
772
|
+
const innerHtml = useInnerHtml();
|
|
738
773
|
return /* @__PURE__ */ jsxs("div", { className: "dm-toolbar-dropdown-wrapper", children: [
|
|
739
774
|
/* @__PURE__ */ jsx(
|
|
740
775
|
"button",
|
|
@@ -748,7 +783,7 @@ function ToolbarDropdown({
|
|
|
748
783
|
tabIndex,
|
|
749
784
|
disabled: isDisabled,
|
|
750
785
|
"data-dropdown": dropdown.name,
|
|
751
|
-
dangerouslySetInnerHTML:
|
|
786
|
+
dangerouslySetInnerHTML: innerHtml(triggerHtml),
|
|
752
787
|
onMouseDown: (e) => {
|
|
753
788
|
e.preventDefault();
|
|
754
789
|
},
|
|
@@ -1140,7 +1175,8 @@ function useBubbleMenu(options) {
|
|
|
1140
1175
|
}
|
|
1141
1176
|
setTrailing({
|
|
1142
1177
|
isNodeSelection: isNode,
|
|
1143
|
-
|
|
1178
|
+
// Hidden without a live notionColorOpen listener: the trigger would be dead.
|
|
1179
|
+
showColorPickerButton: hasNotionColorPicker && ed.listenerCount("notionColorOpen") > 0,
|
|
1144
1180
|
showBlockMenuButton: hasBlockContextMenu,
|
|
1145
1181
|
blockMenuButtonDisabled: blockMenuDisabled,
|
|
1146
1182
|
currentTextColorVar: textVar,
|
|
@@ -1297,6 +1333,7 @@ function DomternalBubbleMenu({
|
|
|
1297
1333
|
}, []);
|
|
1298
1334
|
const colorBtnRef = useRef(null);
|
|
1299
1335
|
const blockMenuBtnRef = useRef(null);
|
|
1336
|
+
const innerHtml = useInnerHtml();
|
|
1300
1337
|
return /* @__PURE__ */ jsxs("div", { ref: menuRef, className: "dm-bubble-menu", role: "toolbar", "aria-label": "Text formatting", children: [
|
|
1301
1338
|
resolvedItems.map((item) => {
|
|
1302
1339
|
if (item.type === "separator") {
|
|
@@ -1335,7 +1372,7 @@ function DomternalBubbleMenu({
|
|
|
1335
1372
|
title: btn.label,
|
|
1336
1373
|
"aria-label": btn.label,
|
|
1337
1374
|
"aria-pressed": active,
|
|
1338
|
-
dangerouslySetInnerHTML:
|
|
1375
|
+
dangerouslySetInnerHTML: innerHtml(getCachedHtml(btn.icon)),
|
|
1339
1376
|
onMouseDown: (e) => {
|
|
1340
1377
|
e.preventDefault();
|
|
1341
1378
|
},
|
|
@@ -1395,7 +1432,7 @@ function DomternalBubbleMenu({
|
|
|
1395
1432
|
title: trailing.blockMenuButtonDisabled ? "Block actions (select within a single block)" : "More options",
|
|
1396
1433
|
"aria-label": "More options",
|
|
1397
1434
|
"aria-haspopup": "menu",
|
|
1398
|
-
dangerouslySetInnerHTML:
|
|
1435
|
+
dangerouslySetInnerHTML: innerHtml(getCachedHtml("dotsThree")),
|
|
1399
1436
|
onMouseDown: (e) => {
|
|
1400
1437
|
e.preventDefault();
|
|
1401
1438
|
},
|
|
@@ -1420,6 +1457,7 @@ function BubbleDropdown({
|
|
|
1420
1457
|
}) {
|
|
1421
1458
|
const triggerRef = useRef(null);
|
|
1422
1459
|
const panelRef = useRef(null);
|
|
1460
|
+
const innerHtml = useInnerHtml();
|
|
1423
1461
|
const dropdownActive = dropdown.items.some((sub) => isItemActive(sub));
|
|
1424
1462
|
const activeChild = dropdown.dynamicIcon ? dropdown.items.find((sub) => isItemActive(sub)) : void 0;
|
|
1425
1463
|
const triggerIcon = activeChild?.icon ?? dropdown.icon;
|
|
@@ -1471,7 +1509,7 @@ function BubbleDropdown({
|
|
|
1471
1509
|
"aria-label": dropdown.label,
|
|
1472
1510
|
title: dropdown.label,
|
|
1473
1511
|
"data-dropdown": dropdown.name,
|
|
1474
|
-
dangerouslySetInnerHTML:
|
|
1512
|
+
dangerouslySetInnerHTML: innerHtml(triggerHtml),
|
|
1475
1513
|
onMouseDown: (e) => {
|
|
1476
1514
|
e.preventDefault();
|
|
1477
1515
|
},
|
|
@@ -1496,7 +1534,7 @@ function BubbleDropdown({
|
|
|
1496
1534
|
className: `dm-toolbar-dropdown-item${subActive ? " dm-toolbar-dropdown-item--active" : ""}`,
|
|
1497
1535
|
role: "menuitem",
|
|
1498
1536
|
"aria-label": sub.label,
|
|
1499
|
-
dangerouslySetInnerHTML:
|
|
1537
|
+
dangerouslySetInnerHTML: innerHtml(subHtml),
|
|
1500
1538
|
onMouseDown: (e) => {
|
|
1501
1539
|
e.preventDefault();
|
|
1502
1540
|
},
|
|
@@ -1682,6 +1720,7 @@ function FloatingMenuItemButton({
|
|
|
1682
1720
|
iconHtml,
|
|
1683
1721
|
onClick
|
|
1684
1722
|
}) {
|
|
1723
|
+
const innerHtml = useInnerHtml();
|
|
1685
1724
|
const handleClick = useCallback(() => {
|
|
1686
1725
|
onClick(item);
|
|
1687
1726
|
}, [item, onClick]);
|
|
@@ -1708,7 +1747,7 @@ function FloatingMenuItemButton({
|
|
|
1708
1747
|
{
|
|
1709
1748
|
className: "dm-floating-menu-item-icon",
|
|
1710
1749
|
"aria-hidden": "true",
|
|
1711
|
-
dangerouslySetInnerHTML:
|
|
1750
|
+
dangerouslySetInnerHTML: innerHtml(iconHtml)
|
|
1712
1751
|
}
|
|
1713
1752
|
),
|
|
1714
1753
|
/* @__PURE__ */ jsx("span", { className: "dm-floating-menu-item-label", children: item.label }),
|
|
@@ -2099,6 +2138,7 @@ function DomternalContent({ className }) {
|
|
|
2099
2138
|
const editorDom = editor.view.dom;
|
|
2100
2139
|
if (editorDom.parentElement !== container) {
|
|
2101
2140
|
container.appendChild(editorDom);
|
|
2141
|
+
editor.adoptPresetClass();
|
|
2102
2142
|
}
|
|
2103
2143
|
}, [editor]);
|
|
2104
2144
|
const classes = className ? `dm-editor ${className}` : "dm-editor";
|
|
@@ -2203,6 +2243,7 @@ function EditorContent({ editor, innerRef, ...htmlProps }) {
|
|
|
2203
2243
|
const editorDom = editor.view.dom;
|
|
2204
2244
|
if (editorDom.parentElement !== container) {
|
|
2205
2245
|
container.appendChild(editorDom);
|
|
2246
|
+
editor.adoptPresetClass();
|
|
2206
2247
|
}
|
|
2207
2248
|
return () => {
|
|
2208
2249
|
};
|