@openeditor/ui 0.0.29 → 0.0.30

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/dist/index.js CHANGED
@@ -1,19 +1,178 @@
1
- import { useOpenEditorThemeStyle, sortSlashMenuItems, getDefaultSlashMenuItems, useOpenEditorBlockInteraction, OpenEditorBlockDragHandle, useOpenEditorController, OpenEditorThemeProvider, OpenEditorContent } from '@openeditor/react';
1
+ import { Menu } from '@base-ui/react/menu';
2
+ import { Toolbar } from '@base-ui/react/toolbar';
3
+ import { useOpenEditorThemeStyle, OpenEditorTableBubbleMenu, OpenEditorBubbleMenu, sortSlashMenuItems, getDefaultSlashMenuItems, useOpenEditorBlockInteraction, OpenEditorBlockDragHandle, useOpenEditorController, OpenEditorThemeProvider, OpenEditorContent } from '@openeditor/react';
2
4
  export { OpenEditorContent, OpenEditorThemeProvider, createOpenEditorThemeStyle, openEditorThemeTokenNames, useOpenEditorController } from '@openeditor/react';
5
+ import { Pilcrow, List, ListOrdered, ListTodo, Quote, Code, Heading1, Heading2, Heading3, Heading4, Heading5, Heading6, Bold, Italic, Underline, Strikethrough, Link, GripVertical, Rows3, BetweenHorizontalStart, BetweenHorizontalEnd, Trash2, Columns3, BetweenVerticalStart, BetweenVerticalEnd, PanelTop, PanelLeft, TableCellsSplit, TableCellsMerge, ChevronDown, ChevronRight, X, ArrowDown, ArrowUp, Upload, FileText, GitBranch, MessageSquare, Table, Columns2, Image, Minus, Type, Copy, CopyPlus } from 'lucide-react';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
7
  export { OpenEditorEmojiPicker } from '@openeditor/emoji';
4
8
  import { Button } from '@base-ui/react/button';
5
9
  import { Dialog } from '@base-ui/react/dialog';
6
10
  import { Input } from '@base-ui/react/input';
7
- import { Menu } from '@base-ui/react/menu';
8
11
  import { Popover } from '@base-ui/react/popover';
9
12
  import { Toggle } from '@base-ui/react/toggle';
10
13
  import { ToggleGroup } from '@base-ui/react/toggle-group';
11
- import { Toolbar } from '@base-ui/react/toolbar';
12
- import { Pilcrow, List, ListOrdered, ListTodo, Quote, Code, Heading1, Heading2, Heading3, Heading4, Heading5, Heading6, Bold, Italic, Underline, Strikethrough, Link, ChevronRight, GripVertical, ChevronDown, X, ArrowDown, ArrowUp, Upload, FileText, GitBranch, MessageSquare, Table, Columns2, Image, Minus, Type, Copy, CopyPlus, Trash2 } from 'lucide-react';
13
- import { useState, useRef, useMemo, useEffect, Fragment as Fragment$1 } from 'react';
14
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
14
+ import { useState, useRef, useEffect, useId, useMemo, useCallback, Fragment as Fragment$1 } from 'react';
15
15
 
