@domternal/react 1.0.3 → 1.1.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 CHANGED
@@ -19,7 +19,7 @@ and React 19.
19
19
  pnpm add @domternal/react @domternal/core @domternal/theme react react-dom
20
20
  ```
21
21
 
22
- `react` (>=18), `react-dom` (>=18), and `@domternal/core` (>=1.0.0 <2.0.0) are peer dependencies. Import the theme
22
+ `react` (>=18), `react-dom` (>=18), and `@domternal/core` (>=1.1.0 <2.0.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
@@ -321,7 +321,7 @@ interface DomternalEditorProps extends Omit<UseEditorOptions, 'outputFormat'> {
321
321
  value?: Content;
322
322
  /** Called when content changes (controlled mode). */
323
323
  onChange?: (value: string | JSONContent) => void;
324
- /** Additional content rendered inside the dm-editor wrapper. */
324
+ /** Additional content inside the provider, before the dm-editor wrapper as siblings. */
325
325
  children?: React.ReactNode;
326
326
  }
327
327
  interface DomternalEditorRef {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createContext, forwardRef, useImperativeHandle, useRef, useEffect, useState, useMemo, useCallback, useSyncExternalStore, useContext, Fragment, useLayoutEffect, createElement } from 'react';
1
+ import { createContext, forwardRef, useImperativeHandle, useRef, useEffect, useState, useMemo, useSyncExternalStore, useContext, useCallback, Fragment, useLayoutEffect, createElement } from 'react';
2
2
  import { Document, Paragraph, Text, BaseKeymap, History, Editor, refocusEditorAfterCommand, positionFloatingOnce, PluginKey, createFloatingMenuPlugin, FloatingMenuController, defaultIcons, positionFloating, ToolbarController, defaultBubbleContexts, buildBubbleItemMaps, createBubbleShouldShow, createBubbleMenuPlugin, resolveBubbleNames, resolveBubbleMenuItems } from '@domternal/core';
3
3
  export { Editor, generateHTML, generateJSON, generateText } from '@domternal/core';
4
4
  import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
@@ -95,10 +95,7 @@ function useEditor(options = {}, deps) {
95
95
  const existing = instanceRef.current;
96
96
  if (existing && !existing.isDestroyed) {
97
97
  const mount = editorRef.current;
98
- if (mount && existing.view.dom.parentElement !== mount) {
99
- mount.appendChild(existing.view.dom);
100
- existing.adoptPresetClass();
101
- }
98
+ if (mount) existing.adoptDom(mount);
102
99
  callbacksRef.current.onCreate?.(existing);
103
100
  return () => {
104
101
  destroyCurrentEditor();
@@ -222,28 +219,68 @@ function getFullState(editor) {
222
219
  };
223
220
  }
224
221
  function useEditorStateSelector(editor, selector) {
225
- const selectorRef = useRef(selector);
226
- selectorRef.current = selector;
227
- const subscribe = useCallback(
228
- (callback) => {
229
- if (!editor || editor.isDestroyed) return () => {
222
+ const store = useMemo(() => createEditorStore(editor), [editor]);
223
+ const getSnapshot = useMemo(() => {
224
+ let previous;
225
+ let selected;
226
+ return () => {
227
+ const snapshot = store.getSnapshot();
228
+ if (snapshot !== previous) {
229
+ previous = snapshot;
230
+ selected = snapshot ? selector(snapshot.editor) : void 0;
231
+ }
232
+ return selected;
233
+ };
234
+ }, [store, selector]);
235
+ return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
236
+ }
237
+ function createEditorStore(editor) {
238
+ let snapshot;
239
+ let destroyed = false;
240
+ const listeners = /* @__PURE__ */ new Set();
241
+ const getSnapshot = () => {
242
+ if (!editor || destroyed || editor.isDestroyed) return null;
243
+ const state = editor.state;
244
+ const focused = editor.isFocused;
245
+ const editable = editor.isEditable;
246
+ if (snapshot?.state !== state || snapshot.focused !== focused || snapshot.editable !== editable) {
247
+ snapshot = { editor, state, focused, editable };
248
+ }
249
+ return snapshot;
250
+ };
251
+ const notify = () => {
252
+ snapshot = void 0;
253
+ listeners.forEach((listener) => {
254
+ listener();
255
+ });
256
+ };
257
+ const onDestroy = () => {
258
+ destroyed = true;
259
+ notify();
260
+ };
261
+ return {
262
+ getSnapshot,
263
+ subscribe(callback) {
264
+ if (!editor || destroyed || editor.isDestroyed) return () => {
230
265
  };
231
- editor.on("transaction", callback);
232
- editor.on("focus", callback);
233
- editor.on("blur", callback);
266
+ listeners.add(callback);
267
+ if (listeners.size === 1) {
268
+ editor.on("transaction", notify);
269
+ editor.on("focus", notify);
270
+ editor.on("blur", notify);
271
+ editor.on("destroy", onDestroy);
272
+ }
234
273
  return () => {
235
- editor.off("transaction", callback);
236
- editor.off("focus", callback);
237
- editor.off("blur", callback);
274
+ listeners.delete(callback);
275
+ if (listeners.size === 0) {
276
+ editor.off("transaction", notify);
277
+ editor.off("focus", notify);
278
+ editor.off("blur", notify);
279
+ editor.off("destroy", onDestroy);
280
+ }
238
281
  };
239
- },
240
- [editor]
241
- );
242
- const getSnapshot = useCallback(() => {
243
- if (!editor || editor.isDestroyed) return void 0;
244
- return selectorRef.current(editor);
245
- }, [editor]);
246
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
282
+ }
283
+ };
247
284
  }
248
285
  var EditorContext = createContext({ editor: null });
249
286
  function EditorProvider({ editor, children }) {
@@ -678,6 +715,7 @@ function ToolbarDropdownPanel({
678
715
  onItemClick
679
716
  }) {
680
717
  const innerHtml = useInnerHtml();
718
+ const { getTooltip } = useTooltip();
681
719
  if (dropdown.layout === "grid") {
682
720
  return /* @__PURE__ */ jsx(
683
721
  "div",
@@ -694,7 +732,7 @@ function ToolbarDropdownPanel({
694
732
  role: "menuitem",
695
733
  tabIndex: -1,
696
734
  "aria-label": sub.label,
697
- title: sub.label,
735
+ title: getTooltip(sub),
698
736
  style: { backgroundColor: sub.color },
699
737
  onMouseDown: (e) => {
700
738
  e.preventDefault();
@@ -712,6 +750,7 @@ function ToolbarDropdownPanel({
712
750
  role: "menuitem",
713
751
  tabIndex: -1,
714
752
  "aria-label": sub.label,
753
+ title: getTooltip(sub),
715
754
  dangerouslySetInnerHTML: innerHtml(getCachedItemContent(sub.icon, sub.label)),
716
755
  onMouseDown: (e) => {
717
756
  e.preventDefault();
@@ -740,6 +779,7 @@ function ToolbarDropdownPanel({
740
779
  role: "menuitem",
741
780
  tabIndex: -1,
742
781
  "aria-label": sub.label,
782
+ title: getTooltip(sub),
743
783
  ref: (el) => {
744
784
  if (el && sub.style) el.setAttribute("style", sub.style);
745
785
  },
@@ -1585,7 +1625,10 @@ function FloatingMenuItemButton({
1585
1625
  dangerouslySetInnerHTML: innerHtml(iconHtml)
1586
1626
  }
1587
1627
  ),
1588
- /* @__PURE__ */ jsx("span", { className: "dm-floating-menu-item-label", children: item.label }),
1628
+ item.description ? /* @__PURE__ */ jsxs("span", { className: "dm-floating-menu-item-text", children: [
1629
+ /* @__PURE__ */ jsx("span", { className: "dm-floating-menu-item-label", children: item.label }),
1630
+ /* @__PURE__ */ jsx("span", { className: "dm-floating-menu-item-description", children: item.description })
1631
+ ] }) : /* @__PURE__ */ jsx("span", { className: "dm-floating-menu-item-label", children: item.label }),
1589
1632
  item.shortcut && /* @__PURE__ */ jsx("span", { className: "dm-floating-menu-item-shortcut", "aria-hidden": "true", children: item.shortcut })
1590
1633
  ]
1591
1634
  }
@@ -1973,11 +2016,12 @@ function DomternalContent({ className }) {
1973
2016
  useEffect(() => {
1974
2017
  const container = containerRef.current;
1975
2018
  if (!container || !editor || editor.isDestroyed) return;
1976
- const editorDom = editor.view.dom;
1977
- if (editorDom.parentElement !== container) {
1978
- container.appendChild(editorDom);
1979
- editor.adoptPresetClass();
1980
- }
2019
+ editor.adoptDom(container);
2020
+ return () => {
2021
+ if (!editor.isDestroyed && editor.view.dom.parentElement === container) {
2022
+ editor.adoptDom(document.createElement("div"));
2023
+ }
2024
+ };
1981
2025
  }, [editor]);
1982
2026
  const classes = className ? `dm-editor ${className}` : "dm-editor";
1983
2027
  return /* @__PURE__ */ jsx("div", { className: classes, "data-dm-editor-ui": "", children: /* @__PURE__ */ jsx("div", { ref: containerRef }) });
@@ -2078,12 +2122,11 @@ function EditorContent({ editor, innerRef, ...htmlProps }) {
2078
2122
  useEffect(() => {
2079
2123
  const container = containerRef.current;
2080
2124
  if (!container || !editor || editor.isDestroyed) return;
2081
- const editorDom = editor.view.dom;
2082
- if (editorDom.parentElement !== container) {
2083
- container.appendChild(editorDom);
2084
- editor.adoptPresetClass();
2085
- }
2125
+ editor.adoptDom(container);
2086
2126
  return () => {
2127
+ if (!editor.isDestroyed && editor.view.dom.parentElement === container) {
2128
+ editor.adoptDom(document.createElement("div"));
2129
+ }
2087
2130
  };
2088
2131
  }, [editor]);
2089
2132
  return /* @__PURE__ */ jsx(