@manuscripts/body-editor 3.7.24 → 3.7.25

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.
Files changed (30) hide show
  1. package/dist/cjs/commands.js +10 -1
  2. package/dist/cjs/components/LanguageDropdown/index.js +68 -8
  3. package/dist/cjs/components/outline/DraggableTree.js +18 -3
  4. package/dist/cjs/components/outline/ManuscriptOutline.js +48 -1
  5. package/dist/cjs/components/outline/Outline.js +6 -0
  6. package/dist/cjs/components/toolbar/ListMenuItem.js +94 -7
  7. package/dist/cjs/components/toolbar/type-selector/OptionComponent.js +64 -4
  8. package/dist/cjs/components/toolbar/type-selector/TypeSelector.js +2 -1
  9. package/dist/cjs/components/toolbar/type-selector/styles.js +11 -1
  10. package/dist/cjs/keys/misc.js +1 -0
  11. package/dist/cjs/versions.js +1 -1
  12. package/dist/es/commands.js +8 -0
  13. package/dist/es/components/LanguageDropdown/index.js +68 -8
  14. package/dist/es/components/outline/DraggableTree.js +18 -3
  15. package/dist/es/components/outline/ManuscriptOutline.js +49 -2
  16. package/dist/es/components/outline/Outline.js +6 -0
  17. package/dist/es/components/toolbar/ListMenuItem.js +61 -7
  18. package/dist/es/components/toolbar/type-selector/OptionComponent.js +29 -3
  19. package/dist/es/components/toolbar/type-selector/TypeSelector.js +3 -2
  20. package/dist/es/components/toolbar/type-selector/styles.js +11 -1
  21. package/dist/es/keys/misc.js +2 -1
  22. package/dist/es/versions.js +1 -1
  23. package/dist/types/commands.d.ts +1 -0
  24. package/dist/types/components/LanguageDropdown/index.d.ts +1 -0
  25. package/dist/types/components/toolbar/ListMenuItem.d.ts +1 -0
  26. package/dist/types/components/toolbar/type-selector/OptionComponent.d.ts +2 -1
  27. package/dist/types/components/toolbar/type-selector/styles.d.ts +1 -1
  28. package/dist/types/versions.d.ts +1 -1
  29. package/package.json +2 -2
  30. package/styles/Editor.css +7 -4
@@ -16,7 +16,7 @@
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.addHeaderRow = exports.addRows = exports.addInlineComment = exports.addNodeComment = exports.createAndFillTableElement = exports.selectAllIsolating = exports.ignoreAtomBlockNodeForward = exports.isAtEndOfTextBlock = exports.ignoreMetaNodeBackspaceCommand = exports.ignoreAtomBlockNodeBackward = exports.isTextSelection = exports.isAtStartOfTextBlock = exports.insertTOCSection = exports.insertBibliographySection = exports.insertList = exports.insertKeywords = exports.insertAward = exports.insertAffiliation = exports.insertContributors = exports.insertGraphicalAbstract = exports.insertBackmatterSection = exports.insertAbstractSection = exports.insertSection = exports.insertBoxElement = exports.insertInlineFootnote = exports.insertFootnotesElement = exports.insertTableElementFooter = exports.insertInlineEquation = exports.insertCrossReference = exports.insertInlineCitation = exports.insertLink = exports.insertSectionLabel = exports.findPosBeforeFirstSubsection = exports.insertBreak = exports.deleteBlock = exports.insertBlock = exports.insertAttachment = exports.insertSupplement = exports.insertTable = exports.insertEmbed = exports.insertFigure = exports.insertGeneralTableFootnote = exports.insertInlineTableFootnote = exports.createBlock = exports.createSelection = exports.canInsert = exports.blockActive = exports.isNodeSelection = exports.markActive = exports.addToStart = void 0;
19
- exports.paste = exports.copySelection = exports.ignoreEnterInSubtitles = exports.insertHeroImage = exports.activateSearchReplace = exports.activateSearch = exports.autoComplete = exports.addColumns = void 0;
19
+ exports.paste = exports.copySelection = exports.exitEditorToContainer = exports.ignoreEnterInSubtitles = exports.insertHeroImage = exports.activateSearchReplace = exports.activateSearch = exports.autoComplete = exports.addColumns = void 0;
20
20
  const json_schema_1 = require("@manuscripts/json-schema");
21
21
  const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
22
22
  const transform_1 = require("@manuscripts/transform");
@@ -1296,6 +1296,15 @@ const ignoreEnterInSubtitles = (state) => {
1296
1296
  return false;
1297
1297
  };
1298
1298
  exports.ignoreEnterInSubtitles = ignoreEnterInSubtitles;