16
- // src/index.tsx
16
+ // src/table-menu.tsx
17
+ var runTableCommandOnMouseDown = (event, controller, command) => {
18
+ if (event.button !== 0) return;
19
+ event.preventDefault();
20
+ controller.table.run(command);
21
+ };
22
+ var runTableCommandOnKeyboardClick = (event, controller, command) => {
23
+ if (event.detail === 0) controller.table.run(command);
24
+ };
25
+ var TableActionMenu = ({
26
+ controller,
27
+ container,
28
+ label,
29
+ Icon,
30
+ actions
31
+ }) => {
32
+ const themeStyle = useOpenEditorThemeStyle();
33
+ return /* @__PURE__ */ jsxs(Menu.Root, { modal: false, children: [
34
+ /* @__PURE__ */ jsxs(
35
+ Menu.Trigger,
36
+ {
37
+ "aria-label": `${label} actions`,
38
+ className: "oe-table-toolbar-trigger",
39
+ onMouseDown: (event) => event.preventDefault(),
40
+ type: "button",
41
+ children: [
42
+ /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true", size: 15, strokeWidth: 2 }),
43
+ /* @__PURE__ */ jsx("span", { children: label }),
44
+ /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true", size: 12, strokeWidth: 2 })
45
+ ]
46
+ }
47
+ ),
48
+ /* @__PURE__ */ jsx(Menu.Portal, { container, children: /* @__PURE__ */ jsx(
49
+ Menu.Positioner,
50
+ {
51
+ align: "start",
52
+ collisionAvoidance: { side: "flip", align: "shift", fallbackAxisSide: "none" },
53
+ collisionPadding: 12,
54
+ positionMethod: "fixed",
55
+ side: "bottom",
56
+ sideOffset: 8,
57
+ children: /* @__PURE__ */ jsx(Menu.Popup, { "aria-label": `${label} actions`, className: "oe-table-action-menu", style: themeStyle, children: actions.map((action) => /* @__PURE__ */ jsxs(
58
+ Menu.Item,
59
+ {
60
+ className: "oe-table-action-item",
61
+ "data-danger": action.danger ? "" : void 0,
62
+ disabled: !controller.table.can(action.command),
63
+ onClick: (event) => runTableCommandOnKeyboardClick(event, controller, action.command),
64
+ onMouseDown: (event) => runTableCommandOnMouseDown(event, controller, action.command),
65
+ children: [
66
+ /* @__PURE__ */ jsx(action.Icon, { "aria-hidden": "true", size: 15, strokeWidth: 2 }),
67
+ /* @__PURE__ */ jsx("span", { children: action.label })
68
+ ]
69
+ },
70
+ action.command
71
+ )) })
72
+ }
73
+ ) })
74
+ ] });
75
+ };
76
+ var TableToolbarButton = ({
77
+ active,
78
+ children,
79
+ controller,
80
+ command,
81
+ label,
82
+ danger
83
+ }) => /* @__PURE__ */ jsx(
84
+ Toolbar.Button,
85
+ {
86
+ "aria-label": label,
87
+ className: "oe-table-toolbar-button",
88
+ "data-active": active ? "" : void 0,
89
+ "data-danger": danger ? "" : void 0,
90
+ disabled: !controller.table.can(command),
91
+ onClick: (event) => runTableCommandOnKeyboardClick(event, controller, command),
92
+ onMouseDown: (event) => runTableCommandOnMouseDown(event, controller, command),
93
+ title: label,
94
+ type: "button",
95
+ children
96
+ }
97
+ );
98
+ var rowActions = [
99
+ { command: "addRowBefore", label: "Insert row above", Icon: BetweenHorizontalStart },
100
+ { command: "addRowAfter", label: "Insert row below", Icon: BetweenHorizontalEnd },
101
+ { command: "deleteRow", label: "Delete row", Icon: Trash2, danger: true }
102
+ ];
103
+ var columnActions = [
104
+ { command: "addColumnBefore", label: "Insert column left", Icon: BetweenVerticalStart },
105
+ { command: "addColumnAfter", label: "Insert column right", Icon: BetweenVerticalEnd },
106
+ { command: "deleteColumn", label: "Delete column", Icon: Trash2, danger: true }
107
+ ];
108
+ var TableToolbar = ({
109
+ controller,
110
+ container,
111
+ state
112
+ }) => /* @__PURE__ */ jsx("div", { onMouseDown: (event) => event.preventDefault(), children: /* @__PURE__ */ jsxs(Toolbar.Root, { "aria-label": "Table editing", className: "oe-table-toolbar", children: [
113
+ /* @__PURE__ */ jsx(
114
+ TableActionMenu,
115
+ {
116
+ actions: rowActions,
117
+ container,
118
+ controller,
119
+ Icon: Rows3,
120
+ label: "Row"
121
+ }
122
+ ),
123
+ /* @__PURE__ */ jsx(
124
+ TableActionMenu,
125
+ {
126
+ actions: columnActions,
127
+ container,
128
+ controller,
129
+ Icon: Columns3,
130
+ label: "Column"
131
+ }
132
+ ),
133
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "oe-table-toolbar-separator" }),
134
+ /* @__PURE__ */ jsx(
135
+ TableToolbarButton,
136
+ {
137
+ active: state.headerRow,
138
+ command: "toggleHeaderRow",
139
+ controller,
140
+ label: state.headerRow ? "Remove header row" : "Make first row a header",
141
+ children: /* @__PURE__ */ jsx(PanelTop, { "aria-hidden": "true", size: 15, strokeWidth: 2 })
142
+ }
143
+ ),
144
+ /* @__PURE__ */ jsx(
145
+ TableToolbarButton,
146
+ {
147
+ active: state.headerColumn,
148
+ command: "toggleHeaderColumn",
149
+ controller,
150
+ label: state.headerColumn ? "Remove header column" : "Make first column a header",
151
+ children: /* @__PURE__ */ jsx(PanelLeft, { "aria-hidden": "true", size: 15, strokeWidth: 2 })
152
+ }
153
+ ),
154
+ state.canSplitCell ? /* @__PURE__ */ jsx(TableToolbarButton, { command: "splitCell", controller, label: "Split cell", children: /* @__PURE__ */ jsx(TableCellsSplit, { "aria-hidden": "true", size: 15, strokeWidth: 2 }) }) : /* @__PURE__ */ jsx(TableToolbarButton, { command: "mergeCells", controller, label: "Merge selected cells", children: /* @__PURE__ */ jsx(TableCellsMerge, { "aria-hidden": "true", size: 15, strokeWidth: 2 }) }),
155
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "oe-table-toolbar-separator" }),
156
+ /* @__PURE__ */ jsx(TableToolbarButton, { command: "deleteTable", controller, danger: true, label: "Delete table", children: /* @__PURE__ */ jsx(Trash2, { "aria-hidden": "true", size: 15, strokeWidth: 2 }) })
157
+ ] }) });
158
+ var OpenEditorTableMenu = ({
159
+ controller,
160
+ className = "oe-table-menu",
161
+ container
162
+ }) => {
163
+ const themeStyle = useOpenEditorThemeStyle();
164
+ if (!controller.ready) return null;
165
+ return /* @__PURE__ */ jsx(
166
+ OpenEditorTableBubbleMenu,
167
+ {
168
+ className,
169
+ container,
170
+ controller,
171
+ style: themeStyle,
172
+ children: (state) => /* @__PURE__ */ jsx(TableToolbar, { container, controller, state })
173
+ }
174
+ );
175
+ };
17
176
  var headingDefinitions = [
18
177
  { level: 1, Icon: Heading1 },
19
178
  { level: 2, Icon: Heading2 },
@@ -118,10 +277,10 @@ var pointRect = (left, top) => ({
118
277
  height: 0
119
278
  });
120
279
  var getAnchorRect = (state) => state.anchorRect ?? pointRect(state.left, state.top);
121
- var toVirtualAnchor = (state, contextElement) => ({
280
+ var toVirtualAnchor = (state, contextElement, resolveRect) => ({
122
281
  contextElement: contextElement ?? void 0,
123
282
  getBoundingClientRect: () => {
124
- const rect = getAnchorRect(state);
283
+ const rect = resolveRect?.() ?? getAnchorRect(state);
125
284
  return {
126
285
  ...rect,
127
286
  x: rect.left,
@@ -341,20 +500,9 @@ var OpenEditorSelectionBubble = ({
341
500
  container
342
501
  }) => {
343
502
  const themeStyle = useOpenEditorThemeStyle();
344
- const { selectionBubbleState } = controller;
345
503
  const rootElement = controller.getRootElement();
346
504
  const [linkDialogOpen, setLinkDialogOpen] = useState(false);
347
- const lastAnchorRef = useRef(null);
348
- if (selectionBubbleState) {
349
- lastAnchorRef.current = selectionBubbleState;
350
- }
351
- const anchor = useMemo(
352
- () => {
353
- const anchorState = selectionBubbleState ?? lastAnchorRef.current;
354
- return anchorState ? toVirtualAnchor(anchorState, rootElement) : null;
355
- },
356
- [rootElement, selectionBubbleState]
357
- );
505
+ const toolbarRef = useRef(null);
358
506
  const actions = controller.ready ? [
359
507
  {
360
508
  key: "bold",
@@ -392,45 +540,68 @@ var OpenEditorSelectionBubble = ({
392
540
  run: () => controller.toggleInlineCode()
393
541
  }
394
542
  ] : [];
395
- if (!controller.ready || !selectionBubbleState && !linkDialogOpen) {
396
- return null;
397
- }
543
+ useEffect(() => {
544
+ if (!rootElement) return;
545
+ const focusToolbar = (event) => {
546
+ if (!(event.altKey && event.key === "F10")) return;
547
+ const toolbar = toolbarRef.current;
548
+ const menu = toolbar?.parentElement?.parentElement;
549
+ if (!toolbar || !menu || getComputedStyle(menu).visibility === "hidden") return;
550
+ const firstControl = toolbarRef.current?.querySelector(
551
+ "button:not(:disabled), [href], [tabindex]:not([tabindex='-1'])"
552
+ );
553
+ if (!firstControl) return;
554
+ event.preventDefault();
555
+ firstControl.focus();
556
+ };
557
+ rootElement.addEventListener("keydown", focusToolbar, true);
558
+ return () => rootElement.removeEventListener("keydown", focusToolbar, true);
559
+ }, [rootElement]);
560
+ if (!controller.ready) return null;
398
561
  return /* @__PURE__ */ jsxs(Fragment, { children: [
399
- /* @__PURE__ */ jsx(Popover.Root, { open: Boolean(selectionBubbleState), modal: false, children: /* @__PURE__ */ jsx(Popover.Portal, { container, children: /* @__PURE__ */ jsx(
400
- Popover.Positioner,
562
+ /* @__PURE__ */ jsx(
563
+ OpenEditorBubbleMenu,
401
564
  {
402
- anchor,
403
- positionMethod: "fixed",
404
- side: "top",
405
- align: "center",
406
- sideOffset: 10,
407
- collisionPadding: 12,
408
- collisionAvoidance: { side: "flip", align: "shift", fallbackAxisSide: "none" },
565
+ className,
566
+ container,
567
+ controller,
568
+ style: themeStyle,
409
569
  children: /* @__PURE__ */ jsx(
410
- Popover.Popup,
570
+ "div",
411
571
  {
412
- className,
413
- style: themeStyle,
572
+ onKeyDown: (event) => {
573
+ if (event.key !== "Escape") return;
574
+ event.preventDefault();
575
+ controller.focus();
576
+ },
414
577
  onMouseDown: (event) => event.preventDefault(),
415
- children: /* @__PURE__ */ jsxs(Toolbar.Root, { className: "oe-selection-toolbar", "aria-label": "Selection formatting", children: [
416
- /* @__PURE__ */ jsx(SelectionBlockTransformMenu, { controller, container }),
417
- actions.map((action) => /* @__PURE__ */ jsx(SelectionButton, { action }, action.key)),
418
- /* @__PURE__ */ jsx(
419
- Toolbar.Button,
420
- {
421
- type: "button",
422
- "aria-label": "Link",
423
- className: "oe-toolbar-button",
424
- "data-active": controller.isActive("link") ? "" : void 0,
425
- onClick: () => setLinkDialogOpen(true),
426
- children: /* @__PURE__ */ jsx(Link, { "aria-hidden": "true", className: "oe-toolbar-icon", size: 15, strokeWidth: 2 })
427
- }
428
- )
429
- ] })
578
+ children: /* @__PURE__ */ jsxs(
579
+ Toolbar.Root,
580
+ {
581
+ className: "oe-selection-toolbar",
582
+ "aria-label": "Selection formatting",
583
+ ref: toolbarRef,
584
+ children: [
585
+ /* @__PURE__ */ jsx(SelectionBlockTransformMenu, { controller, container }),
586
+ actions.map((action) => /* @__PURE__ */ jsx(SelectionButton, { action }, action.key)),
587
+ /* @__PURE__ */ jsx(
588
+ Toolbar.Button,
589
+ {
590
+ type: "button",
591
+ "aria-label": "Link",
592
+ className: "oe-toolbar-button",
593
+ "data-active": controller.isActive("link") ? "" : void 0,
594
+ onClick: () => setLinkDialogOpen(true),
595
+ children: /* @__PURE__ */ jsx(Link, { "aria-hidden": "true", className: "oe-toolbar-icon", size: 15, strokeWidth: 2 })
596
+ }
597
+ )
598
+ ]
599
+ }
600
+ )
430
601
  }
431
602
  )
432
603
  }
433
- ) }) }),
604
+ ),
434
605
  /* @__PURE__ */ jsx(
435
606
  OpenEditorLinkDialog,
436
607
  {
@@ -450,103 +621,137 @@ var OpenEditorSlashMenu = ({
450
621
  const themeStyle = useOpenEditorThemeStyle();
451
622
  const slashMenuItems = sortSlashMenuItems(items ?? getDefaultSlashMenuItems(controller));
452
623
  const slashState = controller.slashState;
453
- const [headingPanelOpen, setHeadingPanelOpen] = useState(false);
624
+ const [selectedIndex, setSelectedIndex] = useState(0);
625
+ const selectedIndexRef = useRef(0);
626
+ const menuId = useId();
627
+ const menuRef = useRef(null);
454
628
  const anchor = useMemo(
455
629
  () => slashState ? toVirtualAnchor(
456
630
  slashState,
457
- controller.getRootElement()
631
+ controller.getRootElement(),
632
+ () => controller.getPositionRect(slashState.to)
458
633
  ) : null,
459
634
  [controller, slashState]
460
635
  );
461
- useEffect(() => setHeadingPanelOpen(false), [slashState?.query]);
462
- if (!slashState) {
463
- return null;
464
- }
465
- const matchingItems = slashMenuItems.filter((item) => {
636
+ const matchingItems = slashState ? slashMenuItems.filter((item) => {
466
637
  const haystack = `${item.label} ${item.group} ${item.keywords?.join(" ") ?? ""}`.toLowerCase();
467
638
  return haystack.includes(slashState.query.toLowerCase());
468
- });
469
- const firstHeadingKey = matchingItems.find((item) => isHeadingKey(item.key))?.key;
639
+ }) : [];
640
+ selectedIndexRef.current = selectedIndex;
641
+ useEffect(() => {
642
+ selectedIndexRef.current = 0;
643
+ setSelectedIndex(0);
644
+ }, [slashState?.from, slashState?.query]);
645
+ useEffect(() => {
646
+ if (selectedIndex < matchingItems.length) return;
647
+ setSelectedIndex(Math.max(0, matchingItems.length - 1));
648
+ }, [matchingItems.length, selectedIndex]);
649
+ useEffect(() => {
650
+ const editorElement = controller.getRootElement();
651
+ if (!slashState || !editorElement) return;
652
+ const previous = {
653
+ controls: editorElement.getAttribute("aria-controls"),
654
+ expanded: editorElement.getAttribute("aria-expanded"),
655
+ activeDescendant: editorElement.getAttribute("aria-activedescendant")
656
+ };
657
+ const selectedId = matchingItems[selectedIndex] ? `${menuId}-option-${selectedIndex}` : null;
658
+ editorElement.setAttribute("aria-controls", menuId);
659
+ editorElement.setAttribute("aria-expanded", "true");
660
+ if (selectedId) editorElement.setAttribute("aria-activedescendant", selectedId);
661
+ else editorElement.removeAttribute("aria-activedescendant");
662
+ const handleKeyDown = (event) => {
663
+ if (event.isComposing) return false;
664
+ let nextIndex = null;
665
+ const currentIndex = selectedIndexRef.current;
666
+ if (event.key === "ArrowDown") nextIndex = (currentIndex + 1) % Math.max(1, matchingItems.length);
667
+ if (event.key === "ArrowUp") nextIndex = (currentIndex - 1 + Math.max(1, matchingItems.length)) % Math.max(1, matchingItems.length);
668
+ if (event.key === "Home" || event.key === "PageUp") nextIndex = 0;
669
+ if (event.key === "End" || event.key === "PageDown") nextIndex = Math.max(0, matchingItems.length - 1);
670
+ if (nextIndex !== null) {
671
+ event.preventDefault();
672
+ selectedIndexRef.current = nextIndex;
673
+ setSelectedIndex(nextIndex);
674
+ return true;
675
+ }
676
+ if (event.key === "Enter") {
677
+ const item = matchingItems[selectedIndexRef.current];
678
+ if (!item) return false;
679
+ event.preventDefault();
680
+ event.stopPropagation();
681
+ item.execute({ controller, range: { from: slashState.from, to: slashState.to } });
682
+ return true;
683
+ }
684
+ return false;
685
+ };
686
+ const unregisterKeyboardHandler = controller.registerSlashMenuKeyboardHandler(handleKeyDown);
687
+ return () => {
688
+ unregisterKeyboardHandler();
689
+ for (const [attribute, value] of [
690
+ ["aria-controls", previous.controls],
691
+ ["aria-expanded", previous.expanded],
692
+ ["aria-activedescendant", previous.activeDescendant]
693
+ ]) {
694
+ if (value === null) editorElement.removeAttribute(attribute);
695
+ else editorElement.setAttribute(attribute, value);
696
+ }
697
+ };
698
+ }, [controller, matchingItems, menuId, selectedIndex, slashState]);
699
+ useEffect(() => {
700
+ menuRef.current?.querySelector("[data-highlighted]")?.scrollIntoView({ block: "nearest" });
701
+ }, [selectedIndex]);
702
+ if (!slashState) return null;
470
703
  return /* @__PURE__ */ jsx(Popover.Root, { open: true, modal: false, children: /* @__PURE__ */ jsx(Popover.Portal, { container, children: /* @__PURE__ */ jsx(
471
704
  Popover.Positioner,
472
705
  {
473
706
  anchor,
707
+ className: "oe-anchored-overlay-positioner",
474
708
  positionMethod: "fixed",
475
709
  side: "bottom",
476
710
  align: "start",
477
711
  sideOffset: 8,
478
712
  collisionPadding: 12,
479
713
  collisionAvoidance: { side: "flip", align: "shift", fallbackAxisSide: "none" },
480
- children: /* @__PURE__ */ jsxs(
714
+ children: /* @__PURE__ */ jsx(
481
715
  Popover.Popup,
482
716
  {
483
717
  className: "oe-cascade-menu",
484
- style: themeStyle,
485
718
  finalFocus: false,
719
+ style: themeStyle,
486
720
  initialFocus: false,
487
- onMouseLeave: () => setHeadingPanelOpen(false),
488
- children: [
489
- /* @__PURE__ */ jsx("div", { className: `${className} oe-cascade-panel`, children: /* @__PURE__ */ jsx("div", { className: "oe-slash-menu-scroll", children: matchingItems.map((item) => {
490
- if (isHeadingKey(item.key)) {
491
- if (item.key !== firstHeadingKey) return null;
721
+ children: /* @__PURE__ */ jsx(
722
+ "div",
723
+ {
724
+ "aria-label": "Insert block",
725
+ className: `${className} oe-cascade-panel`,
726
+ id: menuId,
727
+ ref: menuRef,
728
+ role: "listbox",
729
+ children: /* @__PURE__ */ jsx("div", { className: "oe-slash-menu-scroll", children: matchingItems.map((item, index) => {
730
+ const Icon = getSlashMenuIcon(item);
492
731
  return /* @__PURE__ */ jsxs(
493
732
  "button",
494
733
  {
734
+ "aria-selected": index === selectedIndex,
495
735
  type: "button",
496
736
  className: "oe-slash-menu-item",
497
- onClick: () => setHeadingPanelOpen(true),
498
- onFocus: () => setHeadingPanelOpen(true),
499
- onMouseEnter: () => setHeadingPanelOpen(true),
737
+ "data-highlighted": index === selectedIndex ? "" : void 0,
738
+ id: `${menuId}-option-${index}`,
739
+ onMouseEnter: () => setSelectedIndex(index),
500
740
  onMouseDown: (event) => event.preventDefault(),
741
+ onClick: () => {
742
+ item.execute({ controller, range: { from: slashState.from, to: slashState.to } });
743
+ },
744
+ role: "option",
501
745
  children: [
502
- /* @__PURE__ */ jsx(Heading1, { "aria-hidden": "true", className: "oe-slash-menu-icon", size: 15, strokeWidth: 2 }),
503
- /* @__PURE__ */ jsx("span", { children: "Heading" }),
504
- /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true", className: "oe-submenu-chevron", size: 13, strokeWidth: 2 })
746
+ /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "oe-slash-menu-icon", size: 15, strokeWidth: 2 }),
747
+ /* @__PURE__ */ jsx("span", { children: item.label })
505
748
  ]
506
749
  },
507
- "heading"
750
+ item.key
508
751
  );
509
- }
510
- const Icon = getSlashMenuIcon(item);
511
- return /* @__PURE__ */ jsxs(
512
- "button",
513
- {
514
- type: "button",
515
- className: "oe-slash-menu-item",
516
- onMouseEnter: () => setHeadingPanelOpen(false),
517
- onMouseDown: (event) => event.preventDefault(),
518
- onClick: () => {
519
- item.execute({ controller, range: { from: slashState.from, to: slashState.to } });
520
- },
521
- children: [
522
- /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "oe-slash-menu-icon", size: 15, strokeWidth: 2 }),
523
- /* @__PURE__ */ jsx("span", { children: item.label })
524
- ]
525
- },
526
- item.key
527
- );
528
- }) }) }),
529
- headingPanelOpen ? /* @__PURE__ */ jsx("div", { className: "oe-slash-menu oe-slash-submenu oe-cascade-panel", children: matchingItems.filter((item) => isHeadingKey(item.key)).map((headingItem) => {
530
- const HeadingIcon = getSlashMenuIcon(headingItem);
531
- return /* @__PURE__ */ jsxs(
532
- "button",
533
- {
534
- type: "button",
535
- className: "oe-slash-menu-item",
536
- onMouseDown: (event) => event.preventDefault(),
537
- onClick: () => {
538
- headingItem.execute({ controller, range: { from: slashState.from, to: slashState.to } });
539
- },
540
- role: "menuitem",
541
- children: [
542
- /* @__PURE__ */ jsx(HeadingIcon, { "aria-hidden": true, className: "oe-slash-menu-icon", size: 15, strokeWidth: 2 }),
543
- /* @__PURE__ */ jsx("span", { children: headingItem.label })
544
- ]
545
- },
546
- headingItem.key
547
- );
548
- }) }) : null
549
- ]
752
+ }) })
753
+ }
754
+ )
550
755
  }
551
756
  )
552
757
  }
@@ -610,8 +815,28 @@ var OpenEditorBlockMenu = ({
610
815
  const [open, setOpen] = useState(false);
611
816
  const handleElementRef = useRef(null);
612
817
  const lastBlockRef = useRef(null);
818
+ const controllerRef = useRef(controller);
819
+ controllerRef.current = controller;
613
820
  if (block) lastBlockRef.current = block;
614
821
  const activeBlock = block ?? (open ? lastBlockRef.current : null);
822
+ const setMenuOpen = useCallback((nextOpen) => {
823
+ const selected = lastBlockRef.current;
824
+ if (nextOpen) {
825
+ if (!selected || !controllerRef.current.selectBlock(selected)) {
826
+ setOpen(false);
827
+ controllerRef.current.setBlockHandleLocked(false);
828
+ return;
829
+ }
830
+ controllerRef.current.setBlockHandleLocked(true);
831
+ setOpen(true);
832
+ return;
833
+ }
834
+ setOpen(false);
835
+ controllerRef.current.setBlockHandleLocked(false);
836
+ }, []);
837
+ useEffect(() => () => {
838
+ controllerRef.current.setBlockHandleLocked(false);
839
+ }, []);
615
840
  if (!controller.editable) return null;
616
841
  const handle = /* @__PURE__ */ jsx(
617
842
  OpenEditorBlockDragHandle,
@@ -622,25 +847,18 @@ var OpenEditorBlockMenu = ({
622
847
  elementRef: handleElementRef,
623
848
  expanded: open,
624
849
  onActivate: () => {
625
- const nextOpen = !open;
626
- setOpen(nextOpen);
627
- controller.setBlockHandleLocked(nextOpen);
628
- if (nextOpen && activeBlock) controller.selectBlock(activeBlock);
850
+ setMenuOpen(!open);
629
851
  },
630
852
  onDraggingChange: (dragging) => {
631
- if (dragging) setOpen(false);
853
+ if (dragging) setMenuOpen(false);
632
854
  },
633
855
  children: activeBlock ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cx("oe-block-handle", className), children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true", size: 16, strokeWidth: 2 }) }) : null
634
856
  }
635
857
  );
636
858
  const actions = activeBlock ? [...builtInBlockActions(controller, activeBlock), ...controller.blockActions] : [];
637
- return /* @__PURE__ */ jsxs(Menu.Root, { modal: false, open, onOpenChange: (nextOpen) => {
638
- setOpen(nextOpen);
639
- controller.setBlockHandleLocked(nextOpen);
640
- if (nextOpen && activeBlock) controller.selectBlock(activeBlock);
641
- }, children: [
859
+ return /* @__PURE__ */ jsxs(Menu.Root, { modal: false, open, onOpenChange: setMenuOpen, children: [
642
860
  handle,
643
- activeBlock ? /* @__PURE__ */ jsx(Menu.Portal, { container, children: /* @__PURE__ */ jsx(
861
+ open && activeBlock ? /* @__PURE__ */ jsx(Menu.Portal, { container, children: /* @__PURE__ */ jsx(
644
862
  Menu.Positioner,
645
863
  {
646
864
  align: "start",
@@ -669,6 +887,7 @@ var OpenEditorBlockMenu = ({
669
887
  "data-danger": action.group === "danger" ? "" : void 0,
670
888
  disabled: action.disabled?.(context),
671
889
  onClick: () => {
890
+ setMenuOpen(false);
672
891
  void action.run(context);
673
892
  },
674
893
  children: [
@@ -697,11 +916,12 @@ var OpenEditor = ({
697
916
  /* @__PURE__ */ jsx(OpenEditorContent, { controller, className: contentClassName }),
698
917
  /* @__PURE__ */ jsx(OpenEditorBlockMenu, { controller, container: overlayContainer }),
699
918
  /* @__PURE__ */ jsx(OpenEditorSelectionBubble, { controller, container: overlayContainer }),
919
+ /* @__PURE__ */ jsx(OpenEditorTableMenu, { controller, container: overlayContainer }),
700
920
  /* @__PURE__ */ jsx(OpenEditorSlashMenu, { controller, container: overlayContainer })
701
921
  ] });
702
922
  return theme ? /* @__PURE__ */ jsx(OpenEditorThemeProvider, { theme, children: editor }) : editor;
703
923
  };
704
924
 
705
- export { OpenEditor, OpenEditorBlockMenu, OpenEditorButton, OpenEditorInput, OpenEditorSelectionBubble, OpenEditorSlashMenu, OpenEditorToggle, OpenEditorToggleGroup };
925
+ export { OpenEditor, OpenEditorBlockMenu, OpenEditorButton, OpenEditorInput, OpenEditorSelectionBubble, OpenEditorSlashMenu, OpenEditorTableMenu, OpenEditorToggle, OpenEditorToggleGroup };
706
926
  //# sourceMappingURL=index.js.map
707
927
  //# sourceMappingURL=index.js.map