1299
+ const exitEditorToContainer = () => {
1300
+ const editorContainer = document.getElementById('editor');
1301
+ if (editorContainer) {
1302
+ editorContainer.focus();
1303
+ return true;
1304
+ }
1305
+ return false;
1306
+ };
1307
+ exports.exitEditorToContainer = exitEditorToContainer;
1299
1308
  const copySelection = (state, dispatch, view) => {
1300
1309
  const { selection } = state;
1301
1310
  const clipboard = navigator?.clipboard;
@@ -55,14 +55,24 @@ const style_guide_1 = require("@manuscripts/style-guide");
55
55
  const react_1 = __importStar(require("react"));
56
56
  const styled_components_1 = __importDefault(require("styled-components"));
57
57
  const languages_1 = require("./languages");
58
- const LanguageOptionItem = ({ language, isSelected, onSelect }) => (react_1.default.createElement(StyledLanguageOption, { key: language.code, onClick: (event) => onSelect(event, language.code) },
59
- language.name,
60
- language.nativeName && ` (${language.nativeName})`,
61
- isSelected && (react_1.default.createElement(TickIconWrapper, null,
62
- react_1.default.createElement(style_guide_1.TickIcon, null)))));
63
- const LanguageDropdown = ({ onLanguageSelect, onClose, currentLanguage = 'en', showButton = false, selectedLanguageDisplay, onCloseParent, languages, }) => {
58
+ const LanguageOptionItem = ({ language, isSelected, onSelect }) => {
59
+ const handleKeyDown = (event) => {
60
+ if (event.key === 'Enter') {
61
+ event.preventDefault();
62
+ onSelect(event, language.code);
63
+ }
64
+ };
65
+ return (react_1.default.createElement(StyledLanguageOption, { key: language.code, onClick: (event) => onSelect(event, language.code), onKeyDown: handleKeyDown, tabIndex: 0, role: "submenuitem" },
66
+ language.name,
67
+ language.nativeName && ` (${language.nativeName})`,
68
+ isSelected && (react_1.default.createElement(TickIconWrapper, null,
69
+ react_1.default.createElement(style_guide_1.TickIcon, null)))));
70
+ };
71
+ const LanguageDropdown = ({ onLanguageSelect, onClose, currentLanguage = 'en', showButton = false, selectedLanguageDisplay, onCloseParent, languages, menuItemRef, }) => {
64
72
  const [isOpen, setIsOpen] = (0, react_1.useState)(!showButton);
65
73
  const dropdownRef = (0, react_1.useRef)(null);
74
+ const dropdownMenuRef = (0, react_1.useRef)(null);
75
+ const languageButtonRef = (0, react_1.useRef)(null);
66
76
  (0, react_1.useEffect)(() => {
67
77
  const handleClickOutside = (event) => {
68
78
  if (dropdownRef.current &&
@@ -79,10 +89,45 @@ const LanguageDropdown = ({ onLanguageSelect, onClose, currentLanguage = 'en', s
79
89
  document.removeEventListener('mousedown', handleClickOutside);
80
90
  };
81
91
  }, [isOpen, onClose, onCloseParent]);
92
+ (0, react_1.useEffect)(() => {
93
+ if (isOpen && dropdownMenuRef.current) {
94
+ const firstOption = dropdownMenuRef.current.querySelector('[role="submenuitem"]');
95
+ firstOption?.focus();
96
+ }
97
+ }, [isOpen]);
82
98
  const toggleDropdown = (event) => {
83
99
  event.stopPropagation();
84
100
  setIsOpen(!isOpen);
85
101
  };
102
+ const handleKeyDown = (event) => {
103
+ if (event.key === 'Enter' || event.key === 'ArrowRight') {
104
+ event.preventDefault();
105
+ toggleDropdown(event);
106
+ }
107
+ };
108
+ const handleMenuKeyDown = (event) => {
109
+ if (!dropdownMenuRef.current)
110
+ return;
111
+ const menuItems = Array.from(dropdownMenuRef.current.querySelectorAll('[role="submenuitem"]'));
112
+ if (menuItems.length === 0)
113
+ return;
114
+ const currentIndex = menuItems.findIndex((item) => item === document.activeElement);
115
+ if (event.key === 'ArrowDown') {
116
+ event.preventDefault();
117
+ const nextIndex = (currentIndex + 1) % menuItems.length;
118
+ menuItems[nextIndex]?.focus();
119
+ }
120
+ else if (event.key === 'ArrowUp') {
121
+ event.preventDefault();
122
+ const prevIndex = currentIndex <= 0 ? menuItems.length - 1 : currentIndex - 1;
123
+ menuItems[prevIndex]?.focus();
124
+ }
125
+ else if (event.key === 'Escape' || event.key === 'ArrowLeft') {
126
+ event.preventDefault();
127
+ setIsOpen(false);
128
+ setTimeout(() => languageButtonRef.current?.focus(), 0);
129
+ }
130
+ };
86
131
  const handleSelect = (event, languageCode) => {
87
132
  event.stopPropagation();
88
133
  onLanguageSelect(languageCode);
@@ -91,13 +136,16 @@ const LanguageDropdown = ({ onLanguageSelect, onClose, currentLanguage = 'en', s
91
136
  return (0, languages_1.getSelectedLanguageName)(languageCode, languages);
92
137
  };
93
138
  return (react_1.default.createElement(style_guide_1.DropdownContainer, { ref: dropdownRef },
94
- showButton && (react_1.default.createElement(LanguageButton, { onClick: toggleDropdown },
139
+ showButton && (react_1.default.createElement(LanguageButton, { ref: (el) => {
140
+ languageButtonRef.current = el;
141
+ menuItemRef?.(el);
142
+ }, onClick: toggleDropdown, onKeyDown: handleKeyDown, tabIndex: 0, role: "menuitem" },
95
143
  react_1.default.createElement(ButtonContent, null,
96
144
  react_1.default.createElement(ButtonLabel, null,
97
145
  "Document language ",
98
146
  react_1.default.createElement(style_guide_1.TriangleCollapsedIcon, null)),
99
147
  react_1.default.createElement(SelectedLanguage, null, selectedLanguageDisplay || getDisplayName(currentLanguage))))),
100
- isOpen && (react_1.default.createElement(DropdownMenu, { direction: "right", width: 231, height: 400, top: 18 },
148
+ isOpen && (react_1.default.createElement(DropdownMenu, { ref: dropdownMenuRef, direction: "right", width: 231, height: 400, top: 18, onKeyDown: handleMenuKeyDown, role: "menu" },
101
149
  !showButton && react_1.default.createElement(DropdownTitle, null, "Choose language"),
102
150
  languages.map((language) => (react_1.default.createElement(LanguageOptionItem, { key: language.code, language: language, isSelected: currentLanguage === language.code, onSelect: handleSelect })))))));
103
151
  };
@@ -140,6 +188,12 @@ const LanguageButton = styled_components_1.default.div `
140
188
  &:hover {
141
189
  background: ${(props) => props.theme.colors.background.fifth};
142
190
  }
191
+
192
+ &:focus-visible {
193
+ outline: 2px solid ${(props) => props.theme.colors.outline.focus};
194
+ outline-offset: -2px;
195
+ background: ${(props) => props.theme.colors.background.fifth};
196
+ }
143
197
  `;
144
198
  const ButtonContent = styled_components_1.default.span `
145
199
  display: flex;
@@ -196,5 +250,11 @@ const StyledLanguageOption = styled_components_1.default.div `
196
250
  &:hover {
197
251
  background-color: #f2fbfc;
198
252
  }
253
+
254
+ &:focus-visible {
255
+ outline: 2px solid ${(props) => props.theme.colors.outline.focus};
256
+ outline-offset: -2px;
257
+ background: ${(props) => props.theme.colors.background.fifth};
258
+ }
199
259
  `;
200
260
  exports.default = LanguageDropdown;
@@ -215,15 +215,30 @@ const DraggableTree = ({ tree, view, depth, can, }) => {
215
215
  const menu = new context_menu_1.ContextMenu(tree.node, view, () => tree.pos - 1);
216
216
  menu.showEditMenu(e.currentTarget);
217
217
  };
218
+ const handleKeyDown = (e) => {
219
+ if (e.key === 'Enter') {
220
+ e.preventDefault();
221
+ e.stopPropagation();
222
+ if (items.length > 0) {
223
+ toggleOpen();
224
+ }
225
+ else {
226
+ const link = e.currentTarget.querySelector('a');
227
+ if (link) {
228
+ link.click();
229
+ }
230
+ }
231
+ }
232
+ };
218
233
  dragRef(dropRef(ref));
219
234
  const dragClass = isDragging ? 'dragging' : '';
220
235
  const dropClass = isOver && dropSide ? `drop-${dropSide}` : '';
221
236
  const deletedClass = isDeletedItem ? 'deleted' : '';
222
237
  const heroImageClass = isHeroImage ? 'hero-image' : '';
223
238
  return (react_1.default.createElement(Outline_1.Outline, { ref: ref, className: `${dragClass} ${dropClass} ${deletedClass} ${heroImageClass}` },
224
- !isTop && node.type.name != 'manuscript' && (react_1.default.createElement(Outline_1.OutlineItem, { depth: isHeroImage ? 1 : depth, onContextMenu: handleContextMenu },
225
- items.length ? (react_1.default.createElement(Outline_1.OutlineItemArrow, { "aria-label": `${isOpen ? 'Collapse' : 'Expand'} ${node.type.name}`, onClick: toggleOpen }, isOpen ? react_1.default.createElement(style_guide_1.TriangleExpandedIcon, null) : react_1.default.createElement(style_guide_1.TriangleCollapsedIcon, null))) : (react_1.default.createElement(Outline_1.OutlineItemNoArrow, null)),
226
- react_1.default.createElement(Outline_1.OutlineItemLink, { to: `#${node.attrs.id}` },
239
+ !isTop && node.type.name != 'manuscript' && (react_1.default.createElement(Outline_1.OutlineItem, { depth: isHeroImage ? 1 : depth, onContextMenu: handleContextMenu, onKeyDown: handleKeyDown, tabIndex: -1, "data-outline-item": true },
240
+ items.length ? (react_1.default.createElement(Outline_1.OutlineItemArrow, { "aria-label": `${isOpen ? 'Collapse' : 'Expand'} ${node.type.name}`, onClick: toggleOpen, tabIndex: -1 }, isOpen ? react_1.default.createElement(style_guide_1.TriangleExpandedIcon, null) : react_1.default.createElement(style_guide_1.TriangleCollapsedIcon, null))) : (react_1.default.createElement(Outline_1.OutlineItemNoArrow, null)),
241
+ react_1.default.createElement(Outline_1.OutlineItemLink, { to: `#${node.attrs.id}`, tabIndex: -1 },
227
242
  react_1.default.createElement(Outline_1.OutlineItemIcon, null, (0, node_type_icons_1.nodeTypeIcon)(node.type)),
228
243
  react_1.default.createElement(Outline_1.OutlineItemLinkText, { className: `outline-text-${node.type.name}` }, itemText(node))))),
229
244
  items.length ? (react_1.default.createElement("div", { className: `subtree ${isOpen ? '' : 'collapsed'}` }, items.map((subtree, index) => (react_1.default.createElement(exports.DraggableTree, { key: subtree.node.attrs.id || 'subtree-' + index, tree: subtree, view: view, depth: !tree.parent ? depth : depth + 1, can: can }))))) : null));
@@ -54,6 +54,7 @@ const use_debounce_1 = require("../hooks/use-debounce");
54
54
  const DraggableTree_1 = require("./DraggableTree");
55
55
  const ManuscriptOutline = (props) => {
56
56
  const [values, setValues] = (0, react_1.useState)();
57
+ const containerRef = (0, react_1.useRef)(null);
57
58
  const debouncedProps = (0, use_debounce_1.useDebounce)(props, 500);
58
59
  (0, react_1.useEffect)(() => {
59
60
  const { doc, view } = debouncedProps;
@@ -69,6 +70,52 @@ const ManuscriptOutline = (props) => {
69
70
  setValues(undefined);
70
71
  }
71
72
  }, [debouncedProps, props.can]);
72
- return values && values.view ? react_1.default.createElement(DraggableTree_1.DraggableTree, { ...values, depth: 0 }) : null;
73
+ const getOutlineItems = (0, react_1.useCallback)(() => {
74
+ if (!containerRef.current)
75
+ return [];
76
+ const allItems = Array.from(containerRef.current.querySelectorAll('[data-outline-item]'));
77
+ return allItems.filter((item) => {
78
+ let parent = item.parentElement;
79
+ while (parent && parent !== containerRef.current) {
80
+ if (parent.classList.contains('subtree') &&
81
+ parent.classList.contains('collapsed')) {
82
+ return false;
83
+ }
84
+ parent = parent.parentElement;
85
+ }
86
+ return true;
87
+ });
88
+ }, []);
89
+ (0, react_1.useEffect)(() => {
90
+ const items = getOutlineItems();
91
+ items.forEach((item, index) => {
92
+ item.tabIndex = index === 0 ? 0 : -1;
93
+ });
94
+ }, [getOutlineItems, values]);
95
+ const handleKeyDown = (0, react_1.useCallback)((event) => {
96
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') {
97
+ return;
98
+ }
99
+ const target = event.target;
100
+ if (!target.hasAttribute('data-outline-item')) {
101
+ return;
102
+ }
103
+ const items = getOutlineItems();
104
+ const currentIndex = items.indexOf(target);
105
+ if (currentIndex === -1)
106
+ return;
107
+ event.preventDefault();
108
+ let nextIndex;
109
+ if (event.key === 'ArrowDown') {
110
+ nextIndex = (currentIndex + 1) % items.length;
111
+ }
112
+ else {
113
+ nextIndex = (currentIndex - 1 + items.length) % items.length;
114
+ }
115
+ const nextItem = items[nextIndex];
116
+ nextItem?.focus();
117
+ }, [getOutlineItems]);
118
+ return values && values.view ? (react_1.default.createElement("div", { ref: containerRef, onKeyDown: handleKeyDown },
119
+ react_1.default.createElement(DraggableTree_1.DraggableTree, { ...values, depth: 0 }))) : null;
73
120
  };
74
121
  exports.ManuscriptOutline = ManuscriptOutline;
@@ -50,6 +50,12 @@ exports.OutlineItem = styled_components_1.default.div `
50
50
  &:hover {
51
51
  background: ${(props) => props.theme.colors.background.fifth};
52
52
  }
53
+
54
+ &:focus-visible {
55
+ background: ${(props) => props.theme.colors.background.fifth};
56
+ outline: 2px solid #bce7f6;
57
+ outline-offset: -2px;
58
+ }
53
59
  `;
54
60
  exports.OutlineItemArrow = styled_components_1.default.button `
55
61
  display: inline-block;
@@ -1,25 +1,107 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.Label = exports.Block = exports.BlockItem = exports.StyleBlock = exports.ListContainer = exports.ListStyles = exports.ListMenuItem = void 0;
7
40
  const style_guide_1 = require("@manuscripts/style-guide");
8
- const react_1 = __importDefault(require("react"));
41
+ const react_1 = __importStar(require("react"));
9
42
  const styled_components_1 = __importDefault(require("styled-components"));
10
- const ListMenuItem = ({ menu, handleClick, }) => {
43
+ const ListMenuItem = ({ menu, handleClick, closeAll, }) => {
44
+ const styleRefs = (0, react_1.useRef)([]);
45
+ (0, react_1.useEffect)(() => {
46
+ if (menu.isOpen && styleRefs.current.length > 0) {
47
+ styleRefs.current[0]?.focus();
48
+ }
49
+ }, [menu.isOpen]);
11
50
  if (!menu.submenu) {
12
51
  return null;
13
52
  }
14
53
  const styles = menu.submenu.map((m) => m.id);
54
+ const handleStyleClick = (s, i) => {
55
+ handleClick([i]);
56
+ closeAll();
57
+ };
58
+ const handleKeyDown = (e) => {
59
+ const items = styleRefs.current.filter(Boolean);
60
+ if (items.length === 0) {
61
+ return;
62
+ }
63
+ const currentIndex = items.indexOf(document.activeElement);
64
+ if (currentIndex === -1) {
65
+ return;
66
+ }
67
+ e.preventDefault();
68
+ e.stopPropagation();
69
+ switch (e.key) {
70
+ case 'ArrowDown':
71
+ case 'ArrowRight':
72
+ items[(currentIndex + 1) % items.length]?.focus();
73
+ break;
74
+ case 'ArrowUp':
75
+ case 'ArrowLeft':
76
+ items[(currentIndex - 1 + items.length) % items.length]?.focus();
77
+ break;
78
+ case 'Escape':
79
+ closeAll();
80
+ break;
81
+ case 'Enter': {
82
+ const el = document.activeElement;
83
+ el?.click();
84
+ break;
85
+ }
86
+ }
87
+ };
15
88
  return (react_1.default.createElement(style_guide_1.SubmenuContainer, null,
16
- react_1.default.createElement(style_guide_1.SubmenuLabel, { menu: menu, handleClick: handleClick }),
17
- menu.isOpen && (react_1.default.createElement(style_guide_1.NestedSubmenusContainer, null,
18
- react_1.default.createElement(exports.ListStyles, { styles: styles, onClick: (s, i) => handleClick([i]) })))));
89
+ react_1.default.createElement(style_guide_1.SubmenuLabel, { menu: menu, handleClick: handleClick, closeAll: closeAll }),
90
+ menu.isOpen && (react_1.default.createElement(style_guide_1.NestedSubmenusContainer, { onKeyDown: handleKeyDown },
91
+ react_1.default.createElement(exports.ListStyles, { styles: styles, onClick: handleStyleClick, styleRefs: styleRefs })))));
19
92
  };
20
93
  exports.ListMenuItem = ListMenuItem;
21
- const ListStyles = ({ styles, onClick, }) => {
22
- return (react_1.default.createElement(exports.ListContainer, null, styles.map((style, index) => (react_1.default.createElement(exports.StyleBlock, { "data-cy": "submenu", key: index, onClick: () => onClick(style, index) }, styleItems[style].map((item, index) => (react_1.default.createElement(exports.BlockItem, { key: index },
94
+ const ListStyles = ({ styles, onClick, styleRefs, }) => {
95
+ return (react_1.default.createElement(exports.ListContainer, null, styles.map((style, index) => (react_1.default.createElement(exports.StyleBlock, { "data-cy": "submenu", key: index, ref: (el) => {
96
+ if (styleRefs) {
97
+ styleRefs.current[index] = el;
98
+ }
99
+ }, onClick: () => onClick(style, index), onKeyDown: (e) => {
100
+ if (e.key === 'Enter') {
101
+ e.preventDefault();
102
+ onClick(style, index);
103
+ }
104
+ }, tabIndex: 0 }, styleItems[style].map((item, index) => (react_1.default.createElement(exports.BlockItem, { key: index },
23
105
  react_1.default.createElement(exports.Label, { hide: item === '-' }, item),
24
106
  react_1.default.createElement(exports.Block, null)))))))));
25
107
  };
@@ -56,6 +138,11 @@ exports.StyleBlock = styled_components_1.default.div `
56
138
  &:active {
57
139
  border-color: ${(props) => props.theme.colors.border.primary};
58
140
  }
141
+
142
+ &:focus-visible {
143
+ outline: 2px solid ${(props) => props.theme.colors.outline.focus};
144
+ outline-offset: -2px;
145
+ }
59
146
  `;
60
147
  exports.BlockItem = styled_components_1.default.div `
61
148
  display: flex;
@@ -1,11 +1,45 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.OptionComponent = void 0;
39
+ exports.CustomControl = exports.OptionComponent = void 0;
7
40
  const style_guide_1 = require("@manuscripts/style-guide");
8
- const react_1 = __importDefault(require("react"));
41
+ const react_1 = __importStar(require("react"));
42
+ const react_select_1 = require("react-select");
9
43
  const styled_components_1 = __importDefault(require("styled-components"));
10
44
  const helpers_1 = require("../helpers");
11
45
  const OptionContainer = styled_components_1.default.div `
@@ -15,6 +49,7 @@ const OptionContainer = styled_components_1.default.div `
15
49
  font-size: 14px;
16
50
  cursor: pointer;
17
51
  padding: 8px;
52
+ background: ${(props) => (props.isFocused ? '#f2fbfc' : 'transparent')};
18
53
 
19
54
  &:hover {
20
55
  background: ${(props) => props.theme.colors.background.fifth};
@@ -22,10 +57,35 @@ const OptionContainer = styled_components_1.default.div `
22
57
  `;
23
58
  const OptionLabel = styled_components_1.default.span ``;
24
59
  const TickIconWrapper = styled_components_1.default.div ``;
25
- const OptionComponent = ({ innerProps, data, }) => {
26
- return (react_1.default.createElement(OptionContainer, { ...innerProps, ref: null },
60
+ const OptionComponent = ({ innerProps, data, isFocused, innerRef, }) => {
61
+ return (react_1.default.createElement(OptionContainer, { ...innerProps, isFocused: isFocused, ref: innerRef },
27
62
  react_1.default.createElement(OptionLabel, null, (0, helpers_1.titleCase)((0, helpers_1.optionName)(data.nodeType))),
28
63
  data.isSelected && (react_1.default.createElement(TickIconWrapper, null,
29
64
  react_1.default.createElement(style_guide_1.TickIcon, null)))));
30
65
  };
31
66
  exports.OptionComponent = OptionComponent;
67
+ const CustomControl = (props) => {
68
+ const controlRef = (0, react_1.useRef)(null);
69
+ (0, react_1.useLayoutEffect)(() => {
70
+ if (controlRef.current) {
71
+ controlRef.current.setAttribute('data-toolbar-button', 'true');
72
+ }
73
+ }, []);
74
+ const handleKeyDown = (e) => {
75
+ if (e.key === 'Enter') {
76
+ e.preventDefault();
77
+ const { selectProps } = props;
78
+ if (selectProps.menuIsOpen) {
79
+ selectProps.onMenuClose?.();
80
+ }
81
+ else {
82
+ selectProps.onMenuOpen?.();
83
+ }
84
+ }
85
+ };
86
+ return (react_1.default.createElement(react_select_1.components.Control, { ...props, innerRef: controlRef, innerProps: {
87
+ ...props.innerProps,
88
+ onKeyDown: handleKeyDown,
89
+ } }));
90
+ };
91
+ exports.CustomControl = CustomControl;
@@ -87,7 +87,8 @@ const TypeSelector = ({ state, dispatch, view }) => {
87
87
  }, value: options.length === 1
88
88
  ? options[0]
89
89
  : (0, helpers_1.findSelectedOption)(options), options: options, components: {
90
+ Control: OptionComponent_1.CustomControl,
90
91
  Option: OptionComponent_1.OptionComponent,
91
- }, classNamePrefix: "type-selector", styles: styles_1.customStyles, isDisabled: options.length <= 1 || !isInBody || !(0, utils_1.isEditAllowed)(state), isSearchable: false }));
92
+ }, classNamePrefix: "type-selector", styles: styles_1.customStyles, isDisabled: options.length <= 1 || !isInBody || !(0, utils_1.isEditAllowed)(state), isSearchable: false, tabIndex: -1 }));
92
93
  };
93
94
  exports.TypeSelector = TypeSelector;
@@ -7,9 +7,19 @@ exports.customStyles = exports.StyledSelect = void 0;
7
7
  const react_select_1 = __importDefault(require("react-select"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
9
  exports.StyledSelect = (0, styled_components_1.default)((react_select_1.default)) `
10
- & > div:hover {
10
+ .type-selector__control:hover {
11
11
  border-color: ${(props) => props.theme.colors.border.secondary};
12
12
  }
13
+
14
+ .type-selector__control:focus-visible {
15
+ border-color: ${(props) => props.theme.colors.outline.focus} !important;
16
+ box-shadow: 0 0 0 1px ${(props) => props.theme.colors.outline.focus} !important;
17
+ outline: none;
18
+
19
+ .type-selector__dropdown-indicator {
20
+ color: #6e6e6e;
21
+ }
22
+ }
13
23
  `;
14
24
  exports.customStyles = {
15
25
  control: (styles) => ({
@@ -26,6 +26,7 @@ const customKeymap = {
26
26
  Backspace: (0, prosemirror_commands_1.chainCommands)(prosemirror_inputrules_1.undoInputRule, commands_1.ignoreAtomBlockNodeBackward, commands_1.ignoreMetaNodeBackspaceCommand, (0, list_1.skipCommandTracking)(prosemirror_commands_1.joinBackward)),
27
27
  Delete: commands_1.ignoreAtomBlockNodeForward,
28
28
  Tab: (0, prosemirror_tables_1.goToNextCell)(1),
29
+ Escape: commands_1.exitEditorToContainer,
29
30
  'Mod-z': prosemirror_history_1.undo,
30
31
  'Mod-y': prosemirror_history_1.redo,
31
32
  'Shift-Mod-z': prosemirror_history_1.redo,
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MATHJAX_VERSION = exports.VERSION = void 0;
4
- exports.VERSION = '3.7.24';
4
+ exports.VERSION = '3.7.25';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -1236,6 +1236,14 @@ export const ignoreEnterInSubtitles = (state) => {
1236
1236
  }
1237
1237
  return false;
1238
1238
  };
1239
+ export const exitEditorToContainer = () => {
1240
+ const editorContainer = document.getElementById('editor');
1241
+ if (editorContainer) {
1242
+ editorContainer.focus();
1243
+ return true;
1244
+ }
1245
+ return false;
1246
+ };
1239
1247
  export const copySelection = (state, dispatch, view) => {
1240
1248
  const { selection } = state;
1241
1249
  const clipboard = navigator?.clipboard;
@@ -17,14 +17,24 @@ import { DropdownContainer, DropdownList, TickIcon, TriangleCollapsedIcon, } fro
17
17
  import React, { useEffect, useRef, useState } from 'react';
18
18
  import styled from 'styled-components';
19
19
  import { getSelectedLanguageName } from './languages';
20
- const LanguageOptionItem = ({ language, isSelected, onSelect }) => (React.createElement(StyledLanguageOption, { key: language.code, onClick: (event) => onSelect(event, language.code) },
21
- language.name,
22
- language.nativeName && ` (${language.nativeName})`,
23
- isSelected && (React.createElement(TickIconWrapper, null,
24
- React.createElement(TickIcon, null)))));
25
- const LanguageDropdown = ({ onLanguageSelect, onClose, currentLanguage = 'en', showButton = false, selectedLanguageDisplay, onCloseParent, languages, }) => {
20
+ const LanguageOptionItem = ({ language, isSelected, onSelect }) => {
21
+ const handleKeyDown = (event) => {
22
+ if (event.key === 'Enter') {
23
+ event.preventDefault();
24
+ onSelect(event, language.code);
25
+ }
26
+ };
27
+ return (React.createElement(StyledLanguageOption, { key: language.code, onClick: (event) => onSelect(event, language.code), onKeyDown: handleKeyDown, tabIndex: 0, role: "submenuitem" },
28
+ language.name,
29
+ language.nativeName && ` (${language.nativeName})`,
30
+ isSelected && (React.createElement(TickIconWrapper, null,
31
+ React.createElement(TickIcon, null)))));
32
+ };
33
+ const LanguageDropdown = ({ onLanguageSelect, onClose, currentLanguage = 'en', showButton = false, selectedLanguageDisplay, onCloseParent, languages, menuItemRef, }) => {
26
34
  const [isOpen, setIsOpen] = useState(!showButton);
27
35
  const dropdownRef = useRef(null);
36
+ const dropdownMenuRef = useRef(null);
37
+ const languageButtonRef = useRef(null);
28
38
  useEffect(() => {
29
39
  const handleClickOutside = (event) => {
30
40
  if (dropdownRef.current &&
@@ -41,10 +51,45 @@ const LanguageDropdown = ({ onLanguageSelect, onClose, currentLanguage = 'en', s
41
51
  document.removeEventListener('mousedown', handleClickOutside);
42
52
  };
43
53
  }, [isOpen, onClose, onCloseParent]);
54
+ useEffect(() => {
55
+ if (isOpen && dropdownMenuRef.current) {
56
+ const firstOption = dropdownMenuRef.current.querySelector('[role="submenuitem"]');
57
+ firstOption?.focus();
58
+ }
59
+ }, [isOpen]);
44
60
  const toggleDropdown = (event) => {
45
61
  event.stopPropagation();
46
62
  setIsOpen(!isOpen);
47
63
  };
64
+ const handleKeyDown = (event) => {
65
+ if (event.key === 'Enter' || event.key === 'ArrowRight') {
66
+ event.preventDefault();
67
+ toggleDropdown(event);
68
+ }
69
+ };
70
+ const handleMenuKeyDown = (event) => {
71
+ if (!dropdownMenuRef.current)
72
+ return;
73
+ const menuItems = Array.from(dropdownMenuRef.current.querySelectorAll('[role="submenuitem"]'));
74
+ if (menuItems.length === 0)
75
+ return;
76
+ const currentIndex = menuItems.findIndex((item) => item === document.activeElement);
77
+ if (event.key === 'ArrowDown') {
78
+ event.preventDefault();
79
+ const nextIndex = (currentIndex + 1) % menuItems.length;
80
+ menuItems[nextIndex]?.focus();
81
+ }
82
+ else if (event.key === 'ArrowUp') {
83
+ event.preventDefault();
84
+ const prevIndex = currentIndex <= 0 ? menuItems.length - 1 : currentIndex - 1;
85
+ menuItems[prevIndex]?.focus();
86
+ }
87
+ else if (event.key === 'Escape' || event.key === 'ArrowLeft') {
88
+ event.preventDefault();
89
+ setIsOpen(false);
90
+ setTimeout(() => languageButtonRef.current?.focus(), 0);
91
+ }
92
+ };
48
93
  const handleSelect = (event, languageCode) => {
49
94
  event.stopPropagation();
50
95
  onLanguageSelect(languageCode);
@@ -53,13 +98,16 @@ const LanguageDropdown = ({ onLanguageSelect, onClose, currentLanguage = 'en', s
53
98
  return getSelectedLanguageName(languageCode, languages);
54
99
  };
55
100
  return (React.createElement(DropdownContainer, { ref: dropdownRef },
56
- showButton && (React.createElement(LanguageButton, { onClick: toggleDropdown },
101
+ showButton && (React.createElement(LanguageButton, { ref: (el) => {
102
+ languageButtonRef.current = el;
103
+ menuItemRef?.(el);
104
+ }, onClick: toggleDropdown, onKeyDown: handleKeyDown, tabIndex: 0, role: "menuitem" },
57
105
  React.createElement(ButtonContent, null,
58
106
  React.createElement(ButtonLabel, null,
59
107
  "Document language ",
60
108
  React.createElement(TriangleCollapsedIcon, null)),
61
109
  React.createElement(SelectedLanguage, null, selectedLanguageDisplay || getDisplayName(currentLanguage))))),
62
- isOpen && (React.createElement(DropdownMenu, { direction: "right", width: 231, height: 400, top: 18 },
110
+ isOpen && (React.createElement(DropdownMenu, { ref: dropdownMenuRef, direction: "right", width: 231, height: 400, top: 18, onKeyDown: handleMenuKeyDown, role: "menu" },
63
111
  !showButton && React.createElement(DropdownTitle, null, "Choose language"),
64
112
  languages.map((language) => (React.createElement(LanguageOptionItem, { key: language.code, language: language, isSelected: currentLanguage === language.code, onSelect: handleSelect })))))));
65
113
  };
@@ -102,6 +150,12 @@ const LanguageButton = styled.div `
102
150
  &:hover {
103
151
  background: ${(props) => props.theme.colors.background.fifth};
104
152
  }
153
+
154
+ &:focus-visible {
155
+ outline: 2px solid ${(props) => props.theme.colors.outline.focus};
156
+ outline-offset: -2px;
157
+ background: ${(props) => props.theme.colors.background.fifth};
158
+ }
105
159
  `;
106
160
  const ButtonContent = styled.span `
107
161
  display: flex;
@@ -158,5 +212,11 @@ const StyledLanguageOption = styled.div `
158
212
  &:hover {
159
213
  background-color: #f2fbfc;
160
214
  }
215
+
216
+ &:focus-visible {
217
+ outline: 2px solid ${(props) => props.theme.colors.outline.focus};
218
+ outline-offset: -2px;
219
+ background: ${(props) => props.theme.colors.background.fifth};
220
+ }
161
221
  `;
162
222
  export default LanguageDropdown;
@@ -178,15 +178,30 @@ export const DraggableTree = ({ tree, view, depth, can, }) => {
178
178
  const menu = new ContextMenu(tree.node, view, () => tree.pos - 1);
179
179
  menu.showEditMenu(e.currentTarget);
180
180
  };
181
+ const handleKeyDown = (e) => {
182
+ if (e.key === 'Enter') {
183
+ e.preventDefault();
184
+ e.stopPropagation();
185
+ if (items.length > 0) {
186
+ toggleOpen();
187
+ }
188
+ else {
189
+ const link = e.currentTarget.querySelector('a');
190
+ if (link) {
191
+ link.click();
192
+ }
193
+ }
194
+ }
195
+ };
181
196
  dragRef(dropRef(ref));
182
197
  const dragClass = isDragging ? 'dragging' : '';
183
198
  const dropClass = isOver && dropSide ? `drop-${dropSide}` : '';
184
199
  const deletedClass = isDeletedItem ? 'deleted' : '';
185
200
  const heroImageClass = isHeroImage ? 'hero-image' : '';
186
201
  return (React.createElement(Outline, { ref: ref, className: `${dragClass} ${dropClass} ${deletedClass} ${heroImageClass}` },
187
- !isTop && node.type.name != 'manuscript' && (React.createElement(OutlineItem, { depth: isHeroImage ? 1 : depth, onContextMenu: handleContextMenu },
188
- items.length ? (React.createElement(OutlineItemArrow, { "aria-label": `${isOpen ? 'Collapse' : 'Expand'} ${node.type.name}`, onClick: toggleOpen }, isOpen ? React.createElement(TriangleExpandedIcon, null) : React.createElement(TriangleCollapsedIcon, null))) : (React.createElement(OutlineItemNoArrow, null)),
189
- React.createElement(OutlineItemLink, { to: `#${node.attrs.id}` },
202
+ !isTop && node.type.name != 'manuscript' && (React.createElement(OutlineItem, { depth: isHeroImage ? 1 : depth, onContextMenu: handleContextMenu, onKeyDown: handleKeyDown, tabIndex: -1, "data-outline-item": true },
203
+ items.length ? (React.createElement(OutlineItemArrow, { "aria-label": `${isOpen ? 'Collapse' : 'Expand'} ${node.type.name}`, onClick: toggleOpen, tabIndex: -1 }, isOpen ? React.createElement(TriangleExpandedIcon, null) : React.createElement(TriangleCollapsedIcon, null))) : (React.createElement(OutlineItemNoArrow, null)),
204
+ React.createElement(OutlineItemLink, { to: `#${node.attrs.id}`, tabIndex: -1 },
190
205
  React.createElement(OutlineItemIcon, null, nodeTypeIcon(node.type)),
191
206
  React.createElement(OutlineItemLinkText, { className: `outline-text-${node.type.name}` }, itemText(node))))),
192
207
  items.length ? (React.createElement("div", { className: `subtree ${isOpen ? '' : 'collapsed'}` }, items.map((subtree, index) => (React.createElement(DraggableTree, { key: subtree.node.attrs.id || 'subtree-' + index, tree: subtree, view: view, depth: !tree.parent ? depth : depth + 1, can: can }))))) : null));
@@ -13,11 +13,12 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import React, { useEffect, useState } from 'react';
16
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
17
17
  import { useDebounce } from '../hooks/use-debounce';
18
18
  import { buildTree, DraggableTree } from './DraggableTree';
19
19
  export const ManuscriptOutline = (props) => {
20
20
  const [values, setValues] = useState();
21
+ const containerRef = useRef(null);
21
22
  const debouncedProps = useDebounce(props, 500);
22
23
  useEffect(() => {
23
24
  const { doc, view } = debouncedProps;
@@ -33,5 +34,51 @@ export const ManuscriptOutline = (props) => {
33
34
  setValues(undefined);
34
35
  }
35
36
  }, [debouncedProps, props.can]);
36
- return values && values.view ? React.createElement(DraggableTree, { ...values, depth: 0 }) : null;
37
+ const getOutlineItems = useCallback(() => {
38
+ if (!containerRef.current)
39
+ return [];
40
+ const allItems = Array.from(containerRef.current.querySelectorAll('[data-outline-item]'));
41
+ return allItems.filter((item) => {
42
+ let parent = item.parentElement;
43
+ while (parent && parent !== containerRef.current) {
44
+ if (parent.classList.contains('subtree') &&
45
+ parent.classList.contains('collapsed')) {
46
+ return false;
47
+ }
48
+ parent = parent.parentElement;
49
+ }
50
+ return true;
51
+ });
52
+ }, []);
53
+ useEffect(() => {
54
+ const items = getOutlineItems();
55
+ items.forEach((item, index) => {
56
+ item.tabIndex = index === 0 ? 0 : -1;
57
+ });
58
+ }, [getOutlineItems, values]);
59
+ const handleKeyDown = useCallback((event) => {
60
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') {
61
+ return;
62
+ }
63
+ const target = event.target;
64
+ if (!target.hasAttribute('data-outline-item')) {
65
+ return;
66
+ }
67
+ const items = getOutlineItems();
68
+ const currentIndex = items.indexOf(target);
69
+ if (currentIndex === -1)
70
+ return;
71
+ event.preventDefault();
72
+ let nextIndex;
73
+ if (event.key === 'ArrowDown') {
74
+ nextIndex = (currentIndex + 1) % items.length;
75
+ }
76
+ else {
77
+ nextIndex = (currentIndex - 1 + items.length) % items.length;
78
+ }
79
+ const nextItem = items[nextIndex];
80
+ nextItem?.focus();
81
+ }, [getOutlineItems]);
82
+ return values && values.view ? (React.createElement("div", { ref: containerRef, onKeyDown: handleKeyDown },
83
+ React.createElement(DraggableTree, { ...values, depth: 0 }))) : null;
37
84
  };
@@ -44,6 +44,12 @@ export const OutlineItem = styled.div `
44
44
  &:hover {
45
45
  background: ${(props) => props.theme.colors.background.fifth};
46
46
  }
47
+
48
+ &:focus-visible {
49
+ background: ${(props) => props.theme.colors.background.fifth};
50
+ outline: 2px solid #bce7f6;
51
+ outline-offset: -2px;
52
+ }
47
53
  `;
48
54
  export const OutlineItemArrow = styled.button `
49
55
  display: inline-block;
@@ -1,18 +1,67 @@
1
1
  import { NestedSubmenusContainer, SubmenuContainer, SubmenuLabel, } from '@manuscripts/style-guide';
2
- import React from 'react';
2
+ import React, { useEffect, useRef } from 'react';
3
3
  import styled from 'styled-components';
4
- export const ListMenuItem = ({ menu, handleClick, }) => {
4
+ export const ListMenuItem = ({ menu, handleClick, closeAll, }) => {
5
+ const styleRefs = useRef([]);
6
+ useEffect(() => {
7
+ if (menu.isOpen && styleRefs.current.length > 0) {
8
+ styleRefs.current[0]?.focus();
9
+ }
10
+ }, [menu.isOpen]);
5
11
  if (!menu.submenu) {
6
12
  return null;
7
13
  }
8
14
  const styles = menu.submenu.map((m) => m.id);
15
+ const handleStyleClick = (s, i) => {
16
+ handleClick([i]);
17
+ closeAll();
18
+ };
19
+ const handleKeyDown = (e) => {
20
+ const items = styleRefs.current.filter(Boolean);
21
+ if (items.length === 0) {
22
+ return;
23
+ }
24
+ const currentIndex = items.indexOf(document.activeElement);
25
+ if (currentIndex === -1) {
26
+ return;
27
+ }
28
+ e.preventDefault();
29
+ e.stopPropagation();
30
+ switch (e.key) {
31
+ case 'ArrowDown':
32
+ case 'ArrowRight':
33
+ items[(currentIndex + 1) % items.length]?.focus();
34
+ break;
35
+ case 'ArrowUp':
36
+ case 'ArrowLeft':
37
+ items[(currentIndex - 1 + items.length) % items.length]?.focus();
38
+ break;
39
+ case 'Escape':
40
+ closeAll();
41
+ break;
42
+ case 'Enter': {
43
+ const el = document.activeElement;
44
+ el?.click();
45
+ break;
46
+ }
47
+ }
48
+ };
9
49
  return (React.createElement(SubmenuContainer, null,
10
- React.createElement(SubmenuLabel, { menu: menu, handleClick: handleClick }),
11
- menu.isOpen && (React.createElement(NestedSubmenusContainer, null,
12
- React.createElement(ListStyles, { styles: styles, onClick: (s, i) => handleClick([i]) })))));
50
+ React.createElement(SubmenuLabel, { menu: menu, handleClick: handleClick, closeAll: closeAll }),
51
+ menu.isOpen && (React.createElement(NestedSubmenusContainer, { onKeyDown: handleKeyDown },
52
+ React.createElement(ListStyles, { styles: styles, onClick: handleStyleClick, styleRefs: styleRefs })))));
13
53
  };
14
- export const ListStyles = ({ styles, onClick, }) => {
15
- return (React.createElement(ListContainer, null, styles.map((style, index) => (React.createElement(StyleBlock, { "data-cy": "submenu", key: index, onClick: () => onClick(style, index) }, styleItems[style].map((item, index) => (React.createElement(BlockItem, { key: index },
54
+ export const ListStyles = ({ styles, onClick, styleRefs, }) => {
55
+ return (React.createElement(ListContainer, null, styles.map((style, index) => (React.createElement(StyleBlock, { "data-cy": "submenu", key: index, ref: (el) => {
56
+ if (styleRefs) {
57
+ styleRefs.current[index] = el;
58
+ }
59
+ }, onClick: () => onClick(style, index), onKeyDown: (e) => {
60
+ if (e.key === 'Enter') {
61
+ e.preventDefault();
62
+ onClick(style, index);
63
+ }
64
+ }, tabIndex: 0 }, styleItems[style].map((item, index) => (React.createElement(BlockItem, { key: index },
16
65
  React.createElement(Label, { hide: item === '-' }, item),
17
66
  React.createElement(Block, null)))))))));
18
67
  };
@@ -48,6 +97,11 @@ export const StyleBlock = styled.div `
48
97
  &:active {
49
98
  border-color: ${(props) => props.theme.colors.border.primary};
50
99
  }
100
+
101
+ &:focus-visible {
102
+ outline: 2px solid ${(props) => props.theme.colors.outline.focus};
103
+ outline-offset: -2px;
104
+ }
51
105
  `;
52
106
  export const BlockItem = styled.div `
53
107
  display: flex;
@@ -1,5 +1,6 @@
1
1
  import { TickIcon } from '@manuscripts/style-guide';
2
- import React from 'react';
2
+ import React, { useLayoutEffect, useRef } from 'react';
3
+ import { components } from 'react-select';
3
4
  import styled from 'styled-components';
4
5
  import { optionName, titleCase } from '../helpers';
5
6
  const OptionContainer = styled.div `
@@ -9,6 +10,7 @@ const OptionContainer = styled.div `
9
10
  font-size: 14px;
10
11
  cursor: pointer;
11
12
  padding: 8px;
13
+ background: ${(props) => (props.isFocused ? '#f2fbfc' : 'transparent')};
12
14
 
13
15
  &:hover {
14
16
  background: ${(props) => props.theme.colors.background.fifth};
@@ -16,9 +18,33 @@ const OptionContainer = styled.div `
16
18
  `;
17
19
  const OptionLabel = styled.span ``;
18
20
  const TickIconWrapper = styled.div ``;
19
- export const OptionComponent = ({ innerProps, data, }) => {
20
- return (React.createElement(OptionContainer, { ...innerProps, ref: null },
21
+ export const OptionComponent = ({ innerProps, data, isFocused, innerRef, }) => {
22
+ return (React.createElement(OptionContainer, { ...innerProps, isFocused: isFocused, ref: innerRef },
21
23
  React.createElement(OptionLabel, null, titleCase(optionName(data.nodeType))),
22
24
  data.isSelected && (React.createElement(TickIconWrapper, null,
23
25
  React.createElement(TickIcon, null)))));
24
26
  };
27
+ export const CustomControl = (props) => {
28
+ const controlRef = useRef(null);
29
+ useLayoutEffect(() => {
30
+ if (controlRef.current) {
31
+ controlRef.current.setAttribute('data-toolbar-button', 'true');
32
+ }
33
+ }, []);
34
+ const handleKeyDown = (e) => {
35
+ if (e.key === 'Enter') {
36
+ e.preventDefault();
37
+ const { selectProps } = props;
38
+ if (selectProps.menuIsOpen) {
39
+ selectProps.onMenuClose?.();
40
+ }
41
+ else {
42
+ selectProps.onMenuOpen?.();
43
+ }
44
+ }
45
+ };
46
+ return (React.createElement(components.Control, { ...props, innerRef: controlRef, innerProps: {
47
+ ...props.innerProps,
48
+ onKeyDown: handleKeyDown,
49
+ } }));
50
+ };
@@ -5,7 +5,7 @@ import { findClosestParentElement } from '../../../lib/hierarchy';
5
5
  import { isDeleted } from '../../../lib/track-changes-utils';
6
6
  import { isEditAllowed } from '../../../lib/utils';
7
7
  import { demoteSectionToParagraph, findSelectedOption, optionName, promoteParagraphToSection, titleCase, } from '../helpers';
8
- import { OptionComponent } from './OptionComponent';
8
+ import { CustomControl, OptionComponent } from './OptionComponent';
9
9
  import { customStyles, StyledSelect } from './styles';
10
10
  const buildOptions = (state) => {
11
11
  const { doc, selection: { $from, $to }, schema, } = state;
@@ -81,6 +81,7 @@ export const TypeSelector = ({ state, dispatch, view }) => {
81
81
  }, value: options.length === 1
82
82
  ? options[0]
83
83
  : findSelectedOption(options), options: options, components: {
84
+ Control: CustomControl,
84
85
  Option: OptionComponent,
85
- }, classNamePrefix: "type-selector", styles: customStyles, isDisabled: options.length <= 1 || !isInBody || !isEditAllowed(state), isSearchable: false }));
86
+ }, classNamePrefix: "type-selector", styles: customStyles, isDisabled: options.length <= 1 || !isInBody || !isEditAllowed(state), isSearchable: false, tabIndex: -1 }));
86
87
  };
@@ -1,9 +1,19 @@
1
1
  import Select from 'react-select';
2
2
  import styled from 'styled-components';
3
3
  export const StyledSelect = styled((Select)) `
4
- & > div:hover {
4
+ .type-selector__control:hover {
5
5
  border-color: ${(props) => props.theme.colors.border.secondary};
6
6
  }
7
+
8
+ .type-selector__control:focus-visible {
9
+ border-color: ${(props) => props.theme.colors.outline.focus} !important;
10
+ box-shadow: 0 0 0 1px ${(props) => props.theme.colors.outline.focus} !important;
11
+ outline: none;
12
+
13
+ .type-selector__dropdown-indicator {
14
+ color: #6e6e6e;
15
+ }
16
+ }
7
17
  `;
8
18
  export const customStyles = {
9
19
  control: (styles) => ({
@@ -18,12 +18,13 @@ import { chainCommands, createParagraphNear, exitCode, joinBackward, joinDown, j
18
18
  import { redo, undo } from 'prosemirror-history';
19
19
  import { undoInputRule } from 'prosemirror-inputrules';
20
20
  import { goToNextCell } from 'prosemirror-tables';
21
- import { activateSearch, activateSearchReplace, addToStart, autoComplete, ignoreAtomBlockNodeBackward, ignoreAtomBlockNodeForward, ignoreEnterInSubtitles, ignoreMetaNodeBackspaceCommand, insertBlock, insertBreak, insertCrossReference, insertInlineCitation, insertInlineEquation, insertSection, selectAllIsolating, } from '../commands';
21
+ import { activateSearch, activateSearchReplace, addToStart, autoComplete, exitEditorToContainer, ignoreAtomBlockNodeBackward, ignoreAtomBlockNodeForward, ignoreEnterInSubtitles, ignoreMetaNodeBackspaceCommand, insertBlock, insertBreak, insertCrossReference, insertInlineCitation, insertInlineEquation, insertSection, selectAllIsolating, } from '../commands';
22
22
  import { skipCommandTracking } from './list';
23
23
  const customKeymap = {
24
24
  Backspace: chainCommands(undoInputRule, ignoreAtomBlockNodeBackward, ignoreMetaNodeBackspaceCommand, skipCommandTracking(joinBackward)),
25
25
  Delete: ignoreAtomBlockNodeForward,
26
26
  Tab: goToNextCell(1),
27
+ Escape: exitEditorToContainer,
27
28
  'Mod-z': undo,
28
29
  'Mod-y': redo,
29
30
  'Shift-Mod-z': redo,
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.7.24';
1
+ export const VERSION = '3.7.25';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -84,5 +84,6 @@ export declare const activateSearch: (state: ManuscriptEditorState, dispatch?: D
84
84
  export declare const activateSearchReplace: (state: ManuscriptEditorState, dispatch?: Dispatch) => boolean;
85
85
  export declare const insertHeroImage: () => (state: ManuscriptEditorState, dispatch?: Dispatch, view?: EditorView) => boolean;
86
86
  export declare const ignoreEnterInSubtitles: (state: ManuscriptEditorState) => boolean;
87
+ export declare const exitEditorToContainer: EditorAction;
87
88
  export declare const copySelection: (state: ManuscriptEditorState, dispatch?: Dispatch, view?: EditorView) => boolean;
88
89
  export declare const paste: (format: "html" | "text") => (state: ManuscriptEditorState, dispatch?: Dispatch, view?: EditorView) => boolean;
@@ -24,6 +24,7 @@ interface LanguageDropdownProps {
24
24
  selectedLanguageDisplay?: string;
25
25
  onCloseParent?: () => void;
26
26
  languages: Language[];
27
+ menuItemRef?: (el: HTMLDivElement | null) => void;
27
28
  }
28
29
  declare const LanguageDropdown: React.FC<LanguageDropdownProps>;
29
30
  export default LanguageDropdown;
@@ -4,6 +4,7 @@ export declare const ListMenuItem: React.FC<MenuComponentProps>;
4
4
  export interface ListSubmenuItemsProps {
5
5
  styles: string[];
6
6
  onClick: (style: string, index: number) => void;
7
+ styleRefs?: React.MutableRefObject<(HTMLDivElement | null)[]>;
7
8
  }
8
9
  export declare const ListStyles: React.FC<ListSubmenuItemsProps>;
9
10
  export declare const ListContainer: import("styled-components").StyledComponent<"div", any, {}, never>;
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
- import { OptionProps } from 'react-select';
2
+ import { ControlProps, OptionProps } from 'react-select';
3
3
  import { Option } from './TypeSelector';
4
4
  export declare const OptionComponent: React.FC<OptionProps<Option, false>>;
5
+ export declare const CustomControl: (props: ControlProps<Option, false>) => React.JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { CSSObjectWithLabel } from 'react-select';
2
2
  import { Option } from './TypeSelector';
3
- export declare const StyledSelect: import("styled-components").StyledComponent<(props: Omit<import("react-select/dist/declarations/src/Select").PublicBaseSelectProps<Option, false, import("react-select").GroupBase<Option>>, "onChange" | "inputValue" | "menuIsOpen" | "onInputChange" | "onMenuOpen" | "onMenuClose" | "value"> & Partial<import("react-select/dist/declarations/src/Select").PublicBaseSelectProps<Option, false, import("react-select").GroupBase<Option>>> & import("react-select/dist/declarations/src/useStateManager").StateManagerAdditionalProps<Option> & import("react").RefAttributes<import("react-select/dist/declarations/src/Select").default<Option, false, import("react-select").GroupBase<Option>>>) => import("react").ReactElement, any, {}, never>;
3
+ export declare const StyledSelect: import("styled-components").StyledComponent<(props: Omit<import("react-select/dist/declarations/src/Select").PublicBaseSelectProps<Option, false, import("react-select").GroupBase<Option>>, "onChange" | "value" | "inputValue" | "menuIsOpen" | "onInputChange" | "onMenuOpen" | "onMenuClose"> & Partial<import("react-select/dist/declarations/src/Select").PublicBaseSelectProps<Option, false, import("react-select").GroupBase<Option>>> & import("react-select/dist/declarations/src/useStateManager").StateManagerAdditionalProps<Option> & import("react").RefAttributes<import("react-select/dist/declarations/src/Select").default<Option, false, import("react-select").GroupBase<Option>>>) => import("react").ReactElement, any, {}, never>;
4
4
  export declare const customStyles: {
5
5
  control: (styles: CSSObjectWithLabel) => CSSObjectWithLabel;
6
6
  indicatorSeparator: () => CSSObjectWithLabel;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.7.24";
1
+ export declare const VERSION = "3.7.25";
2
2
  export declare const MATHJAX_VERSION = "3.2.2";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@manuscripts/body-editor",
3
3
  "description": "Prosemirror components for editing and viewing manuscripts",
4
- "version": "3.7.24",
4
+ "version": "3.7.25",
5
5
  "repository": "github:Atypon-OpenSource/manuscripts-body-editor",
6
6
  "license": "Apache-2.0",
7
7
  "main": "dist/cjs",
@@ -38,7 +38,7 @@
38
38
  "@citation-js/plugin-ris": "0.7.18",
39
39
  "@iarna/word-count": "1.1.2",
40
40
  "@manuscripts/json-schema": "2.2.12",
41
- "@manuscripts/style-guide": "3.3.12",
41
+ "@manuscripts/style-guide": "3.3.13",
42
42
  "@manuscripts/track-changes-plugin": "2.2.3",
43
43
  "@manuscripts/transform": "4.3.12",
44
44
  "@popperjs/core": "2.11.8",
package/styles/Editor.css CHANGED
@@ -1,6 +1,13 @@
1
1
  :root {
2
2
  --body-side-margin: 65px;
3
3
  }
4
+
5
+ /* Focus styling for editor container when accessed via Escape key */
6
+ #editor:focus {
7
+ outline: 2px solid #3dadff !important;
8
+ outline-offset: 2px;
9
+ }
10
+
4
11
  .ProseMirror.manuscript-editor {
5
12
  font-size: 16px;
6
13
  line-height: 1.5;
@@ -985,10 +992,6 @@
985
992
  filter: brightness(80%);
986
993
  }
987
994
 
988
- .ProseMirror [tabindex]:focus {
989
- outline: none;
990
- }
991
-
992
995
  .ProseMirror .manuscript-toc {
993
996
  white-space: normal;
994
997
  }